From 64b59757322e29c331c0a75262baec4382673e6f Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Fri, 28 Jan 2011 01:38:16 +0200
Subject: [PATCH] Now texture handling is fast. Also now players are saved on
 disk.

---
 data/grass.png          | Bin 1599 -> 1675 bytes
 data/grass_side.png     | Bin 1695 -> 1690 bytes
 minetest.vcproj         |   4 +
 src/CMakeLists.txt      |  47 ++--
 src/environment.cpp     | 181 +++++++++++++++
 src/environment.h       |   4 +
 src/inventory.cpp       |   8 +-
 src/inventory.h         |  43 ++--
 src/irrlichtwrapper.cpp | 471 +++++++++++++++-------------------------
 src/irrlichtwrapper.h   |  87 +++++++-
 src/main.cpp            |  61 +++---
 src/mapblock.cpp        |  73 +++----
 src/mapnode.cpp         |  70 +++---
 src/mapnode.h           |  76 ++++---
 src/mineral.cpp         |  49 +++++
 src/mineral.h           |  20 +-
 src/player.cpp          |  57 ++++-
 src/player.h            |  28 ++-
 src/server.cpp          | 194 +++++++----------
 src/server.h            |   2 +
 src/texture.h           | 124 +++++++++++
 src/tile.h              |  19 ++
 src/utility.h           | 187 ++++++++++++++--
 23 files changed, 1158 insertions(+), 647 deletions(-)
 create mode 100644 src/mineral.cpp
 create mode 100644 src/texture.h

