Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
+GNU Lesser General Public License for more details.
-You should have received a copy of the GNU General Public License along
+You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "server.h"
-#include "utility.h"
#include <iostream>
#include <queue>
#include "clientserver.h"
#include "constants.h"
#include "voxel.h"
#include "config.h"
-#include "servercommand.h"
#include "filesys.h"
#include "mapblock.h"
#include "serverobject.h"
#include "sha1.h"
#include "base64.h"
#include "tool.h"
-#include "utility_string.h"
#include "sound.h" // dummySoundManager
#include "event_manager.h"
#include "hex.h"
+#include "util/string.h"
+#include "util/pointedthing.h"
+#include "util/mathconstants.h"
+#include "rollback.h"
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
BEGIN_DEBUG_EXCEPTION_HANDLER
bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");
+
+ v3s16 last_tried_pos(-32768,-32768,-32768); // For error output
/*
Get block info from queue, emerge them and send them
After queue is empty, exit.
*/
while(getRun())
- {
+ try{
QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
if(qptr == NULL)
break;
v3s16 &p = q->pos;
v2s16 p2d(p.X,p.Z);
+
+ last_tried_pos = p;
/*
Do not generate over-limit
client->SetBlocksNotSent(modified_blocks);
}
}
-
+ }
+ catch(VersionMismatchException &e)
+ {
+ std::ostringstream err;
+ err<<"World data version mismatch in MapBlock "<<PP(last_tried_pos)<<std::endl;
+ err<<"----"<<std::endl;
+ err<<"\""<<e.what()<<"\""<<std::endl;
+ err<<"See debug.txt."<<std::endl;
+ err<<"World probably saved by a newer version of Minetest."<<std::endl;
+ m_server->setAsyncFatalError(err.str());
+ }
+ catch(SerializationError &e)
+ {
+ std::ostringstream err;
+ err<<"Invalid data in MapBlock "<<PP(last_tried_pos)<<std::endl;
+ err<<"----"<<std::endl;
+ err<<"\""<<e.what()<<"\""<<std::endl;
+ err<<"See debug.txt."<<std::endl;
+ err<<"You can ignore this using [ignore_world_load_errors = true]."<<std::endl;
+ m_server->setAsyncFatalError(err.str());
}
END_DEBUG_EXCEPTION_HANDLER(errorstream)
m_nearest_unsent_reset_timer += dtime;
if(m_nothing_to_send_pause_timer >= 0)
- {
return;
- }
+
+ Player *player = server->m_env->getPlayer(peer_id);
+ // This can happen sometimes; clients and players are not in perfect sync.
+ if(player == NULL)
+ return;
// Won't send anything if already sending
if(m_blocks_sending.size() >= g_settings->getU16
//TimeTaker timer("RemoteClient::GetNextBlocks");
- Player *player = server->m_env->getPlayer(peer_id);
-
- assert(player != NULL);
-
v3f playerpos = player->getPosition();
v3f playerspeed = player->getSpeed();
v3f playerspeeddir(0,0,0);
FOV setting. The default of 72 degrees is fine.
*/
- float camera_fov = (72.0*PI/180) * 4./3.;
+ float camera_fov = (72.0*M_PI/180) * 4./3.;
if(isBlockInSight(p, camera_pos, camera_dir, camera_fov, 10000*BS) == false)
{
continue;
/*timer_result = timer.stop(true);
if(timer_result != 0)
- infostream<<"GetNextBlocks duration: "<<timer_result<<" (!=0)"<<std::endl;*/
+ infostream<<"GetNextBlocks timeout: "<<timer_result<<" (!=0)"<<std::endl;*/
}
void RemoteClient::GotBlock(v3s16 p)
m_env(NULL),
m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
m_banmanager(path_world+DIR_DELIM+"ipban.txt"),
+ m_rollback(NULL),
+ m_rollback_sink_enabled(true),
+ m_enable_rollback_recording(false),
m_lua(NULL),
m_itemdef(createItemDefManager()),
m_nodedef(createNodeDefManager()),
infostream<<"- config: "<<m_path_config<<std::endl;
infostream<<"- game: "<<m_gamespec.path<<std::endl;
+ // Create rollback manager
+ std::string rollback_path = m_path_world+DIR_DELIM+"rollback.txt";
+ m_rollback = createRollbackManager(rollback_path, this);
+
// Add world mod search path
m_modspaths.push_front(m_path_world + DIR_DELIM + "worldmods");
// Add addon mod search path
}
// Path to builtin.lua
- std::string builtinpath = porting::path_share + DIR_DELIM + "builtin"
- + DIR_DELIM + "builtin.lua";
+ std::string builtinpath = getBuiltinLuaPath() + DIR_DELIM + "builtin.lua";
// Create world if it doesn't exist
if(!initializeWorld(m_path_world, m_gamespec.id))
m_env = new ServerEnvironment(new ServerMap(path_world, this), m_lua,
this, this);
-
+
// Give environment reference to scripting api
scriptapi_add_environment(m_lua, m_env);
// Delete things in the reverse order of creation
delete m_env;
+ delete m_rollback;
delete m_event;
delete m_itemdef;
delete m_nodedef;
// Deinitialize scripting
infostream<<"Server: Deinitializing scripting"<<std::endl;
script_deinit(m_lua);
+
+ // Delete detached inventories
+ {
+ for(std::map<std::string, Inventory*>::iterator
+ i = m_detached_inventories.begin();
+ i != m_detached_inventories.end(); i++){
+ delete i->second;
+ }
+ }
}
void Server::start(unsigned short port)
counter = 0.0;
m_emergethread.trigger();
+
+ // Update m_enable_rollback_recording here too
+ m_enable_rollback_recording =
+ g_settings->getBool("enable_rollback_recording");
}
}
<<m_con.GetPeerAddress(peer_id).serializeString()<<std::endl;
// Get password
- char password[PASSWORD_SIZE];
+ char given_password[PASSWORD_SIZE];
if(datasize < 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE)
{
// old version - assume blank password
- password[0] = 0;
+ given_password[0] = 0;
}
else
{
for(u32 i=0; i<PASSWORD_SIZE-1; i++)
{
- password[i] = data[23+i];
+ given_password[i] = data[23+i];
}
- password[PASSWORD_SIZE-1] = 0;
+ given_password[PASSWORD_SIZE-1] = 0;
+ }
+
+ if(!base64_is_valid(given_password)){
+ infostream<<"Server: "<<playername
+ <<" supplied invalid password hash"<<std::endl;
+ SendAccessDenied(m_con, peer_id, L"Invalid password hash");
+ return;
}
- std::string checkpwd;
+ std::string checkpwd; // Password hash to check against
bool has_auth = scriptapi_get_auth(m_lua, playername, &checkpwd, NULL);
+ // If no authentication info exists for user, create it
if(!has_auth){
+ if(!isSingleplayer() &&
+ g_settings->getBool("disallow_empty_password") &&
+ std::string(given_password) == ""){
+ SendAccessDenied(m_con, peer_id, L"Empty passwords are "
+ L"disallowed. Set a password and try again.");
+ return;
+ }
std::wstring raw_default_password =
narrow_to_wide(g_settings->get("default_password"));
- std::string use_password =
+ std::string initial_password =
translatePassword(playername, raw_default_password);
// If default_password is empty, allow any initial password
if (raw_default_password.length() == 0)
- use_password = password;
+ initial_password = given_password;
- scriptapi_create_auth(m_lua, playername, use_password);
+ scriptapi_create_auth(m_lua, playername, initial_password);
}
has_auth = scriptapi_get_auth(m_lua, playername, &checkpwd, NULL);
return;
}
- if(password != checkpwd){
+ if(given_password != checkpwd){
infostream<<"Server: peer_id="<<peer_id
<<": supplied invalid password for "
<<playername<<std::endl;
verbosestream<<"Server: Got TOSERVER_INIT2 from "
<<peer_id<<std::endl;
+ Player *player = m_env->getPlayer(peer_id);
+ if(!player){
+ verbosestream<<"Server: TOSERVER_INIT2: "
+ <<"Player not found; ignoring."<<std::endl;
+ return;
+ }
getClient(peer_id)->serialization_version
= getClient(peer_id)->pending_serialization_version;
// Send node definitions
SendNodeDef(m_con, peer_id, m_nodedef);
- // Send texture announcement
+ // Send media announcement
sendMediaAnnouncement(peer_id);
- // Send player info to all players
- //SendPlayerInfos();
+ // Send privileges
+ SendPlayerPrivileges(peer_id);
+
+ // Send inventory formspec
+ SendPlayerInventoryFormspec(peer_id);
- // Send inventory to player
+ // Send inventory
UpdateCrafting(peer_id);
SendInventory(peer_id);
-
- Player *player = m_env->getPlayer(peer_id);
// Send HP
SendPlayerHP(peer_id);
+ // Send detached inventories
+ sendDetachedInventories(peer_id);
+
// Show death screen if necessary
if(player->hp == 0)
SendDeathscreen(m_con, peer_id, false, v3f(0,0,0));
std::wstring message;
message += L"*** ";
message += name;
- message += L" joined game";
+ message += L" joined the game.";
BroadcastChatMessage(message);
}
}
// Warnings about protocol version can be issued here
if(getClient(peer_id)->net_proto_version < PROTOCOL_VERSION)
{
- SendChatMessage(peer_id, L"# Server: WARNING: YOUR CLIENT IS OLD AND MAY WORK PROPERLY WITH THIS SERVER");
+ SendChatMessage(peer_id, L"# Server: WARNING: YOUR CLIENT IS OLD AND MAY WORK PROPERLY WITH THIS SERVER!");
}
/*
}
else if(command == TOSERVER_SIGNNODETEXT)
{
- if(!checkPriv(player->getName(), "interact"))
- return;
- /*
- u16 command
- v3s16 p
- u16 textlen
- textdata
- */
- std::string datastring((char*)&data[2], datasize-2);
- std::istringstream is(datastring, std::ios_base::binary);
- u8 buf[6];
- // Read stuff
- is.read((char*)buf, 6);
- v3s16 p = readV3S16(buf);
- is.read((char*)buf, 2);
- u16 textlen = readU16(buf);
- std::string text;
- for(u16 i=0; i<textlen; i++)
- {
- is.read((char*)buf, 1);
- text += (char)buf[0];
- }
-
- NodeMetadata *meta = m_env->getMap().getNodeMetadata(p);
- if(!meta)
- return;
-
- meta->setText(text);
-
- actionstream<<player->getName()<<" writes \""<<text<<"\" to sign"
- <<" at "<<PP(p)<<std::endl;
-
- v3s16 blockpos = getNodeBlockPos(p);
- MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos);
- if(block)
- {
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- "sign node text");
- }
-
- setBlockNotSent(blockpos);
+ infostream<<"Server: SIGNNODETEXT not supported anymore"
+ <<std::endl;
+ return;
}
else if(command == TOSERVER_INVENTORY_ACTION)
{
return;
}
+ // If something goes wrong, this player is to blame
+ RollbackScopeActor rollback_scope(m_rollback,
+ std::string("player:")+player->getName());
+
/*
Note: Always set inventory not sent, to repair cases
where the client made a bad prediction.
delete a;
return;
}
-
- // If player is not an admin, check for ownership of src and dst
- if(!checkPriv(player->getName(), "server"))
- {
- std::string owner_from = getInventoryOwner(ma->from_inv);
- if(owner_from != "" && owner_from != player->getName())
- {
- infostream<<"WARNING: "<<player->getName()
- <<" tried to access an inventory that"
- <<" belongs to "<<owner_from<<std::endl;
- delete a;
- return;
- }
-
- std::string owner_to = getInventoryOwner(ma->to_inv);
- if(owner_to != "" && owner_to != player->getName())
- {
- infostream<<"WARNING: "<<player->getName()
- <<" tried to access an inventory that"
- <<" belongs to "<<owner_to<<std::endl;
- delete a;
- return;
- }
- }
}
/*
Handle restrictions and special cases of the drop action
delete a;
return;
}
- // If player is not an admin, check for ownership
- else if(!checkPriv(player->getName(), "server"))
- {
- std::string owner_from = getInventoryOwner(da->from_inv);
- if(owner_from != "" && owner_from != player->getName())
- {
- infostream<<"WARNING: "<<player->getName()
- <<" tried to access an inventory that"
- <<" belongs to "<<owner_from<<std::endl;
- delete a;
- return;
- }
- }
}
/*
Handle restrictions and special cases of the craft action
delete a;
return;
}
-
- // If player is not an admin, check for ownership of inventory
- if(!checkPriv(player->getName(), "server"))
- {
- std::string owner_craft = getInventoryOwner(ca->craft_inv);
- if(owner_craft != "" && owner_craft != player->getName())
- {
- infostream<<"WARNING: "<<player->getName()
- <<" tried to access an inventory that"
- <<" belongs to "<<owner_craft<<std::endl;
- delete a;
- return;
- }
- }
}
// Do the action
message += (wchar_t)readU16(buf);
}
+ // If something goes wrong, this player is to blame
+ RollbackScopeActor rollback_scope(m_rollback,
+ std::string("player:")+player->getName());
+
// Get player name of this client
std::wstring name = narrow_to_wide(player->getName());
// Whether to send to other players
bool send_to_others = false;
- // Parse commands
+ // Commands are implemented in Lua, so only catch invalid
+ // commands that were not "eaten" and send an error back
if(message[0] == L'/')
{
- size_t strip_size = 1;
- if (message[1] == L'#') // support old-style commans
- ++strip_size;
- message = message.substr(strip_size);
-
- WStrfnd f1(message);
- f1.next(L" "); // Skip over /#whatever
- std::wstring paramstring = f1.next(L"");
-
- ServerCommandContext *ctx = new ServerCommandContext(
- str_split(message, L' '),
- paramstring,
- this,
- m_env,
- player);
-
- std::wstring reply(processServerCommand(ctx));
- send_to_sender = ctx->flags & SEND_TO_SENDER;
- send_to_others = ctx->flags & SEND_TO_OTHERS;
-
- if (ctx->flags & SEND_NO_PREFIX)
- line += reply;
+ message = message.substr(1);
+ send_to_sender = true;
+ if(message.length() == 0)
+ line += L"-!- Empty command";
else
- line += L"Server: " + reply;
-
- delete ctx;
-
+ line += L"-!- Invalid command: " + str_split(message, L' ')[0];
}
else
{
line += message;
send_to_others = true;
} else {
- line += L"Server: You are not allowed to shout";
+ line += L"-!- You don't have permission to shout.";
send_to_sender = true;
}
}
newpwd += c;
}
+ if(!base64_is_valid(newpwd)){
+ infostream<<"Server: "<<player->getName()<<" supplied invalid password hash"<<std::endl;
+ // Wrong old password supplied!!
+ SendChatMessage(peer_id, L"Invalid new password hash supplied. Password NOT changed.");
+ return;
+ }
+
infostream<<"Server: Client requests a password change from "
<<"'"<<oldpwd<<"' to '"<<newpwd<<"'"<<std::endl;
bool success = scriptapi_set_password(m_lua, playername, newpwd);
if(success){
actionstream<<player->getName()<<" changes password"<<std::endl;
- SendChatMessage(peer_id, L"Password change successful");
+ SendChatMessage(peer_id, L"Password change successful.");
} else {
actionstream<<player->getName()<<" tries to change password but "
<<"it fails"<<std::endl;
- SendChatMessage(peer_id, L"Password change failed or inavailable");
+ SendChatMessage(peer_id, L"Password change failed or inavailable.");
}
}
else if(command == TOSERVER_PLAYERITEM)
*/
if(!checkPriv(player->getName(), "interact"))
{
- infostream<<"Ignoring interaction from player "<<player->getName()
- <<" (no interact privilege)"<<std::endl;
+ actionstream<<player->getName()<<" attempted to interact with "
+ <<pointed.dump()<<" without 'interact' privilege"
+ <<std::endl;
+ // Re-send block to revert change on client-side
+ RemoteClient *client = getClient(peer_id);
+ // Digging completed -> under
+ if(action == 2){
+ v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
+ client->SetBlockNotSent(blockpos);
+ }
+ // Placement -> above
+ if(action == 3){
+ v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
+ client->SetBlockNotSent(blockpos);
+ }
return;
}
+ /*
+ If something goes wrong, this player is to blame
+ */
+ RollbackScopeActor rollback_scope(m_rollback,
+ std::string("player:")+player->getName());
+
/*
0: start digging or punch object
*/
}
if(n.getContent() != CONTENT_IGNORE)
scriptapi_node_on_punch(m_lua, p_under, n, playersao);
+ // Cheat prevention
+ playersao->noCheatDigStart(p_under);
}
else if(pointed.type == POINTEDTHING_OBJECT)
{
*/
else if(action == 2)
{
- // Only complete digging of nodes
+ // Only digging of nodes
if(pointed.type == POINTEDTHING_NODE)
{
MapNode n(CONTENT_IGNORE);
m_emerge_queue.addBlock(peer_id,
getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK);
}
- if(n.getContent() != CONTENT_IGNORE)
+
+ /* Cheat prevention */
+ bool is_valid_dig = true;
+ if(!isSingleplayer() && !g_settings->getBool("disable_anticheat"))
+ {
+ v3s16 nocheat_p = playersao->getNoCheatDigPos();
+ float nocheat_t = playersao->getNoCheatDigTime();
+ playersao->noCheatDigEnd();
+ // If player didn't start digging this, ignore dig
+ if(nocheat_p != p_under){
+ infostream<<"Server: NoCheat: "<<player->getName()
+ <<" started digging "
+ <<PP(nocheat_p)<<" and completed digging "
+ <<PP(p_under)<<"; not digging."<<std::endl;
+ is_valid_dig = false;
+ }
+ // Get player's wielded item
+ ItemStack playeritem;
+ InventoryList *mlist = playersao->getInventory()->getList("main");
+ if(mlist != NULL)
+ playeritem = mlist->getItem(playersao->getWieldIndex());
+ ToolCapabilities playeritem_toolcap =
+ playeritem.getToolCapabilities(m_itemdef);
+ // Get diggability and expected digging time
+ DigParams params = getDigParams(m_nodedef->get(n).groups,
+ &playeritem_toolcap);
+ // If can't dig, try hand
+ if(!params.diggable){
+ const ItemDefinition &hand = m_itemdef->get("");
+ const ToolCapabilities *tp = hand.tool_capabilities;
+ if(tp)
+ params = getDigParams(m_nodedef->get(n).groups, tp);
+ }
+ // If can't dig, ignore dig
+ if(!params.diggable){
+ infostream<<"Server: NoCheat: "<<player->getName()
+ <<" completed digging "<<PP(p_under)
+ <<", which is not diggable with tool. not digging."
+ <<std::endl;
+ is_valid_dig = false;
+ }
+ // If time is considerably too short, ignore dig
+ // Check time only for medium and slow timed digs
+ if(params.diggable && params.time > 0.3 && nocheat_t < 0.5 * params.time){
+ infostream<<"Server: NoCheat: "<<player->getName()
+ <<" completed digging "
+ <<PP(p_under)<<" in "<<nocheat_t<<"s; expected "
+ <<params.time<<"s; not digging."<<std::endl;
+ is_valid_dig = false;
+ }
+ }
+
+ /* Actually dig node */
+
+ if(is_valid_dig && n.getContent() != CONTENT_IGNORE)
scriptapi_node_on_dig(m_lua, p_under, n, playersao);
+
+ // Send unusual result (that is, node not being removed)
+ if(m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR)
+ {
+ // Re-send block to revert change on client-side
+ RemoteClient *client = getClient(peer_id);
+ v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
+ client->SetBlockNotSent(blockpos);
+ }
}
} // action == 2
// Placement was handled in lua
// Apply returned ItemStack
- if(g_settings->getBool("creative_mode") == false)
- playersao->setWieldedItem(item);
+ playersao->setWieldedItem(item);
+ }
+
+ // If item has node placement prediction, always send the above
+ // node to make sure the client knows what exactly happened
+ if(item.getDefinition(m_itemdef).node_placement_prediction != ""){
+ RemoteClient *client = getClient(peer_id);
+ v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
+ client->SetBlockNotSent(blockpos);
}
-
} // action == 3
/*
item, playersao, pointed))
{
// Apply returned ItemStack
- if(g_settings->getBool("creative_mode") == false)
- playersao->setWieldedItem(item);
+ playersao->setWieldedItem(item);
}
} // action == 4
m_playing_sounds.erase(i++);
}
}
+ else if(command == TOSERVER_NODEMETA_FIELDS)
+ {
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+
+ v3s16 p = readV3S16(is);
+ std::string formname = deSerializeString(is);
+ int num = readU16(is);
+ std::map<std::string, std::string> fields;
+ for(int k=0; k<num; k++){
+ std::string fieldname = deSerializeString(is);
+ std::string fieldvalue = deSerializeLongString(is);
+ fields[fieldname] = fieldvalue;
+ }
+
+ // If something goes wrong, this player is to blame
+ RollbackScopeActor rollback_scope(m_rollback,
+ std::string("player:")+player->getName());
+
+ // Check the target node for rollback data; leave others unnoticed
+ RollbackNode rn_old(&m_env->getMap(), p, this);
+
+ scriptapi_node_on_receive_fields(m_lua, p, formname, fields,
+ playersao);
+
+ // Report rollback data
+ RollbackNode rn_new(&m_env->getMap(), p, this);
+ if(rollback() && rn_new != rn_old){
+ RollbackAction action;
+ action.setSetNode(p, rn_old, rn_new);
+ rollback()->reportAction(action);
+ }
+ }
+ else if(command == TOSERVER_INVENTORY_FIELDS)
+ {
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+
+ std::string formname = deSerializeString(is);
+ int num = readU16(is);
+ std::map<std::string, std::string> fields;
+ for(int k=0; k<num; k++){
+ std::string fieldname = deSerializeString(is);
+ std::string fieldvalue = deSerializeLongString(is);
+ fields[fieldname] = fieldvalue;
+ }
+
+ scriptapi_on_player_receive_fields(m_lua, playersao, formname, fields);
+ }
else
{
infostream<<"Server::ProcessData(): Ignoring "
return meta->getInventory();
}
break;
- default:
- assert(0);
- }
- return NULL;
-}
-std::string Server::getInventoryOwner(const InventoryLocation &loc)
-{
- switch(loc.type){
- case InventoryLocation::UNDEFINED:
- {}
- break;
- case InventoryLocation::CURRENT_PLAYER:
- {}
- break;
- case InventoryLocation::PLAYER:
+ case InventoryLocation::DETACHED:
{
- return loc.name;
- }
- break;
- case InventoryLocation::NODEMETA:
- {
- NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p);
- if(!meta)
- return "";
- return meta->getOwner();
+ if(m_detached_inventories.count(loc.name) == 0)
+ return NULL;
+ return m_detached_inventories[loc.name];
}
break;
default:
assert(0);
}
- return "";
+ return NULL;
}
void Server::setInventoryModified(const InventoryLocation &loc)
{
{
v3s16 blockpos = getNodeBlockPos(loc.p);
- NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p);
- if(meta)
- meta->inventoryModified();
-
MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos);
if(block)
block->raiseModified(MOD_STATE_WRITE_NEEDED);
setBlockNotSent(blockpos);
}
break;
+ case InventoryLocation::DETACHED:
+ {
+ sendDetachedInventoryToAll(loc.name);
+ }
+ break;
default:
assert(0);
}
m_con.Send(peer_id, 0, data, true);
}
+void Server::SendPlayerPrivileges(u16 peer_id)
+{
+ Player *player = m_env->getPlayer(peer_id);
+ assert(player);
+ if(player->peer_id == PEER_ID_INEXISTENT)
+ return;
+
+ std::set<std::string> privs;
+ scriptapi_get_auth(m_lua, player->getName(), NULL, &privs);
+
+ std::ostringstream os(std::ios_base::binary);
+ writeU16(os, TOCLIENT_PRIVILEGES);
+ writeU16(os, privs.size());
+ for(std::set<std::string>::const_iterator i = privs.begin();
+ i != privs.end(); i++){
+ os<<serializeString(*i);
+ }
+
+ // Make data buffer
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as reliable
+ m_con.Send(peer_id, 0, data, true);
+}
+
+void Server::SendPlayerInventoryFormspec(u16 peer_id)
+{
+ Player *player = m_env->getPlayer(peer_id);
+ assert(player);
+ if(player->peer_id == PEER_ID_INEXISTENT)
+ return;
+
+ std::ostringstream os(std::ios_base::binary);
+ writeU16(os, TOCLIENT_INVENTORY_FORMSPEC);
+ os<<serializeLongString(player->inventory_formspec);
+
+ // Make data buffer
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as reliable
+ m_con.Send(peer_id, 0, data, true);
+}
+
s32 Server::playSound(const SimpleSoundSpec &spec,
const ServerSoundParams ¶ms)
{
paths.push_back(mod.path + DIR_DELIM + "sounds");
paths.push_back(mod.path + DIR_DELIM + "media");
}
+ std::string path_all = "textures";
+ paths.push_back(path_all + DIR_DELIM + "all");
// Collect media file information from paths into cache
for(std::list<std::string>::iterator i = paths.begin();
}
}
+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;
+ return;
+ }
+ Inventory *inv = m_detached_inventories[name];
+
+ std::ostringstream os(std::ios_base::binary);
+ writeU16(os, TOCLIENT_DETACHED_INVENTORY);
+ os<<serializeString(name);
+ inv->serialize(os);
+
+ // Make data buffer
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as reliable
+ m_con.Send(peer_id, 0, data, true);
+}
+
+void Server::sendDetachedInventoryToAll(const std::string &name)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ for(core::map<u16, RemoteClient*>::Iterator
+ i = m_clients.getIterator();
+ i.atEnd() == false; i++){
+ RemoteClient *client = i.getNode()->getValue();
+ sendDetachedInventory(name, client->peer_id);
+ }
+}
+
+void Server::sendDetachedInventories(u16 peer_id)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ for(std::map<std::string, Inventory*>::iterator
+ i = m_detached_inventories.begin();
+ i != m_detached_inventories.end(); i++){
+ const std::string &name = i->first;
+ //Inventory *inv = i->second;
+ sendDetachedInventory(name, peer_id);
+ }
+}
+
/*
Something random
*/
// Get a preview for crafting
ItemStack preview;
- // No crafting in creative mode
- if(g_settings->getBool("creative_mode") == false)
- getCraftingResult(&player->inventory, preview, false, this);
+ getCraftingResult(&player->inventory, preview, false, this);
// Put the new preview in
InventoryList *plist = player->inventory.getList("craftpreview");
// Uptime
os<<L", uptime="<<m_uptime.get();
// Information about clients
+ core::map<u16, RemoteClient*>::Iterator i;
+ bool first;
os<<L", clients={";
- for(core::map<u16, RemoteClient*>::Iterator
- i = m_clients.getIterator();
+ for(i = m_clients.getIterator(), first = true;
i.atEnd() == false; i++)
{
// Get client and check that it is valid
if(player != NULL)
name = narrow_to_wide(player->getName());
// Add name to information string
- os<<name<<L",";
+ if(!first)
+ os<<L",";
+ else
+ first = false;
+ os<<name;
}
os<<L"}";
if(((ServerMap*)(&m_env->getMap()))->isSavingEnabled() == false)
return (privs.count(priv) != 0);
}
+void Server::reportPrivsModified(const std::string &name)
+{
+ if(name == ""){
+ for(core::map<u16, RemoteClient*>::Iterator
+ i = m_clients.getIterator();
+ i.atEnd() == false; i++){
+ RemoteClient *client = i.getNode()->getValue();
+ Player *player = m_env->getPlayer(client->peer_id);
+ reportPrivsModified(player->getName());
+ }
+ } else {
+ Player *player = m_env->getPlayer(name.c_str());
+ if(!player)
+ return;
+ SendPlayerPrivileges(player->peer_id);
+ PlayerSAO *sao = player->getPlayerSAO();
+ if(!sao)
+ return;
+ sao->updatePrivileges(
+ getPlayerEffectivePrivs(name),
+ isSingleplayer());
+ }
+}
+
+void Server::reportInventoryFormspecModified(const std::string &name)
+{
+ Player *player = m_env->getPlayer(name.c_str());
+ if(!player)
+ return;
+ SendPlayerInventoryFormspec(player->peer_id);
+}
+
// Saves g_settings to configpath given at initialization
void Server::saveConfig()
{
m_emerge_queue.addBlock(PEER_ID_INEXISTENT, blockpos, flags);
}
+Inventory* Server::createDetachedInventory(const std::string &name)
+{
+ if(m_detached_inventories.count(name) > 0){
+ infostream<<"Server clearing detached inventory \""<<name<<"\""<<std::endl;
+ delete m_detached_inventories[name];
+ } else {
+ infostream<<"Server creating detached inventory \""<<name<<"\""<<std::endl;
+ }
+ Inventory *inv = new Inventory(m_itemdef);
+ assert(inv);
+ m_detached_inventories[name] = inv;
+ sendDetachedInventoryToAll(name);
+ return inv;
+}
+
+class BoolScopeSet
+{
+public:
+ BoolScopeSet(bool *dst, bool val):
+ m_dst(dst)
+ {
+ m_orig_state = *m_dst;
+ *m_dst = val;
+ }
+ ~BoolScopeSet()
+ {
+ *m_dst = m_orig_state;
+ }
+private:
+ bool *m_dst;
+ bool m_orig_state;
+};
+
+// actions: time-reversed list
+// Return value: success/failure
+bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,
+ std::list<std::string> *log)
+{
+ infostream<<"Server::rollbackRevertActions(len="<<actions.size()<<")"<<std::endl;
+ ServerMap *map = (ServerMap*)(&m_env->getMap());
+ // Disable rollback report sink while reverting
+ BoolScopeSet rollback_scope_disable(&m_rollback_sink_enabled, false);
+
+ // Fail if no actions to handle
+ if(actions.empty()){
+ log->push_back("Nothing to do.");
+ return false;
+ }
+
+ int num_tried = 0;
+ int num_failed = 0;
+
+ for(std::list<RollbackAction>::const_iterator
+ i = actions.begin();
+ i != actions.end(); i++)
+ {
+ const RollbackAction &action = *i;
+ num_tried++;
+ bool success = action.applyRevert(map, this, this);
+ if(!success){
+ num_failed++;
+ std::ostringstream os;
+ os<<"Revert of step ("<<num_tried<<") "<<action.toString()<<" failed";
+ infostream<<"Map::rollbackRevertActions(): "<<os.str()<<std::endl;
+ if(log)
+ log->push_back(os.str());
+ }else{
+ std::ostringstream os;
+ os<<"Succesfully reverted step ("<<num_tried<<") "<<action.toString();
+ infostream<<"Map::rollbackRevertActions(): "<<os.str()<<std::endl;
+ if(log)
+ log->push_back(os.str());
+ }
+ }
+
+ infostream<<"Map::rollbackRevertActions(): "<<num_failed<<"/"<<num_tried
+ <<" failed"<<std::endl;
+
+ // Call it done if less than half failed
+ return num_failed <= num_tried/2;
+}
+
// IGameDef interface
// Under envlock
IItemDefManager* Server::getItemDefManager()
{
return m_event;
}
+IRollbackReportSink* Server::getRollbackReportSink()
+{
+ if(!m_enable_rollback_recording)
+ return NULL;
+ if(!m_rollback_sink_enabled)
+ return NULL;
+ return m_rollback;
+}
IWritableItemDefManager* Server::getWritableItemDefManager()
{
}
return NULL;
}
+void Server::getModNames(core::list<std::string> &modlist)
+{
+ for(core::list<ModSpec>::Iterator i = m_mods.begin(); i != m_mods.end(); i++)
+ {
+ modlist.push_back((*i).name);
+ }
+}
+std::string Server::getBuiltinLuaPath()
+{
+ return porting::path_share + DIR_DELIM + "builtin";
+}
v3f findSpawnPos(ServerMap &map)
{
/*
Create a new player active object
*/
- PlayerSAO *playersao = new PlayerSAO(m_env, player, peer_id);
+ PlayerSAO *playersao = new PlayerSAO(m_env, player, peer_id,
+ getPlayerEffectivePrivs(player->getName()),
+ isSingleplayer());
/* Add object to environment */
m_env->addActiveObject(playersao);
scriptapi_on_joinplayer(m_lua, playersao);
- /* Creative mode */
- if(g_settings->getBool("creative_mode"))
- playersao->createCreativeInventory();
-
return playersao;
}
std::wstring name = narrow_to_wide(player->getName());
message += L"*** ";
message += name;
- message += L" left game";
+ message += L" left the game.";
if(c.timeout)
message += L" (timed out)";
}