aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPerttu Ahola <celeron55@gmail.com>2010-12-25 01:54:39 +0200
committerPerttu Ahola <celeron55@gmail.com>2010-12-25 01:54:39 +0200
commit699d0e9a5efa555f4b4c7652a0ca571acf0afdb2 (patch)
tree63596db7777bac57301f90b96e40940d8ba59039
parent63e27380dca8b0472ce2f0434a073b439f46f5e7 (diff)
downloadminetest-699d0e9a5efa555f4b4c7652a0ca571acf0afdb2.tar.gz
minetest-699d0e9a5efa555f4b4c7652a0ca571acf0afdb2.tar.bz2
minetest-699d0e9a5efa555f4b4c7652a0ca571acf0afdb2.zip
minecraft-like crafting
-rw-r--r--Makefile4
-rw-r--r--data/stick.pngbin0 -> 947 bytes
-rw-r--r--data/wood.pngbin0 -> 1400 bytes
-rw-r--r--src/inventory.h14
-rw-r--r--src/main.cpp102
-rw-r--r--src/mapnode.cpp2
-rw-r--r--src/mapnode.h45
-rw-r--r--src/materials.cpp75
-rw-r--r--src/materials.h98
-rw-r--r--src/server.cpp471
-rw-r--r--src/servermain.cpp2
-rw-r--r--src/tile.cpp1
-rw-r--r--src/tile.h1
13 files changed, 568 insertions, 247 deletions
diff --git a/Makefile b/Makefile
index 5dad9433f..a6e360f3f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
# Makefile for Irrlicht Examples
# It's usually sufficient to change just the target name and source file list
# and be sure that CXX is set to a valid compiler
-SOURCE_FILES = guiTextInputMenu.cpp guiInventoryMenu.cpp irrlichtwrapper.cpp guiPauseMenu.cpp defaultsettings.cpp mapnode.cpp tile.cpp voxel.cpp mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp client.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp main.cpp test.cpp
+SOURCE_FILES = materials.cpp guiTextInputMenu.cpp guiInventoryMenu.cpp irrlichtwrapper.cpp guiPauseMenu.cpp defaultsettings.cpp mapnode.cpp tile.cpp voxel.cpp mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp client.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp main.cpp test.cpp
DEBUG_TARGET = debugtest
DEBUG_SOURCES = $(addprefix src/, $(SOURCE_FILES))
@@ -14,7 +14,7 @@ FAST_BUILD_DIR = fastbuild
FAST_OBJECTS = $(addprefix $(FAST_BUILD_DIR)/, $(SOURCE_FILES:.cpp=.o))
SERVER_TARGET = server
-SERVER_SOURCE_FILES = defaultsettings.cpp mapnode.cpp voxel.cpp mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp servermain.cpp test.cpp
+SERVER_SOURCE_FILES = materials.cpp defaultsettings.cpp mapnode.cpp voxel.cpp mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp servermain.cpp test.cpp
SERVER_SOURCES = $(addprefix src/, $(SERVER_SOURCE_FILES))
SERVER_BUILD_DIR = serverbuild
SERVER_OBJECTS = $(addprefix $(SERVER_BUILD_DIR)/, $(SERVER_SOURCE_FILES:.cpp=.o))
diff --git a/data/stick.png b/data/stick.png
new file mode 100644
index 000000000..7a4663cc3
--- /dev/null
+++ b/data/stick.png
Binary files differ
diff --git a/data/wood.png b/data/wood.png
new file mode 100644
index 000000000..57c1d7c12
--- /dev/null
+++ b/data/wood.png
Binary files differ
diff --git a/src/inventory.h b/src/inventory.h
index 13bd27d8b..ff0086102 100644
--- a/src/inventory.h
+++ b/src/inventory.h
@@ -390,6 +390,20 @@ public:
{
return m_wear;
}
+ // Returns true if weared out
+ bool addWear(u16 add)
+ {
+ if(m_wear >= 65535 - add)
+ {
+ m_wear = 65535;
+ return true;
+ }
+ else
+ {
+ m_wear += add;
+ return false;
+ }
+ }
private:
std::string m_toolname;
u16 m_wear;
diff --git a/src/main.cpp b/src/main.cpp
index 1a9379e41..47dcbf70f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -167,6 +167,8 @@ TODO: Better handling of objects and mobs
- Make separate classes for client and server
- Client should not discriminate between blocks, server should
- Make other players utilize the same framework
+ - This is also needed for objects that don't get sent to client
+ but are used for triggers etc
TODO: Draw big amounts of torches better (that is, throw them in the
same meshbuffer (can the meshcollector class be used?))
@@ -174,6 +176,9 @@ TODO: Draw big amounts of torches better (that is, throw them in the
TODO: Check if the usage of Client::isFetchingBlocks() in
updateViewingRange() actually does something
+TODO: Make an option to the server to disable building and digging near
+ the starting position
+
Doing now:
======================================================================
@@ -239,6 +244,7 @@ TODO: Transferring of the table from server to client
#include "guiPauseMenu.h"
#include "guiInventoryMenu.h"
#include "guiTextInputMenu.h"
+#include "materials.h"
IrrlichtWrapper *g_irrlicht;
@@ -1022,6 +1028,22 @@ private:
s32 m_selection;
};
+// Chat data
+struct ChatLine
+{
+ ChatLine():
+ age(0.0)
+ {
+ }
+ ChatLine(const std::wstring &a_text):
+ age(0.0),
+ text(a_text)
+ {
+ }
+ float age;
+ std::wstring text;
+};
+
int main(int argc, char *argv[])
{
/*
@@ -1039,6 +1061,8 @@ int main(int argc, char *argv[])
debug_stacks_init();
DSTACK(__FUNCTION_NAME);
+
+ initializeMaterialProperties();
try
{
@@ -1541,8 +1565,7 @@ int main(int argc, char *argv[])
L"Chat here\nOther line\nOther line\nOther line\nOther line",
core::rect<s32>(70, 60, 795, 150),
false, true);
- core::list<std::wstring> chat_lines;
- //chat_lines.push_back(L"Minetest-c55 up and running!");
+ core::list<ChatLine> chat_lines;
/*
Some statistics are collected in these
@@ -2102,34 +2125,38 @@ int main(int argc, char *argv[])
if(g_input->getLeftState())
{
MapNode n = client.getNode(nodepos);
-
- // TODO: Get this from some table that is sent by server
- float dig_time_complete = 0.5;
- if(n.d == CONTENT_STONE || n.d == CONTENT_COALSTONE)
+
+ // Get tool name. Default is "" = bare hands
+ std::string toolname = "";
+ InventoryList *mlist = local_inventory.getList("main");
+ if(mlist != NULL)
{
- dig_time_complete = 10.0;
-
- InventoryList *mlist = local_inventory.getList("main");
- if(mlist != NULL)
+ InventoryItem *item = mlist->getItem(g_selected_item);
+ if(item && (std::string)item->getName() == "ToolItem")
{
- InventoryItem *item = mlist->getItem(g_selected_item);
- if(item && (std::string)item->getName() == "ToolItem")
- {
- ToolItem *titem = (ToolItem*)item;
- if(titem->getToolName() == "WPick")
- {
- dig_time_complete = 1.2;
- }
- else if(titem->getToolName() == "STPick")
- {
- dig_time_complete = 0.6;
- }
- }
+ ToolItem *titem = (ToolItem*)item;
+ toolname = titem->getToolName();
}
}
- else if(n.d == CONTENT_TORCH)
+
+ // Get digging properties for material and tool
+ u8 material = n.d;
+ DiggingProperties prop =
+ getDiggingProperties(material, toolname);
+
+ float dig_time_complete = 0.0;
+
+ if(prop.diggable == false)
+ {
+ /*dstream<<"Material "<<(int)material
+ <<" not diggable with \""
+ <<toolname<<"\""<<std::endl;*/
+ // I guess nobody will wait for this long
+ dig_time_complete = 10000000.0;
+ }
+ else
{
- dig_time_complete = 0.0;
+ dig_time_complete = prop.time;
}
if(dig_time_complete >= 0.001)
@@ -2305,14 +2332,14 @@ int main(int argc, char *argv[])
Get chat messages from client
*/
{
- // Get messages
+ // Get new messages
std::wstring message;
while(client.getChatMessage(message))
{
- chat_lines.push_back(message);
- if(chat_lines.size() > 5)
+ chat_lines.push_back(ChatLine(message));
+ if(chat_lines.size() > 7)
{
- core::list<std::wstring>::Iterator
+ core::list<ChatLine>::Iterator
i = chat_lines.begin();
chat_lines.erase(i);
}
@@ -2320,11 +2347,24 @@ int main(int argc, char *argv[])
// Append them to form the whole static text and throw
// it to the gui element
std::wstring whole;
- for(core::list<std::wstring>::Iterator
+ u16 to_be_removed_count = 0;
+ for(core::list<ChatLine>::Iterator
i = chat_lines.begin();
i != chat_lines.end(); i++)
{
- whole += (*i) + L'\n';
+ (*i).age += dtime;
+ if((*i).age > 30.0)
+ {
+ to_be_removed_count++;
+ continue;
+ }
+ whole += (*i).text + L'\n';
+ }
+ for(u16 i=0; i<to_be_removed_count; i++)
+ {
+ core::list<ChatLine>::Iterator
+ i = chat_lines.begin();
+ chat_lines.erase(i);
}
chat_guitext->setText(whole.c_str());
// Update gui element size and position
diff --git a/src/mapnode.cpp b/src/mapnode.cpp
index a8e9f07fc..3dae653ed 100644
--- a/src/mapnode.cpp
+++ b/src/mapnode.cpp
@@ -43,6 +43,7 @@ u16 g_content_tiles[USEFUL_CONTENT_COUNT][6] =
{TILE_WATER,TILE_WATER,TILE_WATER,TILE_WATER,TILE_WATER,TILE_WATER},
{TILE_CLOUD,TILE_CLOUD,TILE_CLOUD,TILE_CLOUD,TILE_CLOUD,TILE_CLOUD},
{TILE_COALSTONE,TILE_COALSTONE,TILE_COALSTONE,TILE_COALSTONE,TILE_COALSTONE,TILE_COALSTONE},
+ {TILE_WOOD,TILE_WOOD,TILE_WOOD,TILE_WOOD,TILE_WOOD,TILE_WOOD},
};
const char * g_content_inventory_textures[USEFUL_CONTENT_COUNT] =
@@ -59,5 +60,6 @@ const char * g_content_inventory_textures[USEFUL_CONTENT_COUNT] =
"../data/water.png",
"../data/cloud.png",
"../data/coalstone.png",
+ "../data/wood.png",
};
diff --git a/src/mapnode.h b/src/mapnode.h
index ad85d88e8..659d585b5 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -77,6 +77,7 @@ enum Content
CONTENT_OCEAN,
CONTENT_CLOUD,
CONTENT_COALSTONE,
+ CONTENT_WOOD,
// This is set to the number of the actual values in this enum
USEFUL_CONTENT_COUNT
@@ -96,6 +97,7 @@ inline bool light_propagates_content(u8 m)
/*
If true, the material allows lossless sunlight propagation.
+ NOTE: It doesn't seem to go through torches regardlessly of this
*/
inline bool sunlight_propagates_content(u8 m)
{
@@ -153,14 +155,12 @@ inline bool content_buildable_to(u8 m)
*/
inline bool is_ground_content(u8 m)
{
- return(
- m == CONTENT_STONE ||
- m == CONTENT_GRASS ||
- m == CONTENT_GRASS_FOOTSTEPS ||
- m == CONTENT_MESE ||
- m == CONTENT_MUD ||
- m == CONTENT_COALSTONE
- );
+ return (m != CONTENT_WATER
+ && m != CONTENT_TORCH
+ && m != CONTENT_TREE
+ && m != CONTENT_LEAVES
+ && m != CONTENT_OCEAN
+ && m != CONTENT_CLOUD);
}
inline bool is_mineral(u8 c)
@@ -169,12 +169,18 @@ inline bool is_mineral(u8 c)
|| c == CONTENT_COALSTONE);
}
-/*inline bool content_has_faces(u8 c)
+inline bool liquid_replaces_content(u8 c)
{
- return (m != CONTENT_IGNORE
- && m != CONTENT_AIR
- && m != CONTENT_TORCH);
-}*/
+ return (c == CONTENT_AIR || c == CONTENT_TORCH);
+}
+
+/*
+ When placing a node, drection info is added to it if this is true
+*/
+inline bool content_directional(u8 c)
+{
+ return (c == CONTENT_TORCH);
+}
/*
Nodes make a face if contents differ and solidness differs.
@@ -201,19 +207,6 @@ inline u8 face_contents(u8 m1, u8 m2)
return 2;
}
-inline bool liquid_replaces_content(u8 c)
-{
- return (c == CONTENT_AIR || c == CONTENT_TORCH);
-}
-
-/*
- When placing a node, drection info is added to it if this is true
-*/
-inline bool content_directional(u8 c)
-{
- return (c == CONTENT_TORCH);
-}
-
/*
Packs directions like (1,0,0), (1,-1,0)
*/
diff --git a/src/materials.cpp b/src/materials.cpp
new file mode 100644
index 000000000..5c1419580
--- /dev/null
+++ b/src/materials.cpp
@@ -0,0 +1,75 @@
+#include "materials.h"
+
+#define MATERIAL_PROPERTIES_COUNT 256
+
+// These correspond to the CONTENT_* constants
+MaterialProperties g_material_properties[MATERIAL_PROPERTIES_COUNT];
+
+bool g_material_properties_initialized = false;
+
+void setStoneLikeDiggingProperties(u8 material, float toughness)
+{
+ g_material_properties[material].setDiggingProperties("",
+ DiggingProperties(true, 15.0*toughness, 0));
+ g_material_properties[material].setDiggingProperties("WPick",
+ DiggingProperties(true, 2.0*toughness, 65535./20.*toughness));
+ g_material_properties[material].setDiggingProperties("STPick",
+ DiggingProperties(true, 1.0*toughness, 65535./50.*toughness));
+}
+
+void initializeMaterialProperties()
+{
+ /*
+ Now, the g_material_properties array is already initialized
+ by the constructors to such that no digging is possible.
+
+ Add some digging properties to them.
+ */
+
+ setStoneLikeDiggingProperties(CONTENT_STONE, 1.0);
+
+ g_material_properties[CONTENT_GRASS].setDiggingProperties("",
+ DiggingProperties(true, 0.5, 0));
+
+ g_material_properties[CONTENT_TORCH].setDiggingProperties("",
+ DiggingProperties(true, 0.0, 0));
+
+ g_material_properties[CONTENT_TREE].setDiggingProperties("",
+ DiggingProperties(true, 1.5, 0));
+
+ g_material_properties[CONTENT_LEAVES].setDiggingProperties("",
+ DiggingProperties(true, 0.5, 0));
+
+ g_material_properties[CONTENT_GRASS_FOOTSTEPS].setDiggingProperties("",
+ DiggingProperties(true, 0.5, 0));
+
+ setStoneLikeDiggingProperties(CONTENT_MESE, 0.5);
+
+ g_material_properties[CONTENT_MUD].setDiggingProperties("",
+ DiggingProperties(true, 0.5, 0));
+
+ setStoneLikeDiggingProperties(CONTENT_COALSTONE, 1.5);
+
+ g_material_properties[CONTENT_WOOD].setDiggingProperties("",
+ DiggingProperties(true, 1.0, 0));
+
+
+ g_material_properties_initialized = true;
+}
+
+MaterialProperties * getMaterialProperties(u8 material)
+{
+ assert(g_material_properties_initialized);
+ return &g_material_properties[material];
+}
+
+DiggingProperties getDiggingProperties(u8 material, const std::string &tool)
+{
+ MaterialProperties *mprop = getMaterialProperties(material);
+ if(mprop == NULL)
+ // Not diggable
+ return DiggingProperties();
+
+ return mprop->getDiggingProperties(tool);
+}
+
diff --git a/src/materials.h b/src/materials.h
new file mode 100644
index 000000000..ae2deac88
--- /dev/null
+++ b/src/materials.h
@@ -0,0 +1,98 @@
+/*
+Minetest-c55
+Copyright (C) 2010 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
+(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.
+
+You should have received a copy of the GNU 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.
+*/
+
+#ifndef MATERIALS_HEADER
+#define MATERIALS_HEADER
+
+/*
+ Material properties
+*/
+
+#include "common_irrlicht.h"
+#include "inventory.h"
+#include <string>
+
+struct DiggingProperties
+{
+ DiggingProperties():
+ diggable(false),
+ time(0.0),
+ wear(0)
+ {
+ }
+ DiggingProperties(bool a_diggable, float a_time, u16 a_wear):
+ diggable(a_diggable),
+ time(a_time),
+ wear(a_wear)
+ {
+ }
+ bool diggable;
+ // Digging time in seconds
+ float time;
+ // Caused wear
+ u16 wear;
+};
+
+class MaterialProperties
+{
+public:
+ MaterialProperties()
+ {
+ dstream<<__FUNCTION_NAME<<std::endl;
+ }
+
+ void setDiggingProperties(const std::string toolname,
+ const DiggingProperties &prop)
+ {
+ m_digging_properties[toolname] = prop;
+ }
+
+ DiggingProperties getDiggingProperties(const std::string toolname)
+ {
+ core::map<std::string, DiggingProperties>::Node *n;
+ n = m_digging_properties.find(toolname);
+ if(n == NULL)
+ {
+ // Not diggable by this tool, try to get defaults
+ n = m_digging_properties.find("");
+ if(n == NULL)
+ {
+ // Not diggable at all
+ return DiggingProperties();
+ }
+ }
+ // Return found properties
+ return n->getValue();
+ }
+
+private:
+ // toolname="": default properties (digging by hand)
+ // Key is toolname
+ core::map<std::string, DiggingProperties> m_digging_properties;
+};
+
+void initializeMaterialProperties();
+
+// Material correspond to the CONTENT_* constants
+MaterialProperties * getMaterialProperties(u8 material);
+// For getting the default properties, set tool=""
+DiggingProperties getDiggingProperties(u8 material, const std::string &tool);
+
+#endif
+
diff --git a/src/server.cpp b/src/server.cpp
index c0af61b98..e1f7ba739 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "main.h"
#include "constants.h"
#include "voxel.h"
+#include "materials.h"
#define BLOCK_EMERGE_FLAG_FROMDISK (1<<0)
@@ -1821,9 +1822,47 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
if(g_settings.getBool("creative_mode") == false)
{
- // Add to inventory and send inventory
+ /*
+ Wear out tool
+ */
+ InventoryList *mlist = player->inventory.getList("main");
+ if(mlist != NULL)
+ {
+ InventoryItem *item = mlist->getItem(item_i);
+ if(item && (std::string)item->getName() == "ToolItem")
+ {
+ ToolItem *titem = (ToolItem*)item;
+ std::string toolname = titem->getToolName();
+
+ // Get digging properties for material and tool
+ DiggingProperties prop =
+ getDiggingProperties(material, toolname);
+
+ if(prop.diggable == false)
+ {
+ derr_server<<"Server: WARNING: Player digged"
+ <<" with impossible material + tool"
+ <<" combination"<<std::endl;
+ }
+
+ bool weared_out = titem->addWear(prop.wear);
+
+ if(weared_out)
+ {
+ mlist->deleteItem(item_i);
+ }
+ }
+ }
+
+ /*
+ Add digged item to inventory
+ */
InventoryItem *item = new MaterialItem(material, 1);
player->inventory.addItem("main", item);
+
+ /*
+ Send inventory
+ */
SendInventory(player->peer_id);
}
@@ -2404,138 +2443,6 @@ void Server::peerAdded(con::Peer *peer)
c.peer_id = peer->id;
c.timeout = false;
m_peer_change_queue.push_back(c);
-
-#if 0
- // NOTE: Connection is already locked when this is called.
- // NOTE: Environment is already locked when this is called.
-
- // Error check
- core::map<u16, RemoteClient*>::Node *n;
- n = m_clients.find(peer->id);
- // The client shouldn't already exist
- assert(n == NULL);
-
- // Create client
- RemoteClient *client = new RemoteClient();
- client->peer_id = peer->id;
- m_clients.insert(client->peer_id, client);
-
- // Create player
- {
- Player *player = m_env.getPlayer(peer->id);
-
- // The player shouldn't already exist
- assert(player == NULL);
-
- player = new ServerRemotePlayer();
- player->peer_id = peer->id;
-
- /*
- Set player position
- */
-
- // We're going to throw the player to this position
- //v2s16 nodepos(29990,29990);
- //v2s16 nodepos(9990,9990);
- v2s16 nodepos(0,0);
- v2s16 sectorpos = getNodeSectorPos(nodepos);
- // Get zero sector (it could have been unloaded to disk)
- m_env.getMap().emergeSector(sectorpos);
- // Get ground height at origin
- f32 groundheight = m_env.getMap().getGroundHeight(nodepos, true);
- // The sector should have been generated -> groundheight exists
- assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
- // Don't go underwater
- if(groundheight < WATER_LEVEL)
- groundheight = WATER_LEVEL;
-
- player->setPosition(intToFloat(v3s16(
- nodepos.X,
- groundheight + 1,
- nodepos.Y
- )));
-
- /*
- Add player to environment
- */
-
- m_env.addPlayer(player);
-
- /*
- Add stuff to inventory
- */
-
- if(g_settings.getBool("creative_mode"))
- {
- // Give a good pick
- {
- InventoryItem *item = new ToolItem("STPick", 32000);
- void* r = player->inventory.addItem("main", item);
- assert(r == NULL);
- }
- // Give all materials
- assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
- for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
- {
- // Skip some materials
- if(i == CONTENT_OCEAN)
- continue;
-
- InventoryItem *item = new MaterialItem(i, 1);
- player->inventory.addItem("main", item);
- }
- // Sign
- {
- InventoryItem *item = new MapBlockObjectItem("Sign Example text");
- void* r = player->inventory.addItem("main", item);
- assert(r == NULL);
- }
- /*// Rat
- {
- InventoryItem *item = new MapBlockObjectItem("Rat");
- bool r = player->inventory.addItem("main", item);
- assert(r == true);
- }*/
- }
- else
- {
- {
- InventoryItem *item = new CraftItem("Stick", 4);
- void* r = player->inventory.addItem("main", item);
- assert(r == NULL);
- }
- {
- InventoryItem *item = new ToolItem("WPick", 32000);
- void* r = player->inventory.addItem("main", item);
- assert(r == NULL);
- }
- {
- InventoryItem *item = new ToolItem("STPick", 32000);
- void* r = player->inventory.addItem("main", item);
- assert(r == NULL);
- }
- /*// Give some lights
- {
- InventoryItem *item = new MaterialItem(CONTENT_TORCH, 999);
- bool r = player->inventory.addItem("main", item);
- assert(r == true);
- }
- // and some signs
- for(u16 i=0; i<4; i++)
- {
- InventoryItem *item = new MapBlockObjectItem("Sign Example text");
- bool r = player->inventory.addItem("main", item);
- assert(r == true);
- }*/
- /*// Give some other stuff
- {
- InventoryItem *item = new MaterialItem(CONTENT_TREE, 999);
- bool r = player->inventory.addItem("main", item);
- assert(r == true);
- }*/
- }
- }
-#endif
}
void Server::deletingPeer(con::Peer *peer, bool timeout)
@@ -2549,46 +2456,6 @@ void Server::deletingPeer(con::Peer *peer, bool timeout)
c.peer_id = peer->id;
c.timeout = timeout;
m_peer_change_queue.push_back(c);
-
-#if 0
- // NOTE: Connection is already locked when this is called.
-
- // NOTE: Environment is already locked when this is called.
- // NOTE: Locking environment cannot be moved here because connection
- // is already locked and env has to be locked before
-
- // Error check
- core::map<u16, RemoteClient*>::Node *n;
- n = m_clients.find(peer->id);
- // The client should exist
- assert(n != NULL);
-
- // Send information about leaving in chat
- {
- std::wstring name = L"unknown";
- Player *player = m_env.getPlayer(peer->id);
- if(player != NULL)
- name = narrow_to_wide(player->getName());
-
- std::wstring message;
- message += L"*** ";
- message += name;
- message += L" left game";
- BroadcastChatMessage(message);
- }
-
- // Delete player
- {
- m_env.removePlayer(peer->id);
- }
-
- // Delete client
- delete m_clients[peer->id];
- m_clients.remove(peer->id);
-
- // Send player info to all clients
- SendPlayerInfos();
-#endif
}
void Server::SendObjectData(float dtime)
@@ -2647,6 +2514,159 @@ void Server::SendPlayerInfos()
m_con.SendToAll(0, data, true);
}
+enum ItemSpecType
+{
+ ITEM_NONE,
+ ITEM_MATERIAL,
+ ITEM_CRAFT,
+ ITEM_TOOL,
+ ITEM_MBO
+};
+
+struct ItemSpec
+{
+ ItemSpec():
+ type(ITEM_NONE)
+ {
+ }
+ ItemSpec(enum ItemSpecType a_type, std::string a_name):
+ type(a_type),
+ name(a_name),
+ num(65535)
+ {
+ }
+ ItemSpec(enum ItemSpecType a_type, u16 a_num):
+ type(a_type),
+ name(""),
+ num(a_num)
+ {
+ }
+ enum ItemSpecType type;
+ // Only other one of these is used
+ std::string name;
+ u16 num;
+};
+
+/*
+ items: a pointer to an array of 9 pointers to items
+ specs: a pointer to an array of 9 ItemSpecs
+*/
+bool checkItemCombination(InventoryItem **items, ItemSpec *specs)
+{
+ u16 items_min_x = 100;
+ u16 items_max_x = 100;
+ u16 items_min_y = 100;
+ u16 items_max_y = 100;
+ for(u16 y=0; y<3; y++)
+ for(u16 x=0; x<3; x++)
+ {
+ if(items[y*3 + x] == NULL)
+ continue;
+ if(items_min_x == 100 || x < items_min_x)
+ items_min_x = x;
+ if(items_min_y == 100 || y < items_min_y)
+ items_min_y = y;
+ if(items_max_x == 100 || x > items_max_x)
+ items_max_x = x;
+ if(items_max_y == 100 || y > items_max_y)
+ items_max_y = y;
+ }
+ // No items at all, just return false
+ if(items_min_x == 100)
+ return false;
+
+ u16 items_w = items_max_x - items_min_x + 1;
+ u16 items_h = items_max_y - items_min_y + 1;
+
+ u16 specs_min_x = 100;
+ u16 specs_max_x = 100;
+ u16 specs_min_y = 100;
+ u16 specs_max_y = 100;
+ for(u16 y=0; y<3; y++)
+ for(u16 x=0; x<3; x++)
+ {
+ if(specs[y*3 + x].type == ITEM_NONE)
+ continue;
+ if(specs_min_x == 100 || x < specs_min_x)
+ specs_min_x = x;
+ if(specs_min_y == 100 || y < specs_min_y)
+ specs_min_y = y;
+ if(specs_max_x == 100 || x > specs_max_x)
+ specs_max_x = x;
+ if(specs_max_y == 100 || y > specs_max_y)
+ specs_max_y = y;
+ }
+ // No specs at all, just return false
+ if(specs_min_x == 100)
+ return false;
+
+ u16 specs_w = specs_max_x - specs_min_x + 1;
+ u16 specs_h = specs_max_y - specs_min_y + 1;
+
+ // Different sizes
+ if(items_w != specs_w || items_h != specs_h)
+ return false;
+
+ for(u16 y=0; y<specs_h; y++)
+ for(u16 x=0; x<specs_w; x++)
+ {
+ u16 items_x = items_min_x + x;
+ u16 items_y = items_min_y + y;
+ u16 specs_x = specs_min_x + x;
+ u16 specs_y = specs_min_y + y;
+ InventoryItem *item = items[items_y * 3 + items_x];
+ ItemSpec &spec = specs[specs_y * 3 + specs_x];
+
+ if(spec.type == ITEM_NONE)
+ {
+ // Has to be no item
+ if(item != NULL)
+ return false;
+ continue;
+ }
+
+ // There should be an item
+ if(item == NULL)
+ return false;
+
+ std::string itemname = item->getName();
+
+ if(spec.type == ITEM_MATERIAL)
+ {
+ if(itemname != "MaterialItem")
+ return false;
+ MaterialItem *mitem = (MaterialItem*)item;
+ if(mitem->getMaterial() != spec.num)
+ return false;
+ }
+ else if(spec.type == ITEM_CRAFT)
+ {
+ if(itemname != "CraftItem")
+ return false;
+ CraftItem *mitem = (CraftItem*)item;
+ if(mitem->getSubName() != spec.name)
+ return false;
+ }
+ else if(spec.type == ITEM_TOOL)
+ {
+ // Not supported yet
+ assert(0);
+ }
+ else if(spec.type == ITEM_MBO)
+ {
+ // Not supported yet
+ assert(0);
+ }
+ else
+ {
+ // Not supported yet
+ assert(0);
+ }
+ }
+
+ return true;
+}
+
void Server::SendInventory(u16 peer_id)
{
DSTACK(__FUNCTION_NAME);
@@ -2670,31 +2690,96 @@ void Server::SendInventory(u16 peer_id)
{
items[i] = clist->getItem(i);
}
+
+ bool found = false;
+
+ // Wood
+ if(!found)
+ {
+ ItemSpec specs[9];
+ specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
+ if(checkItemCombination(items, specs))
+ {
+ rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
+ found = true;
+ }
+ }
+
+ // Stick
+ if(!found)
+ {
+ ItemSpec specs[9];
+ specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ if(checkItemCombination(items, specs))
+ {
+ rlist->addItem(new CraftItem("Stick", 4));
+ found = true;
+ }
+ }
+
// Sign
- if(clist->getUsedSlots() == 1 && items[0])
+ if(!found)
{
- if((std::string)items[0]->getName() == "MaterialItem")
+ ItemSpec specs[9];
+ specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
+ if(checkItemCombination(items, specs))
{
- MaterialItem *mitem = (MaterialItem*)items[0];
- if(mitem->getMaterial() == CONTENT_TREE)
- {
- rlist->addItem(new MapBlockObjectItem("Sign"));
- }
+ rlist->addItem(new MapBlockObjectItem("Sign"));
+ found = true;
}
}
+
// Torch
- if(clist->getUsedSlots() == 2 && items[0] && items[3])
- {
- if(
- (std::string)items[0]->getName() == "MaterialItem"
- && ((MaterialItem*)items[0])->getMaterial() == CONTENT_COALSTONE
- && (std::string)items[3]->getName() == "MaterialItem"
- && ((MaterialItem*)items[3])->getMaterial() == CONTENT_TREE
- )
+ if(!found)
+ {
+ ItemSpec specs[9];
+ specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COALSTONE);
+ specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
+ if(checkItemCombination(items, specs))
{
rlist->addItem(new MaterialItem(CONTENT_TORCH, 4));
+ found = true;
}
}
+
+ // Wooden pick
+ if(!found)
+ {
+ ItemSpec specs[9];
+ specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
+ specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
+ specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
+ if(checkItemCombination(items, specs))
+ {
+ rlist->addItem(new ToolItem("WPick", 0));
+ found = true;
+ }
+ }
+
+ // Stone pick
+ if(!found)
+ {
+ ItemSpec specs[9];
+ specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
+ specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
+ specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
+ specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
+ specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
+ if(checkItemCombination(items, specs))
+ {
+ rlist->addItem(new ToolItem("STPick", 0));
+ found = true;
+ }
+ }
+
}
/*
@@ -2959,6 +3044,16 @@ void Server::handlePeerChange(PeerChange &c)
else
{
{
+ InventoryItem *item = new MaterialItem(CONTENT_COALSTONE, 6);
+ void* r = player->inventory.addItem("main", item);
+ assert(r == NULL);
+ }
+ {
+ InventoryItem *item = new MaterialItem(CONTENT_WOOD, 6);
+ void* r = player->inventory.addItem("main", item);
+ assert(r == NULL);
+ }
+ {
InventoryItem *item = new CraftItem("Stick", 4);
void* r = player->inventory.addItem("main", item);
assert(r == NULL);
diff --git a/src/servermain.cpp b/src/servermain.cpp
index c2d697cfe..b8bc0dc54 100644
--- a/src/servermain.cpp
+++ b/src/servermain.cpp
@@ -123,6 +123,8 @@ int main(int argc, char *argv[])
DSTACK(__FUNCTION_NAME);
+ initializeMaterialProperties();
+
try
{
diff --git a/src/tile.cpp b/src/tile.cpp
index 25d9c00d0..a9470dc8e 100644
--- a/src/tile.cpp
+++ b/src/tile.cpp
@@ -36,6 +36,7 @@ const char * g_tile_texture_paths[TILES_COUNT] =
"../data/mud_with_grass.png",
"../data/cloud.png",
"../data/coalstone.png",
+ "../data/wood.png",
};
const char * tile_texture_path_get(u32 i)
diff --git a/src/tile.h b/src/tile.h
index 9823160c2..c35c27e64 100644
--- a/src/tile.h
+++ b/src/tile.h
@@ -40,6 +40,7 @@ enum TileID
TILE_MUD_WITH_GRASS,
TILE_CLOUD,
TILE_COALSTONE,
+ TILE_WOOD,
// Count of tile ids
TILES_COUNT
_("Load configuration from specified file")))); allowed_options->insert(std::make_pair("port", ValueSpec(VALUETYPE_STRING, _("Set network port (UDP)")))); allowed_options->insert(std::make_pair("run-unittests", ValueSpec(VALUETYPE_FLAG, _("Run the unit tests and exit")))); allowed_options->insert(std::make_pair("map-dir", ValueSpec(VALUETYPE_STRING, _("Same as --world (deprecated)")))); allowed_options->insert(std::make_pair("world", ValueSpec(VALUETYPE_STRING, _("Set world path (implies local game if used with option --go)")))); allowed_options->insert(std::make_pair("worldname", ValueSpec(VALUETYPE_STRING, _("Set world by name (implies local game if used with option --go)")))); allowed_options->insert(std::make_pair("worldlist", ValueSpec(VALUETYPE_STRING, _("Get list of worlds ('path' lists paths, " "'name' lists names, 'both' lists both)")))); allowed_options->insert(std::make_pair("quiet", ValueSpec(VALUETYPE_FLAG, _("Print to console errors only")))); allowed_options->insert(std::make_pair("color", ValueSpec(VALUETYPE_STRING, _("Coloured logs ('always', 'never' or 'auto'), defaults to 'auto'" )))); allowed_options->insert(std::make_pair("info", ValueSpec(VALUETYPE_FLAG, _("Print more information to console")))); allowed_options->insert(std::make_pair("verbose", ValueSpec(VALUETYPE_FLAG, _("Print even more information to console")))); allowed_options->insert(std::make_pair("trace", ValueSpec(VALUETYPE_FLAG, _("Print enormous amounts of information to log and console")))); allowed_options->insert(std::make_pair("logfile", ValueSpec(VALUETYPE_STRING, _("Set logfile path ('' = no logging)")))); allowed_options->insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING, _("Set gameid (\"--gameid list\" prints available ones)")))); allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING, _("Migrate from current map backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("migrate-players", ValueSpec(VALUETYPE_STRING, _("Migrate from current players backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("migrate-auth", ValueSpec(VALUETYPE_STRING, _("Migrate from current auth backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); #ifndef SERVER allowed_options->insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG, _("Show available video modes")))); allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, _("Run speed tests")))); allowed_options->insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING, _("Address to connect to. ('' = local game)")))); allowed_options->insert(std::make_pair("random-input", ValueSpec(VALUETYPE_FLAG, _("Enable random user input, for testing")))); allowed_options->insert(std::make_pair("server", ValueSpec(VALUETYPE_FLAG, _("Run dedicated server")))); allowed_options->insert(std::make_pair("name", ValueSpec(VALUETYPE_STRING, _("Set player name")))); allowed_options->insert(std::make_pair("password", ValueSpec(VALUETYPE_STRING, _("Set password")))); allowed_options->insert(std::make_pair("password-file", ValueSpec(VALUETYPE_STRING, _("Set password from contents of file")))); allowed_options->insert(std::make_pair("go", ValueSpec(VALUETYPE_FLAG, _("Disable main menu")))); allowed_options->insert(std::make_pair("console", ValueSpec(VALUETYPE_FLAG, _("Starts with the console (Windows only)")))); #endif } static void print_help(const OptionList &allowed_options) { std::cout << _("Allowed options:") << std::endl; print_allowed_options(allowed_options); } static void print_allowed_options(const OptionList &allowed_options) { for (const auto &allowed_option : allowed_options) { std::ostringstream os1(std::ios::binary); os1 << " --" << allowed_option.first; if (allowed_option.second.type != VALUETYPE_FLAG) os1 << _(" <value>"); std::cout << padStringRight(os1.str(), 30); if (allowed_option.second.help) std::cout << allowed_option.second.help; std::cout << std::endl; } } static void print_version() { std::cout << PROJECT_NAME_C " " << g_version_hash << " (" << porting::getPlatformName() << ")" << std::endl; #ifndef SERVER std::cout << "Using Irrlicht " IRRLICHT_SDK_VERSION << std::endl; #endif std::cout << g_build_info << std::endl; } static void list_game_ids() { std::set<std::string> gameids = getAvailableGameIds(); for (const std::string &gameid : gameids) std::cout << gameid <<std::endl; } static void list_worlds(bool print_name, bool print_path) { std::cout << _("Available worlds:") << std::endl; std::vector<WorldSpec> worldspecs = getAvailableWorlds(); print_worldspecs(worldspecs, std::cout, print_name, print_path); } static void print_worldspecs(const std::vector<WorldSpec> &worldspecs, std::ostream &os, bool print_name, bool print_path) { for (const WorldSpec &worldspec : worldspecs) { std::string name = worldspec.name; std::string path = worldspec.path; if (print_name && print_path) { os << "\t" << name << "\t\t" << path << std::endl; } else if (print_name) { os << "\t" << name << std::endl; } else if (print_path) { os << "\t" << path << std::endl; } } } static void print_modified_quicktune_values() { bool header_printed = false; std::vector<std::string> names = getQuicktuneNames(); for (const std::string &name : names) { QuicktuneValue val = getQuicktuneValue(name); if (!val.modified) continue; if (!header_printed) { dstream << "Modified quicktune values:" << std::endl; header_printed = true; } dstream << name << " = " << val.getString() << std::endl; } } static bool setup_log_params(const Settings &cmd_args) { // Quiet mode, print errors only if (cmd_args.getFlag("quiet")) { g_logger.removeOutput(&stderr_output); g_logger.addOutputMaxLevel(&stderr_output, LL_ERROR); } // Coloured log messages (see log.h) std::string color_mode; if (cmd_args.exists("color")) { color_mode = cmd_args.get("color"); #if !defined(_WIN32) } else { char *color_mode_env = getenv("MT_LOGCOLOR"); if (color_mode_env) color_mode = color_mode_env; #endif } if (color_mode != "") { if (color_mode == "auto") { Logger::color_mode = LOG_COLOR_AUTO; } else if (color_mode == "always") { Logger::color_mode = LOG_COLOR_ALWAYS; } else if (color_mode == "never") { Logger::color_mode = LOG_COLOR_NEVER; } else { errorstream << "Invalid color mode: " << color_mode << std::endl; return false; } } // If trace is enabled, enable logging of certain things if (cmd_args.getFlag("trace")) { dstream << _("Enabling trace level debug output") << std::endl; g_logger.setTraceEnabled(true); dout_con_ptr = &verbosestream; // This is somewhat old socket_enable_debug_output = true; // Sockets doesn't use log.h } // In certain cases, output info level on stderr if (cmd_args.getFlag("info") || cmd_args.getFlag("verbose") || cmd_args.getFlag("trace") || cmd_args.getFlag("speedtests")) g_logger.addOutput(&stderr_output, LL_INFO); // In certain cases, output verbose level on stderr if (cmd_args.getFlag("verbose") || cmd_args.getFlag("trace")) g_logger.addOutput(&stderr_output, LL_VERBOSE); return true; } static bool create_userdata_path() { bool success; #ifdef __ANDROID__ if (!fs::PathExists(porting::path_user)) { success = fs::CreateDir(porting::path_user); } else { success = true; } porting::copyAssets(); #else // Create user data directory success = fs::CreateDir(porting::path_user); #endif return success; } static bool init_common(const Settings &cmd_args, int argc, char *argv[]) { startup_message(); set_default_settings(g_settings); // Initialize sockets sockets_init(); atexit(sockets_cleanup); if (!read_config_file(cmd_args)) return false; init_log_streams(cmd_args); // Initialize random seed srand(time(0)); mysrand(time(0)); // Initialize HTTP fetcher httpfetch_init(g_settings->getS32("curl_parallel_limit")); init_gettext(porting::path_locale.c_str(), g_settings->get("language"), argc, argv); return true; } static void startup_message() { infostream << PROJECT_NAME << " " << _("with") << " SER_FMT_VER_HIGHEST_READ=" << (int)SER_FMT_VER_HIGHEST_READ << ", " << g_build_info << std::endl; } static bool read_config_file(const Settings &cmd_args) { // Path of configuration file in use sanity_check(g_settings_path == ""); // Sanity check if (cmd_args.exists("config")) { bool r = g_settings->readConfigFile(cmd_args.get("config").c_str()); if (!r) { errorstream << "Could not read configuration from \"" << cmd_args.get("config") << "\"" << std::endl; return false; } g_settings_path = cmd_args.get("config"); } else { std::vector<std::string> filenames; filenames.push_back(porting::path_user + DIR_DELIM + "minetest.conf"); // Legacy configuration file location filenames.push_back(porting::path_user + DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); #if RUN_IN_PLACE // Try also from a lower level (to aid having the same configuration // for many RUN_IN_PLACE installs) filenames.push_back(porting::path_user + DIR_DELIM + ".." + DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); #endif for (const std::string &filename : filenames) { bool r = g_settings->readConfigFile(filename.c_str()); if (r) { g_settings_path = filename; break; } } // If no path found, use the first one (menu creates the file) if (g_settings_path.empty()) g_settings_path = filenames[0]; } return true; } static void init_log_streams(const Settings &cmd_args) { std::string log_filename = porting::path_user + DIR_DELIM + DEBUGFILE; if (cmd_args.exists("logfile")) log_filename = cmd_args.get("logfile"); g_logger.removeOutput(&file_log_output); std::string conf_loglev = g_settings->get("debug_log_level"); // Old integer format if (std::isdigit(conf_loglev[0])) { warningstream << "Deprecated use of debug_log_level with an " "integer value; please update your configuration." << std::endl; static const char *lev_name[] = {"", "error", "action", "info", "verbose"}; int lev_i = atoi(conf_loglev.c_str()); if (lev_i < 0 || lev_i >= (int)ARRLEN(lev_name)) { warningstream << "Supplied invalid debug_log_level!" " Assuming action level." << std::endl; lev_i = 2; } conf_loglev = lev_name[lev_i]; } if (log_filename.empty() || conf_loglev.empty()) // No logging return; LogLevel log_level = Logger::stringToLevel(conf_loglev); if (log_level == LL_MAX) { warningstream << "Supplied unrecognized debug_log_level; " "using maximum." << std::endl; } file_log_output.setFile(log_filename, g_settings->getU64("debug_log_size_max") * 1000000); g_logger.addOutputMaxLevel(&file_log_output, log_level); } static bool game_configure(GameParams *game_params, const Settings &cmd_args) { game_configure_port(game_params, cmd_args); if (!game_configure_world(game_params, cmd_args)) { errorstream << "No world path specified or found." << std::endl; return false; } game_configure_subgame(game_params, cmd_args); return true; } static void game_configure_port(GameParams *game_params, const Settings &cmd_args) { if (cmd_args.exists("port")) game_params->socket_port = cmd_args.getU16("port"); else game_params->socket_port = g_settings->getU16("port"); if (game_params->socket_port == 0) game_params->socket_port = DEFAULT_SERVER_PORT; } static bool game_configure_world(GameParams *game_params, const Settings &cmd_args) { if (get_world_from_cmdline(game_params, cmd_args)) return true; if (get_world_from_config(game_params, cmd_args)) return true; return auto_select_world(game_params); } static bool get_world_from_cmdline(GameParams *game_params, const Settings &cmd_args) { std::string commanded_world; // World name std::string commanded_worldname; if (cmd_args.exists("worldname")) commanded_worldname = cmd_args.get("worldname"); // If a world name was specified, convert it to a path if (!commanded_worldname.empty()) { // Get information about available worlds std::vector<WorldSpec> worldspecs = getAvailableWorlds(); bool found = false; for (const WorldSpec &worldspec : worldspecs) { std::string name = worldspec.name; if (name == commanded_worldname) { dstream << _("Using world specified by --worldname on the " "command line") << std::endl; commanded_world = worldspec.path; found = true; break; } } if (!found) { dstream << _("World") << " '" << commanded_worldname << _("' not available. Available worlds:") << std::endl; print_worldspecs(worldspecs, dstream); return false; } game_params->world_path = get_clean_world_path(commanded_world); return !commanded_world.empty(); } if (cmd_args.exists("world")) commanded_world = cmd_args.get("world"); else if (cmd_args.exists("map-dir")) commanded_world = cmd_args.get("map-dir"); else if (cmd_args.exists("nonopt0")) // First nameless argument commanded_world = cmd_args.get("nonopt0"); game_params->world_path = get_clean_world_path(commanded_world); return !commanded_world.empty(); } static bool get_world_from_config(GameParams *game_params, const Settings &cmd_args) { // World directory std::string commanded_world; if (g_settings->exists("map-dir")) commanded_world = g_settings->get("map-dir"); game_params->world_path = get_clean_world_path(commanded_world); return !commanded_world.empty(); } static bool auto_select_world(GameParams *game_params) { // No world was specified; try to select it automatically // Get information about available worlds verbosestream << _("Determining world path") << std::endl; std::vector<WorldSpec> worldspecs = getAvailableWorlds(); std::string world_path; // If there is only a single world, use it if (worldspecs.size() == 1) { world_path = worldspecs[0].path; dstream <<_("Automatically selecting world at") << " [" << world_path << "]" << std::endl; // If there are multiple worlds, list them } else if (worldspecs.size() > 1 && game_params->is_dedicated_server) { std::cerr << _("Multiple worlds are available.") << std::endl; std::cerr << _("Please select one using --worldname <name>" " or --world <path>") << std::endl; print_worldspecs(worldspecs, std::cerr); return false; // If there are no worlds, automatically create a new one } else { // This is the ultimate default world path world_path = porting::path_user + DIR_DELIM + "worlds" + DIR_DELIM + "world"; infostream << "Creating default world at [" << world_path << "]" << std::endl; } assert(world_path != ""); // Post-condition game_params->world_path = world_path; return true; } static std::string get_clean_world_path(const std::string &path) { const std::string worldmt = "world.mt"; std::string clean_path; if (path.size() > worldmt.size() && path.substr(path.size() - worldmt.size()) == worldmt) { dstream << _("Supplied world.mt file - stripping it off.") << std::endl; clean_path = path.substr(0, path.size() - worldmt.size()); } else { clean_path = path; } return path; } static bool game_configure_subgame(GameParams *game_params, const Settings &cmd_args) { bool success; success = get_game_from_cmdline(game_params, cmd_args); if (!success) success = determine_subgame(game_params); return success; } static bool get_game_from_cmdline(GameParams *game_params, const Settings &cmd_args) { SubgameSpec commanded_gamespec; if (cmd_args.exists("gameid")) { std::string gameid = cmd_args.get("gameid"); commanded_gamespec = findSubgame(gameid); if (!commanded_gamespec.isValid()) { errorstream << "Game \"" << gameid << "\" not found" << std::endl; return false; } dstream << _("Using game specified by --gameid on the command line") << std::endl; game_params->game_spec = commanded_gamespec; return true; } return false; } static bool determine_subgame(GameParams *game_params) { SubgameSpec gamespec; assert(game_params->world_path != ""); // Pre-condition verbosestream << _("Determining gameid/gamespec") << std::endl; // If world doesn't exist if (!game_params->world_path.empty() && !getWorldExists(game_params->world_path)) { // Try to take gamespec from command line if (game_params->game_spec.isValid()) { gamespec = game_params->game_spec; infostream << "Using commanded gameid [" << gamespec.id << "]" << std::endl; } else { // Otherwise we will be using "minetest" gamespec = findSubgame(g_settings->get("default_game")); infostream << "Using default gameid [" << gamespec.id << "]" << std::endl; if (!gamespec.isValid()) { errorstream << "Subgame specified in default_game [" << g_settings->get("default_game") << "] is invalid." << std::endl; return false; } } } else { // World exists std::string world_gameid = getWorldGameId(game_params->world_path, false); // If commanded to use a gameid, do so if (game_params->game_spec.isValid()) { gamespec = game_params->game_spec; if (game_params->game_spec.id != world_gameid) { warningstream << "Using commanded gameid [" << gamespec.id << "]" << " instead of world gameid [" << world_gameid << "]" << std::endl; } } else { // If world contains an embedded game, use it; // Otherwise find world from local system. gamespec = findWorldSubgame(game_params->world_path); infostream << "Using world gameid [" << gamespec.id << "]" << std::endl; } } if (!gamespec.isValid()) { errorstream << "Subgame [" << gamespec.id << "] could not be found." << std::endl; return false; } game_params->game_spec = gamespec; return true; } /***************************************************************************** * Dedicated server *****************************************************************************/ static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args) { verbosestream << _("Using world path") << " [" << game_params.world_path << "]" << std::endl; verbosestream << _("Using gameid") << " [" << game_params.game_spec.id << "]" << std::endl; // Bind address std::string bind_str = g_settings->get("bind_address"); Address bind_addr(0, 0, 0, 0, game_params.socket_port); if (g_settings->getBool("ipv6_server")) { bind_addr.setAddress((IPv6AddressBytes*) NULL); } try { bind_addr.Resolve(bind_str.c_str()); } catch (ResolveError &e) { infostream << "Resolving bind address \"" << bind_str << "\" failed: " << e.what() << " -- Listening on all addresses." << std::endl; } if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { errorstream << "Unable to listen on " << bind_addr.serializeString() << L" because IPv6 is disabled" << std::endl; return false; } // Database migration if (cmd_args.exists("migrate")) return migrate_map_database(game_params, cmd_args); if (cmd_args.exists("migrate-players")) return ServerEnvironment::migratePlayersDatabase(game_params, cmd_args); if (cmd_args.exists("migrate-auth")) return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args); if (cmd_args.exists("terminal")) { #if USE_CURSES bool name_ok = true; std::string admin_nick = g_settings->get("name"); name_ok = name_ok && !admin_nick.empty(); name_ok = name_ok && string_allowed(admin_nick, PLAYERNAME_ALLOWED_CHARS); if (!name_ok) { if (admin_nick.empty()) { errorstream << "No name given for admin. " << "Please check your minetest.conf that it " << "contains a 'name = ' to your main admin account." << std::endl; } else { errorstream << "Name for admin '" << admin_nick << "' is not valid. " << "Please check that it only contains allowed characters. " << "Valid characters are: " << PLAYERNAME_ALLOWED_CHARS_USER_EXPL << std::endl; } return false; } ChatInterface iface; bool &kill = *porting::signal_handler_killstatus(); try { // Create server Server server(game_params.world_path, game_params.game_spec, false, bind_addr, true, &iface); server.init(); g_term_console.setup(&iface, &kill, admin_nick); g_term_console.start(); server.start(); // Run server dedicated_server_loop(server, kill); } catch (const ModError &e) { g_term_console.stopAndWaitforThread(); errorstream << "ModError: " << e.what() << std::endl; return false; } catch (const ServerError &e) { g_term_console.stopAndWaitforThread(); errorstream << "ServerError: " << e.what() << std::endl; return false; } // Tell the console to stop, and wait for it to finish, // only then leave context and free iface g_term_console.stop(); g_term_console.wait(); g_term_console.clearKillStatus(); } else { #else errorstream << "Cmd arg --terminal passed, but " << "compiled without ncurses. Ignoring." << std::endl; } { #endif try { // Create server Server server(game_params.world_path, game_params.game_spec, false, bind_addr, true); server.init(); server.start(); // Run server bool &kill = *porting::signal_handler_killstatus(); dedicated_server_loop(server, kill); } catch (const ModError &e) { errorstream << "ModError: " << e.what() << std::endl; return false; } catch (const ServerError &e) { errorstream << "ServerError: " << e.what() << std::endl; return false; } } return true; } static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args) { std::string migrate_to = cmd_args.get("migrate"); Settings world_mt; std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; if (!world_mt.readConfigFile(world_mt_path.c_str())) { errorstream << "Cannot read world.mt!" << std::endl; return false; } if (!world_mt.exists("backend")) { errorstream << "Please specify your current backend in world.mt:" << std::endl << " backend = {sqlite3|leveldb|redis|dummy|postgresql}" << std::endl; return false; } std::string backend = world_mt.get("backend"); if (backend == migrate_to) { errorstream << "Cannot migrate: new backend is same" << " as the old one" << std::endl; return false; } MapDatabase *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt), *new_db = ServerMap::createDatabase(migrate_to, game_params.world_path, world_mt); u32 count = 0; time_t last_update_time = 0; bool &kill = *porting::signal_handler_killstatus(); std::vector<v3s16> blocks; old_db->listAllLoadableBlocks(blocks); new_db->beginSave(); for (std::vector<v3s16>::const_iterator it = blocks.begin(); it != blocks.end(); ++it) { if (kill) return false; std::string data; old_db->loadBlock(*it, &data); if (!data.empty()) { new_db->saveBlock(*it, data); } else { errorstream << "Failed to load block " << PP(*it) << ", skipping it." << std::endl; } if (++count % 0xFF == 0 && time(NULL) - last_update_time >= 1) { std::cerr << " Migrated " << count << " blocks, " << (100.0 * count / blocks.size()) << "% completed.\r"; new_db->endSave(); new_db->beginSave(); last_update_time = time(NULL); } } std::cerr << std::endl; new_db->endSave(); delete old_db; delete new_db; actionstream << "Successfully migrated " << count << " blocks" << std::endl; world_mt.set("backend", migrate_to); if (!world_mt.updateConfigFile(world_mt_path.c_str())) errorstream << "Failed to update world.mt!" << std::endl; else actionstream << "world.mt updated" << std::endl; return true; }