diff --git a/data/grass.png b/data/grass.png
index 88336c01383752157b9842bd5e0a7230cc47b546..8fe9078caffee8e3a78b9ccd6b70b1782ad8955d 100644
GIT binary patch
delta 886
zcmV-+1Bv{<42unr83+ad001BJ|6#Eq4g-G!MoC0LR5;6Z(9cg>1pvVD?|Uufz1P0}
z25f1ewNwTxP=;Yqw+UGmql?M5c=YDMKftR|{s7}{#<PhRW8y*QJS_~D5q`{K7=!Vv
z^angzD5bw!+Shs`*}?1g@=ZR%m*0$boNfoQOQfy~l#i=eBn9o{lslh9IY{K_x7mLv
z=&+xNV=)<dcVwEK^g0KxGl-rMJC$YwWQk~Ch)S}|%^M!v7AuQOuW(8-YfA|Z_Y0IO
zH3o(}#Ny9MrcY>#9z>ttk4bK#ZPn51Ms7WrA-<WX-Y(+_`uOdab?$sRj_a*5@r@dL
z+ZvI{QQnyt#TzhVxP3P=*OBVzl>&dNUBuhlCbpKPCAAr}I}w|0(x(k1r$lJjNJo&!
zXUpua<w+i75RXdR#JBSpBoj!+|GmGA9paE3qz<x-kM-c}D)90B5e)7t_BoeXMpe2<
zCkiZYWVkjJrjUZqznJ3q?3_{)w39q_T}P4ISQ|sk$96b6&mnyE!*nM)>gRv2$FJ~v
z9rU{Uc)GC7*rbKxVHqVNqlyCUqD-x;fn73E(#?dr-{!ni!w{Wu%w@|(q9fyUOa%^)
zH3kOEBr+xL&3;CyR%UZ6!?53pZ`jMqqDJhwM!u59sJ1YK$DMPk*+jKj$5b<*S3yx7
zv}#S3|NaNb*x>4zg_&C)@!fy7e=ru65k)7ykb<Jhh>fz2rO%B`7^Yfn;Rsq;T+vWf
zg|VqH{woqkX`SEye8%m2eR#t@`U8+mpV6hb5Po`gx3lm>BY9Hd`UgS0mTu&3Gu1{C
zlkDZ`uj>d-kzB5YFKi<;WM_3gMdVr-#a6?Z(y|QpTj&jRAuBq@hMj*GTN_k`3shSb
zdJMcb;UimbU>2P0@9i>sV-PiH;mM;k;_IynZo9;2B*^Z+Ibz#e^mt7a&UE@+9^Rku
zkt&{Ya#|%Awvov+`EYiYpC7Ie@Qa9gR(~U0o$Mzze?erxN^&z#Xn2C%O#_)?h3_9O
z;8GlKcnDwrc)g=q+!$iyJlP`ygCRSL3WY+0_-Yl~poI0pL(AIc<;n}NiR88BrH3(@
zDw4~cU53QoIpu5#Yp)4UU;x>gV`+JnW~ZC`4<bBzyu<X(o=cDa04QE<#kVa}1^@s6
M07*qoM6N<$f@sXPdH?_b

delta 810
zcmV+_1J(SC4ZjSK83+OZ005AYXg0AS4g-Gz`bk7VR4C6a&)aU=W&i+S@Ndj-V{>qT
zU@#CvXhIVRTf3-Tk4USgR%+EmlO|1T)oIeCUF{L7-t?+cpQ1Otsj6*CHEq*QNtHGm
z=~5CP2Tmr&=4h}v#29vweQ)p?SHE@9P6s}P40NMX?U9fWYV{kp5`|*JXS2z=;i!L@
zCo%MVba|iODP%>2iP}|dXux4&W>zgJS1vP7()xOHmxlQEX0dW4D{4P5%cSxfCB8OB
z7_j<HqScg|JP5d_*#%C@3)OamITLvGJpILk3yN0RT5sdTB(Xeiwm3{E0xl-G2^{FE
zOxY2ydz{*qMsOUo(}*_a>qAJx@Em_SF~XLl9Bj8r1ra&!=JL4~fS3WI`MuU)tS}t=
z;`z>v+wm!n@;+5@;Er-ZipN~ENxr!f1$e+TWx*-J#24i^Y4O8z5xH#m^6PWeX1l8y
zgj#E$>jXZw4rhN$?H{)r#`}-B#99BVwOzm0;dV~{{zoqM0okc2#3D{1#&LfK@0*5p
zh<0^L*c@t4`XI7wmXuB+8lPZtr78phmZI3boBXWXSN1aEoS$aryl-C%sSTl}@+dU|
z-Y}#rSWg>ZeW3RY1mzg@wKsqM1JU6^jJ$sJ(<cvqh$V0Yq1iA&P&n{~&FrF&(!#1D
z+JfZzx)@-ofZyv5jgEP}P^^E4W55;kjP-trZ!*CU`0jB$y(MiQ<@^h7mjxzbK7wjn
zr|G|57CPEVCR-Z;6Ar=7rZ69!#jrM77Nnq$bkCS@LPv-8zccBc@r1JV3@&hSiIoOJ
zi;+G$JV;&+QXKi~k35pis7^aPyT~29You}+mlu<px^Ig4c!@1{>a~A*kBituQM;B*
zK70Ch#*csy2k(8gaykn}{2Ob{c$BD?1{W?Z|NKiyF{nR%e;8z(wMOkU13vkjIi0l{
zX1l|@_>t9!p>kQ5)OyIqc^Hq@ku-Rc-TglU+!1g(V>wbNsD*!Pnx?s?eO8;gmCo_i
o=AFCqxokUr-sPb@`EnNdKiEKOVpD8NWdHyG07*qoM6N<$f<``yZvX%Q

diff --git a/data/grass_side.png b/data/grass_side.png
index ee12491e8deb8177f481b7038ae3d908b734e966..7bacbfb5efe30f092fdde40e89c924f1b7a6cdac 100644
GIT binary patch
delta 888
zcmV-;1Bd*d4Vn$GFav*8Nkl<ZILlqo%W_%;0D$5Da0uZ5hdT-Pgxo`b5L-J_r`BXT
z8AoTd(Q)ZY7e0V1H~IpO9XGy#8#j*Qb=7HW(@vTywJ`~yZGZqFKoTzDnv(<W>ge|0
z{4<~M$>-sL-DyK|Xy~c}&7(FZy@6uonANxAWU?ptEEei2>}P+|m~>h$&tKtLev8ar
z35{poN=xa1q$fT;MeCr+>(@LuO=cc$>|obRJlV)n*ss%UbqG#*NTeTdkgw3!c+mI*
z0d$f=G1*1!YPtUYGU?qa-F_2K$j|rRZL#`J42Qc(db`7mRFTM1n3=^e-f<(^RtaQ>
zN$9t0G<t<W&}o0-_GSYltzaK$3C}pWyMBNzXdbNI9MEnLlYCkrpXySpH_#lOrG#{<
zim2CtUMr+`t3tQm6#jqzJ5dm9l1<2E%0eva66`~D;jQ=P1<^TY#bK0?4MXI!bslY(
zc;(VG^&EWo@g<H<Yn&-itW@c$Dh8>Kxi`g~#4`?SCkTI^eshI*IKXee?hx?W7;%nr
z_g;$Vl8Hv4i6J5(YXtfYiOx_Dt6s~QYGiVFg4&>i7+<zgZZ?R|#~A496po4ngGREY
zGuBo<;H=YRH(6pfV8=h}W%FT?#DgN$RvxW<fjAwrBO7(Jl`guDh}s5&Y~!M%@aXqH
z(QA7wL`{DzUw@M?zxau0TtcI<<DWEOkR`;Km<@l#hpbG}YL>8!nOI*h5%kLh171cZ
zE;!7qWU|LhMnzm>Zu)A6gZwF;u?fV_zP`%#<1$b7Qv?=9a2fT)qdp9B4fCj-`#)r8
zDCZ>iDqM&|NP3a2jUsdNQ|NRxw7FuLF`tQ%@gaXCgNn9i<&WexZQ&f*(gv5v)kQz$
zZV#hiXaB`>R<4a9hfLi5Ige&9*}`en6OM#<{?`eKR1%k0NBvaA=kV~_qMuyjn96aR
z(6oh8N#Tu^6~4W-$#_76MlGxV2@6X;5_ist1kD`mR+*e#<oT{hsnO!ATla7nZ2b7k
zeQt29jpCj5Bi{UI>LpP1WEiV$n8>SU1f7<8PNm#BX2KG}?Qx?t$|(AC_ICdD5XTqI
zFM<3a6U9u6%+m^rqTm|!VYak*yqzL{bjrJHbENia#4fvV%Wl%S9RC2{X>tF+W2b@u
O0000<MNUMnLSTaBd%}+Z

delta 893
zcmV-@1A_dT4WA9LFav*DNkl<ZILigWTTfaC0KoD8IR_3`k#Z3c6i|xOTBo)%x^<?_
zW*?R{ZThs!!;)n$`zFi2!jio%S(fYr>|t3<XKhw>Do}+9Dxip51P&bF9L|0};pO*!
z!{fVy%`2kn0`02~POF{E$_<;JEs;N~5*i)lR)^EG98TH9!?}M|GKE9($7RIeoEv4(
z1DlnlXdLCd&Bu>|_?#}bcT#w)HuiVUD4yQZR=P~ZgY4!0=Dbj0C<YP3lat7{0hjMo
zbk)Sh%g5x>b<|;-U@Xk<zaFyrY!UBxn_RNXe<znLEYFj8f1Zh`15qi%h}VftZqV!%
z8FSak6`F+GYeIimSreW`1Yzr^U12D@*nA$q;;{1WpvcjyB1gM9gwH<(?`(wJc8kS1
zAL`*bD^DI$DyK;v^)SXQoLido_Z)ojL&A9dHqVRai~RQETOn33Ve0o1v@V@|`R#Wk
zU;T;y=?Up-1AD|pu;<1d_Cfjt`)nU$PZxH7QINMYOhkVb3Nxcf-@Nz;wQ)dIGmu(4
z?4Oz`XkE0EKDyq+PlaYnLkw7{G!@MHkZ?%C6$%j=_an`F1GLd`$yp}7t0>n^Y;7|Z
zi^OTSjwH$$f`*|_;p^84Zmi-p)u@*1{I!)qnlN|J^#LZ)!hSZ%^wN7M_6Clqjb+J<
z!#m)nHp+idvdCATtZ;Sl2Df>UUiJnlpdoE6N2y5H3=JQpW{YaQ%+O>d{ZEN8%Ls+G
zh<#y#4XcTt4-S}}oI$f%sETRsdRLI{P5JS<L>kv39+QY+bZ`z;5}p7j6liud9HkD~
zUW5A|`mj|+%C}d{`{yZ@b4Y=iA#r<{*t8khS7(1xj1cz38Rk{O3vra1#_Gl@%}fTD
zwN2>$IxP(Hg%X`o59#a2bNpM(vX!~vkrYOV$u!&}MDGP?{*fb+s324ZI2Y{LaG?)0
z<{W-fM{TlB6=`k8M)pi%HZ+EgO0ij@cC2$)cuivE0h%nM%PQrph;3$>)Nz*7xQ5!e
zB6@YFi}XR<MLkz${b>w5W`g~@U`t|_MaGOijm9mO2g~$pmy|D#F|K8DDV50jIQCi;
zX>lb&Xv$A_P{P`?@FriwsSoK$BMb*#BGWN$E@UJnMX%QeyA`|Hk9ZBv{w@ClecXN~
T)SRB*00000NkvXXu0mjfKa#pe

diff --git a/minetest.vcproj b/minetest.vcproj
index 71dc5763a..68c6fced7 100644
--- a/minetest.vcproj
+++ b/minetest.vcproj
@@ -271,6 +271,10 @@
 				RelativePath=".\src\materials.cpp"
 				>
 			</File>
+			<File
+				RelativePath=".\src\mineral.cpp"
+				>
+			</File>
 			<File
 				RelativePath=".\src\player.cpp"
 				>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7913f4964..d027c7cd9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -38,18 +38,12 @@ configure_file(
 	"${PROJECT_BINARY_DIR}/cmake_config.h"
 )
 
-set(minetest_SRCS
-	guiMainMenu.cpp
+set(common_SRCS
+	mineral.cpp
 	porting.cpp
-	guiMessageMenu.cpp
 	materials.cpp
-	guiTextInputMenu.cpp
-	guiInventoryMenu.cpp
-	irrlichtwrapper.cpp
-	guiPauseMenu.cpp
 	defaultsettings.cpp
 	mapnode.cpp
-	tile.cpp
 	voxel.cpp
 	mapblockobject.cpp
 	inventory.cpp
@@ -59,7 +53,6 @@ set(minetest_SRCS
 	filesys.cpp
 	connection.cpp
 	environment.cpp
-	client.cpp
 	server.cpp
 	socket.cpp
 	mapblock.cpp
@@ -68,34 +61,24 @@ set(minetest_SRCS
 	map.cpp
 	player.cpp
 	utility.cpp
-	main.cpp
 	test.cpp
 )
 
+set(minetest_SRCS
+	${common_SRCS}
+	guiMainMenu.cpp
+	guiMessageMenu.cpp
+	guiTextInputMenu.cpp
+	guiInventoryMenu.cpp
+	guiPauseMenu.cpp
+	irrlichtwrapper.cpp
+	client.cpp
+	main.cpp
+)
+
 set(minetestserver_SRCS
-	porting.cpp
-	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
+	${common_SRCS}
 	servermain.cpp
-	test.cpp
 )
 
 include_directories(
diff --git a/src/environment.cpp b/src/environment.cpp
index 51ed05422..9d64ff58a 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "environment.h"
+#include "filesys.h"
 
 Environment::Environment(Map *map, std::ostream &dout):
 		m_dout(dout)
@@ -192,6 +193,7 @@ void Environment::addPlayer(Player *player)
 	DSTACK(__FUNCTION_NAME);
 	/*
 		Check that only one local player exists and peer_ids are unique.
+		Also check that names are unique.
 		Exception: there can be multiple players with peer_id=0
 	*/
 #ifndef SERVER
@@ -201,8 +203,12 @@ void Environment::addPlayer(Player *player)
 	*/
 	assert(!(player->isLocal() == true && getLocalPlayer() != NULL));
 #endif
+	// If peer id is non-zero, it has to be unique.
 	if(player->peer_id != 0)
 		assert(getPlayer(player->peer_id) == NULL);
+	// Name has to be unique.
+	assert(getPlayer(player->getName()) == NULL);
+	// Add.
 	m_players.push_back(player);
 }
 
@@ -300,6 +306,181 @@ void Environment::printPlayers(std::ostream &o)
 	}
 }
 
+void Environment::serializePlayers(const std::string &savedir)
+{
+	std::string players_path = savedir + "/players";
+	fs::CreateDir(players_path);
+
+	core::map<Player*, bool> saved_players;
+
+	std::vector<fs::DirListNode> player_files = fs::GetDirListing(players_path);
+	for(u32 i=0; i<player_files.size(); i++)
+	{
+		if(player_files[i].dir)
+			continue;
+		
+		// Full path to this file
+		std::string path = players_path + "/" + player_files[i].name;
+
+		dstream<<"Checking player file "<<path<<std::endl;
+
+		// Load player to see what is its name
+		ServerRemotePlayer testplayer;
+		{
+			// Open file and deserialize
+			std::ifstream is(path.c_str(), std::ios_base::binary);
+			if(is.good() == false)
+			{
+				dstream<<"Failed to read "<<path<<std::endl;
+				continue;
+			}
+			testplayer.deSerialize(is);
+		}
+
+		dstream<<"Loaded test player with name "<<testplayer.getName()<<std::endl;
+		
+		// Search for the player
+		std::string playername = testplayer.getName();
+		Player *player = getPlayer(playername.c_str());
+		if(player == NULL)
+		{
+			dstream<<"Didn't find matching player, ignoring file."<<std::endl;
+			continue;
+		}
+
+		dstream<<"Found matching player, overwriting."<<std::endl;
+
+		// OK, found. Save player there.
+		{
+			// Open file and serialize
+			std::ofstream os(path.c_str(), std::ios_base::binary);
+			if(os.good() == false)
+			{
+				dstream<<"Failed to overwrite "<<path<<std::endl;
+				continue;
+			}
+			player->serialize(os);
+			saved_players.insert(player, true);
+		}
+	}
+
+	for(core::list<Player*>::Iterator i = m_players.begin();
+			i != m_players.end(); i++)
+	{
+		Player *player = *i;
+		if(saved_players.find(player) != NULL)
+		{
+			dstream<<"Player "<<player->getName()
+					<<" was already saved."<<std::endl;
+			continue;
+		}
+		std::string playername = player->getName();
+		// Don't save unnamed player
+		if(playername == "")
+		{
+			dstream<<"Not saving unnamed player."<<std::endl;
+			continue;
+		}
+		/*
+			Find a sane filename
+		*/
+		if(string_allowed(playername, PLAYERNAME_ALLOWED_CHARS) == false)
+			playername = "player";
+		std::string path = players_path + "/" + playername;
+		bool found = false;
+		for(u32 i=0; i<1000; i++)
+		{
+			if(fs::PathExists(path) == false)
+			{
+				found = true;
+				break;
+			}
+			path = players_path + "/" + playername + itos(i);
+		}
+		if(found == false)
+		{
+			dstream<<"Didn't find free file for player"<<std::endl;
+			continue;
+		}
+
+		{
+			dstream<<"Saving player "<<player->getName()<<" to "
+					<<path<<std::endl;
+			// Open file and serialize
+			std::ofstream os(path.c_str(), std::ios_base::binary);
+			if(os.good() == false)
+			{
+				dstream<<"Failed to overwrite "<<path<<std::endl;
+				continue;
+			}
+			player->serialize(os);
+			saved_players.insert(player, true);
+		}
+	}
+}
+
+void Environment::deSerializePlayers(const std::string &savedir)
+{
+	std::string players_path = savedir + "/players";
+
+	core::map<Player*, bool> saved_players;
+
+	std::vector<fs::DirListNode> player_files = fs::GetDirListing(players_path);
+	for(u32 i=0; i<player_files.size(); i++)
+	{
+		if(player_files[i].dir)
+			continue;
+		
+		// Full path to this file
+		std::string path = players_path + "/" + player_files[i].name;
+
+		dstream<<"Checking player file "<<path<<std::endl;
+
+		// Load player to see what is its name
+		ServerRemotePlayer testplayer;
+		{
+			// Open file and deserialize
+			std::ifstream is(path.c_str(), std::ios_base::binary);
+			if(is.good() == false)
+			{
+				dstream<<"Failed to read "<<path<<std::endl;
+				continue;
+			}
+			testplayer.deSerialize(is);
+		}
+
+		dstream<<"Loaded test player with name "<<testplayer.getName()<<std::endl;
+		
+		// Search for the player
+		std::string playername = testplayer.getName();
+		Player *player = getPlayer(playername.c_str());
+		bool newplayer = false;
+		if(player == NULL)
+		{
+			dstream<<"Is a new player"<<std::endl;
+			player = new ServerRemotePlayer();
+			newplayer = true;
+		}
+
+		// Load player
+		{
+			dstream<<"Reading player "<<testplayer.getName()<<" from "
+					<<path<<std::endl;
+			// Open file and deserialize
+			std::ifstream is(path.c_str(), std::ios_base::binary);
+			if(is.good() == false)
+			{
+				dstream<<"Failed to read "<<path<<std::endl;
+				continue;
+			}
+			player->deSerialize(is);
+		}
+
+		if(newplayer)
+			addPlayer(player);
+	}
+}
+
 #ifndef SERVER
 void Environment::updateMeshes(v3s16 blockpos)
 {
diff --git a/src/environment.h b/src/environment.h
index fa7253170..dfc60673b 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -63,6 +63,10 @@ class Environment
 	core::list<Player*> getPlayers();
 	core::list<Player*> getPlayers(bool ignore_disconnected);
 	void printPlayers(std::ostream &o);
+	
+	void serializePlayers(const std::string &savedir);
+	// This loads players as ServerRemotePlayers
+	void deSerializePlayers(const std::string &savedir);
 
 #ifndef SERVER
 	void updateMeshes(v3s16 blockpos);
diff --git a/src/inventory.cpp b/src/inventory.cpp
index 713adefdf..cbe66edb0 100644
--- a/src/inventory.cpp
+++ b/src/inventory.cpp
@@ -217,7 +217,7 @@ void InventoryList::serialize(std::ostream &os)
 		os<<"\n";
 	}
 
-	os<<"end\n";
+	os<<"EndInventoryList\n";
 }
 
 void InventoryList::deSerialize(std::istream &is)
@@ -238,7 +238,7 @@ void InventoryList::deSerialize(std::istream &is)
 		std::string name;
 		std::getline(iss, name, ' ');
 
-		if(name == "end")
+		if(name == "EndInventoryList")
 		{
 			break;
 		}
@@ -497,7 +497,7 @@ void Inventory::serialize(std::ostream &os)
 		list->serialize(os);
 	}
 
-	os<<"end\n";
+	os<<"EndInventory\n";
 }
 
 void Inventory::deSerialize(std::istream &is)
@@ -514,7 +514,7 @@ void Inventory::deSerialize(std::istream &is)
 		std::string name;
 		std::getline(iss, name, ' ');
 
-		if(name == "end")
+		if(name == "EndInventory")
 		{
 			break;
 		}
diff --git a/src/inventory.h b/src/inventory.h
index 797a67509..e7c7adaee 100644
--- a/src/inventory.h
+++ b/src/inventory.h
@@ -122,10 +122,12 @@ class MaterialItem : public InventoryItem
 #ifndef SERVER
 	video::ITexture * getImage()
 	{
-		if(m_content >= USEFUL_CONTENT_COUNT)
+		/*if(m_content >= USEFUL_CONTENT_COUNT)
 			return NULL;
 			
-		return g_irrlicht->getTexture(g_content_inventory_texture_paths[m_content]);
+		return g_irrlicht->getTexture(g_content_inventory_texture_paths[m_content]);*/
+
+		return g_irrlicht->getTexture(content_features(m_content).inventory_texture);
 	}
 #endif
 	std::string getText()
@@ -250,19 +252,19 @@ class CraftItem : public InventoryItem
 #ifndef SERVER
 	video::ITexture * getImage()
 	{
-		std::string basename;
+		std::string name;
 
 		if(m_subname == "Stick")
-			basename = porting::getDataPath("stick.png");
+			name = "stick.png";
 		else if(m_subname == "lump_of_coal")
-			basename = porting::getDataPath("lump_of_coal.png");
+			name = "lump_of_coal.png";
 		else if(m_subname == "lump_of_iron")
-			basename = porting::getDataPath("lump_of_iron.png");
+			name = "lump_of_iron.png";
 		else
-			basename = porting::getDataPath("cloud.png[[mod:crack3");
+			name = "cloud.png";
 		
 		// Get such a texture
-		return g_irrlicht->getTexture(basename);
+		return g_irrlicht->getTexture(name);
 	}
 #endif
 	std::string getText()
@@ -330,28 +332,35 @@ class ToolItem : public InventoryItem
 	{
 		std::string basename;
 		if(m_toolname == "WPick")
-			basename = porting::getDataPath("tool_wpick.png").c_str();
+			basename = "tool_wpick.png";
 		else if(m_toolname == "STPick")
-			basename = porting::getDataPath("tool_stpick.png").c_str();
+			basename = "tool_stpick.png";
 		else if(m_toolname == "MesePick")
-			basename = porting::getDataPath("tool_mesepick.png").c_str();
-		// Default to cloud texture
+			basename = "tool_mesepick.png";
 		else
-			basename = porting::getDataPath("cloud.png").c_str();
-			//basename = tile_texture_path_get(TILE_CLOUD);
+			basename = "cloud.png";
 		
 		/*
-			Calculate some progress value with sane amount of
+			Calculate a progress value with sane amount of
 			maximum states
 		*/
 		u32 maxprogress = 30;
 		u32 toolprogress = (65535-m_wear)/(65535/maxprogress);
 		
-		// Make texture name for the new texture with a progress bar
+		float value_f = (float)toolprogress / (float)maxprogress;
+		std::ostringstream os;
+		os<<"[progressbar"<<value_f;
+
+		TextureSpec spec;
+		spec.addTid(g_irrlicht->getTextureId(basename));
+		spec.addTid(g_irrlicht->getTextureId(os.str()));
+		return g_irrlicht->getTexture(spec);
+
+		/*// Make texture name for the new texture with a progress bar
 		float value_f = (float)toolprogress / (float)maxprogress;
 		std::ostringstream os;
 		os<<basename<<"[[mod:progressbar"<<value_f;
-		return g_irrlicht->getTexture(os.str());
+		return g_irrlicht->getTexture(os.str());*/
 
 		/*// Make texture name for the new texture with a progress bar
 		std::ostringstream os;
diff --git a/src/irrlichtwrapper.cpp b/src/irrlichtwrapper.cpp
index 4e1ebdd74..e5cab98c6 100644
--- a/src/irrlichtwrapper.cpp
+++ b/src/irrlichtwrapper.cpp
@@ -17,13 +17,15 @@ void IrrlichtWrapper::Run()
 	*/
 	if(m_get_texture_queue.size() > 0)
 	{
-		GetRequest<std::string, video::ITexture*, u8, u8>
+		GetRequest<TextureSpec, video::ITexture*, u8, u8>
 				request = m_get_texture_queue.pop();
 
-		dstream<<"got texture request with key="
-				<<request.key<<std::endl;
+		dstream<<"got texture request with"
+				<<" key.tids[0]="<<request.key.tids[0]
+				<<" [1]="<<request.key.tids[1]
+				<<std::endl;
 
-		GetResult<std::string, video::ITexture*, u8, u8>
+		GetResult<TextureSpec, video::ITexture*, u8, u8>
 				result;
 		result.key = request.key;
 		result.callers = request.callers;
@@ -33,9 +35,29 @@ void IrrlichtWrapper::Run()
 	}
 }
 
-video::ITexture* IrrlichtWrapper::getTexture(const std::string &spec)
+textureid_t IrrlichtWrapper::getTextureId(const std::string &name)
 {
-	if(spec == "")
+	u32 id = m_namecache.getId(name);
+	return id;
+}
+
+std::string IrrlichtWrapper::getTextureName(textureid_t id)
+{
+	std::string name("");
+	m_namecache.getValue(id, name);
+	// In case it was found, return the name; otherwise return an empty name.
+	return name;
+}
+
+video::ITexture* IrrlichtWrapper::getTexture(const std::string &name)
+{
+	TextureSpec spec(getTextureId(name));
+	return getTexture(spec);
+}
+
+video::ITexture* IrrlichtWrapper::getTexture(const TextureSpec &spec)
+{
+	if(spec.empty())
 		return NULL;
 	
 	video::ITexture *t = m_texturecache.get(spec);
@@ -44,26 +66,26 @@ video::ITexture* IrrlichtWrapper::getTexture(const std::string &spec)
 	
 	if(get_current_thread_id() == m_main_thread)
 	{
-		dstream<<"Getting texture directly: spec="
-				<<spec<<std::endl;
+		dstream<<"Getting texture directly: spec.tids[0]="
+				<<spec.tids[0]<<std::endl;
 				
 		t = getTextureDirect(spec);
 	}
 	else
 	{
 		// We're gonna ask the result to be put into here
-		ResultQueue<std::string, video::ITexture*, u8, u8> result_queue;
+		ResultQueue<TextureSpec, video::ITexture*, u8, u8> result_queue;
 		
 		// Throw a request in
 		m_get_texture_queue.add(spec, 0, 0, &result_queue);
 		
-		dstream<<"Waiting for texture from main thread: "
-				<<spec<<std::endl;
+		dstream<<"Waiting for texture from main thread: spec.tids[0]="
+				<<spec.tids[0]<<std::endl;
 		
 		try
 		{
 			// Wait result for a second
-			GetResult<std::string, video::ITexture*, u8, u8>
+			GetResult<TextureSpec, video::ITexture*, u8, u8>
 					result = result_queue.pop_front(1000);
 		
 			// Check that at least something worked OK
@@ -83,144 +105,177 @@ video::ITexture* IrrlichtWrapper::getTexture(const std::string &spec)
 	return t;
 }
 
-/*
-	Non-thread-safe functions
-*/
+// Draw a progress bar on the image
+void make_progressbar(float value, video::IImage *image);
 
 /*
-	Texture modifier functions
+	Texture fetcher/maker function, called always from the main thread
 */
 
-// blitted_name = eg. "mineral_coal.png"
-video::ITexture * make_blitname(const std::string &blitted_name,
-		video::ITexture *original,
-		const char *newname, video::IVideoDriver* driver)
+video::ITexture* IrrlichtWrapper::getTextureDirect(const TextureSpec &spec)
 {
-	if(original == NULL)
+	// This would result in NULL image
+	if(spec.empty())
 		return NULL;
 	
-	// Size of the base image
-	core::dimension2d<u32> dim(16, 16);
-	// Position to copy the blitted to in the base image
-	core::position2d<s32> pos_base(0, 0);
-	// Position to copy the blitted from in the blitted image
-	core::position2d<s32> pos_other(0, 0);
-
-	video::IImage *baseimage = driver->createImage(original, pos_base, dim);
-	assert(baseimage);
-
-	video::IImage *blittedimage = driver->createImageFromFile(porting::getDataPath(blitted_name.c_str()).c_str());
-	assert(blittedimage);
-	
-	// Then copy the right part of blittedimage to baseimage
-	
-	blittedimage->copyToWithAlpha(baseimage, v2s32(0,0),
-			core::rect<s32>(pos_other, dim),
-			video::SColor(255,255,255,255),
-			NULL);
+	// Don't generate existing stuff
+	video::ITexture *t = m_texturecache.get(spec);
+	if(t != NULL)
+	{
+		dstream<<"WARNING: Existing stuff requested from "
+				"getTextureDirect()"<<std::endl;
+		return t;
+	}
 	
-	blittedimage->drop();
-
-	// Create texture from resulting image
-
-	video::ITexture *newtexture = driver->addTexture(newname, baseimage);
+	video::IVideoDriver* driver = m_device->getVideoDriver();
 
-	baseimage->drop();
+	/*
+		An image will be built from files and then converted into a texture.
+	*/
+	video::IImage *baseimg = NULL;
 
-	return newtexture;
-}
+	/*
+		Irrlicht requires a name for every texture, with which it
+		will be stored internally in irrlicht.
+	*/
+	std::string texture_name;
 
-video::ITexture * make_crack(u16 progression, video::ITexture *original,
-		const char *newname, video::IVideoDriver* driver)
-{
-	if(original == NULL)
-		return NULL;
-	
-	// Size of the base image
-	core::dimension2d<u32> dim(16, 16);
-	// Size of the crack image
-	//core::dimension2d<u32> dim_crack(16, 16 * CRACK_ANIMATION_LENGTH);
-	// Position to copy the crack to in the base image
-	core::position2d<s32> pos_base(0, 0);
-	// Position to copy the crack from in the crack image
-	core::position2d<s32> pos_other(0, 16 * progression);
-
-	video::IImage *baseimage = driver->createImage(original, pos_base, dim);
-	assert(baseimage);
-
-	video::IImage *crackimage = driver->createImageFromFile(porting::getDataPath("crack.png").c_str());
-	assert(crackimage);
-	
-	// Then copy the right part of crackimage to baseimage
-	
-	crackimage->copyToWithAlpha(baseimage, v2s32(0,0),
-			core::rect<s32>(pos_other, dim),
-			video::SColor(255,255,255,255),
-			NULL);
-	
-	crackimage->drop();
+	for(u32 i=0; i<TEXTURE_SPEC_TEXTURE_COUNT; i++)
+	{
+		textureid_t tid = spec.tids[i];
+		if(tid == 0)
+			continue;
 
-	// Create texture from resulting image
+		std::string name = getTextureName(tid);
+		
+		// Add something to the name so that it is a unique identifier.
+		texture_name += "[";
+		texture_name += name;
+		texture_name += "]";
 
-	video::ITexture *newtexture = driver->addTexture(newname, baseimage);
+		if(name[0] != '[')
+		{
+			// A normal texture; load it from a file
+			std::string path = porting::getDataPath(name.c_str());
+			dstream<<"getTextureDirect(): Loading path \""<<path
+					<<"\""<<std::endl;
+			video::IImage *image = driver->createImageFromFile(path.c_str());
 
-	baseimage->drop();
+			if(image == NULL)
+			{
+				dstream<<"WARNING: Could not load image \""<<name
+						<<"\" from path \""<<path<<"\""
+						<<" while building texture"<<std::endl;
+				continue;
+			}
 
-	return newtexture;
-}
+			// If base image is NULL, load as base.
+			if(baseimg == NULL)
+			{
+				dstream<<"Setting "<<name<<" as base"<<std::endl;
+				/*
+					Copy it this way to get an alpha channel.
+					Otherwise images with alpha cannot be blitted on 
+					images that don't have alpha in the original file.
+				*/
+				// This is a deprecated method
+				//baseimg = driver->createImage(video::ECF_A8R8G8B8, image);
+				core::dimension2d<u32> dim = image->getDimension();
+				baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
+				image->copyTo(baseimg);
+				image->drop();
+				//baseimg = image;
+			}
+			// Else blit on base.
+			else
+			{
+				dstream<<"Blitting "<<name<<" on base"<<std::endl;
+				// Size of the copied area
+				core::dimension2d<u32> dim = image->getDimension();
+				//core::dimension2d<u32> dim(16,16);
+				// Position to copy the blitted to in the base image
+				core::position2d<s32> pos_to(0,0);
+				// Position to copy the blitted from in the blitted image
+				core::position2d<s32> pos_from(0,0);
+				// Blit
+				image->copyToWithAlpha(baseimg, pos_to,
+						core::rect<s32>(pos_from, dim),
+						video::SColor(255,255,255,255),
+						NULL);
+				// Drop image
+				image->drop();
+			}
+		}
+		else
+		{
+			// A special texture modification
+			dstream<<"getTextureDirect(): generating \""<<name<<"\""
+					<<std::endl;
+			if(name.substr(0,6) == "[crack")
+			{
+				u16 progression = stoi(name.substr(6));
+				// Size of the base image
+				core::dimension2d<u32> dim(16, 16);
+				// Size of the crack image
+				//core::dimension2d<u32> dim_crack(16, 16 * CRACK_ANIMATION_LENGTH);
+				// Position to copy the crack to in the base image
+				core::position2d<s32> pos_base(0, 0);
+				// Position to copy the crack from in the crack image
+				core::position2d<s32> pos_other(0, 16 * progression);
+
+				video::IImage *crackimage = driver->createImageFromFile(
+						porting::getDataPath("crack.png").c_str());
+				crackimage->copyToWithAlpha(baseimg, v2s32(0,0),
+						core::rect<s32>(pos_other, dim),
+						video::SColor(255,255,255,255),
+						NULL);
+				crackimage->drop();
+			}
+			else if(name.substr(0,12) == "[progressbar")
+			{
+				float value = stof(name.substr(12));
+				make_progressbar(value, baseimg);
+			}
+			else
+			{
+				dstream<<"WARNING: getTextureDirect(): Invalid "
+						" texture: \""<<name<<"\""<<std::endl;
+			}
+		}
+	}
 
-#if 0
-video::ITexture * make_sidegrass(video::ITexture *original,
-		const char *newname, video::IVideoDriver* driver)
-{
-	if(original == NULL)
+	// If no resulting image, return NULL
+	if(baseimg == NULL)
+	{
+		dstream<<"getTextureDirect(): baseimg is NULL (attempted to"
+				" create texture \""<<texture_name<<"\""<<std::endl;
 		return NULL;
+	}
 	
-	// Size of the base image
-	core::dimension2d<u32> dim(16, 16);
-	// Position to copy the grass to in the base image
-	core::position2d<s32> pos_base(0, 0);
-	// Position to copy the grass from in the grass image
-	core::position2d<s32> pos_other(0, 0);
-
-	video::IImage *baseimage = driver->createImage(original, pos_base, dim);
-	assert(baseimage);
-
-	video::IImage *grassimage = driver->createImageFromFile(porting::getDataPath("grass_side.png").c_str());
-	assert(grassimage);
-	
-	// Then copy the right part of grassimage to baseimage
-	
-	grassimage->copyToWithAlpha(baseimage, v2s32(0,0),
-			core::rect<s32>(pos_other, dim),
-			video::SColor(255,255,255,255),
-			NULL);
-	
-	grassimage->drop();
+	/*// DEBUG: Paint some pixels
+	video::SColor c(255,255,0,0);
+	baseimg->setPixel(1,1, c);
+	baseimg->setPixel(1,14, c);
+	baseimg->setPixel(14,1, c);
+	baseimg->setPixel(14,14, c);*/
 
 	// Create texture from resulting image
+	t = driver->addTexture(texture_name.c_str(), baseimg);
+	baseimg->drop();
 
-	video::ITexture *newtexture = driver->addTexture(newname, baseimage);
+	dstream<<"getTextureDirect(): created texture \""<<texture_name
+			<<"\""<<std::endl;
 
-	baseimage->drop();
+	return t;
 
-	return newtexture;
 }
-#endif
 
-video::ITexture * make_progressbar(float value, video::ITexture *original,
-		const char *newname, video::IVideoDriver* driver)
+void make_progressbar(float value, video::IImage *image)
 {
-	if(original == NULL)
-		return NULL;
-	
-	core::position2d<s32> pos_base(0, 0);
-	core::dimension2d<u32> dim = original->getOriginalSize();
-
-	video::IImage *baseimage = driver->createImage(original, pos_base, dim);
-	assert(baseimage);
+	if(image == NULL)
+		return;
 	
-	core::dimension2d<u32> size = baseimage->getDimension();
+	core::dimension2d<u32> size = image->getDimension();
 
 	u32 barheight = 1;
 	u32 barpad_x = 1;
@@ -242,177 +297,9 @@ video::ITexture * make_progressbar(float value, video::ITexture *original,
 		u32 x = x0 + barpos.X;
 		for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
 		{
-			baseimage->setPixel(x,y, *c);
-		}
-	}
-	
-	video::ITexture *newtexture = driver->addTexture(newname, baseimage);
-
-	baseimage->drop();
-
-	return newtexture;
-}
-
-/*
-	Texture fetcher/maker function, called always from the main thread
-*/
-
-video::ITexture* IrrlichtWrapper::getTextureDirect(const std::string &spec)
-{
-	if(spec == "")
-		return NULL;
-
-	video::IVideoDriver* driver = m_device->getVideoDriver();
-	
-	/*
-		Input (spec) is something like this:
-		"/usr/share/minetest/stone.png[[mod:mineral0[[mod:crack3"
-	*/
-	
-	video::ITexture* t = NULL;
-	std::string modmagic = "[[mod:";
-	Strfnd f(spec);
-	std::string path = f.next(modmagic);
-	t = driver->getTexture(path.c_str());
-	std::string texture_name = path;
-	while(f.atend() == false)
-	{
-		std::string mod = f.next(modmagic);
-		texture_name += modmagic + mod;
-		dstream<<"Making texture \""<<texture_name<<"\""<<std::endl;
-		/*if(mod == "sidegrass")
-		{
-			t = make_sidegrass(t, texture_name.c_str(), driver);
-		}
-		else*/
-		if(mod.substr(0, 9) == "blitname:")
-		{
-			//t = make_sidegrass(t, texture_name.c_str(), driver);
-			t = make_blitname(mod.substr(9), t, texture_name.c_str(), driver);
-		}
-		else if(mod.substr(0,5) == "crack")
-		{
-			u16 prog = stoi(mod.substr(5));
-			t = make_crack(prog, t, texture_name.c_str(), driver);
-		}
-		else if(mod.substr(0,11) == "progressbar")
-		{
-			float value = stof(mod.substr(11));
-			t = make_progressbar(value, t, texture_name.c_str(), driver);
-		}
-		else
-		{
-			dstream<<"Invalid texture mod: \""<<mod<<"\""<<std::endl;
-		}
-	}
-	return t;
-
-#if 0
-	video::ITexture* t = NULL;
-	const char *modmagic = "[[mod:";
-	const s32 modmagic_len = 6;
-	enum{
-		READMODE_PATH,
-		READMODE_MOD
-	} readmode = READMODE_PATH;
-	s32 specsize = spec.size()+1;
-	char *strcache = (char*)malloc(specsize);
-	assert(strcache);
-	char *path = NULL;
-	s32 length = 0;
-	// Next index of modmagic to be found
-	s32 modmagic_i = 0;
-	u32 i=0;
-	for(;;)
-	{
-		strcache[length++] = spec[i];
-		
-		bool got_modmagic = false;
-
-		/*
-			Check modmagic
-		*/
-		if(spec[i] == modmagic[modmagic_i])
-		{
-			modmagic_i++;
-			if(modmagic_i == modmagic_len)
-			{
-				got_modmagic = true;
-				modmagic_i = 0;
-				length -= modmagic_len;
-			}
-		}
-		else
-			modmagic_i = 0;
-		
-		// Set i to be the length of read string
-		i++;
-
-		if(got_modmagic || i >= spec.size())
-		{
-			strcache[length] = '\0';
-			// Now our string is in strcache, ending in \0
-			
-			if(readmode == READMODE_PATH)
-			{
-				// Get initial texture (strcache is path)
-				assert(t == NULL);
-				t = driver->getTexture(strcache);
-				readmode = READMODE_MOD;
-				path = strcache;
-				strcache = (char*)malloc(specsize);
-				assert(strcache);
-			}
-			else
-			{
-				dstream<<"Parsing mod \""<<strcache<<"\""<<std::endl;
-				// The name of the result of adding this mod.
-				// This doesn't have to be fast so std::string is used.
-				std::string name(path);
-				name += "[[mod:";
-				name += strcache;
-				dstream<<"Name of modded texture is \""<<name<<"\""
-						<<std::endl;
-				// Sidegrass
-				if(strcmp(strcache, "sidegrass") == 0)
-				{
-					t = make_sidegrass(t, name.c_str(), driver);
-				}
-				else
-				{
-					dstream<<"Invalid texture mod"<<std::endl;
-				}
-			}
-
-			length = 0;
+			image->setPixel(x,y, *c);
 		}
-
-		if(i >= spec.size())
-			break;
-	}
-
-	/*if(spec.mod == NULL)
-	{
-		dstream<<"IrrlichtWrapper::getTextureDirect: Loading texture "
-				<<spec.path<<std::endl;
-		return driver->getTexture(spec.path.c_str());
 	}
-
-	dstream<<"IrrlichtWrapper::getTextureDirect: Loading and modifying "
-			"texture "<<spec.path<<" to make "<<spec.name<<std::endl;
-
-	video::ITexture *base = driver->getTexture(spec.path.c_str());
-	video::ITexture *result = spec.mod->make(base, spec.name.c_str(), driver);
-
-	delete spec.mod;*/
-	
-	if(strcache)
-		free(strcache);
-	if(path)
-		free(path);
-	
-	return t;
-#endif
 }
 
 
diff --git a/src/irrlichtwrapper.h b/src/irrlichtwrapper.h
index 2506af012..a695bd1e4 100644
--- a/src/irrlichtwrapper.h
+++ b/src/irrlichtwrapper.h
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "common_irrlicht.h"
 #include "debug.h"
 #include "utility.h"
+#include "texture.h"
 
 #include <jmutex.h>
 #include <jmutexautolock.h>
@@ -36,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	threads, because texture pointers have to be handled in
 	background threads.
 */
-
+#if 0
 class TextureCache
 {
 public:
@@ -73,12 +74,55 @@ class TextureCache
 	core::map<std::string, video::ITexture*> m_textures;
 	JMutex m_mutex;
 };
+#endif
+
+/*
+	A thread-safe texture pointer cache
+*/
+class TextureCache
+{
+public:
+	TextureCache()
+	{
+		m_mutex.Init();
+		assert(m_mutex.IsInitialized());
+	}
+	
+	void set(const TextureSpec &spec, video::ITexture *texture)
+	{
+		if(texture == NULL)
+			return;
+		
+		JMutexAutoLock lock(m_mutex);
+
+		m_textures[spec] = texture;
+	}
+	
+	video::ITexture* get(const TextureSpec &spec)
+	{
+		JMutexAutoLock lock(m_mutex);
+
+		core::map<TextureSpec, video::ITexture*>::Node *n;
+		n = m_textures.find(spec);
+
+		if(n != NULL)
+			return n->getValue();
+
+		return NULL;
+	}
+
+private:
+	core::map<TextureSpec, video::ITexture*> m_textures;
+	JMutex m_mutex;
+};
 
 /*
 	A thread-safe wrapper for irrlicht, to be accessed from
 	background worker threads.
 
 	Queues tasks to be done in the main thread.
+
+	Also caches texture specification strings to ids and textures.
 */
 
 class IrrlichtWrapper
@@ -103,30 +147,55 @@ class IrrlichtWrapper
 		return m_device->getTimer()->getRealTime();
 	}
 	
-    /*
-		Path can contain stuff like
-		"/usr/share/minetest/stone.png[[mod:mineral0[[mod:crack3"
+	/*
+		Format of a texture name:
+			"stone.png" (filename in image data directory)
+			"[crack1" (a name starting with "[" is a special feature)
+			"[progress1.0" (a name starting with "[" is a special feature)
+	*/
+	/*
+		Loads texture defined by "name" and assigns a texture id to it.
+		If texture has to be generated, generates it.
+		If the texture has already been loaded, returns existing id.
 	*/
-	video::ITexture* getTexture(const std::string &spec);
+	textureid_t getTextureId(const std::string &name);
+	// The reverse of the above
+	std::string getTextureName(textureid_t id);
+	// Gets a texture based on a filename
+	video::ITexture* getTexture(const std::string &name);
+	// Gets a texture based on a TextureSpec (a textureid_t is fine too)
+	video::ITexture* getTexture(const TextureSpec &spec);
 	
 private:
 	/*
 		Non-thread-safe variants of stuff, for internal use
 	*/
-	video::ITexture* getTextureDirect(const std::string &spec);
+
+	// DEPRECATED NO-OP
+	//video::ITexture* getTextureDirect(const std::string &spec);
+	
+	// Constructs a texture according to spec
+	video::ITexture* getTextureDirect(const TextureSpec &spec);
 	
 	/*
 		Members
 	*/
 	
+	// The id of the thread that can (and has to) use irrlicht directly
 	threadid_t m_main_thread;
-
+	
+	// The irrlicht device
 	JMutex m_device_mutex;
 	IrrlichtDevice *m_device;
+	
+	// Queued texture fetches (to be processed by the main thread)
+	RequestQueue<TextureSpec, video::ITexture*, u8, u8> m_get_texture_queue;
 
+	// Cache of textures by spec
 	TextureCache m_texturecache;
-	
-	RequestQueue<std::string, video::ITexture*, u8, u8> m_get_texture_queue;
+
+	// A mapping from texture id to string spec
+	MutexedIdGenerator<std::string> m_namecache;
 };
 
 #endif
diff --git a/src/main.cpp b/src/main.cpp
index 388ab8089..0dc822474 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -104,8 +104,11 @@ SUGG: Meshes of blocks could be split into 6 meshes facing into
 Gaming ideas:
 -------------
 
-- Aim for something like controlling a single dwarf in Dwarf Fortress.
+- Aim for something like controlling a single dwarf in Dwarf Fortress
 
+- The player could go faster by a crafting a boat, or riding an animal
+
+- Random NPC traders. what else?
 
 Documentation:
 --------------
@@ -165,6 +168,20 @@ TODO: Make fetching sector's blocks more efficient when rendering
 
 TODO: Flowing water animation
 
+FIXME: The new texture stuff is slow on wine
+	- A basic grassy ground block takes 20-40ms
+	- A bit more complicated block can take 270ms
+	  - On linux, a similar one doesn't take long at all (14ms)
+	    - It is NOT a bad std::string implementation of MSVC.
+	- Can take up to 200ms? Is it when loading textures or always?
+	- Updating excess amount of meshes when making footprints is too
+	  slow. It has to be fixed.
+	  -> implement Map::updateNodeMeshes()
+	The fix:
+    * Optimize TileSpec to only contain a reference number that
+	  is fast to compare, which refers to a cached string, or
+	* Make TextureSpec for using instead of strings
+
 Configuration:
 --------------
 
@@ -281,18 +298,6 @@ TODO: Flowing water to actually contain flow direction information
 TODO: Remove duplicate lighting implementation from Map (leave
       VoxelManipulator, which is faster)
 
-FIXME: The new texture stuff is slow on wine
-	- A basic grassy ground block takes 20-40ms
-	- A bit more complicated block can take 270ms
-	  - On linux, a similar one doesn't take long at all (14ms)
-	    - Is it a bad std::string implementation of MSVC?
-	- Can take up to 200ms? Is it when loading textures or always?
-	- Updating excess amount of meshes when making footprints is too
-	  slow. It has to be fixed.
-	  -> implement Map::updateNodeMeshes()
-	TODO: Optimize TileSpec to only contain a reference number that
-	      is fast to compare, which refers to a cached string
-
 Doing now:
 ----------
 
@@ -360,6 +365,7 @@ Doing now:
 #include "filesys.h"
 #include "config.h"
 #include "guiMainMenu.h"
+#include "mineral.h"
 
 IrrlichtWrapper *g_irrlicht;
 
@@ -1445,7 +1451,6 @@ int main(int argc, char *argv[])
 	
 	// C-style stuff initialization
 	initializeMaterialProperties();
-	init_mapnode();
 
 	// Debug handler
 	BEGIN_DEBUG_EXCEPTION_HANDLER
@@ -1683,7 +1688,8 @@ int main(int argc, char *argv[])
 	*/
 
 	init_content_inventory_texture_paths();
-	//init_tile_textures();
+	init_mapnode(g_irrlicht);
+	init_mineral(g_irrlicht);
 
 	/*
 		GUI stuff
@@ -2378,7 +2384,7 @@ int main(int argc, char *argv[])
 		bool nodefound = false;
 		v3s16 nodepos;
 		v3s16 neighbourpos;
-		core::aabbox3d<f32> nodefacebox;
+		core::aabbox3d<f32> nodehilightbox;
 		f32 mindistance = BS * 1001;
 		
 		v3s16 pos_i = floatToInt(player_position);
@@ -2470,7 +2476,7 @@ int main(int argc, char *argv[])
 						nodepos = np;
 						neighbourpos = np;
 						mindistance = distance;
-						nodefacebox = box;
+						nodehilightbox = box;
 					}
 				}
 			}
@@ -2513,7 +2519,16 @@ int main(int argc, char *argv[])
 							nodepos = np;
 							neighbourpos = np + dirs[i];
 							mindistance = distance;
-							nodefacebox = facebox;
+
+							//nodehilightbox = facebox;
+
+							const float d = 0.502;
+							core::aabbox3d<f32> nodebox
+									(-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
+							v3f nodepos_f = intToFloat(nodepos);
+							nodebox.MinEdge += nodepos_f;
+							nodebox.MaxEdge += nodepos_f;
+							nodehilightbox = nodebox;
 						}
 					} // if distance < mindistance
 				} // for dirs
@@ -2531,15 +2546,7 @@ int main(int argc, char *argv[])
 			
 			// Visualize selection
 
-			const float d = 0.502;
-			core::aabbox3d<f32> nodebox(-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
-			v3f nodepos_f = intToFloat(nodepos);
-			//v3f nodepos_f(nodepos.X*BS, nodepos.Y*BS, nodepos.Z*BS);
-			nodebox.MinEdge += nodepos_f;
-			nodebox.MaxEdge += nodepos_f;
-			hilightboxes.push_back(nodebox);
-			
-			//hilightboxes.push_back(nodefacebox);
+			hilightboxes.push_back(nodehilightbox);
 
 			// Handle digging
 			
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index f06dbc811..b346b0980 100644
--- a/src/mapblock.cpp
+++ b/src/mapblock.cpp
@@ -263,6 +263,7 @@ void MapBlock::makeFastFace(TileSpec tile, u8 light, v3f p,
 	
 	//u8 li = decode_light(light);
 	u8 li = light;
+	//u8 li = 255; //DEBUG
 
 	u8 alpha = tile.alpha;
 	/*u8 alpha = 255;
@@ -309,15 +310,16 @@ TileSpec MapBlock::getNodeTile(MapNode mn, v3s16 p, v3s16 face_dir)
 		struct NodeMod mod = n->getValue();
 		if(mod.type == NODEMOD_CHANGECONTENT)
 		{
-			//spec = content_tile(mod.param, face_dir);
 			MapNode mn2(mod.param);
 			spec = mn2.getTile(face_dir);
 		}
 		if(mod.type == NODEMOD_CRACK)
 		{
 			std::ostringstream os;
-			os<<"[[mod:crack"<<mod.param;
-			spec.name += os.str();
+			os<<"[crack"<<mod.param;
+
+			textureid_t tid = g_irrlicht->getTextureId(os.str());
+			spec.spec.addTid(tid);
 		}
 	}
 	
@@ -601,7 +603,8 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 	*/
 	
 	{
-		TimeTaker timer2("updateMesh() collect");
+		// 4-23ms for MAP_BLOCKSIZE=16
+		//TimeTaker timer2("updateMesh() collect");
 
 		// Lock this, as m_temp_mods will be used directly
 		JMutexAutoLock lock(m_temp_mods_mutex);
@@ -667,22 +670,25 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 		// avg 0ms (100ms spikes when loading textures the first time)
 		//TimeTaker timer2("updateMesh() mesh building");
 
+		video::SMaterial material;
+		material.Lighting = false;
+		material.BackfaceCulling = false;
+		material.setFlag(video::EMF_BILINEAR_FILTER, false);
+		material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_OFF);
+		material.setFlag(video::EMF_FOG_ENABLE, true);
+
 		for(u32 i=0; i<fastfaces_new.size(); i++)
 		{
 			FastFace &f = fastfaces_new[i];
 
 			const u16 indices[] = {0,1,2,2,3,0};
 
-			video::ITexture *texture = g_irrlicht->getTexture(f.tile.name);
-			video::SMaterial material;
-			material.Lighting = false;
-			material.BackfaceCulling = false;
-			material.setFlag(video::EMF_BILINEAR_FILTER, false);
-			material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_OFF);
-			material.setFlag(video::EMF_FOG_ENABLE, true);
+			video::ITexture *texture = g_irrlicht->getTexture(f.tile.spec);
 			material.setTexture(0, texture);
 			if(f.tile.alpha != 255)
 				material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+			else
+				material.MaterialType = video::EMT_SOLID;
 			
 			collector.append(material, f.vertices, 4, indices, 6);
 		}
@@ -691,13 +697,22 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 	/*
 		Add special graphics:
 		- torches
-		
-		TODO: Optimize by using same meshbuffer for same textures
+		- flowing water
 	*/
 
 	// 0ms
 	//TimeTaker timer2("updateMesh() adding special stuff");
 
+	// Flowing water material
+	video::SMaterial material_w1;
+	material_w1.setFlag(video::EMF_LIGHTING, false);
+	material_w1.setFlag(video::EMF_BACK_FACE_CULLING, false);
+	material_w1.setFlag(video::EMF_BILINEAR_FILTER, false);
+	material_w1.setFlag(video::EMF_FOG_ENABLE, true);
+	material_w1.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+	material_w1.setTexture(0,
+			g_irrlicht->getTexture("water.png"));
+
 	for(s16 z=0; z<MAP_BLOCKSIZE; z++)
 	for(s16 y=0; y<MAP_BLOCKSIZE; y++)
 	for(s16 x=0; x<MAP_BLOCKSIZE; x++)
@@ -751,17 +766,17 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 					= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
 			if(dir == v3s16(0,-1,0))
 				material.setTexture(0,
-						g_irrlicht->getTexture(porting::getDataPath("torch_on_floor.png").c_str()));
+						g_irrlicht->getTexture("torch_on_floor.png"));
 			else if(dir == v3s16(0,1,0))
 				material.setTexture(0,
-						g_irrlicht->getTexture(porting::getDataPath("torch_on_ceiling.png").c_str()));
+						g_irrlicht->getTexture("torch_on_ceiling.png"));
 			// For backwards compatibility
 			else if(dir == v3s16(0,0,0))
 				material.setTexture(0,
-						g_irrlicht->getTexture(porting::getDataPath("torch_on_floor.png").c_str()));
+						g_irrlicht->getTexture("torch_on_floor.png"));
 			else
 				material.setTexture(0, 
-						g_irrlicht->getTexture(porting::getDataPath("torch.png").c_str()));
+						g_irrlicht->getTexture("torch.png"));
 
 			u16 indices[] = {0,1,2,2,3,0};
 			// Add to mesh collector
@@ -947,19 +962,9 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 					vertices[j].Pos += intToFloat(p + getPosRelative());
 				}
 
-				// Set material
-				video::SMaterial material;
-				material.setFlag(video::EMF_LIGHTING, false);
-				material.setFlag(video::EMF_BACK_FACE_CULLING, false);
-				material.setFlag(video::EMF_BILINEAR_FILTER, false);
-				material.setFlag(video::EMF_FOG_ENABLE, true);
-				material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
-				material.setTexture(0,
-						g_irrlicht->getTexture(porting::getDataPath("water.png").c_str()));
-
 				u16 indices[] = {0,1,2,2,3,0};
 				// Add to mesh collector
-				collector.append(material, vertices, 4, indices, 6);
+				collector.append(material_w1, vertices, 4, indices, 6);
 			}
 			
 			/*
@@ -984,19 +989,9 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 					vertices[i].Pos += intToFloat(p + getPosRelative());
 				}
 
-				// Set material
-				video::SMaterial material;
-				material.setFlag(video::EMF_LIGHTING, false);
-				material.setFlag(video::EMF_BACK_FACE_CULLING, false);
-				material.setFlag(video::EMF_BILINEAR_FILTER, false);
-				material.setFlag(video::EMF_FOG_ENABLE, true);
-				material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
-				material.setTexture(0,
-						g_irrlicht->getTexture(porting::getDataPath("water.png").c_str()));
-
 				u16 indices[] = {0,1,2,2,3,0};
 				// Add to mesh collector
-				collector.append(material, vertices, 4, indices, 6);
+				collector.append(material_w1, vertices, 4, indices, 6);
 			}
 		}
 	}
diff --git a/src/mapnode.cpp b/src/mapnode.cpp
index 7625fab68..d197454fe 100644
--- a/src/mapnode.cpp
+++ b/src/mapnode.cpp
@@ -31,80 +31,85 @@ ContentFeatures::~ContentFeatures()
 
 struct ContentFeatures g_content_features[256];
 
-void init_mapnode()
+ContentFeatures & content_features(u8 i)
+{
+	return g_content_features[i];
+}
+
+void init_mapnode(IrrlichtWrapper *irrlicht)
 {
 	u8 i;
 	ContentFeatures *f = NULL;
 
 	i = CONTENT_STONE;
 	f = &g_content_features[i];
-	f->setAllTextures("stone.png");
+	f->setAllTextures(irrlicht->getTextureId("stone.png"));
 	f->param_type = CPT_MINERAL;
 	f->is_ground_content = true;
 	
 	i = CONTENT_GRASS;
 	f = &g_content_features[i];
-	//f->setAllTextures("mud.png[[mod:sidegrass");
-	f->setAllTextures("mud.png[[mod:blitname:grass_side.png");
-	f->setTexture(0, "grass.png");
-	f->setTexture(1, "mud.png");
-	f->setInventoryImage("grass.png");
+	f->setAllTextures(TextureSpec(irrlicht->getTextureId("mud.png"),
+			irrlicht->getTextureId("grass_side.png")));
+	f->setTexture(0, irrlicht->getTextureId("grass.png"));
+	f->setTexture(1, irrlicht->getTextureId("mud.png"));
+	f->setInventoryTexture(irrlicht->getTextureId("grass.png"));
 	f->param_type = CPT_MINERAL;
 	f->is_ground_content = true;
 	
 	i = CONTENT_GRASS_FOOTSTEPS;
 	f = &g_content_features[i];
-	//f->setAllTextures("mud.png[[mod:sidegrass");
-	f->setAllTextures("mud.png[[mod:blitname:grass_side.png");
-	f->setTexture(0, "grass_footsteps.png");
-	f->setTexture(1, "mud.png");
-	f->setInventoryImage("grass_footsteps.png");
+	f->setAllTextures(TextureSpec(irrlicht->getTextureId("mud.png"),
+			irrlicht->getTextureId("grass_side.png")));
+	f->setTexture(0, irrlicht->getTextureId("grass_footsteps.png"));
+	f->setTexture(1, irrlicht->getTextureId("mud.png"));
+	f->setInventoryTexture(irrlicht->getTextureId("grass_footsteps.png"));
 	f->param_type = CPT_MINERAL;
 	f->is_ground_content = true;
 	
 	i = CONTENT_MUD;
 	f = &g_content_features[i];
-	f->setAllTextures("mud.png");
+	f->setAllTextures(irrlicht->getTextureId("mud.png"));
 	f->param_type = CPT_MINERAL;
 	f->is_ground_content = true;
 	
 	i = CONTENT_SAND;
 	f = &g_content_features[i];
-	f->setAllTextures("mud.png");
+	f->setAllTextures(irrlicht->getTextureId("mud.png"));
 	f->param_type = CPT_MINERAL;
 	f->is_ground_content = true;
 	
 	i = CONTENT_TREE;
 	f = &g_content_features[i];
-	f->setAllTextures("tree.png");
+	f->setAllTextures(irrlicht->getTextureId("tree.png"));
 	f->param_type = CPT_MINERAL;
 	f->is_ground_content = true;
 	
 	i = CONTENT_LEAVES;
 	f = &g_content_features[i];
-	f->setAllTextures("leaves.png");
+	f->setAllTextures(irrlicht->getTextureId("leaves.png"));
 	f->param_type = CPT_MINERAL;
 	f->is_ground_content = true;
 	
 	i = CONTENT_COALSTONE;
 	f = &g_content_features[i];
 	f->translate_to = new MapNode(CONTENT_STONE, MINERAL_COAL);
-	/*f->setAllTextures("coalstone.png");
+	/*f->setAllTextures(irrlicht->getTextureId("coalstone.png"));
 	f->is_ground_content = true;*/
 	
 	i = CONTENT_WOOD;
 	f = &g_content_features[i];
-	f->setAllTextures("wood.png");
+	f->setAllTextures(irrlicht->getTextureId("wood.png"));
 	f->is_ground_content = true;
 	
 	i = CONTENT_MESE;
 	f = &g_content_features[i];
-	f->setAllTextures("mese.png");
+	f->setAllTextures(irrlicht->getTextureId("mese.png"));
 	f->is_ground_content = true;
 	
 	i = CONTENT_CLOUD;
 	f = &g_content_features[i];
-	f->setAllTextures("cloud.png");
+	f->setAllTextures(irrlicht->getTextureId("cloud.png"));
 	f->is_ground_content = true;
 	
 	i = CONTENT_AIR;
@@ -120,7 +125,7 @@ void init_mapnode()
 	
 	i = CONTENT_WATER;
 	f = &g_content_features[i];
-	f->setInventoryImage("water.png");
+	f->setInventoryTexture(irrlicht->getTextureId("water.png"));
 	f->param_type = CPT_LIGHT;
 	f->light_propagates = true;
 	f->solidness = 0; // Drawn separately, makes no faces
@@ -132,8 +137,8 @@ void init_mapnode()
 	
 	i = CONTENT_WATERSOURCE;
 	f = &g_content_features[i];
-	f->setTexture(0, "water.png", WATER_ALPHA);
-	f->setInventoryImage("water.png");
+	f->setTexture(0, irrlicht->getTextureId("water.png"), WATER_ALPHA);
+	f->setInventoryTexture(irrlicht->getTextureId("water.png"));
 	f->param_type = CPT_LIGHT;
 	f->light_propagates = true;
 	f->solidness = 1;
@@ -145,7 +150,7 @@ void init_mapnode()
 	
 	i = CONTENT_TORCH;
 	f = &g_content_features[i];
-	f->setInventoryImage("torch_on_floor.png");
+	f->setInventoryTexture(irrlicht->getTextureId("torch_on_floor.png"));
 	f->param_type = CPT_LIGHT;
 	f->light_propagates = true;
 	f->solidness = 0; // drawn separately, makes no faces
@@ -184,12 +189,10 @@ TileSpec MapNode::getTile(v3s16 dir)
 	if(content_features(d).param_type == CPT_MINERAL)
 	{
 		u8 mineral = param & 0x1f;
-		const char *ts = mineral_block_texture(mineral);
-		if(ts[0] != 0)
-		{
-			spec.name += "[[mod:blitname:";
-			spec.name += ts;
-		}
+		// Add mineral block texture
+		textureid_t tid = mineral_block_texture(mineral);
+		if(tid != 0)
+			spec.spec.addTid(tid);
 	}
 
 	return spec;
@@ -206,14 +209,15 @@ u8 MapNode::getMineral()
 }
 
 // Pointers to c_str()s g_content_features[i].inventory_image_path
-const char * g_content_inventory_texture_paths[USEFUL_CONTENT_COUNT] = {0};
+//const char * g_content_inventory_texture_paths[USEFUL_CONTENT_COUNT] = {0};
 
 void init_content_inventory_texture_paths()
 {
-	for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
+	dstream<<"DEPRECATED "<<__FUNCTION_NAME<<std::endl;
+	/*for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
 	{
 		g_content_inventory_texture_paths[i] =
 				g_content_features[i].inventory_image_path.c_str();
-	}
+	}*/
 }
 
diff --git a/src/mapnode.h b/src/mapnode.h
index c69436c9e..0c52681be 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -27,11 +27,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "exceptions.h"
 #include "serialization.h"
 #include "tile.h"
+#include "irrlichtwrapper.h"
 
-// Initializes all kind of stuff in here.
-// Doesn't depend on anything else.
-// Many things depend on this.
-void init_mapnode();
+/*
+	Initializes all kind of stuff in here.
+	Many things depend on this.
+
+	irrlicht: Used for getting texture ids.
+*/
+void init_mapnode(IrrlichtWrapper *irrlicht);
 
 // Initializes g_content_inventory_texture_paths
 void init_content_inventory_texture_paths();
@@ -129,7 +133,8 @@ struct ContentFeatures
 	*/
 	TileSpec tiles[6];
 
-	std::string inventory_image_path;
+	//std::string inventory_image_path;
+	TextureSpec inventory_texture;
 
 	bool is_ground_content; //TODO: Remove, use walkable instead
 	bool light_propagates;
@@ -162,39 +167,42 @@ struct ContentFeatures
 
 	~ContentFeatures();
 	
-	void setAllTextures(std::string imgname, u8 alpha=255)
+	void setAllTextures(const TextureSpec &spec, u8 alpha=255)
 	{
 		for(u16 i=0; i<6; i++)
 		{
-			tiles[i].name = porting::getDataPath(imgname.c_str());
+			tiles[i].spec = spec;
 			tiles[i].alpha = alpha;
 		}
 		
 		// Set this too so it can be left as is most times
-		if(inventory_image_path == "")
-			inventory_image_path = porting::getDataPath(imgname.c_str());
+		/*if(inventory_image_path == "")
+			inventory_image_path = porting::getDataPath(imgname.c_str());*/
+
+		if(inventory_texture.empty())
+			inventory_texture = spec;
 	}
-	void setTexture(u16 i, std::string imgname, u8 alpha=255)
+	void setTexture(u16 i, const TextureSpec &spec, u8 alpha=255)
 	{
-		tiles[i].name = porting::getDataPath(imgname.c_str());
+		tiles[i].spec = spec;
 		tiles[i].alpha = alpha;
 	}
 
-	void setInventoryImage(std::string imgname)
+	void setInventoryTexture(const TextureSpec &spec)
 	{
-		inventory_image_path = porting::getDataPath(imgname.c_str());
+		inventory_texture = spec;
 	}
-};
 
-// Initialized by init_mapnode()
-extern struct ContentFeatures g_content_features[256];
-
-inline ContentFeatures & content_features(u8 i)
-{
-	return g_content_features[i];
-}
+	/*void setInventoryImage(std::string imgname)
+	{
+		inventory_image_path = porting::getDataPath(imgname.c_str());
+	}*/
+};
 
-extern const char * g_content_inventory_texture_paths[USEFUL_CONTENT_COUNT];
+/*
+	Call this to access the ContentFeature list
+*/
+ContentFeatures & content_features(u8 i);
 
 /*
 	If true, the material allows light propagation and brightness is stored
@@ -203,7 +211,7 @@ extern const char * g_content_inventory_texture_paths[USEFUL_CONTENT_COUNT];
 */
 inline bool light_propagates_content(u8 m)
 {
-	return g_content_features[m].light_propagates;
+	return content_features(m).light_propagates;
 	//return (m == CONTENT_AIR || m == CONTENT_TORCH || m == CONTENT_WATER || m == CONTENT_WATERSOURCE);
 }
 
@@ -214,7 +222,7 @@ inline bool light_propagates_content(u8 m)
 */
 inline bool sunlight_propagates_content(u8 m)
 {
-	return g_content_features[m].sunlight_propagates;
+	return content_features(m).sunlight_propagates;
 	//return (m == CONTENT_AIR || m == CONTENT_TORCH);
 }
 
@@ -228,7 +236,7 @@ inline bool sunlight_propagates_content(u8 m)
 */
 inline u8 content_solidness(u8 m)
 {
-	return g_content_features[m].solidness;
+	return content_features(m).solidness;
 	/*// As of now, every pseudo node like torches are added to this
 	if(m == CONTENT_AIR || m == CONTENT_TORCH || m == CONTENT_WATER)
 		return 0;
@@ -241,28 +249,28 @@ inline u8 content_solidness(u8 m)
 // NOTE: Don't use, use "content_features(m).whatever" instead
 inline bool content_walkable(u8 m)
 {
-	return g_content_features[m].walkable;
+	return content_features(m).walkable;
 	//return (m != CONTENT_AIR && m != CONTENT_WATER && m != CONTENT_WATERSOURCE && m != CONTENT_TORCH);
 }
 
 // NOTE: Don't use, use "content_features(m).whatever" instead
 inline bool content_liquid(u8 m)
 {
-	return g_content_features[m].liquid_type != LIQUID_NONE;
+	return content_features(m).liquid_type != LIQUID_NONE;
 	//return (m == CONTENT_WATER || m == CONTENT_WATERSOURCE);
 }
 
 // NOTE: Don't use, use "content_features(m).whatever" instead
 inline bool content_flowing_liquid(u8 m)
 {
-	return g_content_features[m].liquid_type == LIQUID_FLOWING;
+	return content_features(m).liquid_type == LIQUID_FLOWING;
 	//return (m == CONTENT_WATER);
 }
 
 // NOTE: Don't use, use "content_features(m).whatever" instead
 inline bool content_liquid_source(u8 m)
 {
-	return g_content_features[m].liquid_type == LIQUID_SOURCE;
+	return content_features(m).liquid_type == LIQUID_SOURCE;
 	//return (m == CONTENT_WATERSOURCE);
 }
 
@@ -279,21 +287,21 @@ inline u8 make_liquid_flowing(u8 m)
 // NOTE: Don't use, use "content_features(m).whatever" instead
 inline bool content_pointable(u8 m)
 {
-	return g_content_features[m].pointable;
+	return content_features(m).pointable;
 	//return (m != CONTENT_AIR && m != CONTENT_WATER && m != CONTENT_WATERSOURCE);
 }
 
 // NOTE: Don't use, use "content_features(m).whatever" instead
 inline bool content_diggable(u8 m)
 {
-	return g_content_features[m].diggable;
+	return content_features(m).diggable;
 	//return (m != CONTENT_AIR && m != CONTENT_WATER && m != CONTENT_WATERSOURCE);
 }
 
 // NOTE: Don't use, use "content_features(m).whatever" instead
 inline bool content_buildable_to(u8 m)
 {
-	return g_content_features[m].buildable_to;
+	return content_features(m).buildable_to;
 	//return (m == CONTENT_AIR || m == CONTENT_WATER || m == CONTENT_WATERSOURCE);
 }
 
@@ -303,7 +311,7 @@ inline bool content_buildable_to(u8 m)
 */
 /*inline bool is_ground_content(u8 m)
 {
-	return g_content_features[m].is_ground_content;
+	return content_features(m).is_ground_content;
 }*/
 
 /*
@@ -622,7 +630,7 @@ struct MapNode
 		}
 
 		// Translate deprecated stuff
-		MapNode *translate_to = g_content_features[d].translate_to;
+		MapNode *translate_to = content_features(d).translate_to;
 		if(translate_to)
 		{
 			dstream<<"MapNode: WARNING: Translating "<<d<<" to "
diff --git a/src/mineral.cpp b/src/mineral.cpp
new file mode 100644
index 000000000..506f5b75c
--- /dev/null
+++ b/src/mineral.cpp
@@ -0,0 +1,49 @@
+/*
+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.
+*/
+
+#include "mineral.h"
+
+const char *mineral_filenames[MINERAL_COUNT] =
+{
+	NULL,
+	"mineral_coal.png",
+	"mineral_iron.png"
+};
+
+textureid_t mineral_textures[MINERAL_COUNT] = {0};
+
+void init_mineral(IrrlichtWrapper *irrlicht)
+{
+	for(u32 i=0; i<MINERAL_COUNT; i++)
+	{
+		if(mineral_filenames[i] == NULL)
+			continue;
+		mineral_textures[i] = irrlicht->getTextureId(mineral_filenames[i]);
+	}
+}
+
+textureid_t mineral_block_texture(u8 mineral)
+{
+	if(mineral >= MINERAL_COUNT)
+		return 0;
+	
+	return mineral_textures[mineral];
+}
+
+
diff --git a/src/mineral.h b/src/mineral.h
index e43e48ab8..aa0902e12 100644
--- a/src/mineral.h
+++ b/src/mineral.h
@@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define MINERAL_HEADER
 
 #include "inventory.h"
+#include "texture.h"
+#include "irrlichtwrapper.h"
 
 /*
 	Minerals
@@ -29,22 +31,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	type param.
 */
 
+// Caches textures
+void init_mineral(IrrlichtWrapper *irrlicht);
+
 #define MINERAL_NONE 0
 #define MINERAL_COAL 1
 #define MINERAL_IRON 2
 
-inline const char * mineral_block_texture(u8 mineral)
-{
-	switch(mineral)
-	{
-	case MINERAL_COAL:
-		return "mineral_coal.png";
-	case MINERAL_IRON:
-		return "mineral_iron.png";
-	default:
-		return "";
-	}
-}
+#define MINERAL_COUNT 3
+
+textureid_t mineral_block_texture(u8 mineral);
 
 inline CraftItem * getDiggedMineralItem(u8 mineral)
 {
diff --git a/src/player.cpp b/src/player.cpp
index 8aabb030c..b260e5056 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "map.h"
 #include "connection.h"
 #include "constants.h"
+#include "utility.h"
 
 Player::Player():
 	touching_ground(false),
@@ -34,15 +35,21 @@ Player::Player():
 	m_position(0,0,0)
 {
 	updateName("<not set>");
-	inventory.addList("main", PLAYER_INVENTORY_SIZE);
-	inventory.addList("craft", 9);
-	inventory.addList("craftresult", 1);
+	resetInventory();
 }
 
 Player::~Player()
 {
 }
 
+void Player::resetInventory()
+{
+	inventory.clear();
+	inventory.addList("main", PLAYER_INVENTORY_SIZE);
+	inventory.addList("craft", 9);
+	inventory.addList("craftresult", 1);
+}
+
 // Y direction is ignored
 void Player::accelerate(v3f target_speed, f32 max_increase)
 {
@@ -80,6 +87,50 @@ void Player::accelerate(v3f target_speed, f32 max_increase)
 #endif
 }
 
+void Player::serialize(std::ostream &os)
+{
+	// Utilize a Settings object for storing values
+	Settings args;
+	args.setS32("version", 1);
+	args.set("name", m_name);
+	args.setFloat("pitch", m_pitch);
+	args.setFloat("yaw", m_yaw);
+	args.setV3F("position", m_position);
+
+	args.writeLines(os);
+
+	os<<"PlayerArgsEnd\n";
+
+	inventory.serialize(os);
+}
+
+void Player::deSerialize(std::istream &is)
+{
+	Settings args;
+	
+	for(;;)
+	{
+		if(is.eof())
+			throw SerializationError
+					("Player::deSerialize(): PlayerArgsEnd not found");
+		std::string line;
+		std::getline(is, line);
+		std::string trimmedline = trim(line);
+		if(trimmedline == "PlayerArgsEnd")
+			break;
+		args.parseConfigLine(line);
+	}
+
+	//args.getS32("version");
+	std::string name = args.get("name");
+	updateName(name.c_str());
+	m_pitch = args.getFloat("pitch");
+	m_yaw = args.getFloat("yaw");
+	m_position = args.getV3F("position");
+
+	inventory.deSerialize(is);
+}
+
 /*
 	RemotePlayer
 */
diff --git a/src/player.h b/src/player.h
index 9330bdd54..5ab027e0a 100644
--- a/src/player.h
+++ b/src/player.h
@@ -29,6 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #define PLAYERNAME_SIZE 20
 
+#define PLAYERNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,"
+
 class Map;
 
 class Player
@@ -37,6 +39,8 @@ class Player
 	Player();
 	virtual ~Player();
 
+	void resetInventory();
+
 	//void move(f32 dtime, Map &map);
 	virtual void move(f32 dtime, Map &map) = 0;
 
@@ -100,6 +104,14 @@ class Player
 	// NOTE: Use peer_id == 0 for disconnected
 	/*virtual bool isClientConnected() { return false; }
 	virtual void setClientConnected(bool) {}*/
+	
+	/*
+		serialize() writes a bunch of text that can contain
+		any characters except a '\0', and such an ending that
+		deSerialize stops reading exactly at the right point.
+	*/
+	void serialize(std::ostream &os);
+	void deSerialize(std::istream &is);
 
 	bool touching_ground;
 	bool in_water;
@@ -119,8 +131,6 @@ class Player
 class ServerRemotePlayer : public Player
 {
 public:
-	/*ServerRemotePlayer(bool client_connected):
-		m_client_connected(client_connected)*/
 	ServerRemotePlayer()
 	{
 	}
@@ -137,18 +147,6 @@ class ServerRemotePlayer : public Player
 	{
 	}
 
-	/*virtual bool isClientConnected()
-	{
-		return m_client_connected;
-	}
-	virtual void setClientConnected(bool client_connected)
-	{
-		m_client_connected = client_connected;
-	}
-
-	// This 
-	bool m_client_connected;*/
-
 private:
 };
 
@@ -252,7 +250,7 @@ class RemotePlayer : public Player, public scene::ISceneNode
 	v3f m_showpos;
 };
 
-#endif
+#endif // !SERVER
 
 #ifndef SERVER
 struct PlayerControl
diff --git a/src/server.cpp b/src/server.cpp
index 541582b65..823a48b90 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -1000,7 +1000,8 @@ Server::Server(
 	m_time_of_day(9000),
 	m_time_counter(0),
 	m_time_of_day_send_timer(0),
-	m_uptime(0)
+	m_uptime(0),
+	m_mapsavedir(mapsavedir)
 {
 	//m_flowwater_timer = 0.0;
 	m_liquid_transform_timer = 0.0;
@@ -1013,10 +1014,16 @@ Server::Server(
 	m_con_mutex.Init();
 	m_step_dtime_mutex.Init();
 	m_step_dtime = 0.0;
+
+	// Load players
+	m_env.deSerializePlayers(m_mapsavedir);
 }
 
 Server::~Server()
 {
+	// Save players
+	m_env.serializePlayers(m_mapsavedir);
+	
 	// Stop threads
 	stop();
 
@@ -1222,82 +1229,6 @@ void Server::AsyncRunStep()
 		}
 	}
 
-#if 0
-	/*
-		Update water
-	*/
-	if(g_settings.getBool("water_moves") == true)
-	{
-		float interval;
-		
-		if(g_settings.getBool("endless_water") == false)
-			interval = 1.0;
-		else
-			interval = 0.25;
-
-		float &counter = m_flowwater_timer;
-		counter += dtime;
-		if(counter >= 0.25 && m_flow_active_nodes.size() > 0)
-		{
-		
-		counter = 0.0;
-
-		core::map<v3s16, MapBlock*> modified_blocks;
-
-		{
-
-			JMutexAutoLock envlock(m_env_mutex);
-			
-			MapVoxelManipulator v(&m_env.getMap());
-			v.m_disable_water_climb =
-					g_settings.getBool("disable_water_climb");
-			
-			if(g_settings.getBool("endless_water") == false)
-				v.flowWater(m_flow_active_nodes, 0, false, 250);
-			else
-				v.flowWater(m_flow_active_nodes, 0, false, 50);
-
-			v.blitBack(modified_blocks);
-
-			ServerMap &map = ((ServerMap&)m_env.getMap());
-			
-			// Update lighting
-			core::map<v3s16, MapBlock*> lighting_modified_blocks;
-			map.updateLighting(modified_blocks, lighting_modified_blocks);
-			
-			// Add blocks modified by lighting to modified_blocks
-			for(core::map<v3s16, MapBlock*>::Iterator
-					i = lighting_modified_blocks.getIterator();
-					i.atEnd() == false; i++)
-			{
-				MapBlock *block = i.getNode()->getValue();
-				modified_blocks.insert(block->getPos(), block);
-			}
-		} // envlock
-
-		/*
-			Set the modified blocks unsent for all the clients
-		*/
-		
-		JMutexAutoLock lock2(m_con_mutex);
-
-		for(core::map<u16, RemoteClient*>::Iterator
-				i = m_clients.getIterator();
-				i.atEnd() == false; i++)
-		{
-			RemoteClient *client = i.getNode()->getValue();
-			
-			if(modified_blocks.size() > 0)
-			{
-				// Remove block from sent history
-				client->SetBlocksNotSent(modified_blocks);
-			}
-		}
-
-		} // interval counter
-	}
-#endif
-	
 	// Periodically print some info
 	{
 		float &counter = m_print_info_timer;
@@ -1476,6 +1407,9 @@ void Server::AsyncRunStep()
 				dout_server<<"Server: Unloaded "<<deleted_count
 						<<" sectors from memory"<<std::endl;
 			}
+
+			// Save players
+			m_env.serializePlayers(m_mapsavedir);
 		}
 	}
 }
@@ -1601,6 +1535,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		Player *player = emergePlayer(playername, "", peer_id);
 		//Player *player = m_env.getPlayer(peer_id);
 
+		/*{
+			// DEBUG: Test serialization
+			std::ostringstream test_os;
+			player->serialize(test_os);
+			dstream<<"Player serialization test: \""<<test_os.str()
+					<<"\""<<std::endl;
+			std::istringstream test_is(test_os.str());
+			player->deSerialize(test_is);
+		}*/
+
 		// If failed, cancel
 		if(player == NULL)
 		{
@@ -2950,7 +2894,7 @@ void Server::SendInventory(u16 peer_id)
 			if(!found)
 			{
 				ItemSpec specs[9];
-				specs[0] = ItemSpec(ITEM_CRAFT, "Coal");
+				specs[0] = ItemSpec(ITEM_CRAFT, "lump_of_coal");
 				specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
 				if(checkItemCombination(items, specs))
 				{
@@ -3147,6 +3091,50 @@ RemoteClient* Server::getClient(u16 peer_id)
 	return n->getValue();
 }
 
+void setCreativeInventory(Player *player)
+{
+	player->resetInventory();
+	
+	// Give some good picks
+	{
+		InventoryItem *item = new ToolItem("STPick", 0);
+		void* r = player->inventory.addItem("main", item);
+		assert(r == NULL);
+	}
+	{
+		InventoryItem *item = new ToolItem("MesePick", 0);
+		void* r = player->inventory.addItem("main", item);
+		assert(r == NULL);
+	}
+
+	/*
+		Give materials
+	*/
+	assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
+	
+	// add torch first
+	InventoryItem *item = new MaterialItem(CONTENT_TORCH, 1);
+	player->inventory.addItem("main", item);
+	
+	// Then others
+	for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
+	{
+		// Skip some materials
+		if(i == CONTENT_WATER || i == CONTENT_TORCH
+			|| i == CONTENT_COALSTONE)
+			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);
+	}
+}
+
 Player *Server::emergePlayer(const char *name, const char *password,
 		u16 peer_id)
 {
@@ -3162,8 +3150,16 @@ Player *Server::emergePlayer(const char *name, const char *password,
 			dstream<<"emergePlayer(): Player already connected"<<std::endl;
 			return NULL;
 		}
+
 		// Got one.
 		player->peer_id = peer_id;
+		
+		// Reset inventory to creative if in creative mode
+		if(g_settings.getBool("creative_mode"))
+		{
+			setCreativeInventory(player);
+		}
+		
 		return player;
 	}
 
@@ -3271,51 +3267,15 @@ Player *Server::emergePlayer(const char *name, const char *password,
 		
 		if(g_settings.getBool("creative_mode"))
 		{
-			// Give some good picks
-			{
-				InventoryItem *item = new ToolItem("STPick", 0);
-				void* r = player->inventory.addItem("main", item);
-				assert(r == NULL);
-			}
-			{
-				InventoryItem *item = new ToolItem("MesePick", 0);
-				void* r = player->inventory.addItem("main", item);
-				assert(r == NULL);
-			}
-
-			/*
-				Give materials
-			*/
-			assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
-			
-			// add torch first
-			InventoryItem *item = new MaterialItem(CONTENT_TORCH, 1);
-			player->inventory.addItem("main", item);
-			
-			// Then others
-			for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
-			{
-				// Skip some materials
-				if(i == CONTENT_WATER || i == CONTENT_TORCH)
-					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);
-			}
+			setCreativeInventory(player);
 		}
 		else
 		{
-			{
+			/*{
 				InventoryItem *item = new ToolItem("WPick", 32000);
 				void* r = player->inventory.addItem("main", item);
 				assert(r == NULL);
-			}
+			}*/
 			/*{
 				InventoryItem *item = new MaterialItem(CONTENT_MESE, 6);
 				void* r = player->inventory.addItem("main", item);
diff --git a/src/server.h b/src/server.h
index a3e1897d9..fcc37631f 100644
--- a/src/server.h
+++ b/src/server.h
@@ -508,6 +508,8 @@ class Server : public con::PeerHandler
 	
 	Queue<PeerChange> m_peer_change_queue;
 
+	std::string m_mapsavedir;
+
 	friend class EmergeThread;
 	friend class RemoteClient;
 };
diff --git a/src/texture.h b/src/texture.h
new file mode 100644
index 000000000..f14efae11
--- /dev/null
+++ b/src/texture.h
@@ -0,0 +1,124 @@
+/*
+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 TEXTURE_HEADER
+#define TEXTURE_HEADER
+
+#include "common_irrlicht.h"
+//#include "utility.h"
+#include "debug.h"
+
+/*
+	All textures are given a "texture id".
+	0 = nothing (a NULL pointer texture)
+*/
+typedef u16 textureid_t;
+
+/*
+	Every texture in the game can be specified by this.
+	
+	It exists instead of specification strings because arbitary
+	texture combinations for map nodes are handled using this,
+	and strings are too slow for that purpose.
+
+	Plain texture pointers are not used because they don't contain
+	content information by themselves. A texture can be completely
+	reconstructed by just looking at this, while this also is a
+	fast unique key to containers.
+*/
+
+#define TEXTURE_SPEC_TEXTURE_COUNT 4
+
+struct TextureSpec
+{
+	TextureSpec()
+	{
+		clear();
+	}
+
+	TextureSpec(textureid_t id0)
+	{
+		clear();
+		tids[0] = id0;
+	}
+
+	TextureSpec(textureid_t id0, textureid_t id1)
+	{
+		clear();
+		tids[0] = id0;
+		tids[1] = id1;
+	}
+
+	void clear()
+	{
+		for(u32 i=0; i<TEXTURE_SPEC_TEXTURE_COUNT; i++)
+		{
+			tids[i] = 0;
+		}
+	}
+
+	bool empty() const
+	{
+		for(u32 i=0; i<TEXTURE_SPEC_TEXTURE_COUNT; i++)
+		{
+			if(tids[i] != 0)
+				return false;
+		}
+		return true;
+	}
+
+	void addTid(textureid_t tid)
+	{
+		for(u32 i=0; i<TEXTURE_SPEC_TEXTURE_COUNT; i++)
+		{
+			if(tids[i] == 0)
+			{
+				tids[i] = tid;
+				return;
+			}
+		}
+		// Too many textures
+		assert(0);
+	}
+
+	bool operator==(const TextureSpec &other) const
+	{
+		for(u32 i=0; i<TEXTURE_SPEC_TEXTURE_COUNT; i++)
+		{
+			if(tids[i] != other.tids[i])
+				return false;
+		}
+		return true;
+	}
+
+	bool operator<(const TextureSpec &other) const
+	{
+		for(u32 i=0; i<TEXTURE_SPEC_TEXTURE_COUNT; i++)
+		{
+			if(tids[i] >= other.tids[i])
+				return false;
+		}
+		return true;
+	}
+
+	// Ids of textures. They are blit on each other.
+	textureid_t tids[TEXTURE_SPEC_TEXTURE_COUNT];
+};
+
+#endif
diff --git a/src/tile.h b/src/tile.h
index b903d92a8..ff495abc4 100644
--- a/src/tile.h
+++ b/src/tile.h
@@ -22,8 +22,26 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "common_irrlicht.h"
 //#include "utility.h"
+#include "texture.h"
 #include <string>
 
+struct TileSpec
+{
+	TileSpec():
+		alpha(255)
+	{
+	}
+
+	bool operator==(TileSpec &other)
+	{
+		return (spec == other.spec && alpha == other.alpha);
+	}
+	
+	TextureSpec spec;
+	u8 alpha;
+};
+
+#if 0
 struct TileSpec
 {
 	TileSpec():
@@ -52,5 +70,6 @@ struct TileSpec
 	std::string name;
 	u8 alpha;
 };
+#endif
 
 #endif
diff --git a/src/utility.h b/src/utility.h
index b517848b1..4f74a0649 100644
--- a/src/utility.h
+++ b/src/utility.h
@@ -760,17 +760,20 @@ class Settings
 {
 public:
 
-	// Returns false on EOF
-	bool parseConfigObject(std::istream &is)
+	void writeLines(std::ostream &os)
 	{
-		if(is.eof())
-			return false;
-		
-		// NOTE: This function will be expanded to allow multi-line settings
-		std::string line;
-		std::getline(is, line);
-		//dstream<<"got line: \""<<line<<"\""<<std::endl;
+		for(core::map<std::string, std::string>::Iterator
+				i = m_settings.getIterator();
+				i.atEnd() == false; i++)
+		{
+			std::string name = i.getNode()->getKey();
+			std::string value = i.getNode()->getValue();
+			os<<name<<" = "<<value<<"\n";
+		}
+	}
 
+	bool parseConfigLine(const std::string &line)
+	{
 		std::string trimmedline = trim(line);
 		
 		// Ignore comments
@@ -798,6 +801,23 @@ class Settings
 		return true;
 	}
 
+	// Returns false on EOF
+	bool parseConfigObject(std::istream &is)
+	{
+		if(is.eof())
+			return false;
+		
+		/*
+			NOTE: This function might be expanded to allow multi-line
+			      settings.
+		*/
+		std::string line;
+		std::getline(is, line);
+		//dstream<<"got line: \""<<line<<"\""<<std::endl;
+
+		return parseConfigLine(line);
+	}
+
 	/*
 		Read configuration file
 
@@ -1089,10 +1109,7 @@ class Settings
 
 	float getFloat(std::string name)
 	{
-		float f;
-		std::istringstream vis(get(name));
-		vis>>f;
-		return f;
+		return stof(get(name));
 	}
 
 	u16 getU16(std::string name)
@@ -1128,6 +1145,34 @@ class Settings
 		return stoi(get(name));
 	}
 
+	v3f getV3F(std::string name)
+	{
+		v3f value;
+		Strfnd f(get(name));
+		f.next("(");
+		value.X = stof(f.next(","));
+		value.Y = stof(f.next(","));
+		value.Z = stof(f.next(")"));
+		return value;
+	}
+
+	void setS32(std::string name, s32 value)
+	{
+		set(name, itos(value));
+	}
+
+	void setFloat(std::string name, float value)
+	{
+		set(name, ftos(value));
+	}
+
+	void setV3F(std::string name, v3f value)
+	{
+		std::ostringstream os;
+		os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
+		set(name, os.str());
+	}
+
 	void clear()
 	{
 		m_settings.clear();
@@ -1628,5 +1673,121 @@ class UniqueQueue
 	core::list<Value> m_list;
 };
 
+#if 0
+template<typename Key, typename Value>
+class MutexedCache
+{
+public:
+	MutexedCache()
+	{
+		m_mutex.Init();
+		assert(m_mutex.IsInitialized());
+	}
+	
+	void set(const Key &name, const Value &value)
+	{
+		JMutexAutoLock lock(m_mutex);
+
+		m_values[name] = value;
+	}
+	
+	bool get(const Key &name, Value *result)
+	{
+		JMutexAutoLock lock(m_mutex);
+
+		typename core::map<Key, Value>::Node *n;
+		n = m_values.find(name);
+
+		if(n == NULL)
+			return false;
+
+		*result = n->getValue();
+		return true;
+	}
+
+private:
+	core::map<Key, Value> m_values;
+	JMutex m_mutex;
+};
+#endif
+
+/*
+	Generates ids for comparable values.
+	Id=0 is reserved for "no value".
+
+	Is fast at:
+	- Returning value by id (very fast)
+	- Returning id by value
+	- Generating a new id for a value
+
+	Is not able to:
+	- Remove an id/value pair (is possible to implement but slow)
+*/
+template<typename T>
+class MutexedIdGenerator
+{
+public:
+	MutexedIdGenerator()
+	{
+		m_mutex.Init();
+		assert(m_mutex.IsInitialized());
+	}
+	
+	// Returns true if found
+	bool getValue(u32 id, T &value)
+	{
+		if(id == 0)
+			return false;
+		JMutexAutoLock lock(m_mutex);
+		if(m_id_to_value.size() < id)
+			return false;
+		value = m_id_to_value[id-1];
+		return true;
+	}
+	
+	// If id exists for value, returns the id.
+	// Otherwise generates an id for the value.
+	u32 getId(const T &value)
+	{
+		JMutexAutoLock lock(m_mutex);
+		typename core::map<T, u32>::Node *n;
+		n = m_value_to_id.find(value);
+		if(n != NULL)
+			return n->getValue();
+		m_id_to_value.push_back(value);
+		u32 new_id = m_id_to_value.size();
+		m_value_to_id.insert(value, new_id);
+		return new_id;
+	}
+
+private:
+	JMutex m_mutex;
+	// Values are stored here at id-1 position (id 1 = [0])
+	core::array<T> m_id_to_value;
+	core::map<T, u32> m_value_to_id;
+};
+
+/*
+	Checks if a string contains only supplied characters
+*/
+inline bool string_allowed(const std::string &s, const std::string &allowed_chars)
+{
+	for(u32 i=0; i<s.size(); i++)
+	{
+		bool confirmed = false;
+		for(u32 j=0; j<allowed_chars.size(); j++)
+		{
+			if(s[i] == allowed_chars[j])
+			{
+				confirmed = true;
+				break;
+			}
+		}
+		if(confirmed == false)
+			return false;
+	}
+	return true;
+}
+
 #endif
 
-- 
GitLab