Initial files
authorPerttu Ahola <celeron55@gmail.com>
Fri, 26 Nov 2010 23:02:21 +0000 (01:02 +0200)
committerPerttu Ahola <celeron55@gmail.com>
Fri, 26 Nov 2010 23:02:21 +0000 (01:02 +0200)
71 files changed:
Makefile [new file with mode: 0644]
bin/Irrlicht.dll [new file with mode: 0644]
data/fontlucida.png [new file with mode: 0644]
data/grass.png [new file with mode: 0644]
data/grass2.png [new file with mode: 0644]
data/grass_footsteps.png [new file with mode: 0644]
data/leaves.png [new file with mode: 0644]
data/light.png [new file with mode: 0644]
data/mese.png [new file with mode: 0644]
data/player.png [new file with mode: 0644]
data/player_back.png [new file with mode: 0644]
data/rat.png [new file with mode: 0644]
data/sign.png [new file with mode: 0644]
data/sign_back.png [new file with mode: 0644]
data/stone.png [new file with mode: 0644]
data/stone_with_arrow.png [new file with mode: 0644]
data/tf.jpg [new file with mode: 0644]
data/tree.png [new file with mode: 0644]
data/water.png [new file with mode: 0644]
data/water_old.png [new file with mode: 0644]
doc/README.txt [new file with mode: 0644]
makepackage_binary.sh [new file with mode: 0755]
minetest.conf.example [new file with mode: 0644]
minetest.sln [new file with mode: 0644]
minetest.vcproj [new file with mode: 0644]
src/client.cpp [new file with mode: 0644]
src/client.h [new file with mode: 0644]
src/clientserver.h [new file with mode: 0644]
src/common_irrlicht.h [new file with mode: 0644]
src/connection.cpp [new file with mode: 0644]
src/connection.h [new file with mode: 0644]
src/constants.h [new file with mode: 0644]
src/debug.cpp [new file with mode: 0644]
src/debug.h [new file with mode: 0644]
src/environment.cpp [new file with mode: 0644]
src/environment.h [new file with mode: 0644]
src/exceptions.h [new file with mode: 0644]
src/filesys.cpp [new file with mode: 0644]
src/filesys.h [new file with mode: 0644]
src/heightmap.cpp [new file with mode: 0644]
src/heightmap.h [new file with mode: 0644]
src/inventory.cpp [new file with mode: 0644]
src/inventory.h [new file with mode: 0644]
src/light.cpp [new file with mode: 0644]
src/light.h [new file with mode: 0644]
src/loadstatus.h [new file with mode: 0644]
src/main.cpp [new file with mode: 0644]
src/main.h [new file with mode: 0644]
src/map.cpp [new file with mode: 0644]
src/map.h [new file with mode: 0644]
src/mapblock.cpp [new file with mode: 0644]
src/mapblock.h [new file with mode: 0644]
src/mapblockobject.cpp [new file with mode: 0644]
src/mapblockobject.h [new file with mode: 0644]
src/mapnode.h [new file with mode: 0644]
src/mapsector.cpp [new file with mode: 0644]
src/mapsector.h [new file with mode: 0644]
src/player.cpp [new file with mode: 0644]
src/player.h [new file with mode: 0644]
src/porting.h [new file with mode: 0644]
src/serialization.cpp [new file with mode: 0644]
src/serialization.h [new file with mode: 0644]
src/server.cpp [new file with mode: 0644]
src/server.h [new file with mode: 0644]
src/socket.cpp [new file with mode: 0644]
src/socket.h [new file with mode: 0644]
src/strfnd.h [new file with mode: 0644]
src/test.cpp [new file with mode: 0644]
src/test.h [new file with mode: 0644]
src/utility.cpp [new file with mode: 0644]
src/utility.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..c8912d1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,72 @@
+# Makefile for Irrlicht Examples\r
+# It's usually sufficient to change just the target name and source file list\r
+# and be sure that CXX is set to a valid compiler\r
+TARGET = test\r
+SOURCE_FILES = 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\r
+SOURCES = $(addprefix src/, $(SOURCE_FILES))\r
+OBJECTS = $(SOURCES:.cpp=.o)\r
+FASTTARGET = fasttest\r
+\r
+IRRLICHTPATH = ../irrlicht/irrlicht-1.7.1\r
+JTHREADPATH = ../jthread/jthread-1.2.1\r
+\r
+CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src\r
+\r
+#CXXFLAGS = -O2 -ffast-math -Wall -fomit-frame-pointer -pipe\r
+CXXFLAGS = -O2 -ffast-math -Wall -g\r
+#CXXFLAGS = -O1 -ffast-math -Wall -g\r
+#CXXFLAGS = -Wall -g -O0\r
+\r
+#CXXFLAGS = -O3 -ffast-math -Wall\r
+#CXXFLAGS = -O3 -ffast-math -Wall -g\r
+#CXXFLAGS = -O2 -ffast-math -Wall -g\r
+\r
+#FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=pentium3\r
+FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=i686\r
+\r
+#Default target\r
+\r
+all: all_linux\r
+\r
+ifeq ($(HOSTTYPE), x86_64)\r
+LIBSELECT=64\r
+endif\r
+\r
+# Target specific settings\r
+\r
+all_linux fast_linux: LDFLAGS = -L/usr/X11R6/lib$(LIBSELECT) -L$(IRRLICHTPATH)/lib/Linux -L$(JTHREADPATH)/src/.libs -lIrrlicht -lGL -lXxf86vm -lXext -lX11 -ljthread\r
+all_linux fast_linux clean_linux: SYSTEM=Linux\r
+\r
+all_win32: LDFLAGS = -L$(IRRLICHTPATH)/lib/Win32-gcc -L$(JTHREADPATH)/Debug -lIrrlicht -lopengl32 -lm -ljthread\r
+all_win32 clean_win32: SYSTEM=Win32-gcc\r
+all_win32 clean_win32: SUF=.exe\r
+\r
+# Name of the binary - only valid for targets which set SYSTEM\r
+\r
+DESTPATH = bin/$(TARGET)$(SUF)\r
+FASTDESTPATH = bin/$(FASTTARGET)$(SUF)\r
+\r
+# Build commands\r
+\r
+all_linux all_win32: $(DESTPATH)\r
+\r
+fast_linux: $(FASTDESTPATH)\r
+\r
+$(FASTDESTPATH): $(SOURCES)\r
+       $(CXX) -o $(FASTDESTPATH) $(SOURCES) $(CPPFLAGS) $(FASTCXXFLAGS) $(LDFLAGS) -DUNITTEST_DISABLE\r
+\r
+$(DESTPATH): $(OBJECTS)\r
+       $(CXX) -o $@ $(OBJECTS) $(LDFLAGS)\r
+\r
+.cpp.o:\r
+       $(CXX) -c -o $@ $< $(CPPFLAGS) $(CXXFLAGS)\r
+\r
+clean: clean_linux clean_win32 clean_fast_linux\r
+\r
+clean_linux clean_win32:\r
+       @$(RM) $(OBJECTS) $(DESTPATH)\r
+\r
+clean_fast_linux:\r
+       @$(RM) $(FASTDESTPATH)\r
+\r
+.PHONY: all all_win32 clean clean_linux clean_win32\r
diff --git a/bin/Irrlicht.dll b/bin/Irrlicht.dll
new file mode 100644 (file)
index 0000000..32cb47f
Binary files /dev/null and b/bin/Irrlicht.dll differ
diff --git a/data/fontlucida.png b/data/fontlucida.png
new file mode 100644 (file)
index 0000000..c63fa02
Binary files /dev/null and b/data/fontlucida.png differ
diff --git a/data/grass.png b/data/grass.png
new file mode 100644 (file)
index 0000000..5636205
Binary files /dev/null and b/data/grass.png differ
diff --git a/data/grass2.png b/data/grass2.png
new file mode 100644 (file)
index 0000000..66b3e58
Binary files /dev/null and b/data/grass2.png differ
diff --git a/data/grass_footsteps.png b/data/grass_footsteps.png
new file mode 100644 (file)
index 0000000..36573ad
Binary files /dev/null and b/data/grass_footsteps.png differ
diff --git a/data/leaves.png b/data/leaves.png
new file mode 100644 (file)
index 0000000..086da80
Binary files /dev/null and b/data/leaves.png differ
diff --git a/data/light.png b/data/light.png
new file mode 100644 (file)
index 0000000..24091a3
Binary files /dev/null and b/data/light.png differ
diff --git a/data/mese.png b/data/mese.png
new file mode 100644 (file)
index 0000000..4c876cd
Binary files /dev/null and b/data/mese.png differ
diff --git a/data/player.png b/data/player.png
new file mode 100644 (file)
index 0000000..90adf97
Binary files /dev/null and b/data/player.png differ
diff --git a/data/player_back.png b/data/player_back.png
new file mode 100644 (file)
index 0000000..530aa75
Binary files /dev/null and b/data/player_back.png differ
diff --git a/data/rat.png b/data/rat.png
new file mode 100644 (file)
index 0000000..d1a0e2a
Binary files /dev/null and b/data/rat.png differ
diff --git a/data/sign.png b/data/sign.png
new file mode 100644 (file)
index 0000000..fc9081a
Binary files /dev/null and b/data/sign.png differ
diff --git a/data/sign_back.png b/data/sign_back.png
new file mode 100644 (file)
index 0000000..779e4bc
Binary files /dev/null and b/data/sign_back.png differ
diff --git a/data/stone.png b/data/stone.png
new file mode 100644 (file)
index 0000000..7b57821
Binary files /dev/null and b/data/stone.png differ
diff --git a/data/stone_with_arrow.png b/data/stone_with_arrow.png
new file mode 100644 (file)
index 0000000..d644706
Binary files /dev/null and b/data/stone_with_arrow.png differ
diff --git a/data/tf.jpg b/data/tf.jpg
new file mode 100644 (file)
index 0000000..d08b6c9
Binary files /dev/null and b/data/tf.jpg differ
diff --git a/data/tree.png b/data/tree.png
new file mode 100644 (file)
index 0000000..98f6f54
Binary files /dev/null and b/data/tree.png differ
diff --git a/data/water.png b/data/water.png
new file mode 100644 (file)
index 0000000..d4457bc
Binary files /dev/null and b/data/water.png differ
diff --git a/data/water_old.png b/data/water_old.png
new file mode 100644 (file)
index 0000000..37b2898
Binary files /dev/null and b/data/water_old.png differ
diff --git a/doc/README.txt b/doc/README.txt
new file mode 100644 (file)
index 0000000..ff07fc4
--- /dev/null
@@ -0,0 +1,115 @@
+Minetest-c55
+---------------
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+Copyright (c) 2010 Perttu Ahola <celeron55@gmail.com>
+
+An InfiniMiner/Minecraft inspired game.
+
+This is a development version:
+- Don't expect it to work as well as a finished game will.
+- Please report any bugs to me. That way I can fix them to the next release.
+
+Server information:
+- I usually have a server running at celer.oni.biz on port 30000
+- If you want to run a server, I can list you on my website and in here.
+
+Features, as of now:
+- Almost Infinite Map (limited to +-31000 blocks in any direction at the moment)
+    - Minecraft alpha has a height restriction of 128 blocks
+- Map Generator capable of taking advantage of the infinite map
+
+Controls:
+- WASD+mouse: Move
+- Mouse L: Dig
+- Mouse R: Place block
+- Mouse Wheel: Change item
+- F: Change item
+- R: Toggle full view range
+
+Configuration file:
+- Can be passed as parameter to the executable.
+- If not given as parameter, these are checked, in order:
+       ../minetest.conf
+       ../../minetest.conf
+
+Running on Windows:
+- The working directory should be ./bin
+
+Running on GNU/Linux:
+- fasttest is a linux binary compiled on a recent Arch Linux installation.
+  It should run on most recent GNU/Linux distributions.
+- Browse to the game ./bin directory and type:
+    LD_LIBRARY_PATH=. ./fasttest
+- If it doesn't work, use wine. I aim at 100% compatibility with wine.
+
+COPYING (License of Minetest-c55):
+- This version of the game is FREEWARE.
+- You can play this version of the game without charge.
+- You can redistribute this version of the game without charge, only with
+  these same license terms you are reading now, and provided that you give
+  proper information about who has made the game (me) and where to get the
+  original copy and new versions (http://celer.oni.biz/~celeron55/minetest).
+- You are not allowed to sell this game for a price.
+
+
+Irrlicht
+---------------
+
+This program uses the Irrlicht Engine. http://irrlicht.sourceforge.net/
+
+ The Irrlicht Engine License
+
+Copyright Â© 2002-2005 Nikolaus Gebhardt
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute
+it freely, subject to the following restrictions:
+
+   1. The origin of this software must not be misrepresented; you
+      must not claim that you wrote the original software. If you use
+         this software in a product, an acknowledgment in the product
+         documentation would be appreciated but is not required.
+   2. Altered source versions must be plainly marked as such, and must
+      not be misrepresented as being the original software.
+   3. This notice may not be removed or altered from any source
+      distribution.
+
+
+JThread
+---------------
+
+This program uses the JThread library. License for JThread follows:
+
+Copyright (c) 2000-2006  Jori Liesenborgs (jori.liesenborgs@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+
diff --git a/makepackage_binary.sh b/makepackage_binary.sh
new file mode 100755 (executable)
index 0000000..c11c45b
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+PACKAGEDIR=../minetest-packages
+PACKAGENAME=minetest-c55-binary-`date +%y%m%d%H%M%S`
+PACKAGEPATH=$PACKAGEDIR/$PACKAGENAME
+
+mkdir -p $PACKAGEPATH
+mkdir -p $PACKAGEPATH/bin
+mkdir -p $PACKAGEPATH/data
+mkdir -p $PACKAGEPATH/doc
+
+cp minetest.conf.example $PACKAGEPATH/
+
+cp bin/minetest.exe $PACKAGEPATH/bin/
+cp bin/Irrlicht.dll $PACKAGEPATH/bin/
+#cp bin/test $PACKAGEPATH/bin/
+cp bin/fasttest $PACKAGEPATH/bin/
+cp ../irrlicht/irrlicht-1.7.1/lib/Linux/libIrrlicht.a $PACKAGEPATH/bin/
+cp ../jthread/jthread-1.2.1/src/.libs/libjthread-1.2.1.so $PACKAGEPATH/bin/
+
+cp -r data/fontlucida.png $PACKAGEPATH/data/
+cp -r data/player.png $PACKAGEPATH/data/
+cp -r data/player_back.png $PACKAGEPATH/data/
+cp -r data/stone.png $PACKAGEPATH/data/
+cp -r data/grass.png $PACKAGEPATH/data/
+cp -r data/grass_footsteps.png $PACKAGEPATH/data/
+cp -r data/water.png $PACKAGEPATH/data/
+cp -r data/tree.png $PACKAGEPATH/data/
+cp -r data/leaves.png $PACKAGEPATH/data/
+cp -r data/mese.png $PACKAGEPATH/data/
+cp -r data/light.png $PACKAGEPATH/data/
+cp -r data/sign.png $PACKAGEPATH/data/
+cp -r data/sign_back.png $PACKAGEPATH/data/
+cp -r data/rat.png $PACKAGEPATH/data/
+
+cp -r doc/README.txt $PACKAGEPATH/doc/README.txt
+
+cd $PACKAGEDIR
+rm $PACKAGENAME.zip
+zip -r $PACKAGENAME.zip $PACKAGENAME
+
diff --git a/minetest.conf.example b/minetest.conf.example
new file mode 100644 (file)
index 0000000..3dd4a1c
--- /dev/null
@@ -0,0 +1,44 @@
+# This file is read by default from:
+# ../minetest.conf
+# ../../minetest.conf
+# Any other path can be chosen by passing the path as a parameter
+# to the program, eg. "minetest.exe ../minetest.conf.example"
+
+dedicated_server =
+
+# Client side stuff
+
+wanted_fps = 30
+fps_max = 60
+viewing_range_nodes_max = 300
+viewing_range_nodes_min = 20
+screenW = 
+screenH = 
+host_game = 
+port = 30000
+address = celer.oni.biz
+name = 
+
+random_input = false
+client_delete_unused_sectors_timeout = 1200
+
+# Server side stuff
+
+# - The possible generators are:
+# (Indeed you can do all of them with only "power" 8))
+# H=value:
+#     constant <value>
+# H=slope.dot(pos):
+#     linear <height> <slope.X> <slope.Y>
+# H=slope.dot(pos^power):
+#     power <height> <slope.X> <slope.Y> <power>
+
+mapgen_heightmap_blocksize = 64
+mapgen_height_randmax = constant 70.0
+mapgen_height_randfactor = constant 0.6
+mapgen_height_base = linear 0 80 0
+mapgen_plants_amount = constant 1.0
+
+creative_mode = false
+
+
diff --git a/minetest.sln b/minetest.sln
new file mode 100644 (file)
index 0000000..d1f144c
--- /dev/null
@@ -0,0 +1,20 @@
+\r
+Microsoft Visual Studio Solution File, Format Version 9.00\r
+# Visual C++ Express 2005\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minetest", "minetest.vcproj", "{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}"\r
+EndProject\r
+Global\r
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution\r
+               Debug|Win32 = Debug|Win32\r
+               Release|Win32 = Release|Win32\r
+       EndGlobalSection\r
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution\r
+               {AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Debug|Win32.ActiveCfg = Debug|Win32\r
+               {AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Debug|Win32.Build.0 = Debug|Win32\r
+               {AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Release|Win32.ActiveCfg = Release|Win32\r
+               {AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Release|Win32.Build.0 = Release|Win32\r
+       EndGlobalSection\r
+       GlobalSection(SolutionProperties) = preSolution\r
+               HideSolutionNode = FALSE\r
+       EndGlobalSection\r
+EndGlobal\r
diff --git a/minetest.vcproj b/minetest.vcproj
new file mode 100644 (file)
index 0000000..c60d9a1
--- /dev/null
@@ -0,0 +1,358 @@
+<?xml version="1.0" encoding="Windows-1252"?>\r
+<VisualStudioProject\r
+       ProjectType="Visual C++"\r
+       Version="8,00"\r
+       Name="minetest"\r
+       ProjectGUID="{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}"\r
+       RootNamespace="minetest"\r
+       >\r
+       <Platforms>\r
+               <Platform\r
+                       Name="Win32"\r
+               />\r
+       </Platforms>\r
+       <ToolFiles>\r
+       </ToolFiles>\r
+       <Configurations>\r
+               <Configuration\r
+                       Name="Debug|Win32"\r
+                       OutputDirectory="$(SolutionDir)\bin"\r
+                       IntermediateDirectory="$(ConfigurationName)"\r
+                       ConfigurationType="1"\r
+                       UseOfATL="1"\r
+                       ATLMinimizesCRunTimeLibraryUsage="true"\r
+                       >\r
+                       <Tool\r
+                               Name="VCPreBuildEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCustomBuildTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXMLDataGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCWebServiceProxyGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCMIDLTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCLCompilerTool"\r
+                               AdditionalIncludeDirectories="&quot;C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include&quot;;&quot;..\jthread\jthread-1.2.1\src&quot;;&quot;..\irrlicht\irrlicht-1.7.1\include&quot;"\r
+                               PreprocessorDefinitions="WIN32"\r
+                               EnableEnhancedInstructionSet="1"\r
+                               FloatingPointModel="2"\r
+                               DebugInformationFormat="1"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManagedResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPreLinkEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCLinkerTool"\r
+                               AdditionalLibraryDirectories="&quot;C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib&quot;;&quot;..\jthread\jthread-1.2.1\Release&quot;;&quot;..\irrlicht\irrlicht-1.7.1\lib\Win32-visualstudio&quot;"\r
+                               IgnoreAllDefaultLibraries="false"\r
+                               GenerateDebugInformation="true"\r
+                       />\r
+                       <Tool\r
+                               Name="VCALinkTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManifestTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXDCMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCBscMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCFxCopTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCAppVerifierTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCWebDeploymentTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPostBuildEventTool"\r
+                       />\r
+               </Configuration>\r
+               <Configuration\r
+                       Name="Release|Win32"\r
+                       OutputDirectory="$(SolutionDir)\bin"\r
+                       IntermediateDirectory="$(ConfigurationName)"\r
+                       ConfigurationType="1"\r
+                       UseOfMFC="0"\r
+                       WholeProgramOptimization="3"\r
+                       >\r
+                       <Tool\r
+                               Name="VCPreBuildEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCustomBuildTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXMLDataGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCWebServiceProxyGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCMIDLTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCLCompilerTool"\r
+                               Optimization="2"\r
+                               InlineFunctionExpansion="2"\r
+                               EnableIntrinsicFunctions="true"\r
+                               FavorSizeOrSpeed="1"\r
+                               OmitFramePointers="true"\r
+                               WholeProgramOptimization="true"\r
+                               AdditionalIncludeDirectories="&quot;C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include&quot;;&quot;..\jthread\jthread-1.2.1\src&quot;;&quot;..\irrlicht\irrlicht-1.7.1\include&quot;"\r
+                               PreprocessorDefinitions="WIN32;_HAS_ITERATOR_DEBUGGING=0,UNITTEST_DISABLE"\r
+                               BufferSecurityCheck="false"\r
+                               EnableEnhancedInstructionSet="1"\r
+                               FloatingPointModel="2"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManagedResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPreLinkEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCLinkerTool"\r
+                               AdditionalLibraryDirectories="&quot;C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib&quot;;&quot;..\jthread\jthread-1.2.1\Release&quot;;&quot;..\irrlicht\irrlicht-1.7.1\lib\Win32-visualstudio&quot;"\r
+                               IgnoreDefaultLibraryNames="libcmtd.lib"\r
+                               LinkTimeCodeGeneration="1"\r
+                       />\r
+                       <Tool\r
+                               Name="VCALinkTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManifestTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXDCMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCBscMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCFxCopTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCAppVerifierTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCWebDeploymentTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPostBuildEventTool"\r
+                       />\r
+               </Configuration>\r
+       </Configurations>\r
+       <References>\r
+       </References>\r
+       <Files>\r
+               <Filter\r
+                       Name="Source Files"\r
+                       Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"\r
+                       UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"\r
+                       >\r
+                       <File\r
+                               RelativePath=".\src\client.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\connection.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\debug.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\environment.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\filesys.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\heightmap.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\inventory.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\light.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\main.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\map.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\mapblock.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\mapblockobject.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\mapsector.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\player.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\serialization.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\server.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\socket.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\test.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\utility.cpp"\r
+                               >\r
+                       </File>\r
+               </Filter>\r
+               <Filter\r
+                       Name="Header Files"\r
+                       Filter="h;hpp;hxx;hm;inl;inc;xsd"\r
+                       UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"\r
+                       >\r
+                       <File\r
+                               RelativePath=".\src\client.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\clientserver.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\common_irrlicht.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\connection.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\constants.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\debug.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\environment.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\exceptions.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\heightmap.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\inventory.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\light.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\loadstatus.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\main.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\map.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\mapblock.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\mapnode.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\mapsector.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\player.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\serialization.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\server.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\socket.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\test.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\src\utility.h"\r
+                               >\r
+                       </File>\r
+               </Filter>\r
+               <Filter\r
+                       Name="Resource Files"\r
+                       Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"\r
+                       UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"\r
+                       >\r
+               </Filter>\r
+       </Files>\r
+       <Globals>\r
+       </Globals>\r
+</VisualStudioProject>\r
diff --git a/src/client.cpp b/src/client.cpp
new file mode 100644 (file)
index 0000000..ee32699
--- /dev/null
@@ -0,0 +1,1639 @@
+#include "client.h"
+#include "utility.h"
+#include <iostream>
+#include "clientserver.h"
+#include "jmutexautolock.h"
+#include "main.h"
+#include <sstream>
+
+#ifdef _WIN32
+       #include <windows.h>
+       #define sleep_ms(x) Sleep(x)
+#else
+       #include <unistd.h>
+       #define sleep_ms(x) usleep(x*1000)
+#endif
+
+/*
+       FIXME: This thread can access the environment at any time
+*/
+
+void * ClientUpdateThread::Thread()
+{
+       ThreadStarted();
+
+       DSTACK(__FUNCTION_NAME);
+
+#if CATCH_UNHANDLED_EXCEPTIONS
+       try
+       {
+#endif
+               while(getRun())
+               {
+                       m_client->asyncStep();
+
+                       bool was = m_client->AsyncProcessData();
+
+                       if(was == false)
+                               sleep_ms(50);
+               }
+#if CATCH_UNHANDLED_EXCEPTIONS
+       }
+       /*
+               This is what has to be done in threads to get suitable debug info
+       */
+       catch(std::exception &e)
+       {
+               dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
+                               <<e.what()<<std::endl;
+               assert(0);
+       }
+#endif
+
+       return NULL;
+}
+
+Client::Client(IrrlichtDevice *device, video::SMaterial *materials,
+               float delete_unused_sectors_timeout,
+               const char *playername):
+       m_thread(this),
+       m_env(new ClientMap(this, materials,
+                       device->getSceneManager()->getRootSceneNode(),
+                       device->getSceneManager(), 666),
+                       dout_client),
+       m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
+       m_device(device),
+       camera_position(0,0,0),
+       camera_direction(0,0,1),
+       m_server_ser_ver(SER_FMT_VER_INVALID),
+       m_step_dtime(0.0),
+       m_delete_unused_sectors_timeout(delete_unused_sectors_timeout),
+       m_inventory_updated(false)
+{
+       //m_fetchblock_mutex.Init();
+       m_incoming_queue_mutex.Init();
+       m_env_mutex.Init();
+       m_con_mutex.Init();
+       m_step_dtime_mutex.Init();
+
+       m_thread.Start();
+       
+       {
+               JMutexAutoLock envlock(m_env_mutex);
+               //m_env.getMap().StartUpdater();
+
+               Player *player = new LocalPlayer();
+
+               player->updateName(playername);
+
+               /*f32 y = BS*2 + BS*20;
+               player->setPosition(v3f(0, y, 0));*/
+               //player->setPosition(v3f(0, y, 30900*BS)); // DEBUG
+               m_env.addPlayer(player);
+       }
+}
+
+Client::~Client()
+{
+       m_thread.setRun(false);
+       while(m_thread.IsRunning())
+               sleep_ms(100);
+}
+
+void Client::connect(Address address)
+{
+       DSTACK(__FUNCTION_NAME);
+       JMutexAutoLock lock(m_con_mutex);
+       m_con.setTimeoutMs(0);
+       m_con.Connect(address);
+}
+
+bool Client::connectedAndInitialized()
+{
+       JMutexAutoLock lock(m_con_mutex);
+
+       if(m_con.Connected() == false)
+               return false;
+       
+       if(m_server_ser_ver == SER_FMT_VER_INVALID)
+               return false;
+       
+       return true;
+}
+
+void Client::step(float dtime)
+{
+       DSTACK(__FUNCTION_NAME);
+       
+       // Limit a bit
+       if(dtime > 2.0)
+               dtime = 2.0;
+       
+       //dstream<<"Client steps "<<dtime<<std::endl;
+
+       {
+               //TimeTaker timer("ReceiveAll()", m_device);
+               // 0ms
+               ReceiveAll();
+       }
+       
+       {
+               //TimeTaker timer("m_con_mutex + m_con.RunTimeouts()", m_device);
+               // 0ms
+               JMutexAutoLock lock(m_con_mutex);
+               m_con.RunTimeouts(dtime);
+       }
+
+       {
+               /*
+                       Delete unused sectors
+               */
+               
+               static float counter = -0.001;
+               counter -= dtime;
+               if(counter <= 0.0)
+               {
+                       counter = 10.0;
+
+                       JMutexAutoLock lock(m_env_mutex);
+
+                       core::list<v3s16> deleted_blocks;
+       
+                       // Delete sector blocks
+                       /*u32 num = m_env.getMap().deleteUnusedSectors
+                                       (m_delete_unused_sectors_timeout,
+                                       true, &deleted_blocks);*/
+                       
+                       // Delete whole sectors
+                       u32 num = m_env.getMap().deleteUnusedSectors
+                                       (m_delete_unused_sectors_timeout,
+                                       false, &deleted_blocks);
+
+                       if(num > 0)
+                       {
+                               dstream<<DTIME<<"Client: Deleted blocks of "<<num
+                                               <<" unused sectors"<<std::endl;
+                               
+                               /*
+                                       Send info to server
+                               */
+
+                               // Env is locked so con can be locked.
+                               JMutexAutoLock lock(m_con_mutex);
+                               
+                               core::list<v3s16>::Iterator i = deleted_blocks.begin();
+                               core::list<v3s16> sendlist;
+                               for(;;)
+                               {
+                                       if(sendlist.size() == 255 || i == deleted_blocks.end())
+                                       {
+                                               if(sendlist.size() == 0)
+                                                       break;
+                                               /*
+                                                       [0] u16 command
+                                                       [2] u8 count
+                                                       [3] v3s16 pos_0
+                                                       [3+6] v3s16 pos_1
+                                                       ...
+                                               */
+                                               u32 replysize = 2+1+6*sendlist.size();
+                                               SharedBuffer<u8> reply(replysize);
+                                               writeU16(&reply[0], TOSERVER_DELETEDBLOCKS);
+                                               reply[2] = sendlist.size();
+                                               u32 k = 0;
+                                               for(core::list<v3s16>::Iterator
+                                                               j = sendlist.begin();
+                                                               j != sendlist.end(); j++)
+                                               {
+                                                       writeV3S16(&reply[2+1+6*k], *j);
+                                                       k++;
+                                               }
+                                               m_con.Send(PEER_ID_SERVER, 1, reply, true);
+
+                                               if(i == deleted_blocks.end())
+                                                       break;
+
+                                               sendlist.clear();
+                                       }
+
+                                       sendlist.push_back(*i);
+                                       i++;
+                               }
+                       }
+               }
+       }
+
+       bool connected = connectedAndInitialized();
+
+       if(connected == false)
+       {
+               static float counter = -0.001;
+               counter -= dtime;
+               if(counter <= 0.0)
+               {
+                       counter = 2.0;
+
+                       JMutexAutoLock envlock(m_env_mutex);
+                       
+                       Player *myplayer = m_env.getLocalPlayer();
+                       assert(myplayer != NULL);
+       
+                       // Send TOSERVER_INIT
+                       // [0] u16 TOSERVER_INIT
+                       // [2] u8 SER_FMT_VER_HIGHEST
+                       // [3] u8[20] player_name
+                       SharedBuffer<u8> data(2+1+20);
+                       writeU16(&data[0], TOSERVER_INIT);
+                       writeU8(&data[2], SER_FMT_VER_HIGHEST);
+                       memcpy(&data[3], myplayer->getName(), 20);
+                       // Send as unreliable
+                       Send(0, data, false);
+               }
+
+               // Not connected, return
+               return;
+       }
+
+       /*
+               Do stuff if connected
+       */
+       
+       {
+               // 0ms
+               JMutexAutoLock lock(m_env_mutex);
+
+               // Control local player (0ms)
+               LocalPlayer *player = m_env.getLocalPlayer();
+               assert(player != NULL);
+               player->applyControl(dtime);
+
+               //TimeTaker envtimer("env step", m_device);
+               // Step environment
+               m_env.step(dtime);
+
+               // Step active blocks
+               for(core::map<v3s16, bool>::Iterator
+                               i = m_active_blocks.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       v3s16 p = i.getNode()->getKey();
+
+                       MapBlock *block = NULL;
+                       try
+                       {
+                               block = m_env.getMap().getBlockNoCreate(p);
+                               block->stepObjects(dtime, false);
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                       }
+               }
+       }
+
+       {
+               // Fetch some nearby blocks
+               //fetchBlocks();
+       }
+
+       {
+               static float counter = 0.0;
+               counter += dtime;
+               if(counter >= 10)
+               {
+                       counter = 0.0;
+                       JMutexAutoLock lock(m_con_mutex);
+                       // connectedAndInitialized() is true, peer exists.
+                       con::Peer *peer = m_con.GetPeer(PEER_ID_SERVER);
+                       dstream<<DTIME<<"Client: avg_rtt="<<peer->avg_rtt<<std::endl;
+               }
+       }
+       {
+               // Update at reasonable intervals (0.2s)
+               static float counter = 0.0;
+               counter += dtime;
+               if(counter >= 0.2)
+               {
+                       counter = 0.0;
+                       sendPlayerPos();
+               }
+       }
+
+#if 0
+       /*
+               Clear old entries from fetchblock history
+       */
+       {
+               JMutexAutoLock lock(m_fetchblock_mutex);
+               
+               core::list<v3s16> remove_queue;
+               core::map<v3s16, float>::Iterator i;
+               i = m_fetchblock_history.getIterator();
+               for(; i.atEnd() == false; i++)
+               {
+                       float value = i.getNode()->getValue();
+                       value += dtime;
+                       i.getNode()->setValue(value);
+                       if(value >= 60.0)
+                               remove_queue.push_back(i.getNode()->getKey());
+               }
+               core::list<v3s16>::Iterator j;
+               j = remove_queue.begin();
+               for(; j != remove_queue.end(); j++)
+               {
+                       m_fetchblock_history.remove(*j);
+               }
+       }
+#endif
+
+       /*{
+               JMutexAutoLock lock(m_step_dtime_mutex);
+               m_step_dtime += dtime;
+       }*/
+       
+       /*
+               BEGIN TEST CODE
+       */
+
+       /*
+               END OF TEST CODE
+       */
+}
+
+float Client::asyncStep()
+{
+       DSTACK(__FUNCTION_NAME);
+       //dstream<<"Client::asyncStep()"<<std::endl;
+       
+       /*float dtime;
+       {
+               JMutexAutoLock lock1(m_step_dtime_mutex);
+               dtime = m_step_dtime;
+               m_step_dtime = 0.0;
+       }
+
+       return dtime;*/
+       return 0.0;
+}
+
+// Virtual methods from con::PeerHandler
+void Client::peerAdded(con::Peer *peer)
+{
+       derr_client<<"Client::peerAdded(): peer->id="
+                       <<peer->id<<std::endl;
+}
+void Client::deletingPeer(con::Peer *peer, bool timeout)
+{
+       derr_client<<"Client::deletingPeer(): "
+                       "Server Peer is getting deleted "
+                       <<"(timeout="<<timeout<<")"<<std::endl;
+}
+
+void Client::ReceiveAll()
+{
+       DSTACK(__FUNCTION_NAME);
+       for(;;)
+       {
+               try{
+                       Receive();
+               }
+               catch(con::NoIncomingDataException &e)
+               {
+                       break;
+               }
+               catch(con::InvalidIncomingDataException &e)
+               {
+                       dout_client<<DTIME<<"Client::ReceiveAll(): "
+                                       "InvalidIncomingDataException: what()="
+                                       <<e.what()<<std::endl;
+               }
+               //TODO: Testing
+               //break;
+       }
+}
+
+void Client::Receive()
+{
+       DSTACK(__FUNCTION_NAME);
+       u32 data_maxsize = 10000;
+       Buffer<u8> data(data_maxsize);
+       u16 sender_peer_id;
+       u32 datasize;
+       {
+               //TimeTaker t1("con mutex and receive", m_device);
+               JMutexAutoLock lock(m_con_mutex);
+               datasize = m_con.Receive(sender_peer_id, *data, data_maxsize);
+       }
+       //TimeTaker t1("ProcessData", m_device);
+       ProcessData(*data, datasize, sender_peer_id);
+}
+
+/*
+       sender_peer_id given to this shall be quaranteed to be a valid peer
+*/
+void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
+{
+       DSTACK(__FUNCTION_NAME);
+       // Ignore packets that don't even fit a command
+       if(datasize < 2)
+               return;
+
+       ToClientCommand command = (ToClientCommand)readU16(&data[0]);
+       
+       /*
+               If this check is removed, be sure to change the queue
+               system to know the ids
+       */
+       if(sender_peer_id != PEER_ID_SERVER)
+       {
+               dout_client<<DTIME<<"Client::ProcessData(): Discarding data not "
+                               "coming from server: peer_id="<<sender_peer_id
+                               <<std::endl;
+               return;
+       }
+
+       con::Peer *peer;
+       {
+               JMutexAutoLock lock(m_con_mutex);
+               // All data is coming from the server
+               // PeerNotFoundException is handled by caller.
+               peer = m_con.GetPeer(PEER_ID_SERVER);
+       }
+
+       u8 ser_version = m_server_ser_ver;
+
+       //dstream<<"Client received command="<<(int)command<<std::endl;
+
+       // Execute fast commands straight away
+
+       if(command == TOCLIENT_INIT)
+       {
+               if(datasize < 3)
+                       return;
+
+               u8 deployed = data[2];
+
+               dout_client<<DTIME<<"Client: TOCLIENT_INIT received with "
+                               "deployed="<<((int)deployed&0xff)<<std::endl;
+
+               if(deployed < SER_FMT_VER_LOWEST
+                               || deployed > SER_FMT_VER_HIGHEST)
+               {
+                       derr_client<<DTIME<<"Client: TOCLIENT_INIT: Server sent "
+                                       <<"unsupported ser_fmt_ver"<<std::endl;
+                       return;
+               }
+               
+               m_server_ser_ver = deployed;
+
+               // Get player position
+               v3s16 playerpos_s16(0, BS*2+BS*20, 0);
+               if(datasize >= 2+1+6)
+                       playerpos_s16 = readV3S16(&data[2+1]);
+               v3f playerpos_f = intToFloat(playerpos_s16) - v3f(0, BS/2, 0);
+
+               { //envlock
+                       JMutexAutoLock envlock(m_env_mutex);
+                       
+                       // Set player position
+                       Player *player = m_env.getLocalPlayer();
+                       assert(player != NULL);
+                       player->setPosition(playerpos_f);
+               }
+               
+               // Reply to server
+               u32 replysize = 2;
+               SharedBuffer<u8> reply(replysize);
+               writeU16(&reply[0], TOSERVER_INIT2);
+               // Send as reliable
+               m_con.Send(PEER_ID_SERVER, 1, reply, true);
+
+               return;
+       }
+       
+       if(ser_version == SER_FMT_VER_INVALID)
+       {
+               dout_client<<DTIME<<"WARNING: Client: Server serialization"
+                               " format invalid or not initialized."
+                               " Skipping incoming command="<<command<<std::endl;
+               return;
+       }
+       
+       // Just here to avoid putting the two if's together when
+       // making some copypasta
+       {}
+
+       if(command == TOCLIENT_PLAYERPOS)
+       {
+               dstream<<"WARNING: Received deprecated TOCLIENT_PLAYERPOS"
+                               <<std::endl;
+               /*u16 our_peer_id;
+               {
+                       JMutexAutoLock lock(m_con_mutex);
+                       our_peer_id = m_con.GetPeerID();
+               }
+               // Cancel if we don't have a peer id
+               if(our_peer_id == PEER_ID_NEW){
+                       dout_client<<DTIME<<"TOCLIENT_PLAYERPOS cancelled: "
+                                       "we have no peer id"
+                                       <<std::endl;
+                       return;
+               }*/
+
+               { //envlock
+                       JMutexAutoLock envlock(m_env_mutex);
+                       
+                       u32 player_size = 2+12+12+4+4;
+                               
+                       u32 player_count = (datasize-2) / player_size;
+                       u32 start = 2;
+                       for(u32 i=0; i<player_count; i++)
+                       {
+                               u16 peer_id = readU16(&data[start]);
+
+                               Player *player = m_env.getPlayer(peer_id);
+
+                               // Skip if player doesn't exist
+                               if(player == NULL)
+                               {
+                                       start += player_size;
+                                       continue;
+                               }
+
+                               // Skip if player is local player
+                               if(player->isLocal())
+                               {
+                                       start += player_size;
+                                       continue;
+                               }
+
+                               v3s32 ps = readV3S32(&data[start+2]);
+                               v3s32 ss = readV3S32(&data[start+2+12]);
+                               s32 pitch_i = readS32(&data[start+2+12+12]);
+                               s32 yaw_i = readS32(&data[start+2+12+12+4]);
+                               /*dstream<<"Client: got "
+                                               <<"pitch_i="<<pitch_i
+                                               <<" yaw_i="<<yaw_i<<std::endl;*/
+                               f32 pitch = (f32)pitch_i / 100.0;
+                               f32 yaw = (f32)yaw_i / 100.0;
+                               v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
+                               v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
+                               player->setPosition(position);
+                               player->setSpeed(speed);
+                               player->setPitch(pitch);
+                               player->setYaw(yaw);
+
+                               /*dstream<<"Client: player "<<peer_id
+                                               <<" pitch="<<pitch
+                                               <<" yaw="<<yaw<<std::endl;*/
+
+                               start += player_size;
+                       }
+               } //envlock
+       }
+       else if(command == TOCLIENT_PLAYERINFO)
+       {
+               u16 our_peer_id;
+               {
+                       JMutexAutoLock lock(m_con_mutex);
+                       our_peer_id = m_con.GetPeerID();
+               }
+               // Cancel if we don't have a peer id
+               if(our_peer_id == PEER_ID_NEW){
+                       dout_client<<DTIME<<"TOCLIENT_PLAYERINFO cancelled: "
+                                       "we have no peer id"
+                                       <<std::endl;
+                       return;
+               }
+               
+               //dstream<<DTIME<<"Client: Server reports players:"<<std::endl;
+
+               { //envlock
+                       JMutexAutoLock envlock(m_env_mutex);
+                       
+                       u32 item_size = 2+PLAYERNAME_SIZE;
+                       u32 player_count = (datasize-2) / item_size;
+                       u32 start = 2;
+                       // peer_ids
+                       core::list<u16> players_alive;
+                       for(u32 i=0; i<player_count; i++)
+                       {
+                               // Make sure the name ends in '\0'
+                               data[start+2+20-1] = 0;
+
+                               u16 peer_id = readU16(&data[start]);
+
+                               players_alive.push_back(peer_id);
+                               
+                               /*dstream<<DTIME<<"peer_id="<<peer_id
+                                               <<" name="<<((char*)&data[start+2])<<std::endl;*/
+
+                               // Don't update the info of the local player
+                               if(peer_id == our_peer_id)
+                               {
+                                       start += item_size;
+                                       continue;
+                               }
+
+                               Player *player = m_env.getPlayer(peer_id);
+
+                               // Create a player if it doesn't exist
+                               if(player == NULL)
+                               {
+                                       player = new RemotePlayer(
+                                                       m_device->getSceneManager()->getRootSceneNode(),
+                                                       m_device,
+                                                       -1);
+                                       player->peer_id = peer_id;
+                                       m_env.addPlayer(player);
+                                       dout_client<<DTIME<<"Client: Adding new player "
+                                                       <<peer_id<<std::endl;
+                               }
+                               
+                               player->updateName((char*)&data[start+2]);
+
+                               start += item_size;
+                       }
+                       
+                       /*
+                               Remove those players from the environment that
+                               weren't listed by the server.
+                       */
+                       //dstream<<DTIME<<"Removing dead players"<<std::endl;
+                       core::list<Player*> players = m_env.getPlayers();
+                       core::list<Player*>::Iterator ip;
+                       for(ip=players.begin(); ip!=players.end(); ip++)
+                       {
+                               // Ingore local player
+                               if((*ip)->isLocal())
+                                       continue;
+                               
+                               // Warn about a special case
+                               if((*ip)->peer_id == 0)
+                               {
+                                       dstream<<DTIME<<"WARNING: Client: Removing "
+                                                       "dead player with id=0"<<std::endl;
+                               }
+
+                               bool is_alive = false;
+                               core::list<u16>::Iterator i;
+                               for(i=players_alive.begin(); i!=players_alive.end(); i++)
+                               {
+                                       if((*ip)->peer_id == *i)
+                                       {
+                                               is_alive = true;
+                                               break;
+                                       }
+                               }
+                               /*dstream<<DTIME<<"peer_id="<<((*ip)->peer_id)
+                                               <<" is_alive="<<is_alive<<std::endl;*/
+                               if(is_alive)
+                                       continue;
+                               dstream<<DTIME<<"Removing dead player "<<(*ip)->peer_id
+                                               <<std::endl;
+                               m_env.removePlayer((*ip)->peer_id);
+                       }
+               } //envlock
+       }
+       else if(command == TOCLIENT_SECTORMETA)
+       {
+               /*
+                       [0] u16 command
+                       [2] u8 sector count
+                       [3...] v2s16 pos + sector metadata
+               */
+               if(datasize < 3)
+                       return;
+
+               //dstream<<"Client received TOCLIENT_SECTORMETA"<<std::endl;
+
+               { //envlock
+                       JMutexAutoLock envlock(m_env_mutex);
+                       
+                       std::string datastring((char*)&data[2], datasize-2);
+                       std::istringstream is(datastring, std::ios_base::binary);
+
+                       u8 buf[4];
+
+                       is.read((char*)buf, 1);
+                       u16 sector_count = readU8(buf);
+                       
+                       //dstream<<"sector_count="<<sector_count<<std::endl;
+
+                       for(u16 i=0; i<sector_count; i++)
+                       {
+                               // Read position
+                               is.read((char*)buf, 4);
+                               v2s16 pos = readV2S16(buf);
+                               /*dstream<<"Client: deserializing sector at "
+                                               <<"("<<pos.X<<","<<pos.Y<<")"<<std::endl;*/
+                               // Create sector
+                               assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
+                               ((ClientMap&)m_env.getMap()).deSerializeSector(pos, is);
+                       }
+               } //envlock
+       }
+       else if(command == TOCLIENT_INVENTORY)
+       {
+               if(datasize < 3)
+                       return;
+
+               //TimeTaker t1("Parsing TOCLIENT_INVENTORY", m_device);
+
+               { //envlock
+                       //TimeTaker t2("mutex locking", m_device);
+                       JMutexAutoLock envlock(m_env_mutex);
+                       //t2.stop();
+                       
+                       //TimeTaker t3("istringstream init", m_device);
+                       std::string datastring((char*)&data[2], datasize-2);
+                       std::istringstream is(datastring, std::ios_base::binary);
+                       //t3.stop();
+                       
+                       //m_env.printPlayers(dstream);
+
+                       //TimeTaker t4("player get", m_device);
+                       Player *player = m_env.getLocalPlayer();
+                       assert(player != NULL);
+                       //t4.stop();
+
+                       //TimeTaker t1("inventory.deSerialize()", m_device);
+                       player->inventory.deSerialize(is);
+                       //t1.stop();
+
+                       m_inventory_updated = true;
+
+                       //dstream<<"Client got player inventory:"<<std::endl;
+                       //player->inventory.print(dstream);
+               }
+       }
+       //DEBUG
+       else if(command == TOCLIENT_OBJECTDATA)
+       //else if(0)
+       {
+               // Strip command word and create a stringstream
+               std::string datastring((char*)&data[2], datasize-2);
+               std::istringstream is(datastring, std::ios_base::binary);
+               
+               { //envlock
+               
+               JMutexAutoLock envlock(m_env_mutex);
+
+               u8 buf[12];
+
+               /*
+                       Read players
+               */
+
+               is.read((char*)buf, 2);
+               u16 playercount = readU16(buf);
+               
+               for(u16 i=0; i<playercount; i++)
+               {
+                       is.read((char*)buf, 2);
+                       u16 peer_id = readU16(buf);
+                       is.read((char*)buf, 12);
+                       v3s32 p_i = readV3S32(buf);
+                       is.read((char*)buf, 12);
+                       v3s32 s_i = readV3S32(buf);
+                       is.read((char*)buf, 4);
+                       s32 pitch_i = readS32(buf);
+                       is.read((char*)buf, 4);
+                       s32 yaw_i = readS32(buf);
+                       
+                       Player *player = m_env.getPlayer(peer_id);
+
+                       // Skip if player doesn't exist
+                       if(player == NULL)
+                       {
+                               continue;
+                       }
+
+                       // Skip if player is local player
+                       if(player->isLocal())
+                       {
+                               continue;
+                       }
+       
+                       f32 pitch = (f32)pitch_i / 100.0;
+                       f32 yaw = (f32)yaw_i / 100.0;
+                       v3f position((f32)p_i.X/100., (f32)p_i.Y/100., (f32)p_i.Z/100.);
+                       v3f speed((f32)s_i.X/100., (f32)s_i.Y/100., (f32)s_i.Z/100.);
+                       
+                       player->setPosition(position);
+                       player->setSpeed(speed);
+                       player->setPitch(pitch);
+                       player->setYaw(yaw);
+               }
+
+               /*
+                       Read block objects
+               */
+
+               // Read active block count
+               is.read((char*)buf, 2);
+               u16 blockcount = readU16(buf);
+               
+               // Initialize delete queue with all active blocks
+               core::map<v3s16, bool> abs_to_delete;
+               for(core::map<v3s16, bool>::Iterator
+                               i = m_active_blocks.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       v3s16 p = i.getNode()->getKey();
+                       /*dstream<<"adding "
+                                       <<"("<<p.x<<","<<p.y<<","<<p.z<<") "
+                                       <<" to abs_to_delete"
+                                       <<std::endl;*/
+                       abs_to_delete.insert(p, true);
+               }
+
+               /*dstream<<"Initial delete queue size: "<<abs_to_delete.size()
+                               <<std::endl;*/
+               
+               for(u16 i=0; i<blockcount; i++)
+               {
+                       // Read blockpos
+                       is.read((char*)buf, 6);
+                       v3s16 p = readV3S16(buf);
+                       // Get block from somewhere
+                       MapBlock *block = NULL;
+                       try{
+                               block = m_env.getMap().getBlockNoCreate(p);
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               //TODO: Create a dummy block?
+                       }
+                       if(block == NULL)
+                       {
+                               dstream<<"WARNING: "
+                                               <<"Could not get block at blockpos "
+                                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
+                                               <<"in TOCLIENT_OBJECTDATA. Ignoring "
+                                               <<"following block object data."
+                                               <<std::endl;
+                               return;
+                       }
+
+                       /*dstream<<"Client updating objects for block "
+                                       <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                                       <<std::endl;*/
+
+                       // Insert to active block list
+                       m_active_blocks.insert(p, true);
+
+                       // Remove from deletion queue
+                       if(abs_to_delete.find(p) != NULL)
+                               abs_to_delete.remove(p);
+
+                       // Update objects of block
+                       block->updateObjects(is, m_server_ser_ver,
+                                       m_device->getSceneManager());
+               }
+               
+               /*dstream<<"Final delete queue size: "<<abs_to_delete.size()
+                               <<std::endl;*/
+               
+               // Delete objects of blocks in delete queue
+               for(core::map<v3s16, bool>::Iterator
+                               i = abs_to_delete.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       v3s16 p = i.getNode()->getKey();
+                       try
+                       {
+                               MapBlock *block = m_env.getMap().getBlockNoCreate(p);
+                               
+                               // Clear objects
+                               block->clearObjects();
+                               // Remove from active blocks list
+                               m_active_blocks.remove(p);
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               dstream<<"WARNAING: Client: "
+                                               <<"Couldn't clear objects of active->inactive"
+                                               <<" block "
+                                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                                               <<" because block was not found"
+                                               <<std::endl;
+                               // Ignore
+                       }
+               }
+
+               } //envlock
+       }
+       // Default to queueing it (for slow commands)
+       else
+       {
+               JMutexAutoLock lock(m_incoming_queue_mutex);
+               
+               IncomingPacket packet(data, datasize);
+               m_incoming_queue.push_back(packet);
+       }
+}
+
+/*
+       Returns true if there was something in queue
+*/
+bool Client::AsyncProcessPacket(LazyMeshUpdater &mesh_updater)
+{
+       DSTACK(__FUNCTION_NAME);
+       
+       try //for catching con::PeerNotFoundException
+       {
+
+       con::Peer *peer;
+       {
+               JMutexAutoLock lock(m_con_mutex);
+               // All data is coming from the server
+               peer = m_con.GetPeer(PEER_ID_SERVER);
+       }
+       
+       u8 ser_version = m_server_ser_ver;
+
+       IncomingPacket packet = getPacket();
+       u8 *data = packet.m_data;
+       u32 datasize = packet.m_datalen;
+       
+       // An empty packet means queue is empty
+       if(data == NULL){
+               return false;
+       }
+       
+       if(datasize < 2)
+               return true;
+       
+       ToClientCommand command = (ToClientCommand)readU16(&data[0]);
+
+       if(command == TOCLIENT_REMOVENODE)
+       {
+               if(datasize < 8)
+                       return true;
+               v3s16 p;
+               p.X = readS16(&data[2]);
+               p.Y = readS16(&data[4]);
+               p.Z = readS16(&data[6]);
+               
+               //TimeTaker t1("TOCLIENT_REMOVENODE", g_device);
+
+               core::map<v3s16, MapBlock*> modified_blocks;
+
+               try
+               {
+                       JMutexAutoLock envlock(m_env_mutex);
+                       //TimeTaker t("removeNodeAndUpdate", m_device);
+                       m_env.getMap().removeNodeAndUpdate(p, modified_blocks);
+               }
+               catch(InvalidPositionException &e)
+               {
+               }
+               
+               for(core::map<v3s16, MapBlock * >::Iterator
+                               i = modified_blocks.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       v3s16 p = i.getNode()->getKey();
+                       //m_env.getMap().updateMeshes(p);
+                       mesh_updater.add(p);
+               }
+       }
+       else if(command == TOCLIENT_ADDNODE)
+       {
+               if(datasize < 8 + MapNode::serializedLength(ser_version))
+                       return true;
+
+               v3s16 p;
+               p.X = readS16(&data[2]);
+               p.Y = readS16(&data[4]);
+               p.Z = readS16(&data[6]);
+               
+               //TimeTaker t1("TOCLIENT_ADDNODE", g_device);
+
+               MapNode n;
+               n.deSerialize(&data[8], ser_version);
+               
+               core::map<v3s16, MapBlock*> modified_blocks;
+
+               try
+               {
+                       JMutexAutoLock envlock(m_env_mutex);
+                       m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
+               }
+               catch(InvalidPositionException &e)
+               {}
+               
+               for(core::map<v3s16, MapBlock * >::Iterator
+                               i = modified_blocks.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       v3s16 p = i.getNode()->getKey();
+                       //m_env.getMap().updateMeshes(p);
+                       mesh_updater.add(p);
+               }
+       }
+       else if(command == TOCLIENT_BLOCKDATA)
+       {
+               // Ignore too small packet
+               if(datasize < 8)
+                       return true;
+               /*if(datasize < 8 + MapBlock::serializedLength(ser_version))
+                       goto getdata;*/
+                       
+               v3s16 p;
+               p.X = readS16(&data[2]);
+               p.Y = readS16(&data[4]);
+               p.Z = readS16(&data[6]);
+               
+               /*dout_client<<DTIME<<"Client: Thread: BLOCKDATA for ("
+                               <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+               /*dstream<<DTIME<<"Client: Thread: BLOCKDATA for ("
+                               <<p.X<<","<<p.Y<<","<<p.Z<<"): ";*/
+               
+               std::string datastring((char*)&data[8], datasize-8);
+               std::istringstream istr(datastring, std::ios_base::binary);
+               
+               MapSector *sector;
+               MapBlock *block;
+               
+               { //envlock
+                       JMutexAutoLock envlock(m_env_mutex);
+                       
+                       v2s16 p2d(p.X, p.Z);
+                       sector = m_env.getMap().emergeSector(p2d);
+                       
+                       v2s16 sp = sector->getPos();
+                       if(sp != p2d)
+                       {
+                               dstream<<"ERROR: Got sector with getPos()="
+                                               <<"("<<sp.X<<","<<sp.Y<<"), tried to get"
+                                               <<"("<<p2d.X<<","<<p2d.Y<<")"<<std::endl;
+                       }
+
+                       assert(sp == p2d);
+                       //assert(sector->getPos() == p2d);
+                       
+                       try{
+                               block = sector->getBlockNoCreate(p.Y);
+                               /*
+                                       Update an existing block
+                               */
+                               //dstream<<"Updating"<<std::endl;
+                               block->deSerialize(istr, ser_version);
+                               //block->setChangedFlag();
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               /*
+                                       Create a new block
+                               */
+                               //dstream<<"Creating new"<<std::endl;
+                               block = new MapBlock(&m_env.getMap(), p);
+                               block->deSerialize(istr, ser_version);
+                               sector->insertBlock(block);
+                               //block->setChangedFlag();
+                       }
+               } //envlock
+               
+               
+               // Old version has zero lighting, update it.
+               if(ser_version == 0 || ser_version == 1)
+               {
+                       derr_client<<"Client: Block in old format: "
+                                       "Calculating lighting"<<std::endl;
+                       core::map<v3s16, MapBlock*> blocks_changed;
+                       blocks_changed.insert(block->getPos(), block);
+                       core::map<v3s16, MapBlock*> modified_blocks;
+                       m_env.getMap().updateLighting(blocks_changed, modified_blocks);
+               }
+
+               /*
+                       Update Mesh of this block and blocks at x-, y- and z-
+               */
+
+               //m_env.getMap().updateMeshes(block->getPos());
+               mesh_updater.add(block->getPos());
+               
+               /*
+                       Acknowledge block.
+               */
+               /*
+                       [0] u16 command
+                       [2] u8 count
+                       [3] v3s16 pos_0
+                       [3+6] v3s16 pos_1
+                       ...
+               */
+               u32 replysize = 2+1+6;
+               SharedBuffer<u8> reply(replysize);
+               writeU16(&reply[0], TOSERVER_GOTBLOCKS);
+               reply[2] = 1;
+               writeV3S16(&reply[3], p);
+               // Send as reliable
+               m_con.Send(PEER_ID_SERVER, 1, reply, true);
+
+#if 0
+               /*
+                       Remove from history
+               */
+               {
+                       JMutexAutoLock lock(m_fetchblock_mutex);
+                       
+                       if(m_fetchblock_history.find(p) != NULL)
+                       {
+                               m_fetchblock_history.remove(p);
+                       }
+                       else
+                       {
+                               /*
+                                       Acknowledge block.
+                               */
+                               /*
+                                       [0] u16 command
+                                       [2] u8 count
+                                       [3] v3s16 pos_0
+                                       [3+6] v3s16 pos_1
+                                       ...
+                               */
+                               u32 replysize = 2+1+6;
+                               SharedBuffer<u8> reply(replysize);
+                               writeU16(&reply[0], TOSERVER_GOTBLOCKS);
+                               reply[2] = 1;
+                               writeV3S16(&reply[3], p);
+                               // Send as reliable
+                               m_con.Send(PEER_ID_SERVER, 1, reply, true);
+                       }
+               }
+#endif
+       }
+       else
+       {
+               dout_client<<DTIME<<"WARNING: Client: Ignoring unknown command "
+                               <<command<<std::endl;
+       }
+
+       return true;
+
+       } //try
+       catch(con::PeerNotFoundException &e)
+       {
+               dout_client<<DTIME<<"Client::AsyncProcessData(): Cancelling: The server"
+                               " connection doesn't exist (a timeout or not yet connected?)"<<std::endl;
+               return false;
+       }
+}
+
+bool Client::AsyncProcessData()
+{
+       LazyMeshUpdater mesh_updater(&m_env);
+       for(;;)
+       {
+               bool r = AsyncProcessPacket(mesh_updater);
+               if(r == false)
+                       break;
+       }
+       return false;
+}
+
+void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
+{
+       JMutexAutoLock lock(m_con_mutex);
+       m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
+}
+
+#if 0
+void Client::fetchBlock(v3s16 p, u8 flags)
+{
+       if(connectedAndInitialized() == false)
+               throw ClientNotReadyException
+               ("ClientNotReadyException: connectedAndInitialized() == false");
+
+       /*dstream<<"Client::fetchBlock(): Sending GETBLOCK for ("
+                       <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+       JMutexAutoLock conlock(m_con_mutex);
+
+       SharedBuffer<u8> data(9);
+       writeU16(&data[0], TOSERVER_GETBLOCK);
+       writeS16(&data[2], p.X);
+       writeS16(&data[4], p.Y);
+       writeS16(&data[6], p.Z);
+       writeU8(&data[8], flags);
+       m_con.Send(PEER_ID_SERVER, 1, data, true);
+}
+
+/*
+       Calls fetchBlock() on some nearby missing blocks.
+
+       Returns when any of various network load indicators go over limit.
+
+       Does nearly the same thing as the old updateChangedVisibleArea()
+*/
+void Client::fetchBlocks()
+{
+       if(connectedAndInitialized() == false)
+               throw ClientNotReadyException
+               ("ClientNotReadyException: connectedAndInitialized() == false");
+}
+#endif
+
+bool Client::isFetchingBlocks()
+{
+       JMutexAutoLock conlock(m_con_mutex);
+       con::Peer *peer = m_con.GetPeerNoEx(PEER_ID_SERVER);
+       // Not really fetching but can't fetch more.
+       if(peer == NULL) return true;
+
+       con::Channel *channel = &(peer->channels[1]);
+       /*
+               NOTE: Channel 0 should always be used for fetching blocks,
+                     and for nothing else.
+       */
+       if(channel->incoming_reliables.size() > 0)
+               return true;
+       if(channel->outgoing_reliables.size() > 0)
+               return true;
+       return false;
+}
+
+IncomingPacket Client::getPacket()
+{
+       JMutexAutoLock lock(m_incoming_queue_mutex);
+       
+       core::list<IncomingPacket>::Iterator i;
+       // Refer to first one
+       i = m_incoming_queue.begin();
+
+       // If queue is empty, return empty packet
+       if(i == m_incoming_queue.end()){
+               IncomingPacket packet;
+               return packet;
+       }
+       
+       // Pop out first packet and return it
+       IncomingPacket packet = *i;
+       m_incoming_queue.erase(i);
+       return packet;
+}
+
+#if 0
+void Client::removeNode(v3s16 nodepos)
+{
+       if(connectedAndInitialized() == false){
+               dout_client<<DTIME<<"Client::removeNode() cancelled (not connected)"
+                               <<std::endl;
+               return;
+       }
+       
+       // Test that the position exists
+       try{
+               JMutexAutoLock envlock(m_env_mutex);
+               m_env.getMap().getNode(nodepos);
+       }
+       catch(InvalidPositionException &e)
+       {
+               dout_client<<DTIME<<"Client::removeNode() cancelled (doesn't exist)"
+                               <<std::endl;
+               return;
+       }
+
+       SharedBuffer<u8> data(8);
+       writeU16(&data[0], TOSERVER_REMOVENODE);
+       writeS16(&data[2], nodepos.X);
+       writeS16(&data[4], nodepos.Y);
+       writeS16(&data[6], nodepos.Z);
+       Send(0, data, true);
+}
+
+void Client::addNodeFromInventory(v3s16 nodepos, u16 i)
+{
+       if(connectedAndInitialized() == false){
+               dout_client<<DTIME<<"Client::addNodeFromInventory() "
+                               "cancelled (not connected)"
+                               <<std::endl;
+               return;
+       }
+       
+       // Test that the position exists
+       try{
+               JMutexAutoLock envlock(m_env_mutex);
+               m_env.getMap().getNode(nodepos);
+       }
+       catch(InvalidPositionException &e)
+       {
+               dout_client<<DTIME<<"Client::addNode() cancelled (doesn't exist)"
+                               <<std::endl;
+               return;
+       }
+
+       //u8 ser_version = m_server_ser_ver;
+
+       // SUGGESTION: The validity of the operation could be checked here too
+
+       u8 datasize = 2 + 6 + 2;
+       SharedBuffer<u8> data(datasize);
+       writeU16(&data[0], TOSERVER_ADDNODE_FROM_INVENTORY);
+       writeS16(&data[2], nodepos.X);
+       writeS16(&data[4], nodepos.Y);
+       writeS16(&data[6], nodepos.Z);
+       writeU16(&data[8], i);
+       Send(0, data, true);
+}
+#endif
+
+void Client::clickGround(u8 button, v3s16 nodepos_undersurface,
+               v3s16 nodepos_oversurface, u16 item)
+{
+       if(connectedAndInitialized() == false){
+               dout_client<<DTIME<<"Client::clickGround() "
+                               "cancelled (not connected)"
+                               <<std::endl;
+               return;
+       }
+       
+       /*
+               length: 19
+               [0] u16 command
+               [2] u8 button (0=left, 1=right)
+               [3] v3s16 nodepos_undersurface
+               [9] v3s16 nodepos_abovesurface
+               [15] u16 item
+       */
+       u8 datasize = 2 + 1 + 6 + 6 + 2;
+       SharedBuffer<u8> data(datasize);
+       writeU16(&data[0], TOSERVER_CLICK_GROUND);
+       writeU8(&data[2], button);
+       writeV3S16(&data[3], nodepos_undersurface);
+       writeV3S16(&data[9], nodepos_oversurface);
+       writeU16(&data[15], item);
+       Send(0, data, true);
+}
+
+void Client::clickObject(u8 button, v3s16 blockpos, s16 id, u16 item)
+{
+       if(connectedAndInitialized() == false){
+               dout_client<<DTIME<<"Client::clickObject() "
+                               "cancelled (not connected)"
+                               <<std::endl;
+               return;
+       }
+       
+       /*
+               [0] u16 command
+               [2] u8 button (0=left, 1=right)
+               [3] v3s16 block
+               [9] s16 id
+               [11] u16 item
+       */
+       u8 datasize = 2 + 1 + 6 + 2 + 2;
+       SharedBuffer<u8> data(datasize);
+       writeU16(&data[0], TOSERVER_CLICK_OBJECT);
+       writeU8(&data[2], button);
+       writeV3S16(&data[3], blockpos);
+       writeS16(&data[9], id);
+       writeU16(&data[11], item);
+       Send(0, data, true);
+}
+
+void Client::release(u8 button)
+{
+       //TODO
+}
+
+void Client::sendSignText(v3s16 blockpos, s16 id, std::string text)
+{
+       /*
+               u16 command
+               v3s16 blockpos
+               s16 id
+               u16 textlen
+               textdata
+       */
+       std::ostringstream os(std::ios_base::binary);
+       u8 buf[12];
+       
+       // Write command
+       writeU16(buf, TOSERVER_SIGNTEXT);
+       os.write((char*)buf, 2);
+       
+       // Write blockpos
+       writeV3S16(buf, blockpos);
+       os.write((char*)buf, 6);
+
+       // Write id
+       writeS16(buf, id);
+       os.write((char*)buf, 2);
+
+       u16 textlen = text.size();
+       // Write text length
+       writeS16(buf, textlen);
+       os.write((char*)buf, 2);
+
+       // Write text
+       os.write((char*)text.c_str(), textlen);
+       
+       // Make data buffer
+       std::string s = os.str();
+       SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+       // Send as reliable
+       Send(0, data, true);
+}
+
+void Client::sendPlayerPos()
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       
+       Player *myplayer = m_env.getLocalPlayer();
+       if(myplayer == NULL)
+               return;
+       
+       u16 our_peer_id;
+       {
+               JMutexAutoLock lock(m_con_mutex);
+               our_peer_id = m_con.GetPeerID();
+       }
+       
+       // Set peer id if not set already
+       if(myplayer->peer_id == PEER_ID_NEW)
+               myplayer->peer_id = our_peer_id;
+       // Check that an existing peer_id is the same as the connection's
+       assert(myplayer->peer_id == our_peer_id);
+       
+       v3f pf = myplayer->getPosition();
+       v3s32 position(pf.X*100, pf.Y*100, pf.Z*100);
+       v3f sf = myplayer->getSpeed();
+       v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100);
+       s32 pitch = myplayer->getPitch() * 100;
+       s32 yaw = myplayer->getYaw() * 100;
+
+       /*
+               Format:
+               [0] u16 command
+               [2] v3s32 position*100
+               [2+12] v3s32 speed*100
+               [2+12+12] s32 pitch*100
+               [2+12+12+4] s32 yaw*100
+       */
+
+       SharedBuffer<u8> data(2+12+12+4+4);
+       writeU16(&data[0], TOSERVER_PLAYERPOS);
+       writeV3S32(&data[2], position);
+       writeV3S32(&data[2+12], speed);
+       writeS32(&data[2+12+12], pitch);
+       writeS32(&data[2+12+12+4], yaw);
+
+       // Send as unreliable
+       Send(0, data, false);
+}
+
+
+void Client::updateCamera(v3f pos, v3f dir)
+{
+       m_env.getMap().updateCamera(pos, dir);
+       camera_position = pos;
+       camera_direction = dir;
+}
+
+MapNode Client::getNode(v3s16 p)
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       return m_env.getMap().getNode(p);
+}
+
+/*f32 Client::getGroundHeight(v2s16 p)
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       return m_env.getMap().getGroundHeight(p);
+}*/
+
+bool Client::isNodeUnderground(v3s16 p)
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       return m_env.getMap().isNodeUnderground(p);
+}
+
+/*Player * Client::getLocalPlayer()
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       return m_env.getLocalPlayer();
+}*/
+
+/*core::list<Player*> Client::getPlayers()
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       return m_env.getPlayers();
+}*/
+
+v3f Client::getPlayerPosition()
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       LocalPlayer *player = m_env.getLocalPlayer();
+       assert(player != NULL);
+       return player->getPosition();
+}
+
+void Client::setPlayerControl(PlayerControl &control)
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       LocalPlayer *player = m_env.getLocalPlayer();
+       assert(player != NULL);
+       player->control = control;
+}
+
+// Returns true if the inventory of the local player has been
+// updated from the server. If it is true, it is set to false.
+bool Client::getLocalInventoryUpdated()
+{
+       // m_inventory_updated is behind envlock
+       JMutexAutoLock envlock(m_env_mutex);
+       bool updated = m_inventory_updated;
+       m_inventory_updated = false;
+       return updated;
+}
+
+// Copies the inventory of the local player to parameter
+void Client::getLocalInventory(Inventory &dst)
+{
+       JMutexAutoLock envlock(m_env_mutex);
+       Player *player = m_env.getLocalPlayer();
+       assert(player != NULL);
+       dst = player->inventory;
+}
+
+MapBlockObject * Client::getSelectedObject(
+               f32 max_d,
+               v3f from_pos_f_on_map,
+               core::line3d<f32> shootline_on_map
+       )
+{
+       JMutexAutoLock envlock(m_env_mutex);
+
+       core::array<DistanceSortedObject> objects;
+
+       for(core::map<v3s16, bool>::Iterator
+                       i = m_active_blocks.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               v3s16 p = i.getNode()->getKey();
+
+               MapBlock *block = NULL;
+               try
+               {
+                       block = m_env.getMap().getBlockNoCreate(p);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       continue;
+               }
+
+               // Calculate from_pos relative to block
+               v3s16 block_pos_i_on_map = block->getPosRelative();
+               v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+               v3f from_pos_f_on_block = from_pos_f_on_map - block_pos_f_on_map;
+
+               block->getObjects(from_pos_f_on_block, max_d, objects);
+       }
+
+       //dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
+       
+       // Sort them.
+       // After this, the closest object is the first in the array.
+       objects.sort();
+
+       for(u32 i=0; i<objects.size(); i++)
+       {
+               MapBlockObject *obj = objects[i].obj;
+               MapBlock *block = obj->getBlock();
+
+               // Calculate shootline relative to block
+               v3s16 block_pos_i_on_map = block->getPosRelative();
+               v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+               core::line3d<f32> shootline_on_block(
+                               shootline_on_map.start - block_pos_f_on_map,
+                               shootline_on_map.end - block_pos_f_on_map
+               );
+
+               if(obj->isSelected(shootline_on_block))
+               {
+                       //dstream<<"Returning selected object"<<std::endl;
+                       return obj;
+               }
+       }
+
+       //dstream<<"No object selected; returning NULL."<<std::endl;
+       return NULL;
+}
+
+void Client::printDebugInfo(std::ostream &os)
+{
+       //JMutexAutoLock lock1(m_fetchblock_mutex);
+       JMutexAutoLock lock2(m_incoming_queue_mutex);
+
+       os<<"m_incoming_queue.getSize()="<<m_incoming_queue.getSize()
+               //<<", m_fetchblock_history.size()="<<m_fetchblock_history.size()
+               //<<", m_opt_not_found_history.size()="<<m_opt_not_found_history.size()
+               <<std::endl;
+}
+       
+
diff --git a/src/client.h b/src/client.h
new file mode 100644 (file)
index 0000000..60c5792
--- /dev/null
@@ -0,0 +1,272 @@
+#ifndef CLIENT_HEADER
+#define CLIENT_HEADER
+
+#include "connection.h"
+#include "environment.h"
+#include "common_irrlicht.h"
+#include "jmutex.h"
+#include <ostream>
+
+class ClientNotReadyException : public BaseException
+{
+public:
+       ClientNotReadyException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class Client;
+
+class ClientUpdateThread : public JThread
+{
+       bool run;
+       JMutex run_mutex;
+
+       Client *m_client;
+
+public:
+
+       ClientUpdateThread(Client *client) : JThread(), run(true), m_client(client)
+       {
+               run_mutex.Init();
+       }
+
+       void * Thread();
+
+       bool getRun()
+       {
+               run_mutex.Lock();
+               bool run_cached = run;
+               run_mutex.Unlock();
+               return run_cached;
+       }
+       void setRun(bool a_run)
+       {
+               run_mutex.Lock();
+               run = a_run;
+               run_mutex.Unlock();
+       }
+};
+
+struct IncomingPacket
+{
+       IncomingPacket()
+       {
+               m_data = NULL;
+               m_datalen = 0;
+               m_refcount = NULL;
+       }
+       IncomingPacket(const IncomingPacket &a)
+       {
+               m_data = a.m_data;
+               m_datalen = a.m_datalen;
+               m_refcount = a.m_refcount;
+               if(m_refcount != NULL)
+                       (*m_refcount)++;
+       }
+       IncomingPacket(u8 *data, u32 datalen)
+       {
+               m_data = new u8[datalen];
+               memcpy(m_data, data, datalen);
+               m_datalen = datalen;
+               m_refcount = new s32(1);
+       }
+       ~IncomingPacket()
+       {
+               if(m_refcount != NULL){
+                       assert(*m_refcount > 0);
+                       (*m_refcount)--;
+                       if(*m_refcount == 0){
+                               if(m_data != NULL)
+                                       delete[] m_data;
+                       }
+               }
+       }
+       /*IncomingPacket & operator=(IncomingPacket a)
+       {
+               m_data = a.m_data;
+               m_datalen = a.m_datalen;
+               m_refcount = a.m_refcount;
+               (*m_refcount)++;
+               return *this;
+       }*/
+       u8 *m_data;
+       u32 m_datalen;
+       s32 *m_refcount;
+};
+
+class LazyMeshUpdater
+{
+public:
+       LazyMeshUpdater(Environment *env)
+       {
+               m_env = env;
+       }
+       ~LazyMeshUpdater()
+       {
+               /*
+                       TODO: This could be optimized. It will currently
+                       double-update some blocks.
+               */
+               for(core::map<v3s16, bool>::Iterator
+                               i = m_blocks.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       v3s16 p = i.getNode()->getKey();
+                       m_env->getMap().updateMeshes(p);
+               }
+               m_blocks.clear();
+       }
+       void add(v3s16 p)
+       {
+               m_blocks.insert(p, true);
+       }
+private:
+       Environment *m_env;
+       core::map<v3s16, bool> m_blocks;
+};
+
+class Client : public con::PeerHandler
+{
+public:
+       /*
+               NOTE: Every public method should be thread-safe
+       */
+       Client(IrrlichtDevice *device, video::SMaterial *materials,
+                       float delete_unused_sectors_timeout,
+                       const char *playername);
+       ~Client();
+       /*
+               The name of the local player should already be set when
+               calling this, as it is sent in the initialization.
+       */
+       void connect(Address address);
+       /*
+               returns true when
+                       m_con.Connected() == true
+                       AND m_server_ser_ver != SER_FMT_VER_INVALID
+               throws con::PeerNotFoundException if connection has been deleted,
+               eg. timed out.
+       */
+       bool connectedAndInitialized();
+       /*
+               Stuff that references the environment is valid only as
+               long as this is not called. (eg. Players)
+               If this throws a PeerNotFoundException, the connection has
+               timed out.
+       */
+       void step(float dtime);
+
+       // Called from updater thread
+       // Returns dtime
+       float asyncStep();
+
+       void ProcessData(u8 *data, u32 datasize, u16 sender_peer_id);
+       // Returns true if something was received
+       bool AsyncProcessPacket(LazyMeshUpdater &mesh_updater);
+       bool AsyncProcessData();
+       void Send(u16 channelnum, SharedBuffer<u8> data, bool reliable);
+
+       //TODO: Remove
+       bool isFetchingBlocks();
+
+       // Pops out a packet from the packet queue
+       IncomingPacket getPacket();
+
+       /*void removeNode(v3s16 nodepos);
+       void addNodeFromInventory(v3s16 nodepos, u16 i);*/
+       void clickGround(u8 button, v3s16 nodepos_undersurface,
+                       v3s16 nodepos_oversurface, u16 item);
+       void clickObject(u8 button, v3s16 blockpos, s16 id, u16 item);
+       void release(u8 button);
+
+       void sendSignText(v3s16 blockpos, s16 id, std::string text);
+       
+       void updateCamera(v3f pos, v3f dir);
+       
+       // Returns InvalidPositionException if not found
+       MapNode getNode(v3s16 p);
+       // Returns InvalidPositionException if not found
+       //f32 getGroundHeight(v2s16 p);
+       // Returns InvalidPositionException if not found
+       bool isNodeUnderground(v3s16 p);
+
+       // Note: The players should not be exposed outside
+       // Return value is valid until client is destroyed
+       //Player * getLocalPlayer();
+       // Return value is valid until step()
+       //core::list<Player*> getPlayers();
+       v3f getPlayerPosition();
+
+       void setPlayerControl(PlayerControl &control);
+       
+       // Returns true if the inventory of the local player has been
+       // updated from the server. If it is true, it is set to false.
+       bool getLocalInventoryUpdated();
+       // Copies the inventory of the local player to parameter
+       void getLocalInventory(Inventory &dst);
+       // TODO: Functions for sending inventory editing commands to
+       //       server
+       
+       // Gets closest object pointed by the shootline
+       // Returns NULL if not found
+       MapBlockObject * getSelectedObject(
+                       f32 max_d,
+                       v3f from_pos_f_on_map,
+                       core::line3d<f32> shootline_on_map
+       );
+
+       // Prints a line or two of info
+       void printDebugInfo(std::ostream &os);
+       
+private:
+       
+       // Virtual methods from con::PeerHandler
+       void peerAdded(con::Peer *peer);
+       void deletingPeer(con::Peer *peer, bool timeout);
+       
+       void ReceiveAll();
+       void Receive();
+       
+       void sendPlayerPos();
+       // This sends the player's current name etc to the server
+       void sendPlayerInfo();
+
+       ClientUpdateThread m_thread;
+       
+       // NOTE: If connection and environment are both to be locked,
+       // environment shall be locked first.
+
+       Environment m_env;
+       JMutex m_env_mutex;
+       
+       con::Connection m_con;
+       JMutex m_con_mutex;
+
+       /*core::map<v3s16, float> m_fetchblock_history;
+       JMutex m_fetchblock_mutex;*/
+
+       core::list<IncomingPacket> m_incoming_queue;
+       JMutex m_incoming_queue_mutex;
+
+       IrrlichtDevice *m_device;
+
+       v3f camera_position;
+       v3f camera_direction;
+       
+       // Server serialization version
+       u8 m_server_ser_ver;
+
+       float m_step_dtime;
+       JMutex m_step_dtime_mutex;
+
+       float m_delete_unused_sectors_timeout;
+       
+       // This is behind m_env_mutex.
+       bool m_inventory_updated;
+
+       core::map<v3s16, bool> m_active_blocks;
+};
+
+#endif
+
diff --git a/src/clientserver.h b/src/clientserver.h
new file mode 100644 (file)
index 0000000..8020692
--- /dev/null
@@ -0,0 +1,174 @@
+#ifndef CLIENTSERVER_HEADER
+#define CLIENTSERVER_HEADER
+
+#define PROTOCOL_ID 0x4f457403
+
+enum ToClientCommand
+{
+       TOCLIENT_INIT=0x10,
+       /*
+               Server's reply to TOSERVER_INIT.
+               Sent second after connected.
+
+               [0] u16 TOSERVER_INIT
+               [2] u8 deployed version
+               [3] v3s16 player's position + v3f(0,BS/2,0) floatToInt'd
+       */
+
+       TOCLIENT_BLOCKDATA=0x20, //TODO: Multiple blocks
+       TOCLIENT_ADDNODE,
+       TOCLIENT_REMOVENODE,
+       
+       TOCLIENT_PLAYERPOS,
+       /*
+               [0] u16 command
+               // Followed by an arbitary number of these:
+               // Number is determined from packet length.
+               [N] u16 peer_id
+               [N+2] v3s32 position*100
+               [N+2+12] v3s32 speed*100
+               [N+2+12+12] s32 pitch*100
+               [N+2+12+12+4] s32 yaw*100
+       */
+
+       TOCLIENT_PLAYERINFO,
+       /*
+               [0] u16 command
+               // Followed by an arbitary number of these:
+               // Number is determined from packet length.
+               [N] u16 peer_id
+               [N] char[20] name
+       */
+       
+       TOCLIENT_OPT_BLOCK_NOT_FOUND, // Not used
+
+       TOCLIENT_SECTORMETA,
+       /*
+               [0] u16 command
+               [2] u8 sector count
+               [3...] v2s16 pos + sector metadata
+       */
+
+       TOCLIENT_INVENTORY,
+       /*
+               [0] u16 command
+               [2] serialized inventory
+       */
+       
+       TOCLIENT_OBJECTDATA,
+       /*
+               Sent as unreliable.
+
+               u16 command
+               u16 number of player positions
+               for each player:
+                       v3s32 position*100
+                       v3s32 speed*100
+                       s32 pitch*100
+                       s32 yaw*100
+               u16 count of blocks
+               for each block:
+                       v3s16 blockpos
+                       block objects
+       */
+};
+
+enum ToServerCommand
+{
+       TOSERVER_INIT=0x10,
+       /*
+               Sent first after connected.
+
+               [0] u16 TOSERVER_INIT
+               [2] u8 SER_FMT_VER_HIGHEST
+               [3] u8[20] player_name
+       */
+
+       TOSERVER_INIT2,
+       /*
+               Sent as an ACK for TOCLIENT_INIT.
+               After this, the server can send data.
+
+               [0] u16 TOSERVER_INIT2
+       */
+
+       TOSERVER_GETBLOCK=0x20, // Not used
+       TOSERVER_ADDNODE, // Not used
+       TOSERVER_REMOVENODE, // deprecated
+
+       TOSERVER_PLAYERPOS,
+       /*
+               [0] u16 command
+               [2] v3s32 position*100
+               [2+12] v3s32 speed*100
+               [2+12+12] s32 pitch*100
+               [2+12+12+4] s32 yaw*100
+       */
+
+       TOSERVER_GOTBLOCKS,
+       /*
+               [0] u16 command
+               [2] u8 count
+               [3] v3s16 pos_0
+               [3+6] v3s16 pos_1
+               ...
+       */
+
+       TOSERVER_DELETEDBLOCKS,
+       /*
+               [0] u16 command
+               [2] u8 count
+               [3] v3s16 pos_0
+               [3+6] v3s16 pos_1
+               ...
+       */
+
+       TOSERVER_ADDNODE_FROM_INVENTORY, // deprecated
+       /*
+               [0] u16 command
+               [2] v3s16 pos
+               [8] u16 i
+       */
+
+       TOSERVER_CLICK_OBJECT,
+       /*
+               length: 13
+               [0] u16 command
+               [2] u8 button (0=left, 1=right)
+               [3] v3s16 blockpos
+               [9] s16 id
+               [11] u16 item
+       */
+
+       TOSERVER_CLICK_GROUND,
+       /*
+               length: 17
+               [0] u16 command
+               [2] u8 button (0=left, 1=right)
+               [3] v3s16 nodepos_undersurface
+               [9] v3s16 nodepos_abovesurface
+               [15] u16 item
+       */
+       
+       TOSERVER_RELEASE,
+       /*
+               length: 3
+               [0] u16 command
+               [2] u8 button
+       */
+
+       TOSERVER_SIGNTEXT,
+       /*
+               u16 command
+               v3s16 blockpos
+               s16 id
+               u16 textlen
+               textdata
+       */
+};
+
+// Flags for TOSERVER_GETBLOCK
+#define TOSERVER_GETBLOCK_FLAG_OPTIONAL (1<<0)
+
+#endif
+
diff --git a/src/common_irrlicht.h b/src/common_irrlicht.h
new file mode 100644 (file)
index 0000000..69f00a7
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef COMMON_IRRLICHT_HEADER
+#define COMMON_IRRLICHT_HEADER
+
+#include <irrlicht.h>
+using namespace irr;
+typedef core::vector3df v3f;
+typedef core::vector3d<s16> v3s16;
+typedef core::vector3d<s32> v3s32;
+
+typedef core::vector2d<f32> v2f;
+typedef core::vector2d<s16> v2s16;
+typedef core::vector2d<s32> v2s32;
+typedef core::vector2d<u32> v2u32;
+typedef core::vector2d<f32> v2f32;
+
+#endif
+
diff --git a/src/connection.cpp b/src/connection.cpp
new file mode 100644 (file)
index 0000000..42bfdfb
--- /dev/null
@@ -0,0 +1,1321 @@
+#include "connection.h"
+#include "main.h"
+#include "serialization.h"
+
+namespace con
+{
+
+BufferedPacket makePacket(Address &address, u8 *data, u32 datasize,
+               u32 protocol_id, u16 sender_peer_id, u8 channel)
+{
+       u32 packet_size = datasize + BASE_HEADER_SIZE;
+       BufferedPacket p(packet_size);
+       p.address = address;
+
+       writeU32(&p.data[0], protocol_id);
+       writeU16(&p.data[4], sender_peer_id);
+       writeU8(&p.data[6], channel);
+
+       memcpy(&p.data[BASE_HEADER_SIZE], data, datasize);
+
+       return p;
+}
+
+BufferedPacket makePacket(Address &address, SharedBuffer<u8> &data,
+               u32 protocol_id, u16 sender_peer_id, u8 channel)
+{
+       return makePacket(address, *data, data.getSize(),
+                       protocol_id, sender_peer_id, channel);
+}
+
+SharedBuffer<u8> makeOriginalPacket(
+               SharedBuffer<u8> data)
+{
+       u32 header_size = 1;
+       u32 packet_size = data.getSize() + header_size;
+       SharedBuffer<u8> b(packet_size);
+
+       writeU8(&b[0], TYPE_ORIGINAL);
+
+       memcpy(&b[header_size], *data, data.getSize());
+
+       return b;
+}
+
+core::list<SharedBuffer<u8> > makeSplitPacket(
+               SharedBuffer<u8> data,
+               u32 chunksize_max,
+               u16 seqnum)
+{
+       // Chunk packets, containing the TYPE_SPLIT header
+       core::list<SharedBuffer<u8> > chunks;
+       
+       u32 chunk_header_size = 7;
+       u32 maximum_data_size = chunksize_max - chunk_header_size;
+       u32 start = 0;
+       u32 end = 0;
+       u32 chunk_num = 0;
+       do{
+               end = start + maximum_data_size - 1;
+               if(end > data.getSize() - 1)
+                       end = data.getSize() - 1;
+               
+               u32 payload_size = end - start + 1;
+               u32 packet_size = chunk_header_size + payload_size;
+
+               SharedBuffer<u8> chunk(packet_size);
+               
+               writeU8(&chunk[0], TYPE_SPLIT);
+               writeU16(&chunk[1], seqnum);
+               // [3] u16 chunk_count is written at next stage
+               writeU16(&chunk[5], chunk_num);
+               memcpy(&chunk[chunk_header_size], &data[start], payload_size);
+
+               chunks.push_back(chunk);
+               
+               start = end + 1;
+               chunk_num++;
+       }
+       while(end != data.getSize() - 1);
+
+       u16 chunk_count = chunks.getSize();
+
+       core::list<SharedBuffer<u8> >::Iterator i = chunks.begin();
+       for(; i != chunks.end(); i++)
+       {
+               // Write chunk_count
+               writeU16(&((*i)[3]), chunk_count);
+       }
+
+       return chunks;
+}
+
+core::list<SharedBuffer<u8> > makeAutoSplitPacket(
+               SharedBuffer<u8> data,
+               u32 chunksize_max,
+               u16 &split_seqnum)
+{
+       u32 original_header_size = 1;
+       core::list<SharedBuffer<u8> > list;
+       if(data.getSize() + original_header_size > chunksize_max)
+       {
+               list = makeSplitPacket(data, chunksize_max, split_seqnum);
+               split_seqnum++;
+               return list;
+       }
+       else
+       {
+               list.push_back(makeOriginalPacket(data));
+       }
+       return list;
+}
+
+SharedBuffer<u8> makeReliablePacket(
+               SharedBuffer<u8> data,
+               u16 seqnum)
+{
+       /*dstream<<"BEGIN SharedBuffer<u8> makeReliablePacket()"<<std::endl;
+       dstream<<"data.getSize()="<<data.getSize()<<", data[0]="
+                       <<((unsigned int)data[0]&0xff)<<std::endl;*/
+       u32 header_size = 3;
+       u32 packet_size = data.getSize() + header_size;
+       SharedBuffer<u8> b(packet_size);
+
+       writeU8(&b[0], TYPE_RELIABLE);
+       writeU16(&b[1], seqnum);
+
+       memcpy(&b[header_size], *data, data.getSize());
+
+       /*dstream<<"data.getSize()="<<data.getSize()<<", data[0]="
+                       <<((unsigned int)data[0]&0xff)<<std::endl;*/
+       //dstream<<"END SharedBuffer<u8> makeReliablePacket()"<<std::endl;
+       return b;
+}
+
+/*
+       ReliablePacketBuffer
+*/
+
+void ReliablePacketBuffer::print()
+{
+       core::list<BufferedPacket>::Iterator i;
+       i = m_list.begin();
+       for(; i != m_list.end(); i++)
+       {
+               u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1]));
+               dout_con<<s<<" ";
+       }
+}
+bool ReliablePacketBuffer::empty()
+{
+       return m_list.empty();
+}
+u32 ReliablePacketBuffer::size()
+{
+       return m_list.getSize();
+}
+RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum)
+{
+       core::list<BufferedPacket>::Iterator i;
+       i = m_list.begin();
+       for(; i != m_list.end(); i++)
+       {
+               u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1]));
+               /*dout_con<<"findPacket(): finding seqnum="<<seqnum
+                               <<", comparing to s="<<s<<std::endl;*/
+               if(s == seqnum)
+                       break;
+       }
+       return i;
+}
+RPBSearchResult ReliablePacketBuffer::notFound()
+{
+       return m_list.end();
+}
+u16 ReliablePacketBuffer::getFirstSeqnum()
+{
+       if(empty())
+               throw NotFoundException("Buffer is empty");
+       BufferedPacket p = *m_list.begin();
+       return readU16(&p.data[BASE_HEADER_SIZE+1]);
+}
+BufferedPacket ReliablePacketBuffer::popFirst()
+{
+       if(empty())
+               throw NotFoundException("Buffer is empty");
+       BufferedPacket p = *m_list.begin();
+       core::list<BufferedPacket>::Iterator i = m_list.begin();
+       m_list.erase(i);
+       return p;
+}
+BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum)
+{
+       RPBSearchResult r = findPacket(seqnum);
+       if(r == notFound()){
+               dout_con<<"Not found"<<std::endl;
+               throw NotFoundException("seqnum not found in buffer");
+       }
+       BufferedPacket p = *r;
+       m_list.erase(r);
+       return p;
+}
+void ReliablePacketBuffer::insert(BufferedPacket &p)
+{
+       assert(p.data.getSize() >= BASE_HEADER_SIZE+3);
+       u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]);
+       assert(type == TYPE_RELIABLE);
+       u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]);
+
+       // Find the right place for the packet and insert it there
+
+       // If list is empty, just add it
+       if(m_list.empty())
+       {
+               m_list.push_back(p);
+               // Done.
+               return;
+       }
+       // Otherwise find the right place
+       core::list<BufferedPacket>::Iterator i;
+       i = m_list.begin();
+       // Find the first packet in the list which has a higher seqnum
+       for(; i != m_list.end(); i++){
+               u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1]));
+               if(s == seqnum){
+                       throw AlreadyExistsException("Same seqnum in list");
+               }
+               if(seqnum_higher(s, seqnum)){
+                       break;
+               }
+       }
+       // If we're at the end of the list, add the packet to the
+       // end of the list
+       if(i == m_list.end())
+       {
+               m_list.push_back(p);
+               // Done.
+               return;
+       }
+       // Insert before i
+       m_list.insert_before(i, p);
+}
+
+void ReliablePacketBuffer::incrementTimeouts(float dtime)
+{
+       core::list<BufferedPacket>::Iterator i;
+       i = m_list.begin();
+       for(; i != m_list.end(); i++){
+               i->time += dtime;
+               i->totaltime += dtime;
+       }
+}
+
+void ReliablePacketBuffer::resetTimedOuts(float timeout)
+{
+       core::list<BufferedPacket>::Iterator i;
+       i = m_list.begin();
+       for(; i != m_list.end(); i++){
+               if(i->time >= timeout)
+                       i->time = 0.0;
+       }
+}
+
+bool ReliablePacketBuffer::anyTotaltimeReached(float timeout)
+{
+       core::list<BufferedPacket>::Iterator i;
+       i = m_list.begin();
+       for(; i != m_list.end(); i++){
+               if(i->totaltime >= timeout)
+                       return true;
+       }
+       return false;
+}
+
+core::list<BufferedPacket> ReliablePacketBuffer::getTimedOuts(float timeout)
+{
+       core::list<BufferedPacket> timed_outs;
+       core::list<BufferedPacket>::Iterator i;
+       i = m_list.begin();
+       for(; i != m_list.end(); i++)
+       {
+               if(i->time >= timeout)
+                       timed_outs.push_back(*i);
+       }
+       return timed_outs;
+}
+
+/*
+       IncomingSplitBuffer
+*/
+
+IncomingSplitBuffer::~IncomingSplitBuffer()
+{
+       core::map<u16, IncomingSplitPacket*>::Iterator i;
+       i = m_buf.getIterator();
+       for(; i.atEnd() == false; i++)
+       {
+               delete i.getNode()->getValue();
+       }
+}
+/*
+       This will throw a GotSplitPacketException when a full
+       split packet is constructed.
+*/
+void IncomingSplitBuffer::insert(BufferedPacket &p, bool reliable)
+{
+       u32 headersize = BASE_HEADER_SIZE + 7;
+       assert(p.data.getSize() >= headersize);
+       u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]);
+       assert(type == TYPE_SPLIT);
+       u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]);
+       u16 chunk_count = readU16(&p.data[BASE_HEADER_SIZE+3]);
+       u16 chunk_num = readU16(&p.data[BASE_HEADER_SIZE+5]);
+
+       // Add if doesn't exist
+       if(m_buf.find(seqnum) == NULL)
+       {
+               IncomingSplitPacket *sp = new IncomingSplitPacket();
+               sp->chunk_count = chunk_count;
+               sp->reliable = reliable;
+               m_buf[seqnum] = sp;
+       }
+       
+       IncomingSplitPacket *sp = m_buf[seqnum];
+       
+       // TODO: These errors should be thrown or something? Dunno.
+       if(chunk_count != sp->chunk_count)
+               derr_con<<"Connection: WARNING: chunk_count="<<chunk_count
+                               <<" != sp->chunk_count="<<sp->chunk_count
+                               <<std::endl;
+       if(reliable != sp->reliable)
+               derr_con<<"Connection: WARNING: reliable="<<reliable
+                               <<" != sp->reliable="<<sp->reliable
+                               <<std::endl;
+
+       // If chunk already exists, cancel
+       if(sp->chunks.find(chunk_num) != NULL)
+               throw AlreadyExistsException("Chunk already in buffer");
+       
+       // Cut chunk data out of packet
+       u32 chunkdatasize = p.data.getSize() - headersize;
+       SharedBuffer<u8> chunkdata(chunkdatasize);
+       memcpy(*chunkdata, &(p.data[headersize]), chunkdatasize);
+       
+       // Set chunk data in buffer
+       sp->chunks[chunk_num] = chunkdata;
+       
+       // If not all chunks are received, return
+       if(sp->allReceived() == false)
+               return;
+
+       // Calculate total size
+       u32 totalsize = 0;
+       core::map<u16, SharedBuffer<u8> >::Iterator i;
+       i = sp->chunks.getIterator();
+       for(; i.atEnd() == false; i++)
+       {
+               totalsize += i.getNode()->getValue().getSize();
+       }
+       
+       SharedBuffer<u8> fulldata(totalsize);
+
+       // Copy chunks to data buffer
+       u32 start = 0;
+       for(u32 chunk_i=0; chunk_i<sp->chunk_count;
+                       chunk_i++)
+       {
+               SharedBuffer<u8> buf = sp->chunks[chunk_i];
+               u16 chunkdatasize = buf.getSize();
+               memcpy(&fulldata[start], *buf, chunkdatasize);
+               start += chunkdatasize;;
+       }
+
+       // Remove sp from buffer
+       m_buf.remove(seqnum);
+       delete sp;
+       
+       throw GotSplitPacketException(fulldata);
+}
+void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout)
+{
+       core::list<u16> remove_queue;
+       core::map<u16, IncomingSplitPacket*>::Iterator i;
+       i = m_buf.getIterator();
+       for(; i.atEnd() == false; i++)
+       {
+               IncomingSplitPacket *p = i.getNode()->getValue();
+               // Reliable ones are not removed by timeout
+               if(p->reliable == true)
+                       continue;
+               p->time += dtime;
+               if(p->time >= timeout)
+                       remove_queue.push_back(i.getNode()->getKey());
+       }
+       core::list<u16>::Iterator j;
+       j = remove_queue.begin();
+       for(; j != remove_queue.end(); j++)
+       {
+               dout_con<<"NOTE: Removing timed out unreliable split packet"
+                               <<std::endl;
+               delete m_buf[*j];
+               m_buf.remove(*j);
+       }
+}
+
+/*
+       Channel
+*/
+
+Channel::Channel()
+{
+       next_outgoing_seqnum = SEQNUM_INITIAL;
+       next_incoming_seqnum = SEQNUM_INITIAL;
+       next_outgoing_split_seqnum = SEQNUM_INITIAL;
+}
+Channel::~Channel()
+{
+}
+
+/*
+       Peer
+*/
+
+Peer::Peer(u16 a_id, Address a_address)
+{
+       id = a_id;
+       address = a_address;
+       timeout_counter = 0.0;
+       //resend_timeout = RESEND_TIMEOUT_MINIMUM;
+       resend_timeout = 0.5;
+       avg_rtt = -1.0;
+       has_sent_with_id = false;
+}
+Peer::~Peer()
+{
+}
+
+void Peer::reportRTT(float rtt)
+{
+       if(rtt < -0.999)
+       {}
+       else if(avg_rtt < 0.0)
+               avg_rtt = rtt;
+       else
+               avg_rtt = rtt * 0.1 + avg_rtt * 0.9;
+       
+       // Calculate resend_timeout
+
+       /*int reliable_count = 0;
+       for(int i=0; i<CHANNEL_COUNT; i++)
+       {
+               reliable_count += channels[i].outgoing_reliables.size();
+       }
+       float timeout = avg_rtt * RESEND_TIMEOUT_FACTOR
+                       * ((float)reliable_count * 1);*/
+       
+       float timeout = avg_rtt * RESEND_TIMEOUT_FACTOR;
+       if(timeout < RESEND_TIMEOUT_MIN)
+               timeout = RESEND_TIMEOUT_MIN;
+       if(timeout > RESEND_TIMEOUT_MAX)
+               timeout = RESEND_TIMEOUT_MAX;
+       resend_timeout = timeout;
+}
+                               
+/*
+       Connection
+*/
+
+Connection::Connection(
+       u32 protocol_id,
+       u32 max_packet_size,
+       float timeout,
+       PeerHandler *peerhandler
+)
+{
+       assert(peerhandler != NULL);
+
+       m_protocol_id = protocol_id;
+       m_max_packet_size = max_packet_size;
+       m_timeout = timeout;
+       m_peer_id = PEER_ID_NEW;
+       //m_waiting_new_peer_id = false;
+       m_indentation = 0;
+       m_peerhandler = peerhandler;
+}
+
+Connection::~Connection()
+{
+       // Clear peers
+       core::map<u16, Peer*>::Iterator j;
+       j = m_peers.getIterator();
+       for(; j.atEnd() == false; j++)
+       {
+               Peer *peer = j.getNode()->getValue();
+               delete peer;
+       }
+}
+
+void Connection::Serve(unsigned short port)
+{
+       m_socket.Bind(port);
+       m_peer_id = PEER_ID_SERVER;
+}
+
+void Connection::Connect(Address address)
+{
+       core::map<u16, Peer*>::Node *node = m_peers.find(PEER_ID_SERVER);
+       if(node != NULL){
+               throw ConnectionException("Already connected to a server");
+       }
+
+       Peer *peer = new Peer(PEER_ID_SERVER, address);
+       m_peers.insert(peer->id, peer);
+       m_peerhandler->peerAdded(peer);
+       
+       m_socket.Bind(0);
+       
+       // Send a dummy packet to server with peer_id = PEER_ID_NEW
+       m_peer_id = PEER_ID_NEW;
+       SharedBuffer<u8> data(0);
+       Send(PEER_ID_SERVER, 0, data, true);
+
+       //m_waiting_new_peer_id = true;
+}
+
+bool Connection::Connected()
+{
+       if(m_peers.size() != 1)
+               return false;
+               
+       core::map<u16, Peer*>::Node *node = m_peers.find(PEER_ID_SERVER);
+       if(node == NULL)
+               return false;
+       
+       if(m_peer_id == PEER_ID_NEW)
+               return false;
+       
+       return true;
+}
+
+SharedBuffer<u8> Channel::ProcessPacket(
+               SharedBuffer<u8> packetdata,
+               Connection *con,
+               u16 peer_id,
+               u8 channelnum,
+               bool reliable)
+{
+       IndentationRaiser iraiser(&(con->m_indentation));
+
+       if(packetdata.getSize() < 1)
+               throw InvalidIncomingDataException("packetdata.getSize() < 1");
+
+       u8 type = readU8(&packetdata[0]);
+       
+       if(type == TYPE_CONTROL)
+       {
+               if(packetdata.getSize() < 2)
+                       throw InvalidIncomingDataException("packetdata.getSize() < 2");
+
+               u8 controltype = readU8(&packetdata[1]);
+
+               if(controltype == CONTROLTYPE_ACK)
+               {
+                       if(packetdata.getSize() < 4)
+                               throw InvalidIncomingDataException
+                                               ("packetdata.getSize() < 4 (ACK header size)");
+
+                       u16 seqnum = readU16(&packetdata[2]);
+                       con->PrintInfo();
+                       dout_con<<"Got CONTROLTYPE_ACK: channelnum="
+                                       <<((int)channelnum&0xff)<<", peer_id="<<peer_id
+                                       <<", seqnum="<<seqnum<<std::endl;
+
+                       try{
+                               BufferedPacket p = outgoing_reliables.popSeqnum(seqnum);
+                               // Get round trip time
+                               float rtt = p.totaltime;
+
+                               // Let peer calculate stuff according to it
+                               // (avg_rtt and resend_timeout)
+                               Peer *peer = con->GetPeer(peer_id);
+                               peer->reportRTT(rtt);
+
+                               //con->PrintInfo(dout_con);
+                               //dout_con<<"RTT = "<<rtt<<std::endl;
+
+                               /*dout_con<<"OUTGOING: ";
+                               con->PrintInfo();
+                               outgoing_reliables.print();
+                               dout_con<<std::endl;*/
+                       }
+                       catch(NotFoundException &e){
+                               con->PrintInfo(derr_con);
+                               derr_con<<"WARNING: ACKed packet not "
+                                               "in outgoing queue"
+                                               <<std::endl;
+                       }
+
+                       throw ProcessedSilentlyException("Got an ACK");
+               }
+               else if(controltype == CONTROLTYPE_SET_PEER_ID)
+               {
+                       if(packetdata.getSize() < 4)
+                               throw InvalidIncomingDataException
+                                               ("packetdata.getSize() < 4 (SET_PEER_ID header size)");
+                       u16 peer_id_new = readU16(&packetdata[2]);
+                       con->PrintInfo();
+                       dout_con<<"Got new peer id: "<<peer_id_new<<"... "<<std::endl;
+
+                       if(con->GetPeerID() != PEER_ID_NEW)
+                       {
+                               con->PrintInfo(derr_con);
+                               derr_con<<"WARNING: Not changing"
+                                               " existing peer id."<<std::endl;
+                       }
+                       else
+                       {
+                               dout_con<<"changing."<<std::endl;
+                               con->SetPeerID(peer_id_new);
+                       }
+                       throw ProcessedSilentlyException("Got a SET_PEER_ID");
+               }
+               else if(controltype == CONTROLTYPE_PING)
+               {
+                       // Just ignore it, the incoming data already reset
+                       // the timeout counter
+                       con->PrintInfo();
+                       dout_con<<"PING"<<std::endl;
+                       throw ProcessedSilentlyException("Got a SET_PEER_ID");
+               }
+               else{
+                       con->PrintInfo(derr_con);
+                       derr_con<<"INVALID TYPE_CONTROL: invalid controltype="
+                                       <<((int)controltype&0xff)<<std::endl;
+                       throw InvalidIncomingDataException("Invalid control type");
+               }
+       }
+       else if(type == TYPE_ORIGINAL)
+       {
+               if(packetdata.getSize() < ORIGINAL_HEADER_SIZE)
+                       throw InvalidIncomingDataException
+                                       ("packetdata.getSize() < ORIGINAL_HEADER_SIZE");
+               con->PrintInfo();
+               dout_con<<"RETURNING TYPE_ORIGINAL to user"
+                               <<std::endl;
+               // Get the inside packet out and return it
+               SharedBuffer<u8> payload(packetdata.getSize() - ORIGINAL_HEADER_SIZE);
+               memcpy(*payload, &packetdata[ORIGINAL_HEADER_SIZE], payload.getSize());
+               return payload;
+       }
+       else if(type == TYPE_SPLIT)
+       {
+               // We have to create a packet again for buffering
+               // This isn't actually too bad an idea.
+               BufferedPacket packet = makePacket(
+                               con->GetPeer(peer_id)->address,
+                               packetdata,
+                               con->GetProtocolID(),
+                               peer_id,
+                               channelnum);
+               try{
+                       // Buffer the packet
+                       incoming_splits.insert(packet, reliable);
+               }
+               // This exception happens when all the pieces of a packet
+               // are collected.
+               catch(GotSplitPacketException &e)
+               {
+                       con->PrintInfo();
+                       dout_con<<"RETURNING TYPE_SPLIT: Constructed full data, "
+                                       <<"size="<<e.getData().getSize()<<std::endl;
+                       return e.getData();
+               }
+               con->PrintInfo();
+               dout_con<<"BUFFERING TYPE_SPLIT"<<std::endl;
+               throw ProcessedSilentlyException("Buffered a split packet chunk");
+       }
+       else if(type == TYPE_RELIABLE)
+       {
+               // Recursive reliable packets not allowed
+               assert(reliable == false);
+
+               if(packetdata.getSize() < RELIABLE_HEADER_SIZE)
+                       throw InvalidIncomingDataException
+                                       ("packetdata.getSize() < RELIABLE_HEADER_SIZE");
+
+               u16 seqnum = readU16(&packetdata[1]);
+
+               bool is_future_packet = seqnum_higher(seqnum, next_incoming_seqnum);
+               bool is_old_packet = seqnum_higher(next_incoming_seqnum, seqnum);
+               
+               con->PrintInfo();
+               if(is_future_packet)
+                       dout_con<<"BUFFERING";
+               else if(is_old_packet)
+                       dout_con<<"OLD";
+               else
+                       dout_con<<"RECUR";
+               dout_con<<" TYPE_RELIABLE seqnum="<<seqnum
+                               <<" next="<<next_incoming_seqnum;
+               dout_con<<" [sending CONTROLTYPE_ACK"
+                               " to peer_id="<<peer_id<<"]";
+               dout_con<<std::endl;
+               
+               //DEBUG
+               //assert(incoming_reliables.size() < 100);
+
+               // Send a CONTROLTYPE_ACK
+               SharedBuffer<u8> reply(4);
+               writeU8(&reply[0], TYPE_CONTROL);
+               writeU8(&reply[1], CONTROLTYPE_ACK);
+               writeU16(&reply[2], seqnum);
+               con->SendAsPacket(peer_id, channelnum, reply, false);
+
+               //if(seqnum_higher(seqnum, next_incoming_seqnum))
+               if(is_future_packet)
+               {
+                       /*con->PrintInfo();
+                       dout_con<<"Buffering reliable packet (seqnum="
+                                       <<seqnum<<")"<<std::endl;*/
+                       
+                       // This one comes later, buffer it.
+                       // Actually we have to make a packet to buffer one.
+                       // Well, we have all the ingredients, so just do it.
+                       BufferedPacket packet = makePacket(
+                                       con->GetPeer(peer_id)->address,
+                                       packetdata,
+                                       con->GetProtocolID(),
+                                       peer_id,
+                                       channelnum);
+                       try{
+                               incoming_reliables.insert(packet);
+                               
+                               /*con->PrintInfo();
+                               dout_con<<"INCOMING: ";
+                               incoming_reliables.print();
+                               dout_con<<std::endl;*/
+                       }
+                       catch(AlreadyExistsException &e)
+                       {
+                       }
+
+                       throw ProcessedSilentlyException("Buffered future reliable packet");
+               }
+               //else if(seqnum_higher(next_incoming_seqnum, seqnum))
+               else if(is_old_packet)
+               {
+                       // An old packet, dump it
+                       throw InvalidIncomingDataException("Got an old reliable packet");
+               }
+
+               next_incoming_seqnum++;
+
+               // Get out the inside packet and re-process it
+               SharedBuffer<u8> payload(packetdata.getSize() - RELIABLE_HEADER_SIZE);
+               memcpy(*payload, &packetdata[RELIABLE_HEADER_SIZE], payload.getSize());
+
+               return ProcessPacket(payload, con, peer_id, channelnum, true);
+       }
+       else
+       {
+               con->PrintInfo(derr_con);
+               derr_con<<"Got invalid type="<<((int)type&0xff)<<std::endl;
+               throw InvalidIncomingDataException("Invalid packet type");
+       }
+       
+       // We should never get here.
+       // If you get here, add an exception or a return to some of the
+       // above conditionals.
+       assert(0);
+       throw BaseException("Error in Channel::ProcessPacket()");
+}
+
+SharedBuffer<u8> Channel::CheckIncomingBuffers(Connection *con,
+               u16 &peer_id)
+{
+       u16 firstseqnum = 0;
+       // Clear old packets from start of buffer
+       try{
+       for(;;){
+               firstseqnum = incoming_reliables.getFirstSeqnum();
+               if(seqnum_higher(next_incoming_seqnum, firstseqnum))
+                       incoming_reliables.popFirst();
+               else
+                       break;
+       }
+       // This happens if all packets are old
+       }catch(con::NotFoundException)
+       {}
+       
+       if(incoming_reliables.empty() == false)
+       {
+               if(firstseqnum == next_incoming_seqnum)
+               {
+                       BufferedPacket p = incoming_reliables.popFirst();
+                       
+                       peer_id = readPeerId(*p.data);
+                       u8 channelnum = readChannel(*p.data);
+                       u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]);
+
+                       con->PrintInfo();
+                       dout_con<<"UNBUFFERING TYPE_RELIABLE"
+                                       <<" seqnum="<<seqnum
+                                       <<" peer_id="<<peer_id
+                                       <<" channel="<<((int)channelnum&0xff)
+                                       <<std::endl;
+
+                       next_incoming_seqnum++;
+                       
+                       u32 headers_size = BASE_HEADER_SIZE + RELIABLE_HEADER_SIZE;
+                       // Get out the inside packet and re-process it
+                       SharedBuffer<u8> payload(p.data.getSize() - headers_size);
+                       memcpy(*payload, &p.data[headers_size], payload.getSize());
+
+                       return ProcessPacket(payload, con, peer_id, channelnum, true);
+               }
+       }
+               
+       throw NoIncomingDataException("No relevant data in buffers");
+}
+
+SharedBuffer<u8> Connection::GetFromBuffers(u16 &peer_id)
+{
+       core::map<u16, Peer*>::Iterator j;
+       j = m_peers.getIterator();
+       for(; j.atEnd() == false; j++)
+       {
+               Peer *peer = j.getNode()->getValue();
+               for(u16 i=0; i<CHANNEL_COUNT; i++)
+               {
+                       Channel *channel = &peer->channels[i];
+                       try{
+                               SharedBuffer<u8> resultdata = channel->CheckIncomingBuffers
+                                               (this, peer_id);
+
+                               return resultdata;
+                       }
+                       catch(NoIncomingDataException &e)
+                       {
+                       }
+                       catch(InvalidIncomingDataException &e)
+                       {
+                       }
+                       catch(ProcessedSilentlyException &e)
+                       {
+                       }
+               }
+       }
+       throw NoIncomingDataException("No relevant data in buffers");
+}
+
+u32 Connection::Receive(u16 &peer_id, u8 *data, u32 datasize)
+{
+       /*
+               Receive a packet from the network
+       */
+       
+       // TODO: We can not know how many layers of header there are.
+       // For now, just assume there are no other than the base headers.
+       u32 packet_maxsize = datasize + BASE_HEADER_SIZE;
+       Buffer<u8> packetdata(packet_maxsize);
+       
+       for(;;)
+       {
+       try
+       {
+               /*
+                       Check if some buffer has relevant data
+               */
+               try{
+                       SharedBuffer<u8> resultdata = GetFromBuffers(peer_id);
+
+                       if(datasize < resultdata.getSize())
+                               throw InvalidIncomingDataException
+                                               ("Buffer too small for received data");
+                               
+                       memcpy(data, *resultdata, resultdata.getSize());
+                       return resultdata.getSize();
+               }
+               catch(NoIncomingDataException &e)
+               {
+               }
+       
+               Address sender;
+
+               s32 received_size = m_socket.Receive(sender, *packetdata, packet_maxsize);
+
+               if(received_size < 0)
+                       throw NoIncomingDataException("No incoming data");
+               if(received_size < BASE_HEADER_SIZE)
+                       throw InvalidIncomingDataException("No full header received");
+               if(readU32(&packetdata[0]) != m_protocol_id)
+                       throw InvalidIncomingDataException("Invalid protocol id");
+               
+               peer_id = readPeerId(*packetdata);
+               u8 channelnum = readChannel(*packetdata);
+               if(channelnum > CHANNEL_COUNT-1){
+                       PrintInfo(derr_con);
+                       derr_con<<"Receive(): Invalid channel "<<channelnum<<std::endl;
+                       throw InvalidIncomingDataException("Channel doesn't exist");
+               }
+
+               if(peer_id == PEER_ID_NEW)
+               {
+                       /*
+                               Somebody is trying to send stuff to us with no peer id.
+                               
+                               Check if the same address and port was added to our peer
+                               list before.
+                               Allow only entries that have has_sent_with_id==false.
+                       */
+
+                       core::map<u16, Peer*>::Iterator j;
+                       j = m_peers.getIterator();
+                       for(; j.atEnd() == false; j++)
+                       {
+                               Peer *peer = j.getNode()->getValue();
+                               if(peer->has_sent_with_id)
+                                       continue;
+                               if(peer->address == sender)
+                                       break;
+                       }
+                       
+                       /*
+                               If no peer was found with the same address and port,
+                               we shall assume it is a new peer and create an entry.
+                       */
+                       if(j.atEnd())
+                       {
+                               // Pass on to adding the peer
+                       }
+                       // Else: A peer was found.
+                       else
+                       {
+                               Peer *peer = j.getNode()->getValue();
+                               peer_id = peer->id;
+                               PrintInfo(derr_con);
+                               derr_con<<"WARNING: Assuming unknown peer to be "
+                                               <<"peer_id="<<peer_id<<std::endl;
+                       }
+               }
+               
+               /*
+                       The peer was not found in our lists. Add it.
+               */
+               if(peer_id == PEER_ID_NEW)
+               {
+                       // Somebody wants to make a new connection
+
+                       // Get a unique peer id (2 or higher)
+                       u16 peer_id_new = 2;
+                       /*
+                               Find an unused peer id
+                       */
+                       for(;;)
+                       {
+                               // Check if exists
+                               if(m_peers.find(peer_id_new) == NULL)
+                                       break;
+                               // Check for overflow
+                               if(peer_id_new == 65535)
+                                       throw ConnectionException
+                                               ("Connection ran out of peer ids");
+                               peer_id_new++;
+                       }
+
+                       PrintInfo();
+                       dout_con<<"Receive(): Got a packet with peer_id=PEER_ID_NEW,"
+                                       " giving peer_id="<<peer_id_new<<std::endl;
+
+                       // Create a peer
+                       Peer *peer = new Peer(peer_id_new, sender);
+                       m_peers.insert(peer->id, peer);
+                       m_peerhandler->peerAdded(peer);
+                       
+                       // Create CONTROL packet to tell the peer id to the new peer.
+                       SharedBuffer<u8> reply(4);
+                       writeU8(&reply[0], TYPE_CONTROL);
+                       writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID);
+                       writeU16(&reply[2], peer_id_new);
+                       SendAsPacket(peer_id_new, 0, reply, true);
+                       
+                       // We're now talking to a valid peer_id
+                       peer_id = peer_id_new;
+
+                       // Go on and process whatever it sent
+               }
+
+               core::map<u16, Peer*>::Node *node = m_peers.find(peer_id);
+
+               if(node == NULL)
+               {
+                       // Peer not found
+                       // This means that the peer id of the sender is not PEER_ID_NEW
+                       // and it is invalid.
+                       PrintInfo(derr_con);
+                       derr_con<<"Receive(): Peer not found"<<std::endl;
+                       throw InvalidIncomingDataException("Peer not found (possible timeout)");
+               }
+
+               Peer *peer = node->getValue();
+
+               // Validate peer address
+               if(peer->address != sender)
+               {
+                       PrintInfo(derr_con);
+                       derr_con<<"Peer "<<peer_id<<" sending from different address."
+                                       " Ignoring."<<std::endl;
+                       throw InvalidIncomingDataException
+                                       ("Peer sending from different address");
+                       /*// If there is more data, receive again
+                       if(m_socket.WaitData(0) == true)
+                               continue;
+                       throw NoIncomingDataException("No incoming data (2)");*/
+               }
+               
+               peer->timeout_counter = 0.0;
+
+               Channel *channel = &(peer->channels[channelnum]);
+               
+               // Throw the received packet to channel->processPacket()
+
+               // Make a new SharedBuffer from the data without the base headers
+               SharedBuffer<u8> strippeddata(received_size - BASE_HEADER_SIZE);
+               memcpy(*strippeddata, &packetdata[BASE_HEADER_SIZE],
+                               strippeddata.getSize());
+               
+               try{
+                       // Process it (the result is some data with no headers made by us)
+                       SharedBuffer<u8> resultdata = channel->ProcessPacket
+                                       (strippeddata, this, peer_id, channelnum);
+                       
+                       PrintInfo();
+                       dout_con<<"ProcessPacket returned data of size "
+                                       <<resultdata.getSize()<<std::endl;
+                       
+                       if(datasize < resultdata.getSize())
+                               throw InvalidIncomingDataException
+                                               ("Buffer too small for received data");
+                       
+                       memcpy(data, *resultdata, resultdata.getSize());
+                       return resultdata.getSize();
+               }
+               catch(ProcessedSilentlyException &e)
+               {
+                       // If there is more data, receive again
+                       if(m_socket.WaitData(0) == true)
+                               continue;
+               }
+               throw NoIncomingDataException("No incoming data (2)");
+       } // try
+       catch(InvalidIncomingDataException &e)
+       {
+               // If there is more data, receive again
+               if(m_socket.WaitData(0) == true)
+                       continue;
+       }
+       } // for
+}
+
+void Connection::SendToAll(u8 channelnum, SharedBuffer<u8> data, bool reliable)
+{
+       core::map<u16, Peer*>::Iterator j;
+       j = m_peers.getIterator();
+       for(; j.atEnd() == false; j++)
+       {
+               Peer *peer = j.getNode()->getValue();
+               Send(peer->id, channelnum, data, reliable);
+       }
+}
+
+void Connection::Send(u16 peer_id, u8 channelnum,
+               SharedBuffer<u8> data, bool reliable)
+{
+       assert(channelnum < CHANNEL_COUNT);
+       
+       Peer *peer = GetPeer(peer_id);
+       Channel *channel = &(peer->channels[channelnum]);
+
+       u32 chunksize_max = m_max_packet_size - BASE_HEADER_SIZE;
+       if(reliable)
+               chunksize_max -= RELIABLE_HEADER_SIZE;
+
+       core::list<SharedBuffer<u8> > originals;
+       originals = makeAutoSplitPacket(data, chunksize_max,
+                       channel->next_outgoing_split_seqnum);
+       
+       core::list<SharedBuffer<u8> >::Iterator i;
+       i = originals.begin();
+       for(; i != originals.end(); i++)
+       {
+               SharedBuffer<u8> original = *i;
+               
+               SendAsPacket(peer_id, channelnum, original, reliable);
+       }
+}
+
+void Connection::SendAsPacket(u16 peer_id, u8 channelnum,
+               SharedBuffer<u8> data, bool reliable)
+{
+       Peer *peer = GetPeer(peer_id);
+       Channel *channel = &(peer->channels[channelnum]);
+
+       if(reliable)
+       {
+               u16 seqnum = channel->next_outgoing_seqnum;
+               channel->next_outgoing_seqnum++;
+
+               SharedBuffer<u8> reliable = makeReliablePacket(data, seqnum);
+
+               // Add base headers and make a packet
+               BufferedPacket p = makePacket(peer->address, reliable,
+                               m_protocol_id, m_peer_id, channelnum);
+               
+               try{
+                       // Buffer the packet
+                       channel->outgoing_reliables.insert(p);
+               }
+               catch(AlreadyExistsException &e)
+               {
+                       PrintInfo(derr_con);
+                       derr_con<<"WARNING: Going to send a reliable packet "
+                                       "seqnum="<<seqnum<<" that is already "
+                                       "in outgoing buffer"<<std::endl;
+                       //assert(0);
+               }
+               
+               // Send the packet
+               RawSend(p);
+       }
+       else
+       {
+               // Add base headers and make a packet
+               BufferedPacket p = makePacket(peer->address, data,
+                               m_protocol_id, m_peer_id, channelnum);
+
+               // Send the packet
+               RawSend(p);
+       }
+}
+
+void Connection::RawSend(const BufferedPacket &packet)
+{
+       m_socket.Send(packet.address, *packet.data, packet.data.getSize());
+}
+
+void Connection::RunTimeouts(float dtime)
+{
+       core::list<u16> timeouted_peers;
+       core::map<u16, Peer*>::Iterator j;
+       j = m_peers.getIterator();
+       for(; j.atEnd() == false; j++)
+       {
+               Peer *peer = j.getNode()->getValue();
+               
+               /*
+                       Check peer timeout
+               */
+               peer->timeout_counter += dtime;
+               if(peer->timeout_counter > m_timeout)
+               {
+                       PrintInfo(derr_con);
+                       derr_con<<"RunTimeouts(): Peer "<<peer->id
+                                       <<" has timed out."
+                                       <<" (source=peer->timeout_counter)"
+                                       <<std::endl;
+                       // Add peer to the list
+                       timeouted_peers.push_back(peer->id);
+                       // Don't bother going through the buffers of this one
+                       continue;
+               }
+
+               float resend_timeout = peer->resend_timeout;
+               for(u16 i=0; i<CHANNEL_COUNT; i++)
+               {
+                       core::list<BufferedPacket> timed_outs;
+                       core::list<BufferedPacket>::Iterator j;
+                       
+                       Channel *channel = &peer->channels[i];
+
+                       // Remove timed out incomplete unreliable split packets
+                       channel->incoming_splits.removeUnreliableTimedOuts(dtime, m_timeout);
+                       
+                       // Increment reliable packet times
+                       channel->outgoing_reliables.incrementTimeouts(dtime);
+
+                       // Check reliable packet total times, remove peer if
+                       // over timeout.
+                       if(channel->outgoing_reliables.anyTotaltimeReached(m_timeout))
+                       {
+                               PrintInfo(derr_con);
+                               derr_con<<"RunTimeouts(): Peer "<<peer->id
+                                               <<" has timed out."
+                                               <<" (source=reliable packet totaltime)"
+                                               <<std::endl;
+                               // Add peer to the to-be-removed list
+                               timeouted_peers.push_back(peer->id);
+                               goto nextpeer;
+                       }
+
+                       // Re-send timed out outgoing reliables
+                       
+                       timed_outs = channel->
+                                       outgoing_reliables.getTimedOuts(resend_timeout);
+
+                       channel->outgoing_reliables.resetTimedOuts(resend_timeout);
+
+                       j = timed_outs.begin();
+                       for(; j != timed_outs.end(); j++)
+                       {
+                               u16 peer_id = readPeerId(*(j->data));
+                               u8 channel = readChannel(*(j->data));
+                               u16 seqnum = readU16(&(j->data[BASE_HEADER_SIZE+1]));
+
+                               PrintInfo(derr_con);
+                               derr_con<<"RE-SENDING timed-out RELIABLE to ";
+                               j->address.print(&derr_con);
+                               derr_con<<"(t/o="<<resend_timeout<<"): "
+                                               <<"from_peer_id="<<peer_id
+                                               <<", channel="<<((int)channel&0xff)
+                                               <<", seqnum="<<seqnum
+                                               <<std::endl;
+
+                               RawSend(*j);
+
+                               // Enlarge avg_rtt and resend_timeout:
+                               // The rtt will be at least the timeout.
+                               // NOTE: This won't affect the timeout of the next
+                               // checked channel because it was cached.
+                               peer->reportRTT(resend_timeout);
+                       }
+               }
+               
+               /*
+                       Send pings
+               */
+               peer->ping_timer += dtime;
+               if(peer->ping_timer >= 5.0)
+               {
+                       // Create and send PING packet
+                       SharedBuffer<u8> data(2);
+                       writeU8(&data[0], TYPE_CONTROL);
+                       writeU8(&data[1], CONTROLTYPE_PING);
+                       SendAsPacket(peer->id, 0, data, true);
+
+                       peer->ping_timer = 0.0;
+               }
+               
+nextpeer:
+               continue;
+       }
+
+       // Remove timeouted peers
+       core::list<u16>::Iterator i = timeouted_peers.begin();
+       for(; i != timeouted_peers.end(); i++)
+       {
+               PrintInfo(derr_con);
+               derr_con<<"RunTimeouts(): Removing peer "<<(*i)<<std::endl;
+               m_peerhandler->deletingPeer(m_peers[*i], true);
+               delete m_peers[*i];
+               m_peers.remove(*i);
+       }
+}
+
+Peer* Connection::GetPeer(u16 peer_id)
+{
+       core::map<u16, Peer*>::Node *node = m_peers.find(peer_id);
+
+       if(node == NULL){
+               // Peer not found
+               throw PeerNotFoundException("Peer not found (possible timeout)");
+       }
+
+       // Error checking
+       assert(node->getValue()->id == peer_id);
+
+       return node->getValue();
+}
+
+Peer* Connection::GetPeerNoEx(u16 peer_id)
+{
+       core::map<u16, Peer*>::Node *node = m_peers.find(peer_id);
+
+       if(node == NULL){
+               return NULL;
+       }
+
+       // Error checking
+       assert(node->getValue()->id == peer_id);
+
+       return node->getValue();
+}
+
+core::list<Peer*> Connection::GetPeers()
+{
+       core::list<Peer*> list;
+       core::map<u16, Peer*>::Iterator j;
+       j = m_peers.getIterator();
+       for(; j.atEnd() == false; j++)
+       {
+               Peer *peer = j.getNode()->getValue();
+               list.push_back(peer);
+       }
+       return list;
+}
+
+void Connection::PrintInfo(std::ostream &out)
+{
+       out<<m_socket.GetHandle();
+       out<<" ";
+       out<<"con "<<m_peer_id<<": ";
+       for(s16 i=0; i<(s16)m_indentation-1; i++)
+               out<<"  ";
+}
+
+void Connection::PrintInfo()
+{
+       PrintInfo(dout_con);
+}
+
+} // namespace
+
diff --git a/src/connection.h b/src/connection.h
new file mode 100644 (file)
index 0000000..c67aa5f
--- /dev/null
@@ -0,0 +1,474 @@
+#ifndef CONNECTION_HEADER
+#define CONNECTION_HEADER
+
+#include <iostream>
+#include <fstream>
+#include "debug.h"
+#include "common_irrlicht.h"
+#include "socket.h"
+#include "utility.h"
+#include "exceptions.h"
+#include "constants.h"
+
+namespace con
+{
+
+/*
+       Exceptions
+*/
+class NotFoundException : public BaseException
+{
+public:
+       NotFoundException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class PeerNotFoundException : public BaseException
+{
+public:
+       PeerNotFoundException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class ConnectionException : public BaseException
+{
+public:
+       ConnectionException(const char *s):
+               BaseException(s)
+       {}
+};
+
+/*class ThrottlingException : public BaseException
+{
+public:
+       ThrottlingException(const char *s):
+               BaseException(s)
+       {}
+};*/
+
+class InvalidIncomingDataException : public BaseException
+{
+public:
+       InvalidIncomingDataException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class InvalidOutgoingDataException : public BaseException
+{
+public:
+       InvalidOutgoingDataException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class NoIncomingDataException : public BaseException
+{
+public:
+       NoIncomingDataException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class ProcessedSilentlyException : public BaseException
+{
+public:
+       ProcessedSilentlyException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class GotSplitPacketException
+{
+       SharedBuffer<u8> m_data;
+public:
+       GotSplitPacketException(SharedBuffer<u8> data):
+               m_data(data)
+       {}
+       SharedBuffer<u8> getData()
+       {
+               return m_data;
+       }
+};
+
+inline u16 readPeerId(u8 *packetdata)
+{
+       return readU16(&packetdata[4]);
+}
+inline u8 readChannel(u8 *packetdata)
+{
+       return readU8(&packetdata[6]);
+}
+
+#define SEQNUM_MAX 65535
+inline bool seqnum_higher(u16 higher, u16 lower)
+{
+       if(lower > higher && lower - higher > SEQNUM_MAX/2){
+               return true;
+       }
+       return (higher > lower);
+}
+
+struct BufferedPacket
+{
+       BufferedPacket(u8 *a_data, u32 a_size):
+               data(a_data, a_size), time(0.0), totaltime(0.0)
+       {}
+       BufferedPacket(u32 a_size):
+               data(a_size), time(0.0), totaltime(0.0)
+       {}
+       SharedBuffer<u8> data; // Data of the packet, including headers
+       float time; // Seconds from buffering the packet or re-sending
+       float totaltime; // Seconds from buffering the packet
+       Address address; // Sender or destination
+};
+
+// This adds the base headers to the data and makes a packet out of it
+BufferedPacket makePacket(Address &address, u8 *data, u32 datasize,
+               u32 protocol_id, u16 sender_peer_id, u8 channel);
+BufferedPacket makePacket(Address &address, SharedBuffer<u8> &data,
+               u32 protocol_id, u16 sender_peer_id, u8 channel);
+
+// Add the TYPE_ORIGINAL header to the data
+SharedBuffer<u8> makeOriginalPacket(
+               SharedBuffer<u8> data);
+
+// Split data in chunks and add TYPE_SPLIT headers to them
+core::list<SharedBuffer<u8> > makeSplitPacket(
+               SharedBuffer<u8> data,
+               u32 chunksize_max,
+               u16 seqnum);
+
+// Depending on size, make a TYPE_ORIGINAL or TYPE_SPLIT packet
+// Increments split_seqnum if a split packet is made
+core::list<SharedBuffer<u8> > makeAutoSplitPacket(
+               SharedBuffer<u8> data,
+               u32 chunksize_max,
+               u16 &split_seqnum);
+
+// Add the TYPE_RELIABLE header to the data
+SharedBuffer<u8> makeReliablePacket(
+               SharedBuffer<u8> data,
+               u16 seqnum);
+
+struct IncomingSplitPacket
+{
+       IncomingSplitPacket()
+       {
+               time = 0.0;
+               reliable = false;
+       }
+       // Key is chunk number, value is data without headers
+       core::map<u16, SharedBuffer<u8> > chunks;
+       u32 chunk_count;
+       float time; // Seconds from adding
+       bool reliable; // If true, isn't deleted on timeout
+
+       bool allReceived()
+       {
+               return (chunks.size() == chunk_count);
+       }
+};
+
+/*
+=== NOTES ===
+
+A packet is sent through a channel to a peer with a basic header:
+TODO: Should we have a receiver_peer_id also?
+       Header (7 bytes):
+       [0] u32 protocol_id
+       [4] u16 sender_peer_id
+       [6] u8 channel
+sender_peer_id:
+       Unique to each peer.
+       value 0 is reserved for making new connections
+       value 1 is reserved for server
+channel:
+       The lower the number, the higher the priority is.
+       Only channels 0, 1 and 2 exist.
+*/
+#define BASE_HEADER_SIZE 7
+#define PEER_ID_NEW 0
+#define PEER_ID_SERVER 1
+#define CHANNEL_COUNT 3
+/*
+Packet types:
+
+CONTROL: This is a packet used by the protocol.
+- When this is processed, nothing is handed to the user.
+       Header (2 byte):
+       [0] u8 type
+       [1] u8 controltype
+controltype and data description:
+       CONTROLTYPE_ACK
+               [2] u16 seqnum
+       CONTROLTYPE_SET_PEER_ID
+               [2] u16 peer_id_new
+       CONTROLTYPE_PING
+       - This can be sent in a reliable packet to get a reply
+*/
+#define TYPE_CONTROL 0
+#define CONTROLTYPE_ACK 0
+#define CONTROLTYPE_SET_PEER_ID 1
+#define CONTROLTYPE_PING 2
+/*
+ORIGINAL: This is a plain packet with no control and no error
+checking at all.
+- When this is processed, it is directly handed to the user.
+       Header (1 byte):
+       [0] u8 type
+*/
+#define TYPE_ORIGINAL 1
+#define ORIGINAL_HEADER_SIZE 1
+/*
+SPLIT: These are sequences of packets forming one bigger piece of
+data.
+- When processed and all the packet_nums 0...packet_count-1 are
+  present (this should be buffered), the resulting data shall be
+  directly handed to the user.
+- If the data fails to come up in a reasonable time, the buffer shall
+  be silently discarded.
+- These can be sent as-is or atop of a RELIABLE packet stream.
+       Header (7 bytes):
+       [0] u8 type
+       [1] u16 seqnum
+       [3] u16 chunk_count
+       [5] u16 chunk_num
+*/
+#define TYPE_SPLIT 2
+/*
+RELIABLE: Delivery of all RELIABLE packets shall be forced by ACKs,
+and they shall be delivered in the same order as sent. This is done
+with a buffer in the receiving and transmitting end.
+- When this is processed, the contents of each packet is recursively
+  processed as packets.
+       Header (3 bytes):
+       [0] u8 type
+       [1] u16 seqnum
+
+*/
+#define TYPE_RELIABLE 3
+#define RELIABLE_HEADER_SIZE 3
+//#define SEQNUM_INITIAL 0x10
+#define SEQNUM_INITIAL 65500
+
+/*
+       A buffer which stores reliable packets and sorts them internally
+       for fast access to the smallest one.
+*/
+
+typedef core::list<BufferedPacket>::Iterator RPBSearchResult;
+
+class ReliablePacketBuffer
+{
+public:
+       
+       void print();
+       bool empty();
+       u32 size();
+       RPBSearchResult findPacket(u16 seqnum);
+       RPBSearchResult notFound();
+       u16 getFirstSeqnum();
+       BufferedPacket popFirst();
+       BufferedPacket popSeqnum(u16 seqnum);
+       void insert(BufferedPacket &p);
+       void incrementTimeouts(float dtime);
+       void resetTimedOuts(float timeout);
+       bool anyTotaltimeReached(float timeout);
+       core::list<BufferedPacket> getTimedOuts(float timeout);
+
+private:
+       core::list<BufferedPacket> m_list;
+};
+
+/*
+       A buffer for reconstructing split packets
+*/
+
+class IncomingSplitBuffer
+{
+public:
+       ~IncomingSplitBuffer();
+       /*
+               This will throw a GotSplitPacketException when a full
+               split packet is constructed.
+       */
+       void insert(BufferedPacket &p, bool reliable);
+       
+       void removeUnreliableTimedOuts(float dtime, float timeout);
+       
+private:
+       // Key is seqnum
+       core::map<u16, IncomingSplitPacket*> m_buf;
+};
+
+class Connection;
+
+struct Channel
+{
+       Channel();
+       ~Channel();
+       /*
+               Processes a packet with the basic header stripped out.
+               Parameters:
+                       packetdata: Data in packet (with no base headers)
+                       con: The connection to which the channel is associated
+                            (used for sending back stuff (ACKs))
+                       peer_id: peer id of the sender of the packet in question
+                       channelnum: channel on which the packet was sent
+                       reliable: true if recursing into a reliable packet
+       */
+       SharedBuffer<u8> ProcessPacket(
+                       SharedBuffer<u8> packetdata,
+                       Connection *con,
+                       u16 peer_id,
+                       u8 channelnum,
+                       bool reliable=false);
+       
+       // Returns next data from a buffer if possible
+       // throws a NoIncomingDataException if no data is available
+       // If found, sets peer_id
+       SharedBuffer<u8> CheckIncomingBuffers(Connection *con,
+                       u16 &peer_id);
+
+       u16 next_outgoing_seqnum;
+       u16 next_incoming_seqnum;
+       u16 next_outgoing_split_seqnum;
+       
+       // This is for buffering the incoming packets that are coming in
+       // the wrong order
+       ReliablePacketBuffer incoming_reliables;
+       // This is for buffering the sent packets so that the sender can
+       // re-send them if no ACK is received
+       ReliablePacketBuffer outgoing_reliables;
+
+       IncomingSplitBuffer incoming_splits;
+};
+
+class Peer;
+
+class PeerHandler
+{
+public:
+       PeerHandler()
+       {
+       }
+       virtual ~PeerHandler()
+       {
+       }
+       
+       /*
+               This is called after the Peer has been inserted into the
+               Connection's peer container.
+       */
+       virtual void peerAdded(Peer *peer) = 0;
+       /*
+               This is called before the Peer has been removed from the
+               Connection's peer container.
+       */
+       virtual void deletingPeer(Peer *peer, bool timeout) = 0;
+};
+
+class Peer
+{
+public:
+
+       Peer(u16 a_id, Address a_address);
+       virtual ~Peer();
+       
+       /*
+               Calculates avg_rtt and resend_timeout.
+
+               rtt=-1 only recalculates resend_timeout
+       */
+       void reportRTT(float rtt);
+
+       Channel channels[CHANNEL_COUNT];
+
+       // Address of the peer
+       Address address;
+       // Unique id of the peer
+       u16 id;
+       // Seconds from last receive
+       float timeout_counter;
+       // Ping timer
+       float ping_timer;
+       // This is changed dynamically
+       float resend_timeout;
+       // Updated when an ACK is received
+       float avg_rtt;
+       // This is set to true when the peer has actually sent something
+       // with the id we have given to it
+       bool has_sent_with_id;
+       
+private:
+};
+
+class Connection
+{
+public:
+       Connection(
+               u32 protocol_id,
+               u32 max_packet_size,
+               float timeout,
+               PeerHandler *peerhandler
+       );
+       ~Connection();
+       void setTimeoutMs(int timeout){ m_socket.setTimeoutMs(timeout); }
+       // Start being a server
+       void Serve(unsigned short port);
+       // Connect to a server
+       void Connect(Address address);
+       bool Connected();
+
+       // Sets peer_id
+       SharedBuffer<u8> GetFromBuffers(u16 &peer_id);
+
+       // The peer_id of sender is stored in peer_id
+       // Return value: I guess this always throws an exception or
+       //               actually gets data
+       u32 Receive(u16 &peer_id, u8 *data, u32 datasize);
+       
+       // These will automatically package the data as an original or split
+       void SendToAll(u8 channelnum, SharedBuffer<u8> data, bool reliable);
+       void Send(u16 peer_id, u8 channelnum, SharedBuffer<u8> data, bool reliable);
+       // Send data as a packet; it will be wrapped in base header and
+       // optionally to a reliable packet.
+       void SendAsPacket(u16 peer_id, u8 channelnum,
+                       SharedBuffer<u8> data, bool reliable);
+       // Sends a raw packet
+       void RawSend(const BufferedPacket &packet);
+       
+       void RunTimeouts(float dtime);
+       // Can throw a PeerNotFoundException
+       Peer* GetPeer(u16 peer_id);
+       // returns NULL if failed
+       Peer* GetPeerNoEx(u16 peer_id);
+       core::list<Peer*> GetPeers();
+
+       void SetPeerID(u16 id){ m_peer_id = id; }
+       u16 GetPeerID(){ return m_peer_id; }
+       u32 GetProtocolID(){ return m_protocol_id; }
+
+       // For debug printing
+       void PrintInfo(std::ostream &out);
+       void PrintInfo();
+       u16 m_indentation;
+
+private:
+       u32 m_protocol_id;
+       float m_timeout;
+       PeerHandler *m_peerhandler;
+       core::map<u16, Peer*> m_peers;
+       u16 m_peer_id;
+       //bool m_waiting_new_peer_id;
+       u32 m_max_packet_size;
+       UDPSocket m_socket;
+};
+
+} // namespace
+
+#endif
+
diff --git a/src/constants.h b/src/constants.h
new file mode 100644 (file)
index 0000000..e91ded0
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef CONSTANTS_HEADER
+#define CONSTANTS_HEADER
+
+#define DEBUGFILE "debug.txt"
+
+// Define for simulating the quirks of sending through internet
+// WARNING: This disables unit testing of socket and connection
+#define INTERNET_SIMULATOR 0
+
+#define CONNECTION_TIMEOUT 30
+
+#define RESEND_TIMEOUT_MIN 0.333
+#define RESEND_TIMEOUT_MAX 3.0
+// resend_timeout = avg_rtt * this
+#define RESEND_TIMEOUT_FACTOR 4
+
+#define PI 3.14159
+
+#define SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT (60*10)
+#define SERVER_MAP_SAVE_INTERVAL (60)
+
+//#define SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT (5)
+//#define SERVER_MAP_SAVE_INTERVAL (5)
+
+#define FOV_ANGLE (PI/2.5)
+
+// The absolute working limit is (2^15 - viewing_range).
+#define MAP_GENERATION_LIMIT (31000)
+
+//#define MAX_SIMULTANEOUS_BLOCK_SENDS 7
+//#define MAX_SIMULTANEOUS_BLOCK_SENDS 3
+#define MAX_SIMULTANEOUS_BLOCK_SENDS 2
+//#define MAX_SIMULTANEOUS_BLOCK_SENDS 1
+
+#define FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING 2.0
+#define LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS 1
+
+// Viewing range stuff
+
+#define FPS_DEFAULT_WANTED 30
+#define FPS_DEFAULT_MAX 60
+
+#define FORCEDFETCH_RANGE 80
+
+#define HEIGHTMAP_RANGE_NODES 300
+
+// The freetime ratio is dynamically kept high enough to make this
+// dtime jitter possible
+// Allow 50% = 0.1
+/*#define DTIME_JITTER_MAX_FRACTION 0.5
+#define FREETIME_RATIO_MIN 0.05
+#define FREETIME_RATIO_MAX 0.4*/
+
+//#define FREETIME_RATIO 0.2
+#define FREETIME_RATIO 0.15
+
+#define SECTOR_HEIGHTMAP_SPLIT 2
+
+#define PLAYER_INVENTORY_SIZE (8*4)
+
+#define SIGN_TEXT_MAX_LENGTH 50
+
+#define ACTIVE_OBJECT_D_BLOCKS 2
+
+#define CATCH_UNJANDLED_EXCEPTIONS 1
+
+#endif
+
diff --git a/src/debug.cpp b/src/debug.cpp
new file mode 100644 (file)
index 0000000..2489413
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "debug.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+       #define WIN32_LEAN_AND_MEAN
+       #include <windows.h>
+       #define sleep_ms(x) Sleep(x)
+#else
+       #include <unistd.h>
+       #define sleep_ms(x) usleep(x*1000)
+#endif
+
+/*
+       Debug output
+*/
+
+FILE *g_debugstreams[DEBUGSTREAM_COUNT] = {stderr, NULL};
+
+void debugstreams_init(bool disable_stderr, const char *filename)
+{
+       if(disable_stderr)
+               g_debugstreams[0] = NULL;
+
+       if(filename)
+               g_debugstreams[1] = fopen(filename, "a");
+               
+       if(g_debugstreams[1])
+       {
+               fprintf(g_debugstreams[1], "\n\n-------------\n");
+               fprintf(g_debugstreams[1],     "  Separator  \n");
+               fprintf(g_debugstreams[1],     "-------------\n\n");
+       }
+}
+
+void debugstreams_deinit()
+{
+       if(g_debugstreams[1] != NULL)
+               fclose(g_debugstreams[1]);
+}
+
+Debugbuf debugbuf(false);
+std::ostream dstream(&debugbuf);
+Debugbuf debugbuf_no_stderr(true);
+std::ostream dstream_no_stderr(&debugbuf_no_stderr);
+Nullstream dummyout;
+
+/*
+       Assert
+*/
+
+void assert_fail(const char *assertion, const char *file,
+               unsigned int line, const char *function)
+{
+       DEBUGPRINT("\nIn thread %x:\n"
+                       "%s:%d: %s: Assertion '%s' failed.\n",
+                       (unsigned int)get_current_thread_id(),
+                       file, line, function, assertion);
+       
+       debug_stacks_print();
+
+       if(g_debugstreams[1])
+               fclose(g_debugstreams[1]);
+
+       //sleep_ms(3000);
+
+       abort();
+}
+
+/*
+       DebugStack
+*/
+
+DebugStack::DebugStack(threadid_t id)
+{
+       threadid = id;
+       stack_i = 0;
+       stack_max_i = 0;
+}
+
+void DebugStack::print(FILE *file, bool everything)
+{
+       fprintf(file, "BEGIN STACK: Debug stack for thread %x:\n",
+                       (unsigned int)threadid);
+
+       for(int i=0; i<stack_max_i; i++)
+       {
+               if(i == stack_i && everything == false)
+                       continue;
+
+               if(everything == true && i == stack_i)
+                       fprintf(file, "END OF STACK.\n"
+                                       "! Continuing beyond stack end:\n");
+
+               fprintf(file, "#%d  %s\n", i, stack[i]);
+       }
+
+       if(stack_i == DEBUG_STACK_SIZE)
+               fprintf(file, "Probably overflown.\n");
+}
+
+
+core::map<threadid_t, DebugStack*> g_debug_stacks;
+JMutex g_debug_stacks_mutex;
+
+void debug_stacks_init()
+{
+       g_debug_stacks_mutex.Init();
+}
+
+void debug_stacks_print()
+{
+       JMutexAutoLock lock(g_debug_stacks_mutex);
+
+       DEBUGPRINT("Debug stacks:\n");
+
+       for(core::map<threadid_t, DebugStack*>::Iterator
+                       i = g_debug_stacks.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               DebugStack *stack = i.getNode()->getValue();
+
+               for(int i=0; i<DEBUGSTREAM_COUNT; i++)
+               {
+                       if(g_debugstreams[i] != NULL)
+                               stack->print(g_debugstreams[i], true);
+               }
+       }
+}
+
+DebugStacker::DebugStacker(const char *text)
+{
+       threadid_t threadid = get_current_thread_id();
+
+       JMutexAutoLock lock(g_debug_stacks_mutex);
+
+       core::map<threadid_t, DebugStack*>::Node *n;
+       n = g_debug_stacks.find(threadid);
+       if(n != NULL)
+       {
+               m_stack = n->getValue();
+       }
+       else
+       {
+               /*DEBUGPRINT("Creating new debug stack for thread %x\n",
+                               (unsigned int)threadid);*/
+               m_stack = new DebugStack(threadid);
+               g_debug_stacks.insert(threadid, m_stack);
+       }
+
+       if(m_stack->stack_i >= DEBUG_STACK_SIZE)
+       {
+               m_overflowed = true;
+       }
+       else
+       {
+               m_overflowed = false;
+
+               snprintf(m_stack->stack[m_stack->stack_i],
+                               DEBUG_STACK_TEXT_SIZE, "%s", text);
+               m_stack->stack_i++;
+               if(m_stack->stack_i > m_stack->stack_max_i)
+                       m_stack->stack_max_i = m_stack->stack_i;
+       }
+}
+
+DebugStacker::~DebugStacker()
+{
+       JMutexAutoLock lock(g_debug_stacks_mutex);
+       
+       if(m_overflowed == true)
+               return;
+       
+       m_stack->stack_i--;
+
+       if(m_stack->stack_i == 0)
+       {
+               threadid_t threadid = m_stack->threadid;
+               /*DEBUGPRINT("Deleting debug stack for thread %x\n",
+                               (unsigned int)threadid);*/
+               delete m_stack;
+               g_debug_stacks.remove(threadid);
+       }
+}
+
diff --git a/src/debug.h b/src/debug.h
new file mode 100644 (file)
index 0000000..014456c
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+/*
+       Debug stack and assertion
+*/
+
+#ifndef DEBUG_HEADER
+#define DEBUG_HEADER
+
+#include <stdio.h>
+#include <jmutex.h>
+#include <jmutexautolock.h>
+#include <iostream>
+#include "common_irrlicht.h"
+
+/*
+       Compatibility stuff
+*/
+
+#if (defined(WIN32) || defined(_WIN32_WCE))
+typedef DWORD threadid_t;
+#define __NORETURN __declspec(noreturn)
+#define __FUNCTION_NAME __FUNCTION__
+#else
+typedef pthread_t threadid_t;
+#define __NORETURN __attribute__ ((__noreturn__))
+#define __FUNCTION_NAME __PRETTY_FUNCTION__
+#endif
+
+inline threadid_t get_current_thread_id()
+{
+#if (defined(WIN32) || defined(_WIN32_WCE))
+       return GetCurrentThreadId();
+#else
+       return pthread_self();
+#endif
+}
+
+/*
+       Debug output
+*/
+
+#define DEBUGSTREAM_COUNT 2
+
+extern FILE *g_debugstreams[DEBUGSTREAM_COUNT];
+
+extern void debugstreams_init(bool disable_stderr, const char *filename);
+extern void debugstreams_deinit();
+
+#define DEBUGPRINT(...)\
+{\
+       for(int i=0; i<DEBUGSTREAM_COUNT; i++)\
+       {\
+               if(g_debugstreams[i] != NULL){\
+                       fprintf(g_debugstreams[i], __VA_ARGS__);\
+                       fflush(g_debugstreams[i]);\
+               }\
+       }\
+}
+
+class Debugbuf : public std::streambuf
+{
+public:
+       Debugbuf(bool disable_stderr)
+       {
+               m_disable_stderr = disable_stderr;
+       }
+
+       int overflow(int c)
+       {
+               for(int i=0; i<DEBUGSTREAM_COUNT; i++)
+               {
+                       if(g_debugstreams[i] == stderr && m_disable_stderr)
+                               continue;
+                       if(g_debugstreams[i] != NULL)
+                               fwrite(&c, 1, 1, g_debugstreams[i]);
+                       //TODO: Is this slow?
+                       fflush(g_debugstreams[i]);
+               }
+               
+               return c;
+       }
+       int xsputn(const char *s, int n)
+       {
+               for(int i=0; i<DEBUGSTREAM_COUNT; i++)
+               {
+                       if(g_debugstreams[i] == stderr && m_disable_stderr)
+                               continue;
+                       if(g_debugstreams[i] != NULL)
+                               fwrite(s, 1, n, g_debugstreams[i]);
+                       //TODO: Is this slow?
+                       fflush(g_debugstreams[i]);
+               }
+
+               return n;
+       }
+       
+private:
+       bool m_disable_stderr;
+};
+
+// This is used to redirect output to /dev/null
+class Nullstream : public std::ostream {
+public:
+       Nullstream():
+               std::ostream(0)
+       {
+       }
+private:
+};
+
+extern Debugbuf debugbuf;
+extern std::ostream dstream;
+extern std::ostream dstream_no_stderr;
+extern Nullstream dummyout;
+
+/*
+       Assert
+*/
+
+__NORETURN extern void assert_fail(
+               const char *assertion, const char *file,
+               unsigned int line, const char *function);
+
+#define ASSERT(expr)\
+       ((expr)\
+       ? (void)(0)\
+       : assert_fail(#expr, __FILE__, __LINE__, __FUNCTION_NAME))
+
+#define assert(expr) ASSERT(expr)
+
+/*
+       DebugStack
+*/
+
+#define DEBUG_STACK_SIZE 50
+#define DEBUG_STACK_TEXT_SIZE 300
+
+struct DebugStack
+{
+       DebugStack(threadid_t id);
+       void print(FILE *file, bool everything);
+       
+       threadid_t threadid;
+       char stack[DEBUG_STACK_SIZE][DEBUG_STACK_TEXT_SIZE];
+       int stack_i; // Points to the lowest empty position
+       int stack_max_i; // Highest i that was seen
+};
+
+extern core::map<threadid_t, DebugStack*> g_debug_stacks;
+extern JMutex g_debug_stacks_mutex;
+
+extern void debug_stacks_init();
+extern void debug_stacks_print();
+
+class DebugStacker
+{
+public:
+       DebugStacker(const char *text);
+       ~DebugStacker();
+
+private:
+       DebugStack *m_stack;
+       bool m_overflowed;
+};
+
+#define DSTACK(...)\
+       char __buf[DEBUG_STACK_TEXT_SIZE];\
+       snprintf(__buf,\
+                       DEBUG_STACK_TEXT_SIZE, __VA_ARGS__);\
+       DebugStacker __debug_stacker(__buf);
+
+#endif
+
diff --git a/src/environment.cpp b/src/environment.cpp
new file mode 100644 (file)
index 0000000..412bb33
--- /dev/null
@@ -0,0 +1,206 @@
+#include "environment.h"
+#include "main.h" // g_device for timing debug
+
+Environment::Environment(Map *map, std::ostream &dout):
+               m_dout(dout)
+{
+       m_map = map;
+}
+
+Environment::~Environment()
+{
+       // Deallocate players
+       for(core::list<Player*>::Iterator i = m_players.begin();
+                       i != m_players.end(); i++)
+       {
+               delete (*i);
+       }
+       
+       delete m_map;
+}
+
+void Environment::step(float dtime)
+{
+       DSTACK(__FUNCTION_NAME);
+       /*
+               Run Map's timers
+       */
+       //TimeTaker maptimerupdatetimer("m_map->timerUpdate()", g_device);
+       // 0ms
+       m_map->timerUpdate(dtime);
+       //maptimerupdatetimer.stop();
+
+       /*
+               Get the highest speed some player is going
+       */
+       //TimeTaker playerspeed("playerspeed", g_device);
+       // 0ms
+       f32 maximum_player_speed = 0.001; // just some small value
+       for(core::list<Player*>::Iterator i = m_players.begin();
+                       i != m_players.end(); i++)
+       {
+               f32 speed = (*i)->getSpeed().getLength();
+               if(speed > maximum_player_speed)
+                       maximum_player_speed = speed;
+       }
+       //playerspeed.stop();
+       
+       // Maximum time increment (for collision detection etc)
+       // Allow 0.1 blocks per increment
+       // time = distance / speed
+       f32 dtime_max_increment = 0.1*BS / maximum_player_speed;
+       // Maximum time increment is 10ms or lower
+       if(dtime_max_increment > 0.01)
+               dtime_max_increment = 0.01;
+       
+       //TimeTaker playerupdate("playerupdate", g_device);
+       
+       /*
+               Stuff that has a maximum time increment
+       */
+       // Don't allow overly huge dtime
+       if(dtime > 0.5)
+               dtime = 0.5;
+
+       u32 loopcount = 0;
+       do
+       {
+               loopcount++;
+
+               f32 dtime_part;
+               if(dtime > dtime_max_increment)
+                       dtime_part = dtime_max_increment;
+               else
+                       dtime_part = dtime;
+               dtime -= dtime_part;
+               
+               /*
+                       Handle players
+               */
+               for(core::list<Player*>::Iterator i = m_players.begin();
+                               i != m_players.end(); i++)
+               {
+                       Player *player = *i;
+                       
+                       // Apply gravity to local player
+                       if(player->isLocal())
+                       {
+                               v3f speed = player->getSpeed();
+                               speed.Y -= 9.81 * BS * dtime_part * 2;
+                               player->setSpeed(speed);
+                       }
+
+                       /*
+                               Move the player.
+                               For local player, this also calculates collision detection.
+                       */
+                       player->move(dtime_part, *m_map);
+                       
+                       /*
+                               Add footsteps to grass
+                       */
+                       //TimeTaker footsteptimer("footstep", g_device);
+                       // 0ms
+                       v3f playerpos = player->getPosition();
+                       // Get node that is at BS/4 under player
+                       v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0));
+                       try{
+                               MapNode n = m_map->getNode(bottompos);
+                               if(n.d == MATERIAL_GRASS)
+                               {
+                                       n.d = MATERIAL_GRASS_FOOTSTEPS;
+                                       m_map->setNode(bottompos, n);
+
+                                       // Update mesh on client
+                                       if(m_map->mapType() == MAPTYPE_CLIENT)
+                                       {
+                                               v3s16 p_blocks = getNodeBlockPos(bottompos);
+                                               MapBlock *b = m_map->getBlockNoCreate(p_blocks);
+                                               b->updateMesh();
+                                       }
+                               }
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                       }
+                       //footsteptimer.stop();
+               }
+       }
+       while(dtime > 0.001);
+       
+       //std::cout<<"Looped "<<loopcount<<" times."<<std::endl;
+}
+
+Map & Environment::getMap()
+{
+       return *m_map;
+}
+
+void Environment::addPlayer(Player *player)
+{
+       DSTACK(__FUNCTION_NAME);
+       //Check that only one local player exists and peer_ids are unique
+       assert(player->isLocal() == false || getLocalPlayer() == NULL);
+       assert(getPlayer(player->peer_id) == NULL);
+       m_players.push_back(player);
+}
+
+void Environment::removePlayer(u16 peer_id)
+{
+       DSTACK(__FUNCTION_NAME);
+re_search:
+       for(core::list<Player*>::Iterator i = m_players.begin();
+                       i != m_players.end(); i++)
+       {
+               Player *player = *i;
+               if(player->peer_id != peer_id)
+                       continue;
+               
+               delete player;
+               m_players.erase(i);
+               // See if there is an another one
+               // (shouldn't be, but just to be sure)
+               goto re_search;
+       }
+}
+
+LocalPlayer * Environment::getLocalPlayer()
+{
+       for(core::list<Player*>::Iterator i = m_players.begin();
+                       i != m_players.end(); i++)
+       {
+               Player *player = *i;
+               if(player->isLocal())
+                       return (LocalPlayer*)player;
+       }
+       return NULL;
+}
+
+Player * Environment::getPlayer(u16 peer_id)
+{
+       for(core::list<Player*>::Iterator i = m_players.begin();
+                       i != m_players.end(); i++)
+       {
+               Player *player = *i;
+               if(player->peer_id == peer_id)
+                       return player;
+       }
+       return NULL;
+}
+
+core::list<Player*> Environment::getPlayers()
+{
+       return m_players;
+}
+
+void Environment::printPlayers(std::ostream &o)
+{
+       o<<"Players in environment:"<<std::endl;
+       for(core::list<Player*>::Iterator i = m_players.begin();
+                       i != m_players.end(); i++)
+       {
+               Player *player = *i;
+               o<<"Player peer_id="<<player->peer_id<<std::endl;
+       }
+}
+
diff --git a/src/environment.h b/src/environment.h
new file mode 100644 (file)
index 0000000..1415112
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef ENVIRONMENT_HEADER
+#define ENVIRONMENT_HEADER
+
+/*
+       This class is the game's environment.
+       It contains:
+       - The map
+       - Players
+       - Other objects
+       - The current time in the game, etc.
+*/
+
+#include <list>
+#include "common_irrlicht.h"
+#include "player.h"
+#include "map.h"
+#include <ostream>
+
+class Environment
+{
+public:
+       // Environment will delete the map passed to the constructor
+       Environment(Map *map, std::ostream &dout);
+       ~Environment();
+       /*
+               This can do anything to the environment, such as removing
+               timed-out players.
+               Also updates Map's timers.
+       */
+       void step(f32 dtime);
+
+       Map & getMap();
+       /*
+               Environment deallocates players after use.
+       */
+       void addPlayer(Player *player);
+       void removePlayer(u16 peer_id);
+       LocalPlayer * getLocalPlayer();
+       Player * getPlayer(u16 peer_id);
+       core::list<Player*> getPlayers();
+       void printPlayers(std::ostream &o);
+private:
+       Map *m_map;
+       core::list<Player*> m_players;
+       // Debug output goes here
+       std::ostream &m_dout;
+};
+
+#endif
+
diff --git a/src/exceptions.h b/src/exceptions.h
new file mode 100644 (file)
index 0000000..cbe13f1
--- /dev/null
@@ -0,0 +1,116 @@
+#ifndef EXCEPTIONS_HEADER
+#define EXCEPTIONS_HEADER
+
+#include <exception>
+
+class BaseException : public std::exception
+{
+public:
+       BaseException(const char *s)
+       {
+               m_s = s;
+       }
+       virtual const char * what() const throw()
+       {
+               return m_s;
+       }
+       const char *m_s;
+};
+
+class AsyncQueuedException : public BaseException
+{
+public:
+       AsyncQueuedException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class NotImplementedException : public BaseException
+{
+public:
+       NotImplementedException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class AlreadyExistsException : public BaseException
+{
+public:
+       AlreadyExistsException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class VersionMismatchException : public BaseException
+{
+public:
+       VersionMismatchException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class FileNotGoodException : public BaseException
+{
+public:
+       FileNotGoodException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class SerializationError : public BaseException
+{
+public:
+       SerializationError(const char *s):
+               BaseException(s)
+       {}
+};
+
+class LoadError : public BaseException
+{
+public:
+       LoadError(const char *s):
+               BaseException(s)
+       {}
+};
+
+class ContainerFullException : public BaseException
+{
+public:
+       ContainerFullException(const char *s):
+               BaseException(s)
+       {}
+};
+
+/*
+       Some "old-style" interrupts:
+*/
+
+class InvalidPositionException : public BaseException
+{
+public:
+       InvalidPositionException():
+               BaseException("Somebody tried to get/set something in a nonexistent position.")
+       {}
+       InvalidPositionException(const char *s):
+               BaseException(s)
+       {}
+};
+
+class TargetInexistentException : public std::exception
+{
+       virtual const char * what() const throw()
+       {
+               return "Somebody tried to refer to something that doesn't exist.";
+       }
+};
+
+class NullPointerException : public std::exception
+{
+       virtual const char * what() const throw()
+       {
+               return "NullPointerException";
+       }
+};
+
+#endif
+
diff --git a/src/filesys.cpp b/src/filesys.cpp
new file mode 100644 (file)
index 0000000..0012470
--- /dev/null
@@ -0,0 +1,205 @@
+#include "filesys.h"
+#include <iostream>
+
+namespace fs
+{
+
+#ifdef _WIN32
+
+#define _WIN32_WINNT 0x0501
+#include <Windows.h>
+#include <stdio.h>
+#include <malloc.h>
+#include <tchar.h> 
+#include <wchar.h> 
+#include <stdio.h>
+
+#define BUFSIZE MAX_PATH
+
+std::vector<DirListNode> GetDirListing(std::string pathstring)
+{
+       std::vector<DirListNode> listing;
+
+       WIN32_FIND_DATA FindFileData;
+       HANDLE hFind = INVALID_HANDLE_VALUE;
+       DWORD dwError;
+       LPTSTR DirSpec;
+       INT retval;
+
+       DirSpec = (LPTSTR) malloc (BUFSIZE);
+
+       if( DirSpec == NULL )
+       {
+         printf( "Insufficient memory available\n" );
+         retval = 1;
+         goto Cleanup;
+       }
+
+       // Check that the input is not larger than allowed.
+       if (pathstring.size() > (BUFSIZE - 2))
+       {
+         _tprintf(TEXT("Input directory is too large.\n"));
+         retval = 3;
+         goto Cleanup;
+       }
+
+       //_tprintf (TEXT("Target directory is %s.\n"), pathstring.c_str());
+
+       sprintf(DirSpec, "%s", (pathstring + "\\*").c_str());
+
+       // Find the first file in the directory.
+       hFind = FindFirstFile(DirSpec, &FindFileData);
+
+       if (hFind == INVALID_HANDLE_VALUE) 
+       {
+         _tprintf (TEXT("Invalid file handle. Error is %u.\n"), 
+                               GetLastError());
+         retval = (-1);
+       } 
+       else 
+       {
+               DirListNode node;
+               node.name = FindFileData.cFileName;
+               node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+               listing.push_back(node);
+
+               // List all the other files in the directory.
+               while (FindNextFile(hFind, &FindFileData) != 0) 
+               {
+                       DirListNode node;
+                       node.name = FindFileData.cFileName;
+                       node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+                       listing.push_back(node);
+               }
+
+               dwError = GetLastError();
+               FindClose(hFind);
+               if (dwError != ERROR_NO_MORE_FILES) 
+               {
+                _tprintf (TEXT("FindNextFile error. Error is %u.\n"), 
+                                  dwError);
+               retval = (-1);
+               goto Cleanup;
+               }
+       }
+       retval  = 0;
+
+Cleanup:
+       free(DirSpec);
+
+       if(retval != 0) listing.clear();
+
+       //for(unsigned int i=0; i<listing.size(); i++){
+       //      std::cout<<listing[i].name<<(listing[i].dir?" (dir)":" (file)")<<std::endl;
+       //}
+       
+       return listing;
+}
+
+bool CreateDir(std::string path)
+{
+       bool r = CreateDirectory(path.c_str(), NULL);
+       if(r == true)
+               return true;
+       if(GetLastError() == ERROR_ALREADY_EXISTS)
+               return true;
+       return false;
+}
+
+bool PathExists(std::string path)
+{
+       return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES);
+}
+
+#else
+
+#ifdef linux
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+std::vector<DirListNode> GetDirListing(std::string pathstring)
+{
+       std::vector<DirListNode> listing;
+
+    DIR *dp;
+    struct dirent *dirp;
+    if((dp  = opendir(pathstring.c_str())) == NULL) {
+               //std::cout<<"Error("<<errno<<") opening "<<pathstring<<std::endl;
+        return listing;
+    }
+
+    while ((dirp = readdir(dp)) != NULL) {
+               if(dirp->d_name[0]!='.'){
+                       DirListNode node;
+                       node.name = dirp->d_name;
+                       if(dirp->d_type == DT_DIR) node.dir = true;
+                       else node.dir = false;
+                       listing.push_back(node);
+               }
+    }
+    closedir(dp);
+
+       return listing;
+}
+
+bool CreateDir(std::string path)
+{
+       int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+       if(r == 0)
+       {
+               return true;
+       }
+       else
+       {
+               // If already exists, return true
+               if(errno == EEXIST)
+                       return true;
+               return false;
+       }
+}
+
+bool PathExists(std::string path)
+{
+       struct stat st;
+       return (stat(path.c_str(),&st) == 0);
+}
+
+#else
+
+#include "boost/filesystem/operations.hpp"
+namespace bfsys = boost::filesystem;
+
+std::vector<DirListNode> GetDirListing(std::string pathstring)
+{
+       std::vector<DirListNode> listing;
+
+       bfsys::path path(pathstring);
+
+       if( !exists( path ) ) return listing;
+
+       bfsys::directory_iterator end_itr; // default construction yields past-the-end
+       for( bfsys::directory_iterator itr( path ); itr != end_itr; ++itr ){
+               DirListNode node;
+               node.name = itr->leaf();
+               node.dir = is_directory(*itr);
+               listing.push_back(node);
+       }
+
+       return listing;
+}
+
+bool CreateDir(std::string path)
+{
+       std::cout<<"CreateDir not implemented in boost"<<std::endl;
+       return false;
+}
+
+#endif
+
+#endif
+
+}
+
diff --git a/src/filesys.h b/src/filesys.h
new file mode 100644 (file)
index 0000000..6222dbf
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef FILESYS_HEADER
+#define FILESYS_HEADER
+
+#include <string>
+#include <vector>
+#include "exceptions.h"
+
+namespace fs
+{
+
+struct DirListNode
+{
+       std::string name;
+       bool dir;
+};
+
+std::vector<DirListNode> GetDirListing(std::string path);
+
+// Returns true if already exists
+bool CreateDir(std::string path);
+
+bool PathExists(std::string path);
+
+}//fs
+
+#endif
+
diff --git a/src/heightmap.cpp b/src/heightmap.cpp
new file mode 100644 (file)
index 0000000..a4027ee
--- /dev/null
@@ -0,0 +1,872 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "heightmap.h"
+
+/*
+       ValueGenerator
+*/
+
+ValueGenerator* ValueGenerator::deSerialize(std::string line)
+{
+       std::istringstream ss(line);
+       //ss.imbue(std::locale("C"));
+       
+       std::string name;
+       std::getline(ss, name, ' ');
+
+       if(name == "constant")
+       {
+               f32 value;
+               ss>>value;
+               
+               return new ConstantGenerator(value);
+       }
+       else if(name == "linear")
+       {
+               f32 height;
+               v2f slope;
+
+               ss>>height;
+               ss>>slope.X;
+               ss>>slope.Y;
+
+               return new LinearGenerator(height, slope);
+       }
+       else if(name == "power")
+       {
+               f32 height;
+               v2f slope;
+               f32 power;
+
+               ss>>height;
+               ss>>slope.X;
+               ss>>slope.Y;
+               ss>>power;
+
+               return new PowerGenerator(height, slope, power);
+       }
+       else
+       {
+               throw SerializationError
+               ("Invalid heightmap generator (deSerialize)");
+       }
+}
+
+/*
+       FixedHeightmap
+*/
+
+f32 FixedHeightmap::avgNeighbours(v2s16 p, s16 d)
+{
+       v2s16 dirs[4] = {
+               v2s16(1,0),
+               v2s16(0,1),
+               v2s16(-1,0),
+               v2s16(0,-1)
+       };
+       f32 sum = 0.0;
+       f32 count = 0.0;
+       for(u16 i=0; i<4; i++){
+               v2s16 p2 = p + dirs[i] * d;
+               f32 n = getGroundHeightParent(p2);
+               if(n < GROUNDHEIGHT_VALID_MINVALUE)
+                       continue;
+               sum += n;
+               count += 1.0;
+       }
+       assert(count > 0.001);
+       return sum / count;
+}
+
+f32 FixedHeightmap::avgDiagNeighbours(v2s16 p, s16 d)
+{
+       v2s16 dirs[4] = {
+               v2s16(1,1),
+               v2s16(-1,-1),
+               v2s16(-1,1),
+               v2s16(1,-1)
+       };
+       f32 sum = 0.0;
+       f32 count = 0.0;
+       for(u16 i=0; i<4; i++){
+               v2s16 p2 = p + dirs[i] * d;
+               f32 n = getGroundHeightParent(p2);
+               if(n < GROUNDHEIGHT_VALID_MINVALUE)
+                       continue;
+               sum += n;
+               count += 1.0;
+       }
+       assert(count > 0.001);
+       return sum / count;
+}
+
+/*
+       Adds a point to transform into a diamond pattern
+       center = Center of the diamond phase (center of a square)
+       a = Side length of the existing square (2, 4, 8, ...)
+
+       Adds the center points of the next squares to next_squares as
+       dummy "true" values.
+*/
+void FixedHeightmap::makeDiamond(
+               v2s16 center,
+               s16 a,
+               f32 randmax,
+               core::map<v2s16, bool> &next_squares)
+{
+       /*dstream<<"makeDiamond(): center="
+                       <<"("<<center.X<<","<<center.Y<<")"
+                       <<", a="<<a<<", randmax="<<randmax
+                       <<", next_squares.size()="<<next_squares.size()
+                       <<std::endl;*/
+
+       f32 n = avgDiagNeighbours(center, a/2);
+       // Add (-1.0...1.0) * randmax
+       n += ((float)rand() / (float)(RAND_MAX/2) - 1.0)*randmax;
+       bool worked = setGroundHeightParent(center, n);
+       
+       if(a >= 2 && worked)
+       {
+               next_squares[center + a/2*v2s16(-1,0)] = true;
+               next_squares[center + a/2*v2s16(1,0)] = true;
+               next_squares[center + a/2*v2s16(0,-1)] = true;
+               next_squares[center + a/2*v2s16(0,1)] = true;
+       }
+}
+
+/*
+       Adds a point to transform into a square pattern
+       center = The point that is added. The center of a diamond.
+       a = Diameter of the existing diamond. (2, 4, 8, 16, ...)
+
+       Adds center points of the next diamonds to next_diamonds.
+*/
+void FixedHeightmap::makeSquare(
+               v2s16 center,
+               s16 a,
+               f32 randmax,
+               core::map<v2s16, bool> &next_diamonds)
+{
+       /*dstream<<"makeSquare(): center="
+                       <<"("<<center.X<<","<<center.Y<<")"
+                       <<", a="<<a<<", randmax="<<randmax
+                       <<", next_diamonds.size()="<<next_diamonds.size()
+                       <<std::endl;*/
+
+       f32 n = avgNeighbours(center, a/2);
+       // Add (-1.0...1.0) * randmax
+       n += ((float)rand() / (float)(RAND_MAX/2) - 1.0)*randmax;
+       bool worked = setGroundHeightParent(center, n);
+       
+       if(a >= 4 && worked)
+       {
+               next_diamonds[center + a/4*v2s16(1,1)] = true;
+               next_diamonds[center + a/4*v2s16(-1,1)] = true;
+               next_diamonds[center + a/4*v2s16(-1,-1)] = true;
+               next_diamonds[center + a/4*v2s16(1,-1)] = true;
+       }
+}
+
+void FixedHeightmap::DiamondSquare(f32 randmax, f32 randfactor)
+{
+       u16 a;
+       if(W < H)
+               a = W-1;
+       else
+               a = H-1;
+       
+       // Check that a is a power of two
+       if((a & (a-1)) != 0)
+               throw;
+       
+       core::map<v2s16, bool> next_diamonds;
+       core::map<v2s16, bool> next_squares;
+
+       next_diamonds[v2s16(a/2, a/2)] = true;
+       
+       while(a >= 2)
+       {
+               next_squares.clear();
+               
+               for(core::map<v2s16, bool>::Iterator
+                               i = next_diamonds.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       v2s16 p = i.getNode()->getKey();
+                       makeDiamond(p, a, randmax, next_squares);
+               }
+
+               //print();
+               
+               next_diamonds.clear();
+               
+               for(core::map<v2s16, bool>::Iterator
+                               i = next_squares.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       v2s16 p = i.getNode()->getKey();
+                       makeSquare(p, a, randmax, next_diamonds);
+               }
+
+               //print();
+               
+               a /= 2;
+               randmax *= randfactor;
+       }
+}
+
+void FixedHeightmap::generateContinued(f32 randmax, f32 randfactor,
+               f32 *corners)
+{
+       DSTACK(__FUNCTION_NAME);
+       /*dstream<<"FixedHeightmap("<<m_pos_on_master.X
+                       <<","<<m_pos_on_master.Y
+                       <<")::generateContinued()"<<std::endl;*/
+
+       // Works only with blocksize=2,4,8,16,32,64,...
+       s16 a = m_blocksize;
+       
+       // Check that a is a power of two
+       if((a & (a-1)) != 0)
+               throw;
+       
+       // Overwrite with GROUNDHEIGHT_NOTFOUND_SETVALUE
+       for(s16 y=0; y<=a; y++){
+               for(s16 x=0; x<=a; x++){
+                       v2s16 p(x,y);
+                       setGroundHeight(p, GROUNDHEIGHT_NOTFOUND_SETVALUE);
+               }
+       }
+
+       /*
+               Seed borders from master heightmap
+               NOTE: Does this actually have any effect on the output?
+       */
+       struct SeedSpec
+       {
+               v2s16 neighbour_start;
+               v2s16 heightmap_start;
+               v2s16 dir;
+       };
+
+       SeedSpec seeds[4] =
+       {
+               { // Z- edge on X-axis
+                       v2s16(0, -1), // neighbour_start
+                       v2s16(0, 0), // heightmap_start
+                       v2s16(1, 0) // dir
+               },
+               { // Z+ edge on X-axis
+                       v2s16(0, m_blocksize),
+                       v2s16(0, m_blocksize),
+                       v2s16(1, 0)
+               },
+               { // X- edge on Z-axis
+                       v2s16(-1, 0),
+                       v2s16(0, 0),
+                       v2s16(0, 1)
+               },
+               { // X+ edge on Z-axis
+                       v2s16(m_blocksize, 0),
+                       v2s16(m_blocksize, 0),
+                       v2s16(0, 1)
+               },
+       };
+
+       for(s16 i=0; i<4; i++){
+               v2s16 npos = seeds[i].neighbour_start + m_pos_on_master * m_blocksize;
+               v2s16 hpos = seeds[i].heightmap_start;
+               for(s16 s=0; s<m_blocksize+1; s++){
+                       f32 h = m_master->getGroundHeight(npos, false);
+                       //dstream<<"h="<<h<<std::endl;
+                       if(h < GROUNDHEIGHT_VALID_MINVALUE)
+                               continue;
+                               //break;
+                       setGroundHeight(hpos, h);
+                       hpos += seeds[i].dir;
+                       npos += seeds[i].dir;
+               }
+       }
+       
+       /*dstream<<"borders seeded:"<<std::endl;
+       print();*/
+
+       /*
+               Fill with corners[] (if not already set)
+       */
+       v2s16 dirs[4] = {
+               v2s16(0,0),
+               v2s16(1,0),
+               v2s16(1,1),
+               v2s16(0,1),
+       };
+       for(u16 i=0; i<4; i++){
+               v2s16 npos = dirs[i] * a;
+               // Don't replace already seeded corners
+               f32 h = getGroundHeight(npos);
+               if(h > GROUNDHEIGHT_VALID_MINVALUE)
+                       continue;
+               setGroundHeight(dirs[i] * a, corners[i]);
+       }
+       
+       /*dstream<<"corners filled:"<<std::endl;
+       print();*/
+
+       DiamondSquare(randmax, randfactor);
+}
+
+u32 FixedHeightmap::serializedLength(u8 version, u16 blocksize)
+{
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: FixedHeightmap format not supported");
+       
+       // Any version
+       {
+               /*// [0] s32 blocksize
+               // [4] v2s16 pos_on_master
+               // [8] s32 data[W*H] (W=H=blocksize+1)
+               return 4 + 4 + (blocksize+1)*(blocksize+1)*4;*/
+
+               // [8] s32 data[W*H] (W=H=blocksize+1)
+               return (blocksize+1)*(blocksize+1)*4;
+       }
+}
+
+u32 FixedHeightmap::serializedLength(u8 version)
+{
+       return serializedLength(version, m_blocksize);
+}
+
+void FixedHeightmap::serialize(u8 *dest, u8 version)
+{
+       //dstream<<"FixedHeightmap::serialize"<<std::endl;
+
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: FixedHeightmap format not supported");
+       
+       // Any version
+       {
+               /*writeU32(&dest[0], m_blocksize);
+               writeV2S16(&dest[4], m_pos_on_master);
+               u32 nodecount = W*H;
+               for(u32 i=0; i<nodecount; i++)
+               {
+                       writeS32(&dest[8+i*4], (s32)(m_data[i]*1000.0));
+               }*/
+
+               u32 nodecount = W*H;
+               for(u32 i=0; i<nodecount; i++)
+               {
+                       writeS32(&dest[i*4], (s32)(m_data[i]*1000.0));
+               }
+       }
+}
+
+void FixedHeightmap::deSerialize(u8 *source, u8 version)
+{
+       /*dstream<<"FixedHeightmap::deSerialize m_blocksize="
+                       <<m_blocksize<<std::endl;*/
+
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: FixedHeightmap format not supported");
+       
+       // Any version
+       {
+               u32 nodecount = (m_blocksize+1)*(m_blocksize+1);
+               for(u32 i=0; i<nodecount; i++)
+               {
+                       m_data[i] = ((f32)readS32(&source[i*4]))/1000.0;
+               }
+
+               /*printf("source[0,1,2,3]=%x,%x,%x,%x\n",
+                               (int)source[0]&0xff,
+                               (int)source[1]&0xff,
+                               (int)source[2]&0xff,
+                               (int)source[3]&0xff);
+                               
+               dstream<<"m_data[0]="<<m_data[0]<<", "
+                               <<"readS32(&source[0])="<<readS32(&source[0])
+                               <<std::endl;
+               dstream<<"m_data[4*4]="<<m_data[4*4]<<", "
+                               <<"readS32(&source[4*4])="<<readS32(&source[4*4])
+                               <<std::endl;*/
+       }
+}
+
+
+void setcolor(f32 h, f32 rangemin, f32 rangemax)
+{
+#ifndef _WIN32
+       const char *colors[] =
+       {
+               "\x1b[40m",
+               "\x1b[44m",
+               "\x1b[46m",
+               "\x1b[42m",
+               "\x1b[43m",
+               "\x1b[41m",
+       };
+       u16 colorcount = sizeof(colors)/sizeof(colors[0]);
+       f32 scaled = (h - rangemin) / (rangemax - rangemin);
+       u8 color = scaled * colorcount;
+       if(color > colorcount-1)
+               color = colorcount-1;
+       /*printf("rangemin=%f, rangemax=%f, h=%f -> color=%i\n",
+               rangemin,
+               rangemax,
+               h,
+               color);*/
+       printf("%s", colors[color]);
+       //printf("\x1b[31;40m");
+       //printf("\x1b[44;1m");
+#endif
+}
+void resetcolor()
+{
+#ifndef _WIN32
+       printf("\x1b[0m");
+#endif
+}
+
+/*
+       UnlimitedHeightmap
+*/
+
+void UnlimitedHeightmap::print()
+{
+       s16 minx =  10000;
+       s16 miny =  10000;
+       s16 maxx = -10000;
+       s16 maxy = -10000;
+       core::map<v2s16, FixedHeightmap*>::Iterator i;
+       i = m_heightmaps.getIterator();
+       if(i.atEnd()){
+               printf("UnlimitedHeightmap::print(): empty.\n");
+               return;
+       }
+       for(; i.atEnd() == false; i++)
+       {
+               v2s16 p = i.getNode()->getValue()->getPosOnMaster();
+               if(p.X < minx) minx = p.X;
+               if(p.Y < miny) miny = p.Y;
+               if(p.X > maxx) maxx = p.X;
+               if(p.Y > maxy) maxy = p.Y;
+       }
+       minx = minx * m_blocksize;
+       miny = miny * m_blocksize;
+       maxx = (maxx+1) * m_blocksize;
+       maxy = (maxy+1) * m_blocksize;
+       printf("UnlimitedHeightmap::print(): from (%i,%i) to (%i,%i)\n",
+                       minx, miny, maxx, maxy);
+       
+       // Calculate range
+       f32 rangemin = 1e10;
+       f32 rangemax = -1e10;
+       for(s32 y=miny; y<=maxy; y++){
+               for(s32 x=minx; x<=maxx; x++){
+                       f32 h = getGroundHeight(v2s16(x,y), false);
+                       if(h < GROUNDHEIGHT_VALID_MINVALUE)
+                               continue;
+                       if(h < rangemin)
+                               rangemin = h;
+                       if(h > rangemax)
+                               rangemax = h;
+               }
+       }
+
+       printf("     ");
+       for(s32 x=minx; x<=maxx; x++){
+               printf("% .3d ", x);
+       }
+       printf("\n");
+       
+       for(s32 y=miny; y<=maxy; y++){
+               printf("% .3d ", y);
+               for(s32 x=minx; x<=maxx; x++){
+                       f32 n = getGroundHeight(v2s16(x,y), false);
+                       if(n < GROUNDHEIGHT_VALID_MINVALUE)
+                               printf("  -   ");
+                       else
+                       {
+                               setcolor(n, rangemin, rangemax);
+                               printf("% -5.1f", getGroundHeight(v2s16(x,y), false));
+                               resetcolor();
+                       }
+               }
+               printf("\n");
+       }
+}
+       
+FixedHeightmap * UnlimitedHeightmap::getHeightmap(v2s16 p_from, bool generate)
+{
+       DSTACK("UnlimitedHeightmap::getHeightmap()");
+       /*
+               We want to check that all neighbours of the wanted heightmap
+               exist.
+               This is because generating the neighboring heightmaps will
+               modify the current one.
+       */
+       
+       if(generate)
+       {
+               // Go through all neighbors (corners also) and the current one
+               // and generate every one of them.
+               for(s16 x=p_from.X-1; x<=p_from.X+1; x++)
+               for(s16 y=p_from.Y-1; y<=p_from.Y+1; y++)
+               {
+                       v2s16 p(x,y);
+
+                       // Check if exists
+                       core::map<v2s16, FixedHeightmap*>::Node *n = m_heightmaps.find(p);
+                       if(n != NULL)
+                               continue;
+                       
+                       // Doesn't exist
+                       // Generate it
+
+                       FixedHeightmap *heightmap = new FixedHeightmap(this, p, m_blocksize);
+
+                       m_heightmaps.insert(p, heightmap);
+                       
+                       f32 corners[4] = {
+                               m_base_generator->getValue(p+v2s16(0,0)),
+                               m_base_generator->getValue(p+v2s16(1,0)),
+                               m_base_generator->getValue(p+v2s16(1,1)),
+                               m_base_generator->getValue(p+v2s16(0,1)),
+                       };
+
+                       f32 randmax = m_randmax_generator->getValue(p);
+                       f32 randfactor = m_randfactor_generator->getValue(p);
+
+                       heightmap->generateContinued(randmax, randfactor, corners);
+               }
+       }
+
+       core::map<v2s16, FixedHeightmap*>::Node *n = m_heightmaps.find(p_from);
+
+       if(n != NULL)
+       {
+               return n->getValue();
+       }
+       else
+       {
+               throw InvalidPositionException
+               ("Something went really wrong in UnlimitedHeightmap::getHeightmap");
+       }
+}
+
+f32 UnlimitedHeightmap::getGroundHeight(v2s16 p, bool generate)
+{
+       v2s16 heightmappos = getNodeHeightmapPos(p);
+       v2s16 relpos = p - heightmappos*m_blocksize;
+       try{
+               FixedHeightmap * href = getHeightmap(heightmappos, generate);
+               f32 h = href->getGroundHeight(relpos);
+               if(h > GROUNDHEIGHT_VALID_MINVALUE)
+                       return h;
+       }
+       catch(InvalidPositionException){}
+       /*
+               If on border or in the (0,0) corner, try to get from
+               overlapping heightmaps
+       */
+       if(relpos.X == 0){
+               try{
+                       FixedHeightmap * href = getHeightmap(
+                                       heightmappos-v2s16(1,0), false);
+                       f32 h = href->getGroundHeight(v2s16(m_blocksize, relpos.Y));
+                       if(h > GROUNDHEIGHT_VALID_MINVALUE)
+                               return h;
+               }
+               catch(InvalidPositionException){}
+       }
+       if(relpos.Y == 0){
+               try{
+                       FixedHeightmap * href = getHeightmap(
+                                       heightmappos-v2s16(0,1), false);
+                       f32 h = href->getGroundHeight(v2s16(relpos.X, m_blocksize));
+                       if(h > GROUNDHEIGHT_VALID_MINVALUE)
+                               return h;
+               }
+               catch(InvalidPositionException){}
+       }
+       if(relpos.X == 0 && relpos.Y == 0){
+               try{
+                       FixedHeightmap * href = getHeightmap(
+                                       heightmappos-v2s16(1,1), false);
+                       f32 h = href->getGroundHeight(v2s16(m_blocksize, m_blocksize));
+                       if(h > GROUNDHEIGHT_VALID_MINVALUE)
+                               return h;
+               }
+               catch(InvalidPositionException){}
+       }
+       return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+}
+
+void UnlimitedHeightmap::setGroundHeight(v2s16 p, f32 y, bool generate)
+{
+       bool was_set = false;
+
+       v2s16 heightmappos = getNodeHeightmapPos(p);
+       v2s16 relpos = p - heightmappos*m_blocksize;
+       /*dstream<<"UnlimitedHeightmap::setGroundHeight(("
+                       <<p.X<<","<<p.Y<<"), "<<y<<"): "
+                       <<"heightmappos=("<<heightmappos.X<<","
+                       <<heightmappos.Y<<") relpos=("
+                       <<relpos.X<<","<<relpos.Y<<")"
+                       <<std::endl;*/
+       try{
+               FixedHeightmap * href = getHeightmap(heightmappos, generate);
+               href->setGroundHeight(relpos, y);
+               was_set = true;
+       }catch(InvalidPositionException){}
+       // Update in neighbour heightmap if it's at border
+       if(relpos.X == 0){
+               try{
+                       FixedHeightmap * href = getHeightmap(
+                                       heightmappos-v2s16(1,0), generate);
+                       href->setGroundHeight(v2s16(m_blocksize, relpos.Y), y);
+                       was_set = true;
+               }catch(InvalidPositionException){}
+       }
+       if(relpos.Y == 0){
+               try{
+                       FixedHeightmap * href = getHeightmap(
+                                       heightmappos-v2s16(0,1), generate);
+                       href->setGroundHeight(v2s16(relpos.X, m_blocksize), y);
+                       was_set = true;
+               }catch(InvalidPositionException){}
+       }
+       if(relpos.X == 0 && relpos.Y == 0){
+               try{
+                       FixedHeightmap * href = getHeightmap(
+                                       heightmappos-v2s16(1,1), generate);
+                       href->setGroundHeight(v2s16(m_blocksize, m_blocksize), y);
+                       was_set = true;
+               }catch(InvalidPositionException){}
+       }
+
+       if(was_set == false)
+       {
+               throw InvalidPositionException
+                               ("UnlimitedHeightmap failed to set height");
+       }
+}
+
+
+void UnlimitedHeightmap::serialize(std::ostream &os, u8 version)
+{
+       //dstream<<"UnlimitedHeightmap::serialize()"<<std::endl;
+
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException
+               ("ERROR: UnlimitedHeightmap format not supported");
+       
+       if(version <= 7)
+       {
+               /*if(m_base_generator->getId() != VALUE_GENERATOR_ID_CONSTANT
+               || m_randmax_generator->getId() != VALUE_GENERATOR_ID_CONSTANT
+               || m_randfactor_generator->getId() != VALUE_GENERATOR_ID_CONSTANT)*/
+               if(std::string(m_base_generator->getName()) != "constant"
+               || std::string(m_randmax_generator->getName()) != "constant"
+               || std::string(m_randfactor_generator->getName()) != "constant")
+               {
+                       throw SerializationError
+                       ("Cannot write UnlimitedHeightmap in old version: "
+                       "Generators are not ConstantGenerators.");
+               }
+
+               f32 basevalue = ((ConstantGenerator*)m_base_generator)->m_value;
+               f32 randmax = ((ConstantGenerator*)m_randmax_generator)->m_value;
+               f32 randfactor = ((ConstantGenerator*)m_randfactor_generator)->m_value;
+
+               // Write version
+               os.write((char*)&version, 1);
+               
+               /*
+                       [0] u16 blocksize
+                       [2] s32 randmax*1000
+                       [6] s32 randfactor*1000
+                       [10] s32 basevalue*1000
+                       [14] u32 heightmap_count
+                       [18] X * (v2s16 pos + heightmap)
+               */
+               u32 heightmap_size =
+                               FixedHeightmap::serializedLength(version, m_blocksize);
+               u32 heightmap_count = m_heightmaps.size();
+
+               //dstream<<"heightmap_size="<<heightmap_size<<std::endl;
+
+               u32 datasize = 2+4+4+4+4+heightmap_count*(4+heightmap_size);
+               SharedBuffer<u8> data(datasize);
+               
+               writeU16(&data[0], m_blocksize);
+               writeU32(&data[2], (s32)(randmax*1000.0));
+               writeU32(&data[6], (s32)(randfactor*1000.0));
+               writeU32(&data[10], (s32)(basevalue*1000.0));
+               writeU32(&data[14], heightmap_count);
+
+               core::map<v2s16, FixedHeightmap*>::Iterator j;
+               j = m_heightmaps.getIterator();
+               u32 i=0;
+               for(; j.atEnd() == false; j++)
+               {
+                       FixedHeightmap *hm = j.getNode()->getValue();
+                       v2s16 pos = j.getNode()->getKey();
+                       writeV2S16(&data[18+i*(4+heightmap_size)], pos);
+                       hm->serialize(&data[18+i*(4+heightmap_size)+4], version);
+                       i++;
+               }
+
+               os.write((char*)*data, data.getSize());
+       }
+       else
+       {
+               // Write version
+               os.write((char*)&version, 1);
+               
+               u8 buf[4];
+               
+               writeU16(buf, m_blocksize);
+               os.write((char*)buf, 2);
+
+               /*m_randmax_generator->serialize(os, version);
+               m_randfactor_generator->serialize(os, version);
+               m_base_generator->serialize(os, version);*/
+               m_randmax_generator->serialize(os);
+               m_randfactor_generator->serialize(os);
+               m_base_generator->serialize(os);
+
+               u32 heightmap_count = m_heightmaps.size();
+               writeU32(buf, heightmap_count);
+               os.write((char*)buf, 4);
+
+               u32 heightmap_size =
+                               FixedHeightmap::serializedLength(version, m_blocksize);
+
+               SharedBuffer<u8> hmdata(heightmap_size);
+
+               core::map<v2s16, FixedHeightmap*>::Iterator j;
+               j = m_heightmaps.getIterator();
+               u32 i=0;
+               for(; j.atEnd() == false; j++)
+               {
+                       v2s16 pos = j.getNode()->getKey();
+                       writeV2S16(buf, pos);
+                       os.write((char*)buf, 4);
+
+                       FixedHeightmap *hm = j.getNode()->getValue();
+                       hm->serialize(*hmdata, version);
+                       os.write((char*)*hmdata, hmdata.getSize());
+
+                       i++;
+               }
+       }
+}
+
+UnlimitedHeightmap * UnlimitedHeightmap::deSerialize(std::istream &is)
+{
+       u8 version;
+       is.read((char*)&version, 1);
+       
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: UnlimitedHeightmap format not supported");
+       
+       if(version <= 7)
+       {
+               /*
+                       [0] u16 blocksize
+                       [2] s32 randmax*1000
+                       [6] s32 randfactor*1000
+                       [10] s32 basevalue*1000
+                       [14] u32 heightmap_count
+                       [18] X * (v2s16 pos + heightmap)
+               */
+               SharedBuffer<u8> data(18);
+               is.read((char*)*data, 18);
+               if(is.gcount() != 18)
+                       throw SerializationError
+                                       ("UnlimitedHeightmap::deSerialize: no enough input data");
+               s16 blocksize = readU16(&data[0]);
+               f32 randmax = (f32)readU32(&data[2]) / 1000.0;
+               f32 randfactor = (f32)readU32(&data[6]) / 1000.0;
+               f32 basevalue = (f32)readU32(&data[10]) / 1000.0;
+               u32 heightmap_count = readU32(&data[14]);
+
+               /*dstream<<"UnlimitedHeightmap::deSerialize():"
+                               <<" blocksize="<<blocksize
+                               <<" heightmap_count="<<heightmap_count
+                               <<std::endl;*/
+
+               u32 heightmap_size =
+                               FixedHeightmap::serializedLength(version, blocksize);
+
+               //dstream<<"heightmap_size="<<heightmap_size<<std::endl;
+
+               ValueGenerator *maxgen = new ConstantGenerator(randmax);
+               ValueGenerator *factorgen = new ConstantGenerator(randfactor);
+               ValueGenerator *basegen = new ConstantGenerator(basevalue);
+
+               UnlimitedHeightmap *hm = new UnlimitedHeightmap
+                               (blocksize, maxgen, factorgen, basegen);
+
+               for(u32 i=0; i<heightmap_count; i++)
+               {
+                       //dstream<<"i="<<i<<std::endl;
+                       SharedBuffer<u8> data(4+heightmap_size);
+                       is.read((char*)*data, 4+heightmap_size);
+                       if(is.gcount() != (s32)(4+heightmap_size)){
+                               delete hm;
+                               throw SerializationError
+                                               ("UnlimitedHeightmap::deSerialize: no enough input data");
+                       }
+                       v2s16 pos = readV2S16(&data[0]);
+                       FixedHeightmap *f = new FixedHeightmap(hm, pos, blocksize);
+                       f->deSerialize(&data[4], version);
+                       hm->m_heightmaps.insert(pos, f);
+               }
+               return hm;
+       }
+       else
+       {
+               u8 buf[4];
+               
+               is.read((char*)buf, 2);
+               s16 blocksize = readU16(buf);
+
+               ValueGenerator *maxgen = ValueGenerator::deSerialize(is);
+               ValueGenerator *factorgen = ValueGenerator::deSerialize(is);
+               ValueGenerator *basegen = ValueGenerator::deSerialize(is);
+
+               is.read((char*)buf, 4);
+               u32 heightmap_count = readU32(buf);
+
+               u32 heightmap_size =
+                               FixedHeightmap::serializedLength(version, blocksize);
+
+               UnlimitedHeightmap *hm = new UnlimitedHeightmap
+                               (blocksize, maxgen, factorgen, basegen);
+
+               for(u32 i=0; i<heightmap_count; i++)
+               {
+                       is.read((char*)buf, 4);
+                       v2s16 pos = readV2S16(buf);
+
+                       SharedBuffer<u8> data(heightmap_size);
+                       is.read((char*)*data, heightmap_size);
+                       if(is.gcount() != (s32)(heightmap_size)){
+                               delete hm;
+                               throw SerializationError
+                                               ("UnlimitedHeightmap::deSerialize: no enough input data");
+                       }
+                       FixedHeightmap *f = new FixedHeightmap(hm, pos, blocksize);
+                       f->deSerialize(*data, version);
+                       hm->m_heightmaps.insert(pos, f);
+               }
+               return hm;
+       }
+}
+
+
diff --git a/src/heightmap.h b/src/heightmap.h
new file mode 100644 (file)
index 0000000..a5da092
--- /dev/null
@@ -0,0 +1,556 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef HEIGHTMAP_HEADER
+#define HEIGHTMAP_HEADER
+
+#include <iostream>
+#include <time.h>
+#include <sstream>
+
+#include "debug.h"
+#include "common_irrlicht.h"
+#include "exceptions.h"
+#include "utility.h"
+#include "serialization.h"
+
+#define GROUNDHEIGHT_NOTFOUND_SETVALUE (-10e6)
+#define GROUNDHEIGHT_VALID_MINVALUE    ( -9e6)
+
+class Heightmappish
+{
+public:
+       virtual f32 getGroundHeight(v2s16 p, bool generate=true) = 0;
+       virtual void setGroundHeight(v2s16 p, f32 y, bool generate=true) = 0;
+       
+       v2f32 getSlope(v2s16 p)
+       {
+               f32 y0 = getGroundHeight(p, false);
+
+               v2s16 dirs[] = {
+                       v2s16(1,0),
+                       v2s16(0,1),
+               };
+
+               v2f32 fdirs[] = {
+                       v2f32(1,0),
+                       v2f32(0,1),
+               };
+
+               v2f32 slopevector(0.0, 0.0);
+               
+               for(u16 i=0; i<2; i++){
+                       f32 y1 = 0.0;
+                       f32 y2 = 0.0;
+                       f32 count = 0.0;
+
+                       v2s16 p1 = p - dirs[i];
+                       y1 = getGroundHeight(p1, false);
+                       if(y1 > GROUNDHEIGHT_VALID_MINVALUE){
+                               y1 -= y0;
+                               count += 1.0;
+                       }
+                       else
+                               y1 = 0;
+
+                       v2s16 p2 = p + dirs[i];
+                       y2 = getGroundHeight(p2, false);
+                       if(y2 > GROUNDHEIGHT_VALID_MINVALUE){
+                               y2 -= y0;
+                               count += 1.0;
+                       }
+                       else
+                               y2 = 0;
+
+                       if(count < 0.001)
+                               return v2f32(0.0, 0.0);
+                       
+                       /*
+                               If y2 is higher than y1, slope is positive
+                       */
+                       f32 slope = (y2 - y1)/count;
+
+                       slopevector += fdirs[i] * slope;
+               }
+
+               return slopevector;
+       }
+
+};
+
+// TODO: Get rid of this dummy wrapper
+class Heightmap : public Heightmappish /*, public ReferenceCounted*/
+{
+};
+
+class WrapperHeightmap : public Heightmap
+{
+       Heightmappish *m_target;
+public:
+
+       WrapperHeightmap(Heightmappish *target):
+               m_target(target)
+       {
+               if(target == NULL)
+                       throw NullPointerException();
+       }
+
+       f32 getGroundHeight(v2s16 p, bool generate=true)
+       {
+               return m_target->getGroundHeight(p, generate);
+       }
+       void setGroundHeight(v2s16 p, f32 y, bool generate=true)
+       {
+               m_target->setGroundHeight(p, y, generate);
+       }
+};
+
+/*
+       Base class that defines a generator that gives out values at
+       positions in 2-dimensional space.
+       Can be given to UnlimitedHeightmap to feed stuff.
+
+       These are always serialized as readable text ending in "\n"
+*/
+class ValueGenerator
+{
+public:
+       ValueGenerator(){}
+       virtual ~ValueGenerator(){}
+       
+       static ValueGenerator* deSerialize(std::string line);
+       
+       static ValueGenerator* deSerialize(std::istream &is)
+       {
+               std::string line;
+               std::getline(is, line, '\n');
+               return deSerialize(line);
+       }
+       
+       void serializeBase(std::ostream &os)
+       {
+               os<<getName()<<" ";
+       }
+
+       // Virtual methods
+       virtual const char * getName() const = 0;
+       virtual f32 getValue(v2s16 p) = 0;
+       virtual void serialize(std::ostream &os) = 0;
+};
+
+class ConstantGenerator : public ValueGenerator
+{
+public:
+       f32 m_value;
+
+       ConstantGenerator(f32 value)
+       {
+               m_value = value;
+       }
+
+       const char * getName() const
+       {
+               return "constant";
+       }
+       
+       f32 getValue(v2s16 p)
+       {
+               return m_value;
+       }
+
+       void serialize(std::ostream &os)
+       {
+               serializeBase(os);
+
+               std::ostringstream ss;
+               //ss.imbue(std::locale("C"));
+
+               ss<<m_value<<"\n";
+
+               os<<ss.str();
+       }
+};
+
+class LinearGenerator : public ValueGenerator
+{
+public:
+       f32 m_height;
+       v2f m_slope;
+
+       LinearGenerator(f32 height, v2f slope)
+       {
+               m_height = height;
+               m_slope = slope;
+       }
+
+       const char * getName() const
+       {
+               return "linear";
+       }
+       
+       f32 getValue(v2s16 p)
+       {
+               return m_height + m_slope.X * p.X + m_slope.Y * p.Y;
+       }
+
+       void serialize(std::ostream &os)
+       {
+               serializeBase(os);
+
+               std::ostringstream ss;
+               //ss.imbue(std::locale("C"));
+
+               ss<<m_height<<" "<<m_slope.X<<" "<<m_slope.Y<<"\n";
+
+               os<<ss.str();
+       }
+};
+
+class PowerGenerator : public ValueGenerator
+{
+public:
+       f32 m_height;
+       v2f m_slope;
+       f32 m_power;
+
+       PowerGenerator(f32 height, v2f slope, f32 power)
+       {
+               m_height = height;
+               m_slope = slope;
+               m_power = power;
+       }
+
+       const char * getName() const
+       {
+               return "power";
+       }
+       
+       f32 getValue(v2s16 p)
+       {
+               return m_height
+                               + m_slope.X * pow((f32)p.X, m_power)
+                               + m_slope.Y * pow((f32)p.Y, m_power);
+       }
+
+       void serialize(std::ostream &os)
+       {
+               serializeBase(os);
+
+               std::ostringstream ss;
+               //ss.imbue(std::locale("C"));
+
+               ss<<m_height<<" "
+                       <<m_slope.X<<" "
+                       <<m_slope.Y<<" "
+                       <<m_power<<"\n";
+
+               os<<ss.str();
+       }
+};
+
+class FixedHeightmap : public Heightmap
+{
+       // A meta-heightmap on which this heightmap is located
+       // (at m_pos_on_master * m_blocksize)
+       Heightmap * m_master;
+       // Position on master heightmap (in blocks)
+       v2s16 m_pos_on_master;
+       s32 m_blocksize; // This is (W-1) = (H-1)
+       // These are the actual size of the data
+       s32 W;
+       s32 H;
+       f32 *m_data;
+
+public:
+
+       FixedHeightmap(Heightmap * master,
+                       v2s16 pos_on_master, s32 blocksize):
+               m_master(master),
+               m_pos_on_master(pos_on_master),
+               m_blocksize(blocksize)
+       {
+               W = m_blocksize+1;
+               H = m_blocksize+1;
+               m_data = NULL;
+               m_data = new f32[(blocksize+1)*(blocksize+1)];
+
+               for(s32 i=0; i<(blocksize+1)*(blocksize+1); i++){
+                       m_data[i] = GROUNDHEIGHT_NOTFOUND_SETVALUE;
+               }
+       }
+
+       ~FixedHeightmap()
+       {
+               if(m_data)
+                       delete[] m_data;
+       }
+
+       v2s16 getPosOnMaster()
+       {
+               return m_pos_on_master;
+       }
+
+       /*
+               TODO: BorderWrapper class or something to allow defining
+               borders that wrap to an another heightmap. The algorithm
+               should be allowed to edit stuff over the border and on
+               the border in that case, too.
+               This will allow non-square heightmaps, too. (probably)
+       */
+
+       void print()
+       {
+               printf("FixedHeightmap::print(): size is %ix%i\n", W, H);
+               for(s32 y=0; y<H; y++){
+                       for(s32 x=0; x<W; x++){
+                               /*if(getSeeded(v2s16(x,y)))
+                                       printf("S");*/
+                               f32 n = getGroundHeight(v2s16(x,y));
+                               if(n < GROUNDHEIGHT_VALID_MINVALUE)
+                                       printf("  -   ");
+                               else
+                                       printf("% -5.1f ", getGroundHeight(v2s16(x,y)));
+                       }
+                       printf("\n");
+               }
+       }
+       
+       bool overborder(v2s16 p)
+       {
+               return (p.X < 0 || p.X >= W || p.Y < 0 || p.Y >= H);
+       }
+
+       bool atborder(v2s16 p)
+       {
+               if(overborder(p))
+                       return false;
+               return (p.X == 0 || p.X == W-1 || p.Y == 0 || p.Y == H-1);
+       }
+
+       void setGroundHeight(v2s16 p, f32 y, bool generate=false)
+       {
+               /*dstream<<"FixedHeightmap::setGroundHeight(("
+                               <<p.X<<","<<p.Y
+                               <<"), "<<y<<")"<<std::endl;*/
+               if(overborder(p))
+                       throw InvalidPositionException();
+               m_data[p.Y*W + p.X] = y;
+       }
+       
+       // Returns true on success, false on railure.
+       bool setGroundHeightParent(v2s16 p, f32 y, bool generate=false)
+       {
+               /*// Position on master
+               v2s16 blockpos_nodes = m_pos_on_master * m_blocksize;
+               v2s16 nodepos_master = blockpos_nodes + p;
+               dstream<<"FixedHeightmap::setGroundHeightParent(("
+                               <<p.X<<","<<p.Y
+                               <<"), "<<y<<"): nodepos_master=("
+                               <<nodepos_master.X<<","
+                               <<nodepos_master.Y<<")"<<std::endl;
+               m_master->setGroundHeight(nodepos_master, y, false);*/
+               
+               // Try to set on master
+               bool master_got_it = false;
+               if(overborder(p) || atborder(p))
+               {
+                       try{
+                               // Position on master
+                               v2s16 blockpos_nodes = m_pos_on_master * m_blocksize;
+                               v2s16 nodepos_master = blockpos_nodes + p;
+                               m_master->setGroundHeight(nodepos_master, y, false);
+
+                               master_got_it = true;
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                       }
+               }
+               
+               if(overborder(p))
+                       return master_got_it;
+               
+               setGroundHeight(p, y);
+
+               return true;
+       }
+       
+       f32 getGroundHeight(v2s16 p, bool generate=false)
+       {
+               if(overborder(p))
+                       return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+               return m_data[p.Y*W + p.X];
+       }
+
+       f32 getGroundHeightParent(v2s16 p)
+       {
+               /*v2s16 blockpos_nodes = m_pos_on_master * m_blocksize;
+               return m_master->getGroundHeight(blockpos_nodes + p, false);*/
+
+               if(overborder(p) == false){
+                       f32 h = getGroundHeight(p);
+                       if(h > GROUNDHEIGHT_VALID_MINVALUE)
+                               return h;
+               }
+               
+               // Position on master
+               v2s16 blockpos_nodes = m_pos_on_master * m_blocksize;
+               f32 h = m_master->getGroundHeight(blockpos_nodes + p, false);
+               return h;
+       }
+
+       f32 avgNeighbours(v2s16 p, s16 d);
+
+       f32 avgDiagNeighbours(v2s16 p, s16 d);
+       
+       void makeDiamond(
+                       v2s16 center,
+                       s16 a,
+                       f32 randmax,
+                       core::map<v2s16, bool> &next_squares);
+
+       void makeSquare(
+                       v2s16 center,
+                       s16 a,
+                       f32 randmax,
+                       core::map<v2s16, bool> &next_diamonds);
+       
+       void DiamondSquare(f32 randmax, f32 randfactor);
+       
+       /*
+               corners: [i]=XY: [0]=00, [1]=10, [2]=11, [3]=10
+       */
+       void generateContinued(f32 randmax, f32 randfactor, f32 *corners);
+
+       
+       static u32 serializedLength(u8 version, u16 blocksize);
+       u32 serializedLength(u8 version);
+       void serialize(u8 *dest, u8 version);
+       void deSerialize(u8 *source, u8 version);
+       /*static FixedHeightmap * deSerialize(u8 *source, u32 size,
+                       u32 &usedsize, Heightmap *master, u8 version);*/
+};
+
+class OneChildHeightmap : public Heightmap
+{
+       s16 m_blocksize;
+
+public:
+
+       FixedHeightmap m_child;
+
+       OneChildHeightmap(s16 blocksize):
+               m_blocksize(blocksize),
+               m_child(this, v2s16(0,0), blocksize)
+       {
+       }
+       
+       f32 getGroundHeight(v2s16 p, bool generate=true)
+       {
+               if(p.X < 0 || p.X > m_blocksize 
+                               || p.Y < 0 || p.Y > m_blocksize)
+                       return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+               return m_child.getGroundHeight(p);
+       }
+       void setGroundHeight(v2s16 p, f32 y, bool generate=true)
+       {
+               //dstream<<"OneChildHeightmap::setGroundHeight()"<<std::endl;
+               if(p.X < 0 || p.X > m_blocksize 
+                               || p.Y < 0 || p.Y > m_blocksize)
+                       throw InvalidPositionException();
+               m_child.setGroundHeight(p, y);
+       }
+};
+
+
+/*
+       This is a dynamic container of an arbitrary number of heightmaps
+       at arbitrary positions.
+       
+       It is able to redirect queries to the corresponding heightmaps and
+       it generates new heightmaps on-the-fly according to the relevant
+       parameters.
+       
+       It doesn't have a master heightmap because it is meant to be used
+       as such itself.
+
+       Child heightmaps are spaced at m_blocksize distances, and are of
+       size (m_blocksize+1)*(m_blocksize+1)
+
+       This is used as the master heightmap of a Map object.
+*/
+class UnlimitedHeightmap: public Heightmap
+{
+private:
+
+       core::map<v2s16, FixedHeightmap*> m_heightmaps;
+       s16 m_blocksize;
+
+       ValueGenerator *m_randmax_generator;
+       ValueGenerator *m_randfactor_generator;
+       ValueGenerator *m_base_generator;
+
+public:
+
+       UnlimitedHeightmap(
+                       s16 blocksize,
+                       ValueGenerator *randmax_generator,
+                       ValueGenerator *randfactor_generator,
+                       ValueGenerator *base_generator
+                       ):
+               m_blocksize(blocksize),
+               m_randmax_generator(randmax_generator),
+               m_randfactor_generator(randfactor_generator),
+               m_base_generator(base_generator)
+       {
+               assert(m_randmax_generator != NULL);
+               assert(m_randfactor_generator != NULL);
+               assert(m_base_generator != NULL);
+       }
+
+       ~UnlimitedHeightmap()
+       {
+               core::map<v2s16, FixedHeightmap*>::Iterator i;
+               i = m_heightmaps.getIterator();
+               for(; i.atEnd() == false; i++)
+               {
+                       delete i.getNode()->getValue();
+               }
+
+               delete m_randmax_generator;
+               delete m_randfactor_generator;
+               delete m_base_generator;
+       }
+
+       /*void setParams(f32 randmax, f32 randfactor)
+       {
+               m_randmax = randmax;
+               m_randfactor = randfactor;
+       }*/
+       
+       void print();
+
+       v2s16 getNodeHeightmapPos(v2s16 p)
+       {
+               return v2s16(
+                               (p.X>=0 ? p.X : p.X-m_blocksize+1) / m_blocksize,
+                               (p.Y>=0 ? p.Y : p.Y-m_blocksize+1) / m_blocksize);
+       }
+
+       // Can throw an InvalidPositionException
+       FixedHeightmap * getHeightmap(v2s16 p, bool generate=true);
+       
+       f32 getGroundHeight(v2s16 p, bool generate=true);
+       void setGroundHeight(v2s16 p, f32 y, bool generate=true);
+       
+       /*static UnlimitedHeightmap * deSerialize(u8 *source, u32 maxsize,
+                       u32 &usedsize, u8 version);*/
+       
+       //SharedBuffer<u8> serialize(u8 version);
+       void serialize(std::ostream &os, u8 version);
+       static UnlimitedHeightmap * deSerialize(std::istream &istr);
+};
+
+#endif
+
diff --git a/src/inventory.cpp b/src/inventory.cpp
new file mode 100644 (file)
index 0000000..4fe21b3
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "inventory.h"
+#include "serialization.h"
+#include "utility.h"
+#include "debug.h"
+#include <sstream>
+#include "main.h"
+
+/*
+       InventoryItem
+*/
+
+InventoryItem::InventoryItem()
+{
+}
+
+InventoryItem::~InventoryItem()
+{
+}
+
+InventoryItem* InventoryItem::deSerialize(std::istream &is)
+{
+       DSTACK(__FUNCTION_NAME);
+
+       //is.imbue(std::locale("C"));
+       // Read name
+       std::string name;
+       std::getline(is, name, ' ');
+       
+       if(name == "MaterialItem")
+       {
+               // u16 reads directly as a number (u8 doesn't)
+               u16 material;
+               is>>material;
+               u16 count;
+               is>>count;
+               if(material > 255)
+                       throw SerializationError("Too large material number");
+               return new MaterialItem(material, count);
+       }
+       else if(name == "MBOItem")
+       {
+               std::string inventorystring;
+               std::getline(is, inventorystring, '|');
+               return new MapBlockObjectItem(inventorystring);
+       }
+       else
+       {
+               dstream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
+               throw SerializationError("Unknown InventoryItem name");
+       }
+}
+
+/*
+       MapBlockObjectItem
+*/
+
+video::ITexture * MapBlockObjectItem::getImage()
+{
+       if(m_inventorystring.substr(0,3) == "Rat")
+               return g_device->getVideoDriver()->getTexture("../data/rat.png");
+       
+       if(m_inventorystring.substr(0,4) == "Sign")
+               return g_device->getVideoDriver()->getTexture("../data/sign.png");
+
+       return NULL;
+}
+std::string MapBlockObjectItem::getText()
+{
+       if(m_inventorystring.substr(0,3) == "Rat")
+               return "";
+       
+       if(m_inventorystring.substr(0,4) == "Sign")
+               return "";
+
+       return "obj";
+}
+
+MapBlockObject * MapBlockObjectItem::createObject
+               (v3f pos, f32 player_yaw, f32 player_pitch)
+{
+       std::istringstream is(m_inventorystring);
+       std::string name;
+       std::getline(is, name, ' ');
+       
+       if(name == "None")
+       {
+               return NULL;
+       }
+       else if(name == "Sign")
+       {
+               std::string text;
+               std::getline(is, text, '|');
+               SignObject *obj = new SignObject(NULL, -1, pos);
+               obj->setText(text);
+               obj->setYaw(-player_yaw);
+               return obj;
+       }
+       else if(name == "Rat")
+       {
+               RatObject *obj = new RatObject(NULL, -1, pos);
+               return obj;
+       }
+       else
+       {
+               return NULL;
+       }
+}
+
+/*
+       Inventory
+*/
+
+Inventory::Inventory(u32 size)
+{
+       m_size = size;
+       clearItems();
+}
+
+Inventory::~Inventory()
+{
+       for(u32 i=0; i<m_items.size(); i++)
+       {
+               delete m_items[i];
+       }
+}
+
+void Inventory::clearItems()
+{
+       m_items.clear();
+       for(u32 i=0; i<m_size; i++)
+       {
+               m_items.push_back(NULL);
+       }
+}
+
+void Inventory::serialize(std::ostream &os)
+{
+       //os.imbue(std::locale("C"));
+       
+       for(u32 i=0; i<m_items.size(); i++)
+       {
+               InventoryItem *item = m_items[i];
+               if(item != NULL)
+               {
+                       os<<"Item ";
+                       item->serialize(os);
+               }
+               else
+               {
+                       os<<"Empty";
+               }
+               os<<"\n";
+       }
+
+       os<<"end\n";
+}
+
+void Inventory::deSerialize(std::istream &is)
+{
+       //is.imbue(std::locale("C"));
+
+       clearItems();
+       u32 item_i = 0;
+
+       for(;;)
+       {
+               std::string line;
+               std::getline(is, line, '\n');
+
+               std::istringstream iss(line);
+               //iss.imbue(std::locale("C"));
+
+               std::string name;
+               std::getline(iss, name, ' ');
+
+               if(name == "end")
+               {
+                       break;
+               }
+               else if(name == "Item")
+               {
+                       if(item_i > getSize() - 1)
+                               throw SerializationError("too many items");
+                       InventoryItem *item = InventoryItem::deSerialize(iss);
+                       m_items[item_i++] = item;
+               }
+               else if(name == "Empty")
+               {
+                       if(item_i > getSize() - 1)
+                               throw SerializationError("too many items");
+                       m_items[item_i++] = NULL;
+               }
+               else
+               {
+                       throw SerializationError("Unknown inventory identifier");
+               }
+       }
+}
+
+Inventory & Inventory::operator = (Inventory &other)
+{
+       m_size = other.m_size;
+       clearItems();
+       for(u32 i=0; i<other.m_items.size(); i++)
+       {
+               InventoryItem *item = other.m_items[i];
+               if(item != NULL)
+               {
+                       m_items[i] = item->clone();
+               }
+       }
+
+       return *this;
+}
+
+u32 Inventory::getSize()
+{
+       return m_items.size();
+}
+
+u32 Inventory::getUsedSlots()
+{
+       u32 num = 0;
+       for(u32 i=0; i<m_items.size(); i++)
+       {
+               InventoryItem *item = m_items[i];
+               if(item != NULL)
+                       num++;
+       }
+       return num;
+}
+
+InventoryItem * Inventory::getItem(u32 i)
+{
+       if(i > m_items.size() - 1)
+               return NULL;
+       return m_items[i];
+}
+
+InventoryItem * Inventory::changeItem(u32 i, InventoryItem *newitem)
+{
+       assert(i < m_items.size());
+
+       InventoryItem *olditem = m_items[i];
+       m_items[i] = newitem;
+       return olditem;
+}
+
+void Inventory::deleteItem(u32 i)
+{
+       assert(i < m_items.size());
+       InventoryItem *item = changeItem(i, NULL);
+       if(item)
+               delete item;
+}
+
+bool Inventory::addItem(InventoryItem *newitem)
+{
+       // If it is a MaterialItem, try to find an already existing one
+       // and just increment the counter
+       if(std::string("MaterialItem") == newitem->getName())
+       {
+               u8 material = ((MaterialItem*)newitem)->getMaterial();
+               u8 count = ((MaterialItem*)newitem)->getCount();
+               for(u32 i=0; i<m_items.size(); i++)
+               {
+                       InventoryItem *item2 = m_items[i];
+                       if(item2 == NULL)
+                               continue;
+                       if(std::string("MaterialItem") != item2->getName())
+                               continue;
+                       // Found one. Check if it is of the right material and has
+                       // free space
+                       MaterialItem *mitem2 = (MaterialItem*)item2;
+                       if(mitem2->getMaterial() != material)
+                               continue;
+                       //TODO: Add all that can be added and add remaining part
+                       // to another place
+                       if(mitem2->freeSpace() < count)
+                               continue;
+                       // Add to the counter
+                       mitem2->add(count);
+                       // Dump the parameter
+                       delete newitem;
+                       return true;
+               }
+       }
+       // Else find an empty position
+       for(u32 i=0; i<m_items.size(); i++)
+       {
+               InventoryItem *item = m_items[i];
+               if(item != NULL)
+                       continue;
+               m_items[i] = newitem;
+               return true;
+       }
+       // Failed
+       return false;
+}
+
+void Inventory::print(std::ostream &o)
+{
+       o<<"Player inventory:"<<std::endl;
+       for(u32 i=0; i<m_items.size(); i++)
+       {
+               InventoryItem *item = m_items[i];
+               if(item != NULL)
+               {
+                       o<<i<<": ";
+                       item->serialize(o);
+                       o<<"\n";
+               }
+       }
+}
+       
+//END
diff --git a/src/inventory.h b/src/inventory.h
new file mode 100644 (file)
index 0000000..8ea8bd6
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef INVENTORY_HEADER
+#define INVENTORY_HEADER
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "common_irrlicht.h"
+#include "debug.h"
+#include "mapblockobject.h"
+// For g_materials
+#include "main.h"
+
+class InventoryItem
+{
+public:
+       InventoryItem();
+       virtual ~InventoryItem();
+       
+       static InventoryItem* deSerialize(std::istream &is);
+       
+       virtual const char* getName() const = 0;
+       // Shall write the name and the parameters
+       virtual void serialize(std::ostream &os) = 0;
+       // Shall make an exact clone of the item
+       virtual InventoryItem* clone() = 0;
+       // Shall return an image to show in the GUI (or NULL)
+       virtual video::ITexture * getImage() { return NULL; }
+       // Shall return a text to show in the GUI
+       virtual std::string getText() { return ""; }
+
+private:
+};
+
+#define MATERIAL_ITEM_MAX_COUNT 99
+
+class MaterialItem : public InventoryItem
+{
+public:
+       MaterialItem(u8 material, u16 count)
+       {
+               m_material = material;
+               m_count = count;
+       }
+       /*
+               Implementation interface
+       */
+       virtual const char* getName() const
+       {
+               return "MaterialItem";
+       }
+       virtual void serialize(std::ostream &os)
+       {
+               //os.imbue(std::locale("C"));
+               os<<getName();
+               os<<" ";
+               os<<(unsigned int)m_material;
+               os<<" ";
+               os<<m_count;
+       }
+       virtual InventoryItem* clone()
+       {
+               return new MaterialItem(m_material, m_count);
+       }
+       video::ITexture * getImage()
+       {
+               return g_materials[m_material].getTexture(0);
+       }
+       std::string getText()
+       {
+               std::ostringstream os;
+               os<<m_count;
+               return os.str();
+       }
+       /*
+               Special methods
+       */
+       u8 getMaterial()
+       {
+               return m_material;
+       }
+       u16 getCount()
+       {
+               return m_count;
+       }
+       u16 freeSpace()
+       {
+               if(m_count > MATERIAL_ITEM_MAX_COUNT)
+                       return 0;
+               return MATERIAL_ITEM_MAX_COUNT - m_count;
+       }
+       void add(u16 count)
+       {
+               assert(m_count + count <= MATERIAL_ITEM_MAX_COUNT);
+               m_count += count;
+       }
+       void remove(u16 count)
+       {
+               assert(m_count >= count);
+               m_count -= count;
+       }
+private:
+       u8 m_material;
+       u16 m_count;
+};
+
+class MapBlockObjectItem : public InventoryItem
+{
+public:
+       /*MapBlockObjectItem(MapBlockObject *obj)
+       {
+               m_inventorystring = obj->getInventoryString();
+       }*/
+       MapBlockObjectItem(std::string inventorystring)
+       {
+               m_inventorystring = inventorystring;
+       }
+       
+       /*
+               Implementation interface
+       */
+       virtual const char* getName() const
+       {
+               return "MBOItem";
+       }
+       virtual void serialize(std::ostream &os)
+       {
+               for(;;)
+               {
+                       size_t t = m_inventorystring.find('|');
+                       if(t == std::string::npos)
+                               break;
+                       m_inventorystring[t] = '?';
+               }
+               os<<getName();
+               os<<" ";
+               os<<m_inventorystring;
+               os<<"|";
+       }
+       virtual InventoryItem* clone()
+       {
+               return new MapBlockObjectItem(m_inventorystring);
+       }
+
+       video::ITexture * getImage();
+       std::string getText();
+
+       /*
+               Special methods
+       */
+       std::string getInventoryString()
+       {
+               return m_inventorystring;
+       }
+
+       MapBlockObject * createObject(v3f pos, f32 player_yaw, f32 player_pitch);
+
+private:
+       std::string m_inventorystring;
+};
+
+//SUGGESTION: Split into ClientInventory and ServerInventory
+class Inventory
+{
+public:
+       Inventory(u32 size);
+       ~Inventory();
+       void clearItems();
+       void serialize(std::ostream &os);
+       void deSerialize(std::istream &is);
+
+       Inventory & operator = (Inventory &other);
+
+       u32 getSize();
+       u32 getUsedSlots();
+       
+       InventoryItem * getItem(u32 i);
+       // Returns old item (or NULL). Parameter can be NULL.
+       InventoryItem * changeItem(u32 i, InventoryItem *newitem);
+       void deleteItem(u32 i);
+       // Adds an item to a suitable place. Returns false if failed.
+       bool addItem(InventoryItem *newitem);
+
+       void print(std::ostream &o);
+       
+private:
+       core::array<InventoryItem*> m_items;
+       u32 m_size;
+};
+
+#endif
+
diff --git a/src/light.cpp b/src/light.cpp
new file mode 100644 (file)
index 0000000..95bb37a
--- /dev/null
@@ -0,0 +1,85 @@
+#include "light.h"
+
+/*
+
+#!/usr/bin/python
+
+from math import *
+from sys import stdout
+
+# We want 0 at light=0 and 255 at light=LIGHT_MAX
+LIGHT_MAX = 15
+
+L = []
+for i in range(1,LIGHT_MAX+1):
+    L.append(int(round(255.0 * 0.69 ** (i-1))))
+       L.append(0)
+
+L.reverse()
+for i in L:
+       stdout.write(str(i)+",\n")
+
+*/
+
+/*
+       The first value should be 0, the last value should be 255.
+*/
+/*u8 light_decode_table[LIGHT_MAX+1] = 
+{
+0,
+2,
+3,
+4,
+6,
+9,
+13,
+19,
+28,
+40,
+58,
+84,
+121,
+176,
+255,
+};*/
+
+/*
+#!/usr/bin/python
+
+from math import *
+from sys import stdout
+
+# We want 0 at light=0 and 255 at light=LIGHT_MAX
+LIGHT_MAX = 14
+#FACTOR = 0.69
+FACTOR = 0.75
+
+L = []
+for i in range(1,LIGHT_MAX+1):
+    L.append(int(round(255.0 * FACTOR ** (i-1))))
+L.append(0)
+
+L.reverse()
+for i in L:
+    stdout.write(str(i)+",\n")
+*/
+u8 light_decode_table[LIGHT_MAX+1] = 
+{
+0,
+6,
+8,
+11,
+14,
+19,
+26,
+34,
+45,
+61,
+81,
+108,
+143,
+191,
+255,
+};
+
+
diff --git a/src/light.h b/src/light.h
new file mode 100644 (file)
index 0000000..b76ac3a
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef LIGHT_HEADER
+#define LIGHT_HEADER
+
+#include "common_irrlicht.h"
+
+// This directly sets the range of light
+#define LIGHT_MAX 14
+// This brightness is reserved for sunlight
+#define LIGHT_SUN 15
+
+inline u8 diminish_light(u8 light)
+{
+       if(light == 0)
+               return 0;
+       if(light >= LIGHT_MAX)
+               return LIGHT_MAX - 1;
+               
+       return light - 1;
+}
+
+inline u8 diminish_light(u8 light, u8 distance)
+{
+       if(distance >= light)
+               return 0;
+       return  light - distance;
+}
+
+inline u8 undiminish_light(u8 light)
+{
+       // We don't know if light should undiminish from this particular 0.
+       // Thus, keep it at 0.
+       if(light == 0)
+               return 0;
+       if(light == LIGHT_MAX)
+               return light;
+       
+       return light + 1;
+}
+
+extern u8 light_decode_table[LIGHT_MAX+1];
+
+inline u8 decode_light(u8 light)
+{
+       if(light == LIGHT_SUN)
+               return light_decode_table[LIGHT_MAX];
+       
+       if(light > LIGHT_MAX)
+               throw;
+       
+       return light_decode_table[light];
+}
+
+#endif
+
diff --git a/src/loadstatus.h b/src/loadstatus.h
new file mode 100644 (file)
index 0000000..a5fb6b3
--- /dev/null
@@ -0,0 +1,144 @@
+#ifndef LOADSTATUS_HEADER
+#define LOADSTATUS_HEADER
+
+class LoadStatus
+{
+       bool ready;
+       JMutex ready_mutex;
+       
+       u32 done;
+       JMutex done_mutex;
+
+       u32 todo;
+       JMutex todo_mutex;
+
+       wchar_t *text;
+       JMutex text_mutex;
+
+public:
+
+       LoadStatus(bool a_ready=false, u32 a_done=0, u32 a_todo=0)
+       {
+               ready = a_ready;
+               done = a_done;
+               todo = a_todo;
+               text = NULL;
+               ready_mutex.Init();
+               done_mutex.Init();
+               todo_mutex.Init();
+               text_mutex.Init();
+       }
+
+       void setReady(bool a_ready)
+       {
+               ready_mutex.Lock();
+               ready = a_ready;
+               ready_mutex.Unlock();
+       }
+
+       bool getReady(void)
+       {
+               ready_mutex.Lock();
+               bool a_ready = ready;
+               ready_mutex.Unlock();
+               return a_ready;
+       }
+
+       void setDone(u32 a_done)
+       {
+               done_mutex.Lock();
+               done = a_done;
+               done_mutex.Unlock();
+       }
+
+       u32 getDone(void)
+       {
+               done_mutex.Lock();
+               u32 a_done = done;
+               done_mutex.Unlock();
+               return a_done;
+       }
+
+       void setTodo(u32 a_todo)
+       {
+               todo_mutex.Lock();
+               todo = a_todo;
+               todo_mutex.Unlock();
+       }
+
+       u32 getTodo(void)
+       {
+               todo_mutex.Lock();
+               u32 a_todo = todo;
+               todo_mutex.Unlock();
+               return a_todo;
+       }
+
+       /*
+               Copies the text if not NULL,
+               If NULL; sets text to NULL.
+       */
+       void setText(const wchar_t *a_text)
+       {
+               text_mutex.Lock();
+               if(text != NULL)
+                       free(text);
+               if(a_text == NULL){
+                       text = NULL;
+                       text_mutex.Unlock();
+                       return;
+               }
+               u32 len = wcslen(a_text);
+               text = (wchar_t*)malloc(sizeof(wchar_t) * (len+1));
+               if(text == NULL) throw;
+               swprintf(text, len+1, L"%ls", a_text);
+               text_mutex.Unlock();
+       }
+       
+       /*
+               Return value must be free'd
+               Return value can be NULL
+       */
+       wchar_t * getText()
+       {
+               text_mutex.Lock();
+               if(text == NULL){
+                       text_mutex.Unlock();
+                       return NULL;
+               }
+               u32 len = wcslen(text);
+               wchar_t *b_text = (wchar_t*)malloc(sizeof(wchar_t) * (len+1));
+               if(b_text == NULL) throw;
+               swprintf(b_text, len+1, L"%ls", text);
+               text_mutex.Unlock();
+               return b_text;
+       }
+       
+       /*
+               Return value must be free'd
+       */
+       wchar_t * getNiceText()
+       {
+               const wchar_t *defaulttext = L"Loading";
+               wchar_t *t = getText();
+               u32 maxlen = 20; // " (%i/%i)"
+               if(t != NULL)
+                       maxlen += wcslen(t);
+               else
+                       maxlen += wcslen(defaulttext);
+               wchar_t *b_text = (wchar_t*)malloc(sizeof(wchar_t) * (maxlen+1));
+               if(b_text == NULL) throw;
+               if(t != NULL)
+                       swprintf(b_text, maxlen+1, L"%ls (%i/%i)",
+                                       t, getDone(), getTodo());
+               else
+                       swprintf(b_text, maxlen+1, L"%ls (%i/%i)",
+                                       defaulttext, getDone(), getTodo());
+               if(t != NULL)
+                       free(t);
+               return b_text;
+       }
+};
+
+#endif
+
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644 (file)
index 0000000..971e0ea
--- /dev/null
@@ -0,0 +1,2339 @@
+/*\r
+(c) 2010 Perttu Ahola <celeron55@gmail.com>\r
+\r
+Minetest\r
+\r
+NOTE: VBO cannot be turned on for fast-changing stuff because there\r
+      is an apparanet memory leak in irrlicht when using it\r
+\r
+SUGGESTION: add a second lighting value to the MS nibble of param of\r
+       air to tell how bright the air node is when there is no sunlight.\r
+       When day changes to night, these two values can be interpolated.\r
+TODO: Fix address to be ipv6 compatible\r
+\r
+TODO: ESC Pause mode in which the cursor is not kept at the center of window.\r
+TODO: Stop player if focus of window is taken away (go to pause mode)\r
+TODO: Optimize and fix makeFastFace or whatever it's called\r
+      - Face calculation is the source of CPU usage on the client\r
+SUGGESTION: The client will calculate and send lighting changes and\r
+  the server will randomly check some of them and kick the client out\r
+  if it fails to calculate them right.\r
+  - Actually, it could just start ignoring them and calculate them\r
+    itself.\r
+SUGGESTION: Combine MapBlock's face caches to so big pieces that VBO\r
+            gets used\r
+            - That is >500 vertices\r
+\r
+TODO: Better dungeons\r
+TODO: There should be very slight natural caves also, starting from\r
+      only a straightened-up cliff\r
+\r
+TODO: Changing of block with mouse wheel or something\r
+TODO: Menus\r
+\r
+TODO: Mobs\r
+      - Server:\r
+        - One single map container with ids as keys\r
+      - Client:\r
+           - ?\r
+TODO: - Keep track of the place of the mob in the last few hundreth's\r
+        of a second - then, if a player hits it, take the value that is\r
+               avg_rtt/2 before the moment the packet is received.\r
+TODO: - Scripting\r
+\r
+SUGGESTION: Modify client to calculate single changes asynchronously\r
+\r
+TODO: Moving players more smoothly. Calculate moving animation from\r
+      data sent by server.\r
+\r
+TODO: There are some lighting-related todos and fixmes in\r
+      ServerMap::emergeBlock\r
+\r
+TODO: Make a dirt node and use it under water\r
+\r
+FIXME: When a new sector is generated, it may change the ground level\r
+       of it's and it's neighbors border that two blocks that are\r
+          above and below each other and that are generated before and\r
+          after the sector heightmap generation (order doesn't matter),\r
+          can have a small gap between each other at the border.\r
+SUGGESTION: Use same technique for sector heightmaps as what we're\r
+            using for UnlimitedHeightmap? (getting all neighbors\r
+                       when generating)\r
+\r
+TODO: Set server to automatically find a good spawning place in some\r
+      place where there is water and land.\r
+         - Map to have a getWalkableNear(p)\r
+\r
+TODO: Transfer more blocks in a single packet\r
+SUGG: A blockdata combiner class, to which blocks are added and at\r
+      destruction it sends all the stuff in as few packets as possible.\r
+\r
+TODO: If player is on ground, mainly fetch ground-level blocks\r
+TODO: Fetch stuff mainly from the viewing direction\r
+\r
+TODO: Expose Connection's seqnums and ACKs to server and client.\r
+      - This enables saving many packets and making a faster connection\r
+         - This also enables server to check if client has received the\r
+           most recent block sent, for example.\r
+\r
+SUGG: Add a time value to the param of footstepped grass and check it\r
+      against a global timer when a block is accessed, to make old\r
+         steps fade away.\r
+\r
+FIXME: There still are *some* tiny glitches in lighting as seen from\r
+       the client side. The server calculates them right but sometimes\r
+          they don't get transferred properly.\r
+          - Server probably checks that a block is not sent, then continues\r
+          to sending it, then the emerge thread marks it as unsent and then\r
+          the sender sends the block as it was before emerging?\r
+TODO: How about adding a "revision" field to MapBlocks?\r
+\r
+TODO: More fine-grained control of client's dumping of blocks from\r
+      memory\r
+\r
+TODO: Somehow prioritize the sending of blocks and combine the block\r
+      send queue lengths\r
+         - Take two blocks to be sent next from each client and assign\r
+           a priority value to them\r
+         - Priority is the same as distance from player\r
+         - Take the highest priority ones and send them. Send as many as\r
+           fits in the global send queue maximum length (sum of lengths\r
+               of client queues)\r
+TODO: Make the amount of blocks sending to client and the total\r
+         amount of blocks dynamically limited. Transferring blocks is the\r
+         main network eater of this system, so it is the one that has\r
+         to be throttled so that RTTs stay low.\r
+FIXME: There is a bug that sometimes the EmergeThread bumps to\r
+       the client's emerge counter being already 0, and also the\r
+          sending queue size of the client can float to 1 or 2, which\r
+          stops the map from loading at all.\r
+          - A quick hack could be applied to ignore the error of\r
+            being at 0 and timing out old entries\r
+SUGG: Make client send GOTBLOCKS before updating meshes\r
+\r
+TODO: Server to load starting inventory from disk\r
+\r
+NOTE: iostream.imbue(std::locale("C")) is very slow\r
+NOTE: Global locale is now set at initialization\r
+\r
+TODO: PLayers to only be hidden when the client quits.\r
+TODO: - Players to be saved on disk, with inventory\r
+TODO: Players to be saved as text in map/players/<name>\r
+\r
+SUGGESTION: A map editing mode (similar to dedicated server mode)\r
+\r
+TODO: Maybe: Create a face calculation queue on the client that is\r
+      processed in a separate thread\r
+TODO: Make client's mesh updates to happen in a thread similar to\r
+      server's EmergeThread.\r
+         - This is not really needed, mesh update is really fast\r
+         - Instead, the lighting update can be slow\r
+         - So, this todo is not really a todo. It is a not-todo.\r
+SUGG: Make server to send all modified blocks after a node change\r
+      after all the stuff including lighting have been updated\r
+\r
+TODO: Make fetching sector's blocks more efficient when rendering\r
+      sectors that have very large amounts of blocks (on client)\r
+\r
+TODO: Make the video backend selectable\r
+\r
+TODO: A timestamp to blocks\r
+\r
+TODO: Client side:\r
+      - The server sends all active objects of the active blocks\r
+           at constant intervals. They should fit in a few packets.\r
+      - The client keeps track of what blocks at the moment are\r
+           having active objects in them.\r
+      - All blocks going in and out of the active buffer are recorded.\r
+           - For outgoing blocks, objects are removed from the blocks\r
+                 and from the scene\r
+           - For incoming blocks, objects are added to the blocks and\r
+                 to the scene.\r
+\r
+TODO: Server side:\r
+      - A "near blocks" buffer, in which some nearby blocks are stored.\r
+         - For all blocks in the buffer, objects are stepped(). This\r
+           means they are active.\r
+      - All blocks going in and out of the buffer are recorded.\r
+           - For outgoing blocks, a timestamp is written.\r
+           - For incoming blocks, the time difference is calculated and\r
+             objects are stepped according to it.\r
+\r
+TODO: Add config parameters for server's sending and generating distance\r
+\r
+TODO: Make amount of trees and other plants configurable\r
+      - Save to a metafile\r
+\r
+TODO: Copy the text of the last picked sign to inventory in creative\r
+      mode\r
+\r
+TODO: Untie client network operations from framerate\r
+\r
+TODO: Make a copy of close-range environment on client for showing\r
+      on screen, with minimal mutexes to slow the main loop down\r
+\r
+TODO: Make a PACKET_COMBINED which contains many subpackets. Utilize\r
+      it by sending more stuff in a single packet.\r
+\r
+Doing now:\r
+======================================================================\r
+\r
+\r
+======================================================================\r
+\r
+*/\r
+\r
+/*\r
+       Setting this to 1 enables a special camera mode that forces\r
+       the renderers to think that the camera statically points from\r
+       the starting place to a static direction.\r
+\r
+       This allows one to move around with the player and see what\r
+       is actually drawn behind solid things etc.\r
+*/\r
+#define FIELD_OF_VIEW_TEST 0\r
+\r
+#ifdef UNITTEST_DISABLE\r
+       #ifdef _WIN32\r
+               #pragma message ("Disabling unit tests")\r
+       #else\r
+               #warning "Disabling unit tests"\r
+       #endif\r
+       // Disable unit tests\r
+       #define ENABLE_TESTS 0\r
+#else\r
+       // Enable unit tests\r
+       #define ENABLE_TESTS 1\r
+#endif\r
+\r
+#ifdef _MSC_VER\r
+#pragma comment(lib, "Irrlicht.lib")\r
+#pragma comment(lib, "jthread.lib")\r
+// This would get rid of the console window\r
+//#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")\r
+#endif\r
+\r
+#ifdef _WIN32\r
+       #define WIN32_LEAN_AND_MEAN\r
+       #include <windows.h>\r
+       #define sleep_ms(x) Sleep(x)\r
+#else\r
+       #include <unistd.h>\r
+       #define sleep_ms(x) usleep(x*1000)\r
+#endif\r
+\r
+#include <iostream>\r
+#include <fstream>\r
+#include <time.h>\r
+#include <jmutexautolock.h>\r
+#include "common_irrlicht.h"\r
+#include "debug.h"\r
+#include "map.h"\r
+#include "player.h"\r
+#include "main.h"\r
+#include "test.h"\r
+#include "environment.h"\r
+#include "server.h"\r
+#include "client.h"\r
+#include "serialization.h"\r
+#include "constants.h"\r
+#include "strfnd.h"\r
+#include "porting.h"\r
+#include <locale.h>\r
+\r
+IrrlichtDevice *g_device = NULL;\r
+\r
+const char *g_material_filenames[MATERIALS_COUNT] =\r
+{\r
+       "../data/stone.png",\r
+       "../data/grass.png",\r
+       "../data/water.png",\r
+       "../data/light.png",\r
+       "../data/tree.png",\r
+       "../data/leaves.png",\r
+       "../data/grass_footsteps.png",\r
+       "../data/mese.png"\r
+};\r
+\r
+video::SMaterial g_materials[MATERIALS_COUNT];\r
+//video::SMaterial g_mesh_materials[3];\r
+\r
+// All range-related stuff below is locked behind this\r
+JMutex g_range_mutex;\r
+\r
+// Blocks are generated in this range from the player\r
+// This is limited vertically to half by Client::fetchBlocks()\r
+s16 g_forcedfetch_range_nodes = FORCEDFETCH_RANGE;\r
+\r
+// Blocks are viewed in this range from the player\r
+s16 g_viewing_range_nodes = 60;\r
+\r
+// This is updated by the client's fetchBlocks routine\r
+//s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;\r
+\r
+// If true, the preceding value has no meaning and all blocks\r
+// already existing in memory are drawn\r
+bool g_viewing_range_all = false;\r
+\r
+// This is the freetime ratio imposed by the dynamic viewing\r
+// range changing code.\r
+// It is controlled by the main loop to the smallest value that\r
+// inhibits glitches (dtime jitter) in the main loop.\r
+//float g_freetime_ratio = FREETIME_RATIO_MAX;\r
+\r
+\r
+/*\r
+       Settings.\r
+       These are loaded from the config file.\r
+*/\r
+\r
+std::string g_dedicated_server;\r
+\r
+// Client stuff\r
+float g_wanted_fps = FPS_DEFAULT_WANTED;\r
+float g_fps_max = FPS_DEFAULT_MAX;\r
+s16 g_viewing_range_nodes_max = 300;\r
+s16 g_viewing_range_nodes_min = 20;\r
+std::string g_screenW;\r
+std::string g_screenH;\r
+std::string g_host_game;\r
+std::string g_port;\r
+std::string g_address;\r
+std::string g_name;\r
+bool g_random_input = false;\r
+float g_client_delete_unused_sectors_timeout = 1200;\r
+\r
+// Server stuff\r
+bool g_creative_mode = false;\r
+MapgenParams g_mapgen_params;\r
+\r
+/*\r
+       Random stuff\r
+*/\r
+\r
+//u16 g_selected_material = 0;\r
+u16 g_selected_item = 0;\r
+\r
+bool g_esc_pressed = false;\r
+\r
+std::wstring g_text_buffer;\r
+bool g_text_buffer_accepted = false;\r
+\r
+// When true, the mouse and keyboard are grabbed\r
+bool g_game_focused = true;\r
+\r
+/*\r
+       Debug streams\r
+*/\r
+\r
+// Connection\r
+std::ostream *dout_con_ptr = &dummyout;\r
+std::ostream *derr_con_ptr = &dstream_no_stderr;\r
+//std::ostream *dout_con_ptr = &dstream_no_stderr;\r
+//std::ostream *derr_con_ptr = &dstream_no_stderr;\r
+//std::ostream *dout_con_ptr = &dstream;\r
+//std::ostream *derr_con_ptr = &dstream;\r
+\r
+// Server\r
+std::ostream *dout_server_ptr = &dstream;\r
+std::ostream *derr_server_ptr = &dstream;\r
+\r
+// Client\r
+std::ostream *dout_client_ptr = &dstream;\r
+std::ostream *derr_client_ptr = &dstream;\r
+\r
+/*\r
+       Config stuff\r
+*/\r
+\r
+// Returns false on EOF\r
+bool parseConfigObject(std::istream &is)\r
+{\r
+       // float g_wanted_fps\r
+       // s16 g_viewing_range_nodes_max\r
+\r
+       if(is.eof())\r
+               return false;\r
+       \r
+       std::string line;\r
+       std::getline(is, line);\r
+       //dstream<<"got line: \""<<line<<"\""<<std::endl;\r
+\r
+       std::string trimmedline = trim(line);\r
+       \r
+       // Ignore comments\r
+       if(trimmedline[0] == '#')\r
+               return true;\r
+\r
+       //dstream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;\r
+\r
+       Strfnd sf(trim(line));\r
+\r
+       std::string name = sf.next("=");\r
+       name = trim(name);\r
+\r
+       if(name == "")\r
+               return true;\r
+       \r
+       std::string value = sf.next("\n");\r
+       value = trim(value);\r
+\r
+       dstream<<"Config name=\""<<name<<"\" value=\""\r
+                       <<value<<"\""<<std::endl;\r
+       \r
+       if(name == "dedicated_server")\r
+               g_dedicated_server = value;\r
+       \r
+       // Client stuff\r
+       else if(name == "wanted_fps")\r
+       {\r
+               g_wanted_fps = atof(value.c_str());\r
+       }\r
+       else if(name == "fps_max")\r
+       {\r
+               g_fps_max = atof(value.c_str());\r
+       }\r
+       else if(name == "viewing_range_nodes_max")\r
+       {\r
+               s32 v = atoi(value.c_str());\r
+               if(v < 0)\r
+                       v = 0;\r
+               if(v > 32767)\r
+                       v = 32767;\r
+               g_viewing_range_nodes_max = v;\r
+       }\r
+       else if(name == "viewing_range_nodes_min")\r
+       {\r
+               s32 v = atoi(value.c_str());\r
+               if(v < 0)\r
+                       v = 0;\r
+               if(v > 32767)\r
+                       v = 32767;\r
+               g_viewing_range_nodes_min = v;\r
+       }\r
+       else if(name=="screenW")\r
+               g_screenW = value;\r
+       else if(name=="screenH")\r
+               g_screenH = value;\r
+       else if(name == "host_game")\r
+               g_host_game = value;\r
+       else if(name == "port")\r
+               g_port = value;\r
+       else if(name == "address")\r
+               g_address = value;\r
+       else if(name == "name")\r
+               g_name = value;\r
+       else if(name == "random_input")\r
+               g_random_input = is_yes(value);\r
+       else if(name == "client_delete_unused_sectors_timeout")\r
+       {\r
+               std::istringstream vis(value);\r
+               //vis.imbue(std::locale("C"));\r
+               vis>>g_client_delete_unused_sectors_timeout;\r
+       }\r
+       \r
+       // Server stuff\r
+       else if(name == "creative_mode")\r
+               g_creative_mode = is_yes(value);\r
+       else if(name == "mapgen_heightmap_blocksize")\r
+       {\r
+               s32 d = atoi(value.c_str());\r
+               if(d > 0 && (d & (d-1)) == 0)\r
+                       g_mapgen_params.heightmap_blocksize = d;\r
+               else\r
+                       dstream<<"Invalid value in config file: \""\r
+                                       <<line<<"\""<<std::endl;\r
+       }\r
+       else if(name == "mapgen_height_randmax")\r
+               g_mapgen_params.height_randmax = value;\r
+       else if(name == "mapgen_height_randfactor")\r
+               g_mapgen_params.height_randfactor = value;\r
+       else if(name == "mapgen_height_base")\r
+               g_mapgen_params.height_base = value;\r
+       else if(name == "mapgen_plants_amount")\r
+               g_mapgen_params.plants_amount = value;\r
+       \r
+       else\r
+       {\r
+               dstream<<"Unknown option in config file: \""\r
+                               <<line<<"\""<<std::endl;\r
+       }\r
+\r
+       return true;\r
+}\r
+\r
+// Returns true on success\r
+bool readConfigFile(const char *filename)\r
+{\r
+       std::ifstream is(filename);\r
+       if(is.good() == false)\r
+       {\r
+               dstream<<DTIME<<"Error opening configuration file: "\r
+                               <<filename<<std::endl;\r
+               return false;\r
+       }\r
+\r
+       dstream<<DTIME<<"Parsing configuration file: "\r
+                       <<filename<<std::endl;\r
+                       \r
+       while(parseConfigObject(is));\r
+       \r
+       return true;\r
+}\r
+\r
+/*\r
+       Timestamp stuff\r
+*/\r
+\r
+JMutex g_timestamp_mutex;\r
+//std::string g_timestamp;\r
+\r
+std::string getTimestamp()\r
+{\r
+       if(g_timestamp_mutex.IsInitialized()==false)\r
+               return "";\r
+       JMutexAutoLock lock(g_timestamp_mutex);\r
+       //return g_timestamp;\r
+       time_t t = time(NULL);\r
+       struct tm *tm = localtime(&t);\r
+       char cs[20];\r
+       strftime(cs, 20, "%H:%M:%S", tm);\r
+       return cs;\r
+}\r
+\r
+class MyEventReceiver : public IEventReceiver\r
+{\r
+public:\r
+       // This is the one method that we have to implement\r
+       virtual bool OnEvent(const SEvent& event)\r
+       {\r
+               // Remember whether each key is down or up\r
+               if(event.EventType == irr::EET_KEY_INPUT_EVENT)\r
+               {\r
+                       keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;\r
+\r
+                       if(event.KeyInput.PressedDown)\r
+                       {\r
+                               //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;\r
+                               if(g_game_focused == false)\r
+                               {\r
+                                       s16 key = event.KeyInput.Key;\r
+                                       if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)\r
+                                       {\r
+                                               g_text_buffer_accepted = true;\r
+                                       }\r
+                                       else if(key == irr::KEY_BACK)\r
+                                       {\r
+                                               if(g_text_buffer.size() > 0)\r
+                                                       g_text_buffer = g_text_buffer.substr\r
+                                                                       (0, g_text_buffer.size()-1);\r
+                                       }\r
+                                       else\r
+                                       {\r
+                                               wchar_t wc = event.KeyInput.Char;\r
+                                               if(wc != 0)\r
+                                                       g_text_buffer += wc;\r
+                                       }\r
+                               }\r
+                               \r
+                               if(event.KeyInput.Key == irr::KEY_ESCAPE)\r
+                               {\r
+                                       if(g_game_focused == true)\r
+                                       {\r
+                                               dstream<<DTIME<<"ESC pressed"<<std::endl;\r
+                                               g_esc_pressed = true;\r
+                                       }\r
+                               }\r
+\r
+                               // Material selection\r
+                               if(event.KeyInput.Key == irr::KEY_KEY_F)\r
+                               {\r
+                                       if(g_game_focused == true)\r
+                                       {\r
+                                               if(g_selected_item < PLAYER_INVENTORY_SIZE-1)\r
+                                                       g_selected_item++;\r
+                                               else\r
+                                                       g_selected_item = 0;\r
+                                               dstream<<DTIME<<"Selected item: "\r
+                                                               <<g_selected_item<<std::endl;\r
+                                       }\r
+                               }\r
+\r
+                               // Viewing range selection\r
+                               if(event.KeyInput.Key == irr::KEY_KEY_R\r
+                                               && g_game_focused)\r
+                               {\r
+                                       JMutexAutoLock lock(g_range_mutex);\r
+                                       if(g_viewing_range_all)\r
+                                       {\r
+                                               g_viewing_range_all = false;\r
+                                               dstream<<DTIME<<"Disabled full viewing range"<<std::endl;\r
+                                       }\r
+                                       else\r
+                                       {\r
+                                               g_viewing_range_all = true;\r
+                                               dstream<<DTIME<<"Enabled full viewing range"<<std::endl;\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+\r
+               if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)\r
+               {\r
+                       if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)\r
+                       {\r
+                               leftclicked = true;\r
+                       }\r
+                       if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)\r
+                       {\r
+                               rightclicked = true;\r
+                       }\r
+                       if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)\r
+                       {\r
+                               /*dstream<<"event.MouseInput.Wheel="\r
+                                               <<event.MouseInput.Wheel<<std::endl;*/\r
+                               if(event.MouseInput.Wheel < 0)\r
+                               {\r
+                                       if(g_selected_item < PLAYER_INVENTORY_SIZE-1)\r
+                                               g_selected_item++;\r
+                                       else\r
+                                               g_selected_item = 0;\r
+                               }\r
+                               else if(event.MouseInput.Wheel > 0)\r
+                               {\r
+                                       if(g_selected_item > 0)\r
+                                               g_selected_item--;\r
+                                       else\r
+                                               g_selected_item = PLAYER_INVENTORY_SIZE-1;\r
+                               }\r
+                       }\r
+               }\r
+\r
+               return false;\r
+       }\r
+\r
+       // This is used to check whether a key is being held down\r
+       virtual bool IsKeyDown(EKEY_CODE keyCode) const\r
+       {\r
+               return keyIsDown[keyCode];\r
+       }\r
+\r
+       MyEventReceiver()\r
+       {\r
+               for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)\r
+                               keyIsDown[i] = false;\r
+               leftclicked = false;\r
+               rightclicked = false;\r
+       }\r
+\r
+       bool leftclicked;\r
+       bool rightclicked;\r
+private:\r
+       // We use this array to store the current state of each key\r
+       bool keyIsDown[KEY_KEY_CODES_COUNT];\r
+       //s32 mouseX;\r
+       //s32 mouseY;\r
+};\r
+\r
+class InputHandler\r
+{\r
+public:\r
+       InputHandler()\r
+       {\r
+       }\r
+       virtual ~InputHandler()\r
+       {\r
+       }\r
+       virtual bool isKeyDown(EKEY_CODE keyCode) = 0;\r
+       virtual v2s32 getMousePos() = 0;\r
+       virtual void setMousePos(s32 x, s32 y) = 0;\r
+       virtual bool getLeftClicked() = 0;\r
+       virtual bool getRightClicked() = 0;\r
+       virtual void resetLeftClicked() = 0;\r
+       virtual void resetRightClicked() = 0;\r
+       \r
+       virtual void step(float dtime) {};\r
+\r
+       virtual void clear() {};\r
+};\r
+\r
+InputHandler *g_input = NULL;\r
+\r
+void focusGame()\r
+{\r
+       g_input->clear();\r
+       g_game_focused = true;\r
+}\r
+\r
+void unFocusGame()\r
+{\r
+       g_game_focused = false;\r
+}\r
+\r
+class RealInputHandler : public InputHandler\r
+{\r
+public:\r
+       RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):\r
+               m_device(device),\r
+               m_receiver(receiver)\r
+       {\r
+       }\r
+       virtual bool isKeyDown(EKEY_CODE keyCode)\r
+       {\r
+               return m_receiver->IsKeyDown(keyCode);\r
+       }\r
+       virtual v2s32 getMousePos()\r
+       {\r
+               return m_device->getCursorControl()->getPosition();\r
+       }\r
+       virtual void setMousePos(s32 x, s32 y)\r
+       {\r
+               m_device->getCursorControl()->setPosition(x, y);\r
+       }\r
+\r
+       virtual bool getLeftClicked()\r
+       {\r
+               if(g_game_focused == false)\r
+                       return false;\r
+               return m_receiver->leftclicked;\r
+       }\r
+       virtual bool getRightClicked()\r
+       {\r
+               if(g_game_focused == false)\r
+                       return false;\r
+               return m_receiver->rightclicked;\r
+       }\r
+       virtual void resetLeftClicked()\r
+       {\r
+               m_receiver->leftclicked = false;\r
+       }\r
+       virtual void resetRightClicked()\r
+       {\r
+               m_receiver->rightclicked = false;\r
+       }\r
+\r
+       void clear()\r
+       {\r
+               resetRightClicked();\r
+               resetLeftClicked();\r
+       }\r
+private:\r
+       IrrlichtDevice *m_device;\r
+       MyEventReceiver *m_receiver;\r
+};\r
+\r
+class RandomInputHandler : public InputHandler\r
+{\r
+public:\r
+       RandomInputHandler()\r
+       {\r
+               leftclicked = false;\r
+               rightclicked = false;\r
+               for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)\r
+                       keydown[i] = false;\r
+       }\r
+       virtual bool isKeyDown(EKEY_CODE keyCode)\r
+       {\r
+               return keydown[keyCode];\r
+       }\r
+       virtual v2s32 getMousePos()\r
+       {\r
+               return mousepos;\r
+       }\r
+       virtual void setMousePos(s32 x, s32 y)\r
+       {\r
+               mousepos = v2s32(x,y);\r
+       }\r
+\r
+       virtual bool getLeftClicked()\r
+       {\r
+               return leftclicked;\r
+       }\r
+       virtual bool getRightClicked()\r
+       {\r
+               return rightclicked;\r
+       }\r
+       virtual void resetLeftClicked()\r
+       {\r
+               leftclicked = false;\r
+       }\r
+       virtual void resetRightClicked()\r
+       {\r
+               rightclicked = false;\r
+       }\r
+\r
+       virtual void step(float dtime)\r
+       {\r
+               {\r
+                       static float counter1 = 0;\r
+                       counter1 -= dtime;\r
+                       if(counter1 < 0.0)\r
+                       {\r
+                               counter1 = 0.1*Rand(1,10);\r
+                               /*if(g_selected_material < USEFUL_MATERIAL_COUNT-1)\r
+                                       g_selected_material++;\r
+                               else\r
+                                       g_selected_material = 0;*/\r
+                               if(g_selected_item < PLAYER_INVENTORY_SIZE-1)\r
+                                       g_selected_item++;\r
+                               else\r
+                                       g_selected_item = 0;\r
+                       }\r
+               }\r
+               {\r
+                       static float counter1 = 0;\r
+                       counter1 -= dtime;\r
+                       if(counter1 < 0.0)\r
+                       {\r
+                               counter1 = 0.1*Rand(1, 40);\r
+                               keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];\r
+                       }\r
+               }\r
+               {\r
+                       static float counter1 = 0;\r
+                       counter1 -= dtime;\r
+                       if(counter1 < 0.0)\r
+                       {\r
+                               counter1 = 0.1*Rand(1, 40);\r
+                               keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];\r
+                       }\r
+               }\r
+               {\r
+                       static float counter1 = 0;\r
+                       counter1 -= dtime;\r
+                       if(counter1 < 0.0)\r
+                       {\r
+                               counter1 = 0.1*Rand(1, 40);\r
+                               keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];\r
+                       }\r
+               }\r
+               {\r
+                       static float counter1 = 0;\r
+                       counter1 -= dtime;\r
+                       if(counter1 < 0.0)\r
+                       {\r
+                               counter1 = 0.1*Rand(1, 40);\r
+                               keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];\r
+                       }\r
+               }\r
+               {\r
+                       static float counter1 = 0;\r
+                       counter1 -= dtime;\r
+                       if(counter1 < 0.0)\r
+                       {\r
+                               counter1 = 0.1*Rand(1, 20);\r
+                               mousespeed = v2s32(Rand(-20,20), Rand(-15,20));\r
+                       }\r
+               }\r
+               {\r
+                       static float counter1 = 0;\r
+                       counter1 -= dtime;\r
+                       if(counter1 < 0.0)\r
+                       {\r
+                               counter1 = 0.1*Rand(1, 30);\r
+                               leftclicked = true;\r
+                       }\r
+               }\r
+               {\r
+                       static float counter1 = 0;\r
+                       counter1 -= dtime;\r
+                       if(counter1 < 0.0)\r
+                       {\r
+                               counter1 = 0.1*Rand(1, 20);\r
+                               rightclicked = true;\r
+                       }\r
+               }\r
+               mousepos += mousespeed;\r
+       }\r
+\r
+       s32 Rand(s32 min, s32 max)\r
+       {\r
+               return (rand()%(max-min+1))+min;\r
+       }\r
+private:\r
+       bool keydown[KEY_KEY_CODES_COUNT];\r
+       v2s32 mousepos;\r
+       v2s32 mousespeed;\r
+       bool leftclicked;\r
+       bool rightclicked;\r
+};\r
+\r
+void updateViewingRange(f32 frametime, Client *client)\r
+{\r
+       // Range_all messes up frametime_avg\r
+       if(g_viewing_range_all == true)\r
+               return;\r
+       \r
+       // Initialize to the target value\r
+       static float frametime_avg = 1.0/g_wanted_fps;\r
+       frametime_avg = frametime_avg * 0.9 + frametime * 0.1;\r
+\r
+       static f32 counter = 0;\r
+       if(counter > 0){\r
+               counter -= frametime;\r
+               return;\r
+       }\r
+       //counter = 1.0; //seconds\r
+       counter = 0.5; //seconds\r
+\r
+       //float freetime_ratio = 0.2;\r
+       //float freetime_ratio = 0.4;\r
+       float freetime_ratio = FREETIME_RATIO;\r
+\r
+       float frametime_wanted = (1.0/(g_wanted_fps/(1.0-freetime_ratio)));\r
+\r
+       float fraction = sqrt(frametime_avg / frametime_wanted);\r
+\r
+       static bool fraction_is_good = false;\r
+       \r
+       float fraction_good_threshold = 0.1;\r
+       float fraction_bad_threshold = 0.25;\r
+       float fraction_limit;\r
+       // Use high limit if fraction is good AND the fraction would\r
+       // lower the range. We want to keep the range fairly high.\r
+       if(fraction_is_good && fraction > 1.0)\r
+               fraction_limit = fraction_bad_threshold;\r
+       else\r
+               fraction_limit = fraction_good_threshold;\r
+\r
+       if(fabs(fraction - 1.0) < fraction_limit)\r
+       {\r
+               fraction_is_good = true;\r
+               return;\r
+       }\r
+       else\r
+       {\r
+               fraction_is_good = false;\r
+       }\r
+\r
+       //dstream<<"frametime_avg="<<frametime_avg<<std::endl;\r
+       //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;\r
+       /*dstream<<"fetching="<<client->isFetchingBlocks()\r
+                       <<" faction = "<<fraction<<std::endl;*/\r
+\r
+       JMutexAutoLock lock(g_range_mutex);\r
+       \r
+       s16 n = (float)g_viewing_range_nodes / fraction;\r
+       if(n < g_viewing_range_nodes_min)\r
+               n = g_viewing_range_nodes_min;\r
+       if(n > g_viewing_range_nodes_max)\r
+               n = g_viewing_range_nodes_max;\r
+\r
+       bool can_change = true;\r
+\r
+       if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)\r
+               can_change = false;\r
+       \r
+       if(can_change)\r
+               g_viewing_range_nodes = n;\r
+\r
+       /*dstream<<"g_viewing_range_nodes = "\r
+                       <<g_viewing_range_nodes<<std::endl;*/\r
+}\r
+\r
+class GUIQuickInventory : public IEventReceiver\r
+{\r
+public:\r
+       GUIQuickInventory(\r
+                       gui::IGUIEnvironment* env,\r
+                       gui::IGUIElement* parent,\r
+                       v2s32 pos,\r
+                       s32 itemcount,\r
+                       Inventory *inventory):\r
+               m_itemcount(itemcount),\r
+               m_inventory(inventory)\r
+       {\r
+               core::rect<s32> imgsize(0,0,48,48);\r
+               core::rect<s32> textsize(0,0,48,16);\r
+               v2s32 spacing(0, 64);\r
+               for(s32 i=0; i<m_itemcount; i++)\r
+               {\r
+                       m_images.push_back(env->addImage(\r
+                               imgsize + pos + spacing*i\r
+                       ));\r
+                       m_images[i]->setScaleImage(true);\r
+                       m_texts.push_back(env->addStaticText(\r
+                               L"",\r
+                               textsize + pos + spacing*i,\r
+                               false, false\r
+                       ));\r
+                       m_texts[i]->setBackgroundColor(\r
+                                       video::SColor(128,0,0,0));\r
+                       m_texts[i]->setTextAlignment(\r
+                                       gui::EGUIA_CENTER,\r
+                                       gui::EGUIA_UPPERLEFT);\r
+               }\r
+       }\r
+\r
+       virtual bool OnEvent(const SEvent& event)\r
+       {\r
+               return false;\r
+       }\r
+\r
+       void setSelection(s32 i)\r
+       {\r
+               m_selection = i;\r
+       }\r
+\r
+       void update()\r
+       {\r
+               s32 start = 0;\r
+\r
+               start = m_selection - m_itemcount / 2;\r
+\r
+               for(s32 i=0; i<m_itemcount; i++)\r
+               {\r
+                       s32 j = i + start;\r
+\r
+                       if(j > (s32)m_inventory->getSize() - 1)\r
+                               j -= m_inventory->getSize();\r
+                       if(j < 0)\r
+                               j += m_inventory->getSize();\r
+                       \r
+                       InventoryItem *item = m_inventory->getItem(j);\r
+                       // Null items\r
+                       if(item == NULL)\r
+                       {\r
+                               m_images[i]->setImage(NULL);\r
+\r
+                               wchar_t t[10];\r
+                               if(m_selection == j)\r
+                                       swprintf(t, 10, L"<-");\r
+                               else\r
+                                       swprintf(t, 10, L"");\r
+                               m_texts[i]->setText(t);\r
+\r
+                               // The next ifs will segfault with a NULL pointer\r
+                               continue;\r
+                       }\r
+                       \r
+                       \r
+                       m_images[i]->setImage(item->getImage());\r
+                       \r
+                       wchar_t t[10];\r
+                       if(m_selection == j)\r
+                               swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());\r
+                       else\r
+                               swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());\r
+                       m_texts[i]->setText(t);\r
+               }\r
+       }\r
+\r
+private:\r
+       s32 m_itemcount;\r
+       core::array<gui::IGUIStaticText*> m_texts;\r
+       core::array<gui::IGUIImage*> m_images;\r
+       Inventory *m_inventory;\r
+       s32 m_selection;\r
+};\r
+\r
+int main(int argc, char *argv[])\r
+{\r
+       /*\r
+               Low-level initialization\r
+       */\r
+\r
+       bool disable_stderr = false;\r
+#ifdef _WIN32\r
+       disable_stderr = true;\r
+#endif\r
+\r
+       debugstreams_init(disable_stderr, DEBUGFILE);\r
+       debug_stacks_init();\r
+\r
+\r
+       DSTACK(__FUNCTION_NAME);\r
+\r
+       try\r
+       {\r
+       \r
+       /*\r
+               Basic initialization\r
+       */\r
+\r
+       dstream<<DTIME<<"minetest-c55"\r
+                       " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST\r
+                       <<", ENABLE_TESTS="<<ENABLE_TESTS\r
+                       <<std::endl;\r
+       \r
+       // Set locale. This is for forcing '.' as the decimal point.\r
+       std::locale::global(std::locale("C"));\r
+       // This enables printing all characters in bitmap font\r
+       setlocale(LC_CTYPE, "en_US");\r
+\r
+       // Initialize sockets\r
+       sockets_init();\r
+       atexit(sockets_cleanup);\r
+       \r
+       // Initialize timestamp mutex\r
+       g_timestamp_mutex.Init();\r
+\r
+       /*\r
+               Run unit tests\r
+       */\r
+       if(ENABLE_TESTS)\r
+       {\r
+               run_tests();\r
+       }\r
+       \r
+       /*\r
+               Initialization\r
+       */\r
+\r
+       // Read config file\r
+       \r
+       if(argc >= 2)\r
+       {\r
+               readConfigFile(argv[1]);\r
+       }\r
+       else\r
+       {\r
+               const char *filenames[2] =\r
+               {\r
+                       "../minetest.conf",\r
+                       "../../minetest.conf"\r
+               };\r
+\r
+               for(u32 i=0; i<2; i++)\r
+               {\r
+                       bool r = readConfigFile(filenames[i]);\r
+                       if(r)\r
+                               break;\r
+               }\r
+       }\r
+\r
+       // Initialize random seed\r
+       srand(time(0));\r
+\r
+       g_range_mutex.Init();\r
+       assert(g_range_mutex.IsInitialized());\r
+\r
+       /*\r
+               Ask some stuff\r
+       */\r
+\r
+       std::cout<<std::endl<<std::endl;\r
+       char templine[100];\r
+       \r
+       std::cout<<"Dedicated server? [y = yes]: ";\r
+       if(g_dedicated_server != "")\r
+       {\r
+               std::cout<<g_dedicated_server<<std::endl;\r
+               snprintf(templine, 100, "%s", g_dedicated_server.c_str());\r
+       }\r
+       else\r
+       {\r
+               std::cin.getline(templine, 100);\r
+       }\r
+\r
+       bool dedicated = false;\r
+       if(templine[0] == 'y')\r
+               dedicated = true;\r
+       if(dedicated)\r
+               std::cout<<"-> yes"<<std::endl;\r
+       else\r
+               std::cout<<"-> no"<<std::endl;\r
+       \r
+       std::cout<<"Port [empty=30000]: ";\r
+       if(g_port != "")\r
+       {\r
+               std::cout<<g_port<<std::endl;\r
+               snprintf(templine, 100, "%s", g_port.c_str());\r
+       }\r
+       else\r
+       {\r
+               std::cin.getline(templine, 100);\r
+       }\r
+       unsigned short port;\r
+       if(templine[0] == 0)\r
+               port = 30000;\r
+       else\r
+               port = atoi(templine);\r
+       std::cout<<"-> "<<port<<std::endl;\r
+       \r
+       if(dedicated)\r
+       {\r
+               DSTACK("Dedicated server branch");\r
+               \r
+               std::cout<<std::endl;\r
+               std::cout<<"========================"<<std::endl;\r
+               std::cout<<"Running dedicated server"<<std::endl;\r
+               std::cout<<"========================"<<std::endl;\r
+               std::cout<<std::endl;\r
+\r
+               Server server("../map", g_creative_mode, g_mapgen_params);\r
+               server.start(port);\r
+       \r
+               for(;;)\r
+               {\r
+                       // This is kind of a hack but can be done like this\r
+                       // because server.step() is very light\r
+                       sleep_ms(100);\r
+                       server.step(0.1);\r
+\r
+                       static int counter = 0;\r
+                       counter--;\r
+                       if(counter <= 0)\r
+                       {\r
+                               counter = 10;\r
+\r
+                               core::list<PlayerInfo> list = server.getPlayerInfo();\r
+                               core::list<PlayerInfo>::Iterator i;\r
+                               static u32 sum_old = 0;\r
+                               u32 sum = PIChecksum(list);\r
+                               if(sum != sum_old)\r
+                               {\r
+                                       std::cout<<DTIME<<"Player info:"<<std::endl;\r
+                                       for(i=list.begin(); i!=list.end(); i++)\r
+                                       {\r
+                                               i->PrintLine(&std::cout);\r
+                                       }\r
+                               }\r
+                               sum_old = sum;\r
+                       }\r
+               }\r
+\r
+               return 0;\r
+       }\r
+\r
+       bool hosting = false;\r
+       char connect_name[100] = "";\r
+\r
+       std::cout<<"Address to connect to [empty = host a game]: ";\r
+       if(g_address != "" && is_yes(g_host_game) == false)\r
+       {\r
+               std::cout<<g_address<<std::endl;\r
+               snprintf(connect_name, 100, "%s", g_address.c_str());\r
+       }\r
+       else\r
+       {\r
+               std::cin.getline(connect_name, 100);\r
+       }\r
+       \r
+       if(connect_name[0] == 0){\r
+               snprintf(connect_name, 100, "127.0.0.1");\r
+               hosting = true;\r
+       }\r
+       \r
+       if(hosting)\r
+               std::cout<<"-> hosting"<<std::endl;\r
+       else\r
+               std::cout<<"-> "<<connect_name<<std::endl;\r
+       \r
+       char playername[PLAYERNAME_SIZE] = "";\r
+       if(g_name != "")\r
+       {\r
+               snprintf(playername, PLAYERNAME_SIZE, "%s", g_name.c_str());\r
+       }\r
+       else\r
+       {\r
+               std::cout<<"Name of player: ";\r
+               std::cin.getline(playername, PLAYERNAME_SIZE);\r
+       }\r
+       std::cout<<"-> \""<<playername<<"\""<<std::endl;\r
+\r
+       /*\r
+               Resolution selection\r
+       */\r
+\r
+       u16 screenW;\r
+       u16 screenH;\r
+       bool fullscreen = false;\r
+       \r
+       if(g_screenW != "" && g_screenH != "")\r
+       {\r
+               screenW = atoi(g_screenW.c_str());\r
+               screenH = atoi(g_screenH.c_str());\r
+       }\r
+       else\r
+       {\r
+               u16 resolutions[][3] = {\r
+                       //W, H, fullscreen\r
+                       {640,480, 0},\r
+                       {800,600, 0},\r
+                       {1024,768, 0},\r
+                       {1280,1024, 0},\r
+                       /*{640,480, 1},\r
+                       {800,600, 1},\r
+                       {1024,768, 1},\r
+                       {1280,1024, 1},*/\r
+               };\r
+\r
+               u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);\r
+               \r
+               for(u16 i=0; i<res_count; i++)\r
+               {\r
+                       std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"\r
+                                       <<resolutions[i][1];\r
+                       if(resolutions[i][2])\r
+                               std::cout<<" fullscreen"<<std::endl;\r
+                       else\r
+                               std::cout<<" windowed"<<std::endl;\r
+               }\r
+               std::cout<<"Select a window resolution number [empty = 2]: ";\r
+               std::cin.getline(templine, 100);\r
+\r
+               u16 r0;\r
+               if(templine[0] == 0)\r
+                       r0 = 2;\r
+               else\r
+                       r0 = atoi(templine);\r
+\r
+               if(r0 > res_count || r0 == 0)\r
+                       r0 = 2;\r
+               \r
+               {\r
+                       u16 i = r0-1;\r
+                       std::cout<<"-> ";\r
+                       std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"\r
+                                       <<resolutions[i][1];\r
+                       if(resolutions[i][2])\r
+                               std::cout<<" fullscreen"<<std::endl;\r
+                       else\r
+                               std::cout<<" windowed"<<std::endl;\r
+               }\r
+\r
+               screenW = resolutions[r0-1][0];\r
+               screenH = resolutions[r0-1][1];\r
+               fullscreen = resolutions[r0-1][2];\r
+       }\r
+\r
+       //\r
+\r
+       MyEventReceiver receiver;\r
+\r
+       video::E_DRIVER_TYPE driverType;\r
+\r
+#ifdef _WIN32\r
+       //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work\r
+       driverType = video::EDT_OPENGL;\r
+#else\r
+       driverType = video::EDT_OPENGL;\r
+#endif\r
+\r
+       // create device and exit if creation failed\r
+\r
+       IrrlichtDevice *device;\r
+       device = createDevice(driverType,\r
+                       core::dimension2d<u32>(screenW, screenH),\r
+                       16, fullscreen, false, false, &receiver);\r
+       // With vsync\r
+       /*device = createDevice(driverType,\r
+                       core::dimension2d<u32>(screenW, screenH),\r
+                       16, fullscreen, false, true, &receiver);*/\r
+\r
+       if (device == 0)\r
+               return 1; // could not create selected driver.\r
+\r
+       g_device = device;\r
+       \r
+       device->setResizable(true);\r
+\r
+       if(g_random_input)\r
+               g_input = new RandomInputHandler();\r
+       else\r
+               g_input = new RealInputHandler(device, &receiver);\r
+       \r
+       /*\r
+               Continue initialization\r
+       */\r
+\r
+       video::IVideoDriver* driver = device->getVideoDriver();\r
+       // These make the textures not to show at all\r
+       //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);\r
+       //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );\r
+\r
+       //driver->setMinHardwareBufferVertexCount(1);\r
+\r
+       scene::ISceneManager* smgr = device->getSceneManager();\r
+\r
+       gui::IGUIEnvironment* guienv = device->getGUIEnvironment();\r
+       gui::IGUISkin* skin = guienv->getSkin();\r
+       gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");\r
+       if(font)\r
+               skin->setFont(font);\r
+       //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));\r
+       skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));\r
+       //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));\r
+       //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));\r
+       skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));\r
+       skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));\r
+       \r
+       const wchar_t *text = L"Loading and connecting...";\r
+       core::vector2d<s32> center(screenW/2, screenH/2);\r
+       core::dimension2d<u32> textd = font->getDimension(text);\r
+       std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;\r
+       // Have to add a bit to disable the text from word wrapping\r
+       //core::vector2d<s32> textsize(textd.Width+4, textd.Height);\r
+       core::vector2d<s32> textsize(300, textd.Height);\r
+       core::rect<s32> textrect(center - textsize/2, center + textsize/2);\r
+\r
+       gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(\r
+                       text, textrect, false, false);\r
+       gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);\r
+\r
+       driver->beginScene(true, true, video::SColor(255,0,0,0));\r
+       guienv->drawAll();\r
+       driver->endScene();\r
+\r
+       /*\r
+               Initialize material array\r
+       */\r
+\r
+       //video::SMaterial g_materials[MATERIALS_COUNT];\r
+       for(u16 i=0; i<MATERIALS_COUNT; i++)\r
+       {\r
+               g_materials[i].Lighting = false;\r
+               g_materials[i].BackfaceCulling = false;\r
+\r
+               const char *filename = g_material_filenames[i];\r
+               if(filename != NULL){\r
+                       video::ITexture *t = driver->getTexture(filename);\r
+                       if(t == NULL){\r
+                               std::cout<<DTIME<<"Texture could not be loaded: \""\r
+                                               <<filename<<"\""<<std::endl;\r
+                               return 1;\r
+                       }\r
+                       g_materials[i].setTexture(0, driver->getTexture(filename));\r
+               }\r
+               //g_materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT);\r
+               g_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);\r
+               //g_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);\r
+               //g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);\r
+               if(i == MATERIAL_WATER)\r
+               {\r
+                       g_materials[i].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;\r
+                       //g_materials[i].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;\r
+               }\r
+       }\r
+\r
+       /*g_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));\r
+       g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));\r
+       g_mesh_materials[2].setTexture(0, driver->getTexture("../data/stone.png"));\r
+       for(u32 i=0; i<3; i++)\r
+       {\r
+               g_mesh_materials[i].Lighting = false;\r
+               g_mesh_materials[i].BackfaceCulling = false;\r
+               g_mesh_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);\r
+               g_mesh_materials[i].setFlag(video::EMF_FOG_ENABLE, true);\r
+       }*/\r
+\r
+       // Make a scope here for the client so that it gets removed\r
+       // before the irrlicht device\r
+       {\r
+\r
+       std::cout<<DTIME<<"Creating server and client"<<std::endl;\r
+       \r
+       /*\r
+               Create server\r
+       */\r
+       SharedPtr<Server> server;\r
+       if(hosting){\r
+               server = new Server("../map", g_creative_mode, g_mapgen_params);\r
+               server->start(port);\r
+       }\r
+       \r
+       /*\r
+               Create client\r
+       */\r
+\r
+       // TODO: Get rid of the g_materials parameter or it's globalness\r
+       Client client(device, g_materials,\r
+                       g_client_delete_unused_sectors_timeout,\r
+                       playername);\r
+       \r
+       Address connect_address(0,0,0,0, port);\r
+       try{\r
+               connect_address.Resolve(connect_name);\r
+       }\r
+       catch(ResolveError &e)\r
+       {\r
+               std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;\r
+               return 0;\r
+       }\r
+       \r
+       std::cout<<DTIME<<"Connecting to server..."<<std::endl;\r
+       client.connect(connect_address);\r
+       \r
+       try{\r
+               while(client.connectedAndInitialized() == false)\r
+               {\r
+                       client.step(0.1);\r
+                       if(server != NULL){\r
+                               server->step(0.1);\r
+                       }\r
+                       sleep_ms(100);\r
+               }\r
+       }\r
+       catch(con::PeerNotFoundException &e)\r
+       {\r
+               std::cout<<DTIME<<"Timed out."<<std::endl;\r
+               return 0;\r
+       }\r
+       \r
+       /*\r
+               Create the camera node\r
+       */\r
+\r
+       scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(\r
+               0, // Camera parent\r
+               v3f(BS*100, BS*2, BS*100), // Look from\r
+               v3f(BS*100+1, BS*2, BS*100), // Look to\r
+               -1 // Camera ID\r
+       );\r
+\r
+       if(camera == NULL)\r
+               return 1;\r
+       \r
+       video::SColor skycolor = video::SColor(255,90,140,200);\r
+\r
+       camera->setFOV(FOV_ANGLE);\r
+\r
+       // Just so big a value that everything rendered is visible\r
+       camera->setFarValue(100000*BS);\r
+\r
+       /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;\r
+       f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;\r
+       \r
+       camera->setFarValue(range);\r
+       \r
+       driver->setFog(\r
+               skycolor,\r
+               video::EFT_FOG_LINEAR,\r
+               range*0.8,\r
+               range,\r
+               0.01,\r
+               false,\r
+               false\r
+               );*/\r
+       \r
+       f32 camera_yaw = 0; // "right/left"\r
+       f32 camera_pitch = 0; // "up/down"\r
+       \r
+       gui_loadingtext->remove();\r
+\r
+       /*\r
+               Add some gui stuff\r
+       */\r
+       \r
+       // First line of debug text\r
+       gui::IGUIStaticText *guitext = guienv->addStaticText(\r
+                       L"Minetest-c55",\r
+                       core::rect<s32>(5, 5, 5+600, 5+textsize.Y),\r
+                       false, false);\r
+       // Second line of debug text\r
+       gui::IGUIStaticText *guitext2 = guienv->addStaticText(\r
+                       L"",\r
+                       core::rect<s32>(5, 5+(textsize.Y+5)*1, 5+600, (5+textsize.Y)*2),\r
+                       false, false);\r
+       \r
+       // At the middle of the screen\r
+       // Object infos are shown in this\r
+       gui::IGUIStaticText *guitext_info = guienv->addStaticText(\r
+                       L"test",\r
+                       core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),\r
+                       false, false);\r
+       \r
+       // This is a copy of the inventory that the client's environment has\r
+       Inventory local_inventory(PLAYER_INVENTORY_SIZE);\r
+       \r
+       GUIQuickInventory *quick_inventory = new GUIQuickInventory\r
+                       (guienv, NULL, v2s32(10, 70), 5, &local_inventory);\r
+       \r
+       /*\r
+               Some statistics are collected in these\r
+       */\r
+       u32 drawtime = 0;\r
+       u32 scenetime = 0;\r
+       u32 endscenetime = 0;\r
+\r
+       /*\r
+               Text input system\r
+       */\r
+       \r
+       struct TextDest\r
+       {\r
+               virtual void sendText(std::string text) = 0;\r
+       };\r
+       \r
+       struct TextDestSign : public TextDest\r
+       {\r
+               TextDestSign(v3s16 blockpos, s16 id, Client *client)\r
+               {\r
+                       m_blockpos = blockpos;\r
+                       m_id = id;\r
+                       m_client = client;\r
+               }\r
+               void sendText(std::string text)\r
+               {\r
+                       dstream<<"Changing text of a sign object: "\r
+                                       <<text<<std::endl;\r
+                       m_client->sendSignText(m_blockpos, m_id, text);\r
+               }\r
+\r
+               v3s16 m_blockpos;\r
+               s16 m_id;\r
+               Client *m_client;\r
+       };\r
+\r
+       TextDest *textbuf_dest = NULL;\r
+       \r
+       //gui::IGUIWindow* input_window = NULL;\r
+       gui::IGUIStaticText* input_guitext = NULL;\r
+\r
+       /*\r
+               Main loop\r
+       */\r
+\r
+       bool first_loop_after_window_activation = true;\r
+\r
+       // Time is in milliseconds\r
+       // NOTE: getRealTime() without run()s causes strange problems in wine\r
+       // NOTE: Have to call run() between calls of this to update the timer\r
+       u32 lasttime = device->getTimer()->getTime();\r
+\r
+       while(device->run())\r
+       {\r
+               // Hilight boxes collected during the loop and displayed\r
+               core::list< core::aabbox3d<f32> > hilightboxes;\r
+               \r
+               // Info text\r
+               std::wstring infotext;\r
+\r
+               //TimeTaker //timer1("//timer1", device);\r
+               \r
+               // Time of frame without fps limit\r
+               float busytime;\r
+               u32 busytime_u32;\r
+               {\r
+                       // not using getRealTime is necessary for wine\r
+                       u32 time = device->getTimer()->getTime();\r
+                       if(time > lasttime)\r
+                               busytime_u32 = time - lasttime;\r
+                       else\r
+                               busytime_u32 = 0;\r
+                       busytime = busytime_u32 / 1000.0;\r
+               }\r
+\r
+               //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;\r
+       \r
+               // Absolutelu necessary for wine!\r
+               device->run();\r
+\r
+               /*\r
+                       Viewing range\r
+               */\r
+               \r
+               //updateViewingRange(dtime, &client);\r
+               updateViewingRange(busytime, &client);\r
+               \r
+               /*\r
+                       FPS limiter\r
+               */\r
+\r
+               {\r
+                       float fps_max = g_fps_max;\r
+                       u32 frametime_min = 1000./fps_max;\r
+                       \r
+                       if(busytime_u32 < frametime_min)\r
+                       {\r
+                               u32 sleeptime = frametime_min - busytime_u32;\r
+                               device->sleep(sleeptime);\r
+                       }\r
+               }\r
+\r
+               // Absolutelu necessary for wine!\r
+               device->run();\r
+\r
+               /*\r
+                       Time difference calculation\r
+               */\r
+               f32 dtime; // in seconds\r
+               \r
+               u32 time = device->getTimer()->getTime();\r
+               if(time > lasttime)\r
+                       dtime = (time - lasttime) / 1000.0;\r
+               else\r
+                       dtime = 0;\r
+               lasttime = time;\r
+\r
+               /*\r
+                       Time average and jitter calculation\r
+               */\r
+\r
+               static f32 dtime_avg1 = 0.0;\r
+               dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;\r
+               f32 dtime_jitter1 = dtime - dtime_avg1;\r
+\r
+               static f32 dtime_jitter1_max_sample = 0.0;\r
+               static f32 dtime_jitter1_max_fraction = 0.0;\r
+               {\r
+                       static f32 jitter1_max = 0.0;\r
+                       static f32 counter = 0.0;\r
+                       if(dtime_jitter1 > jitter1_max)\r
+                               jitter1_max = dtime_jitter1;\r
+                       counter += dtime;\r
+                       if(counter > 0.0)\r
+                       {\r
+                               counter -= 3.0;\r
+                               dtime_jitter1_max_sample = jitter1_max;\r
+                               dtime_jitter1_max_fraction\r
+                                               = dtime_jitter1_max_sample / (dtime_avg1+0.001);\r
+                               jitter1_max = 0.0;\r
+                               \r
+                               /*\r
+                                       Control freetime ratio\r
+                               */\r
+                               /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)\r
+                               {\r
+                                       if(g_freetime_ratio < FREETIME_RATIO_MAX)\r
+                                               g_freetime_ratio += 0.01;\r
+                               }\r
+                               else\r
+                               {\r
+                                       if(g_freetime_ratio > FREETIME_RATIO_MIN)\r
+                                               g_freetime_ratio -= 0.01;\r
+                               }*/\r
+                       }\r
+               }\r
+               \r
+               /*\r
+                       Busytime average and jitter calculation\r
+               */\r
+\r
+               static f32 busytime_avg1 = 0.0;\r
+               busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;\r
+               f32 busytime_jitter1 = busytime - busytime_avg1;\r
+               \r
+               static f32 busytime_jitter1_max_sample = 0.0;\r
+               static f32 busytime_jitter1_min_sample = 0.0;\r
+               {\r
+                       static f32 jitter1_max = 0.0;\r
+                       static f32 jitter1_min = 0.0;\r
+                       static f32 counter = 0.0;\r
+                       if(busytime_jitter1 > jitter1_max)\r
+                               jitter1_max = busytime_jitter1;\r
+                       if(busytime_jitter1 < jitter1_min)\r
+                               jitter1_min = busytime_jitter1;\r
+                       counter += dtime;\r
+                       if(counter > 0.0){\r
+                               counter -= 3.0;\r
+                               busytime_jitter1_max_sample = jitter1_max;\r
+                               busytime_jitter1_min_sample = jitter1_min;\r
+                               jitter1_max = 0.0;\r
+                               jitter1_min = 0.0;\r
+                       }\r
+               }\r
+               \r
+               /*\r
+                       Debug info for client\r
+               */\r
+               {\r
+                       static float counter = 0.0;\r
+                       counter -= dtime;\r
+                       if(counter < 0)\r
+                       {\r
+                               counter = 30.0;\r
+                               client.printDebugInfo(std::cout);\r
+                       }\r
+               }\r
+\r
+               /*\r
+                       Input handler step()\r
+               */\r
+               g_input->step(dtime);\r
+\r
+               /*\r
+                       Special keys\r
+               */\r
+               if(g_esc_pressed)\r
+               {\r
+                       break;\r
+               }\r
+\r
+               /*\r
+                       Player speed control\r
+               */\r
+               \r
+               if(g_game_focused)\r
+               {\r
+                       /*bool a_up,\r
+                       bool a_down,\r
+                       bool a_left,\r
+                       bool a_right,\r
+                       bool a_jump,\r
+                       bool a_superspeed,\r
+                       float a_pitch,\r
+                       float a_yaw*/\r
+                       PlayerControl control(\r
+                               g_input->isKeyDown(irr::KEY_KEY_W),\r
+                               g_input->isKeyDown(irr::KEY_KEY_S),\r
+                               g_input->isKeyDown(irr::KEY_KEY_A),\r
+                               g_input->isKeyDown(irr::KEY_KEY_D),\r
+                               g_input->isKeyDown(irr::KEY_SPACE),\r
+                               g_input->isKeyDown(irr::KEY_KEY_2),\r
+                               camera_pitch,\r
+                               camera_yaw\r
+                       );\r
+                       client.setPlayerControl(control);\r
+               }\r
+               else\r
+               {\r
+                       // Set every key to inactive\r
+                       PlayerControl control;\r
+                       client.setPlayerControl(control);\r
+               }\r
+\r
+               //timer1.stop();\r
+               /*\r
+                       Process environment\r
+               */\r
+               \r
+               {\r
+                       //TimeTaker timer("client.step(dtime)", device);\r
+                       client.step(dtime);\r
+                       //client.step(dtime_avg1);\r
+               }\r
+\r
+               if(server != NULL)\r
+               {\r
+                       //TimeTaker timer("server->step(dtime)", device);\r
+                       server->step(dtime);\r
+               }\r
+\r
+               v3f player_position = client.getPlayerPosition();\r
+               \r
+               //TimeTaker //timer2("//timer2", device);\r
+\r
+               /*\r
+                       Mouse and camera control\r
+               */\r
+               \r
+               if(device->isWindowActive() && g_game_focused)\r
+               {\r
+                       device->getCursorControl()->setVisible(false);\r
+\r
+                       if(first_loop_after_window_activation){\r
+                               //std::cout<<"window active, first loop"<<std::endl;\r
+                               first_loop_after_window_activation = false;\r
+                       }\r
+                       else{\r
+                               s32 dx = g_input->getMousePos().X - 320;\r
+                               s32 dy = g_input->getMousePos().Y - 240;\r
+                               //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;\r
+                               camera_yaw -= dx*0.2;\r
+                               camera_pitch += dy*0.2;\r
+                               if(camera_pitch < -89.5) camera_pitch = -89.5;\r
+                               if(camera_pitch > 89.5) camera_pitch = 89.5;\r
+                       }\r
+                       g_input->setMousePos(320, 240);\r
+               }\r
+               else{\r
+                       device->getCursorControl()->setVisible(true);\r
+\r
+                       //std::cout<<"window inactive"<<std::endl;\r
+                       first_loop_after_window_activation = true;\r
+               }\r
+\r
+               camera_yaw = wrapDegrees(camera_yaw);\r
+               camera_pitch = wrapDegrees(camera_pitch);\r
+               \r
+               v3f camera_direction = v3f(0,0,1);\r
+               camera_direction.rotateYZBy(camera_pitch);\r
+               camera_direction.rotateXZBy(camera_yaw);\r
+\r
+               v3f camera_position =\r
+                               player_position + v3f(0, BS+BS/2, 0);\r
+\r
+               camera->setPosition(camera_position);\r
+               // *100.0 helps in large map coordinates\r
+               camera->setTarget(camera_position + camera_direction * 100.0);\r
+\r
+               if(FIELD_OF_VIEW_TEST){\r
+                       //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));\r
+                       client.updateCamera(v3f(0,0,0), v3f(0,0,1));\r
+               }\r
+               else{\r
+                       //client.m_env.getMap().updateCamera(camera_position, camera_direction);\r
+                       //TimeTaker timer("client.updateCamera", device);\r
+                       client.updateCamera(camera_position, camera_direction);\r
+               }\r
+               \r
+               //timer2.stop();\r
+               //TimeTaker //timer3("//timer3", device);\r
+\r
+               /*\r
+                       Calculate what block is the crosshair pointing to\r
+               */\r
+               \r
+               //u32 t1 = device->getTimer()->getRealTime();\r
+               \r
+               //f32 d = 4; // max. distance\r
+               f32 d = 4; // max. distance\r
+               core::line3d<f32> shootline(camera_position,\r
+                               camera_position + camera_direction * BS * (d+1));\r
+\r
+               MapBlockObject *selected_object = client.getSelectedObject\r
+                               (d*BS, camera_position, shootline);\r
+\r
+               if(selected_object != NULL)\r
+               {\r
+                       //dstream<<"Client returned selected_object != NULL"<<std::endl;\r
+\r
+                       core::aabbox3d<f32> box_on_map\r
+                                       = selected_object->getSelectionBoxOnMap();\r
+\r
+                       hilightboxes.push_back(box_on_map);\r
+\r
+                       infotext = narrow_to_wide(selected_object->infoText());\r
+\r
+                       if(g_input->getLeftClicked())\r
+                       {\r
+                               std::cout<<DTIME<<"Left-clicked object"<<std::endl;\r
+                               client.clickObject(0, selected_object->getBlock()->getPos(),\r
+                                               selected_object->getId(), g_selected_item);\r
+                       }\r
+                       else if(g_input->getRightClicked())\r
+                       {\r
+                               std::cout<<DTIME<<"Right-clicked object"<<std::endl;\r
+                               /*\r
+                                       Check if we want to modify the object ourselves\r
+                               */\r
+                               if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)\r
+                               {\r
+                                       dstream<<"Sign object right-clicked"<<std::endl;\r
+\r
+                                       unFocusGame();\r
+\r
+                                       input_guitext = guienv->addStaticText(L"",\r
+                                                       core::rect<s32>(150,100,350,120),\r
+                                                       true, // border?\r
+                                                       false, // wordwrap?\r
+                                                       NULL);\r
+\r
+                                       input_guitext->setDrawBackground(true);\r
+\r
+                                       g_text_buffer = L"";\r
+                                       g_text_buffer_accepted = false;\r
+                                       textbuf_dest = new TextDestSign(\r
+                                                       selected_object->getBlock()->getPos(),\r
+                                                       selected_object->getId(),\r
+                                                       &client);\r
+                               }\r
+                               /*\r
+                                       Otherwise pass the event to the server as-is\r
+                               */\r
+                               else\r
+                               {\r
+                                       client.clickObject(1, selected_object->getBlock()->getPos(),\r
+                                                       selected_object->getId(), g_selected_item);\r
+                               }\r
+                       }\r
+               }\r
+               else // selected_object == NULL\r
+               {\r
+               \r
+               bool nodefound = false;\r
+               v3s16 nodepos;\r
+               v3s16 neighbourpos;\r
+               core::aabbox3d<f32> nodefacebox;\r
+               f32 mindistance = BS * 1001;\r
+               \r
+               v3s16 pos_i = floatToInt(player_position);\r
+\r
+               /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"\r
+                               <<std::endl;*/\r
+\r
+               s16 a = d;\r
+               s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);\r
+               s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);\r
+               s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);\r
+               s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);\r
+               s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);\r
+               s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);\r
+               \r
+               for(s16 y = ystart; y <= yend; y++){\r
+               for(s16 z = zstart; z <= zend; z++){\r
+               for(s16 x = xstart; x <= xend; x++)\r
+               {\r
+                       try{\r
+                               if(client.getNode(v3s16(x,y,z)).d == MATERIAL_AIR){\r
+                                       continue;\r
+                               }\r
+                       }catch(InvalidPositionException &e){\r
+                               continue;\r
+                       }\r
+\r
+                       v3s16 np(x,y,z);\r
+                       v3f npf = intToFloat(np);\r
+                       \r
+                       f32 d = 0.01;\r
+                       \r
+                       v3s16 directions[6] = {\r
+                               v3s16(0,0,1), // back\r
+                               v3s16(0,1,0), // top\r
+                               v3s16(1,0,0), // right\r
+                               v3s16(0,0,-1),\r
+                               v3s16(0,-1,0),\r
+                               v3s16(-1,0,0),\r
+                       };\r
+\r
+                       for(u16 i=0; i<6; i++){\r
+                       //{u16 i=3;\r
+                               v3f dir_f = v3f(directions[i].X,\r
+                                               directions[i].Y, directions[i].Z);\r
+                               v3f centerpoint = npf + dir_f * BS/2;\r
+                               f32 distance =\r
+                                               (centerpoint - camera_position).getLength();\r
+                               \r
+                               if(distance < mindistance){\r
+                                       //std::cout<<DTIME<<"for centerpoint=("<<centerpoint.X<<","<<centerpoint.Y<<","<<centerpoint.Z<<"): distance < mindistance"<<std::endl;\r
+                                       //std::cout<<DTIME<<"npf=("<<npf.X<<","<<npf.Y<<","<<npf.Z<<")"<<std::endl;\r
+                                       core::CMatrix4<f32> m;\r
+                                       m.buildRotateFromTo(v3f(0,0,1), dir_f);\r
+\r
+                                       // This is the back face\r
+                                       v3f corners[2] = {\r
+                                               v3f(BS/2, BS/2, BS/2),\r
+                                               v3f(-BS/2, -BS/2, BS/2+d)\r
+                                       };\r
+                                       \r
+                                       for(u16 j=0; j<2; j++){\r
+                                               m.rotateVect(corners[j]);\r
+                                               corners[j] += npf;\r
+                                               //std::cout<<DTIME<<"box corners["<<j<<"]: ("<<corners[j].X<<","<<corners[j].Y<<","<<corners[j].Z<<")"<<std::endl;\r
+                                       }\r
+\r
+                                       //core::aabbox3d<f32> facebox(corners[0],corners[1]);\r
+                                       core::aabbox3d<f32> facebox(corners[0]);\r
+                                       facebox.addInternalPoint(corners[1]);\r
+\r
+                                       if(facebox.intersectsWithLine(shootline)){\r
+                                               nodefound = true;\r
+                                               nodepos = np;\r
+                                               neighbourpos = np + directions[i];\r
+                                               mindistance = distance;\r
+                                               nodefacebox = facebox;\r
+                                       }\r
+                               }\r
+                       }\r
+               }}}\r
+\r
+               if(nodefound)\r
+               {\r
+                       //std::cout<<DTIME<<"nodefound == true"<<std::endl;\r
+                       //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;\r
+                       //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;\r
+\r
+                       static v3s16 nodepos_old(-1,-1,-1);\r
+                       if(nodepos != nodepos_old){\r
+                               std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","\r
+                                               <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;\r
+                               nodepos_old = nodepos;\r
+\r
+                               /*wchar_t positiontext[20];\r
+                               swprintf(positiontext, 20, L"(%i,%i,%i)",\r
+                                               nodepos.X, nodepos.Y, nodepos.Z);\r
+                               positiontextgui->setText(positiontext);*/\r
+                       }\r
+\r
+                       hilightboxes.push_back(nodefacebox);\r
+                       \r
+                       if(g_input->getLeftClicked())\r
+                       {\r
+                               //std::cout<<DTIME<<"Removing node"<<std::endl;\r
+                               //client.removeNode(nodepos);\r
+                               std::cout<<DTIME<<"Ground left-clicked"<<std::endl;\r
+                               client.clickGround(0, nodepos, neighbourpos, g_selected_item);\r
+                       }\r
+                       if(g_input->getRightClicked())\r
+                       {\r
+                               //std::cout<<DTIME<<"Placing node"<<std::endl;\r
+                               //client.addNodeFromInventory(neighbourpos, g_selected_item);\r
+                               std::cout<<DTIME<<"Ground right-clicked"<<std::endl;\r
+                               client.clickGround(1, nodepos, neighbourpos, g_selected_item);\r
+                       }\r
+               }\r
+               else{\r
+                       //std::cout<<DTIME<<"nodefound == false"<<std::endl;\r
+                       //positiontextgui->setText(L"");\r
+               }\r
+\r
+               } // selected_object == NULL\r
+               \r
+               g_input->resetLeftClicked();\r
+               g_input->resetRightClicked();\r
+               \r
+               /*\r
+                       Calculate stuff for drawing\r
+               */\r
+\r
+               v2u32 screensize = driver->getScreenSize();\r
+               core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);\r
+\r
+               camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);\r
+\r
+               /*\r
+                       Update gui stuff (0ms)\r
+               */\r
+\r
+               //TimeTaker guiupdatetimer("Gui updating", device);\r
+               \r
+               {\r
+                       wchar_t temptext[100];\r
+\r
+                       static float drawtime_avg = 0;\r
+                       drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;\r
+                       static float scenetime_avg = 0;\r
+                       scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;\r
+                       static float endscenetime_avg = 0;\r
+                       endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;\r
+                       \r
+                       swprintf(temptext, 100, L"Minetest-c55 ("\r
+                                       L"F: item=%i"\r
+                                       L", R: range_all=%i"\r
+                                       L")"\r
+                                       L" drawtime=%.0f, scenetime=%.0f, endscenetime=%.0f",\r
+                                       g_selected_item,\r
+                                       g_viewing_range_all,\r
+                                       drawtime_avg,\r
+                                       scenetime_avg,\r
+                                       endscenetime_avg\r
+                                       );\r
+                       \r
+                       guitext->setText(temptext);\r
+               }\r
+               \r
+               {\r
+                       wchar_t temptext[100];\r
+                       /*swprintf(temptext, 100,\r
+                                       L"("\r
+                                       L"% .3f < btime_jitter < % .3f"\r
+                                       L", dtime_jitter = % .1f %%"\r
+                                       //L", ftime_ratio = % .3f"\r
+                                       L")",\r
+                                       busytime_jitter1_min_sample,\r
+                                       busytime_jitter1_max_sample,\r
+                                       dtime_jitter1_max_fraction * 100.0\r
+                                       //g_freetime_ratio\r
+                                       );*/\r
+                       swprintf(temptext, 100,\r
+                                       L"(% .1f, % .1f, % .1f)"\r
+                                       L" (% .3f < btime_jitter < % .3f"\r
+                                       L", dtime_jitter = % .1f %%)",\r
+                                       player_position.X/BS,\r
+                                       player_position.Y/BS,\r
+                                       player_position.Z/BS,\r
+                                       busytime_jitter1_min_sample,\r
+                                       busytime_jitter1_max_sample,\r
+                                       dtime_jitter1_max_fraction * 100.0\r
+                                       );\r
+\r
+                       guitext2->setText(temptext);\r
+               }\r
+               \r
+               {\r
+                       /*wchar_t temptext[100];\r
+                       swprintf(temptext, 100,\r
+                                       SWPRINTF_CHARSTRING,\r
+                                       infotext.substr(0,99).c_str()\r
+                                       );\r
+\r
+                       guitext_info->setText(temptext);*/\r
+\r
+                       guitext_info->setText(infotext.c_str());\r
+               }\r
+\r
+               /*\r
+                       Inventory\r
+               */\r
+               \r
+               static u16 old_selected_item = 65535;\r
+               if(client.getLocalInventoryUpdated()\r
+                               || g_selected_item != old_selected_item)\r
+               {\r
+                       old_selected_item = g_selected_item;\r
+                       //std::cout<<"Updating local inventory"<<std::endl;\r
+                       client.getLocalInventory(local_inventory);\r
+                       quick_inventory->setSelection(g_selected_item);\r
+                       quick_inventory->update();\r
+               }\r
+\r
+               if(input_guitext != NULL)\r
+               {\r
+                       /*wchar_t temptext[100];\r
+                       swprintf(temptext, 100,\r
+                                       SWPRINTF_CHARSTRING,\r
+                                       g_text_buffer.substr(0,99).c_str()\r
+                                       );*/\r
+                       input_guitext->setText(g_text_buffer.c_str());\r
+               }\r
+\r
+               /*\r
+                       Text input stuff\r
+               */\r
+               if(input_guitext != NULL && g_text_buffer_accepted)\r
+               {\r
+                       input_guitext->remove();\r
+                       input_guitext = NULL;\r
+                       \r
+                       if(textbuf_dest != NULL)\r
+                       {\r
+                               std::string text = wide_to_narrow(g_text_buffer);\r
+                               dstream<<"Sending text: "<<text<<std::endl;\r
+                               textbuf_dest->sendText(text);\r
+                               delete textbuf_dest;\r
+                               textbuf_dest = NULL;\r
+                       }\r
+\r
+                       focusGame();\r
+               }\r
+\r
+               //guiupdatetimer.stop();\r
+\r
+               /*\r
+                       Drawing begins\r
+               */\r
+\r
+               TimeTaker drawtimer("Drawing", device);\r
+\r
+               /*\r
+                       Background color is choosen based on whether the player is\r
+                       much beyond the initial ground level\r
+               */\r
+               /*video::SColor bgcolor;\r
+               v3s16 p0 = Map::floatToInt(player_position);\r
+               // Does this make short random delays?\r
+               // NOTE: no need for this, sky doesn't show underground with\r
+               // enough range\r
+               bool is_underground = client.isNodeUnderground(p0);\r
+               //bool is_underground = false;\r
+               if(is_underground == false)\r
+                       bgcolor = video::SColor(255,90,140,200);\r
+               else\r
+                       bgcolor = video::SColor(255,0,0,0);*/\r
+                       \r
+               //video::SColor bgcolor = video::SColor(255,90,140,200);\r
+               video::SColor bgcolor = skycolor;\r
+               \r
+               // 0ms\r
+               driver->beginScene(true, true, bgcolor);\r
+\r
+               //timer3.stop();\r
+               \r
+               //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;\r
+               \r
+               {\r
+               TimeTaker timer("smgr", device);\r
+               smgr->drawAll();\r
+               scenetime = timer.stop(true);\r
+               }\r
+               \r
+               {\r
+               //TimeTaker timer9("auxiliary drawings", device);\r
+               // 0ms\r
+\r
+               driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),\r
+                               displaycenter + core::vector2d<s32>(10,0),\r
+                               video::SColor(255,255,255,255));\r
+               driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),\r
+                               displaycenter + core::vector2d<s32>(0,10),\r
+                               video::SColor(255,255,255,255));\r
+\r
+               //timer9.stop();\r
+               //TimeTaker //timer10("//timer10", device);\r
+               \r
+               video::SMaterial m;\r
+               m.Thickness = 10;\r
+               m.Lighting = false;\r
+               driver->setMaterial(m);\r
+\r
+               driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);\r
+\r
+               for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();\r
+                               i != hilightboxes.end(); i++)\r
+               {\r
+                       /*std::cout<<"hilightbox min="\r
+                                       <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"\r
+                                       <<" max="\r
+                                       <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"\r
+                                       <<std::endl;*/\r
+                       driver->draw3DBox(*i, video::SColor(255,0,0,0));\r
+               }\r
+\r
+               }\r
+\r
+               //timer10.stop();\r
+               //TimeTaker //timer11("//timer11", device);\r
+\r
+               /*\r
+                       Draw gui\r
+               */\r
+               // 0-1ms\r
+               guienv->drawAll();\r
+               \r
+               // End drawing\r
+               {\r
+               TimeTaker timer("endScene", device);\r
+               driver->endScene();\r
+               endscenetime = timer.stop(true);\r
+               }\r
+\r
+               drawtime = drawtimer.stop(true);\r
+\r
+               /*\r
+                       Drawing ends\r
+               */\r
+               \r
+               static s16 lastFPS = 0;\r
+               //u16 fps = driver->getFPS();\r
+               u16 fps = (1.0/dtime_avg1);\r
+\r
+               if (lastFPS != fps)\r
+               {\r
+                       core::stringw str = L"Minetest [";\r
+                       str += driver->getName();\r
+                       str += "] FPS:";\r
+                       str += fps;\r
+\r
+                       device->setWindowCaption(str.c_str());\r
+                       lastFPS = fps;\r
+               }\r
+               \r
+               /*}\r
+               else\r
+                       device->yield();*/\r
+       }\r
+\r
+       } // client is deleted at this point\r
+       \r
+       delete g_input;\r
+\r
+       /*\r
+       In the end, delete the Irrlicht device.\r
+       */\r
+       device->drop();\r
+\r
+       } //try\r
+       catch(con::PeerNotFoundException &e)\r
+       {\r
+               dstream<<DTIME<<"Connection timed out."<<std::endl;\r
+       }\r
+#if CATCH_EXCEPTIONS\r
+       /*\r
+               This is what has to be done in every thread to get suitable debug info\r
+       */\r
+       catch(std::exception &e)\r
+       {\r
+               dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "\r
+                               <<e.what()<<std::endl;\r
+               assert(0);\r
+       }\r
+#endif\r
+\r
+       debugstreams_deinit();\r
+       \r
+       return 0;\r
+}\r
+\r
+//END\r
diff --git a/src/main.h b/src/main.h
new file mode 100644 (file)
index 0000000..3118e5f
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAIN_HEADER
+#define MAIN_HEADER
+
+#include <string>
+extern std::string getTimestamp();
+#define DTIME (getTimestamp()+": ")
+
+#include <jmutex.h>
+
+extern JMutex g_range_mutex;
+extern s16 g_forcedfetch_range_nodes;
+extern s16 g_viewing_range_nodes;
+//extern s16 g_actual_viewing_range_nodes;
+extern bool g_viewing_range_all;
+
+#include <fstream>
+
+// Debug streams
+extern std::ostream *dout_con_ptr;
+extern std::ostream *derr_con_ptr;
+extern std::ostream *dout_client_ptr;
+extern std::ostream *derr_client_ptr;
+extern std::ostream *dout_server_ptr;
+extern std::ostream *derr_server_ptr;
+
+#define dout_con (*dout_con_ptr)
+#define derr_con (*derr_con_ptr)
+#define dout_client (*dout_client_ptr)
+#define derr_client (*derr_client_ptr)
+#define dout_server (*dout_server_ptr)
+#define derr_server (*derr_server_ptr)
+
+// TODO: Move somewhere else? materials.h?
+// This header is only for MATERIALS_COUNT
+#include "mapnode.h"
+extern video::SMaterial g_materials[MATERIALS_COUNT];
+//extern video::SMaterial g_mesh_materials[3];
+
+extern IrrlichtDevice *g_device;
+
+// Settings
+#include "map.h"
+extern MapgenParams g_mapgen_params;
+
+#endif
+
diff --git a/src/map.cpp b/src/map.cpp
new file mode 100644 (file)
index 0000000..c69c3f2
--- /dev/null
@@ -0,0 +1,2854 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "map.h"
+//#include "player.h"
+#include "main.h"
+#include "jmutexautolock.h"
+#include "client.h"
+#include "filesys.h"
+#include "utility.h"
+
+#ifdef _WIN32
+       #include <windows.h>
+       #define sleep_ms(x) Sleep(x)
+#else
+       #include <unistd.h>
+       #define sleep_ms(x) usleep(x*1000)
+#endif
+
+Map::Map(std::ostream &dout):
+       m_dout(dout),
+       m_camera_position(0,0,0),
+       m_camera_direction(0,0,1),
+       m_sector_cache(NULL),
+       m_hwrapper(this),
+       drawoffset(0,0,0)
+{
+       m_sector_mutex.Init();
+       m_camera_mutex.Init();
+       assert(m_sector_mutex.IsInitialized());
+       assert(m_camera_mutex.IsInitialized());
+       
+       // Get this so that the player can stay on it at first
+       //getSector(v2s16(0,0));
+}
+
+Map::~Map()
+{
+       /*
+               Stop updater thread
+       */
+       /*updater.setRun(false);
+       while(updater.IsRunning())
+               sleep_s(1);*/
+
+       /*
+               Free all MapSectors.
+       */
+       core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+       for(; i.atEnd() == false; i++)
+       {
+               MapSector *sector = i.getNode()->getValue();
+               delete sector;
+       }
+}
+
+/*bool Map::sectorExists(v2s16 p)
+{
+       JMutexAutoLock lock(m_sector_mutex);
+       core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
+       return (n != NULL);
+}*/
+
+MapSector * Map::getSectorNoGenerate(v2s16 p)
+{
+       JMutexAutoLock lock(m_sector_mutex);
+
+       if(m_sector_cache != NULL && p == m_sector_cache_p){
+               MapSector * sector = m_sector_cache;
+               // Reset inactivity timer
+               sector->usage_timer = 0.0;
+               return sector;
+       }
+       
+       core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
+       // If sector doesn't exist, throw an exception
+       if(n == NULL)
+       {
+               throw InvalidPositionException();
+       }
+       
+       MapSector *sector = n->getValue();
+       
+       // Cache the last result
+       m_sector_cache_p = p;
+       m_sector_cache = sector;
+
+       //MapSector * ref(sector);
+       
+       // Reset inactivity timer
+       sector->usage_timer = 0.0;
+       return sector;
+}
+
+MapBlock * Map::getBlockNoCreate(v3s16 p3d)
+{      
+       v2s16 p2d(p3d.X, p3d.Z);
+       MapSector * sector = getSectorNoGenerate(p2d);
+
+       MapBlock *block = sector->getBlockNoCreate(p3d.Y);
+
+       return block;
+}
+
+/*MapBlock * Map::getBlock(v3s16 p3d, bool generate)
+{
+       dstream<<"Map::getBlock() with generate=true called"
+                       <<std::endl;
+       v2s16 p2d(p3d.X, p3d.Z);
+       //MapSector * sector = getSector(p2d, generate);
+       MapSector * sector = getSectorNoGenerate(p2d);
+
+       if(sector == NULL)
+               throw InvalidPositionException();
+
+       return sector->getBlockNoCreate(p3d.Y);
+}*/
+
+f32 Map::getGroundHeight(v2s16 p, bool generate)
+{
+       try{
+               v2s16 sectorpos = getNodeSectorPos(p);
+               MapSector * sref = getSectorNoGenerate(sectorpos);
+               v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
+               f32 y = sref->getGroundHeight(relpos);
+               return y;
+       }
+       catch(InvalidPositionException &e)
+       {
+               return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+       }
+}
+
+void Map::setGroundHeight(v2s16 p, f32 y, bool generate)
+{
+       /*m_dout<<DTIME<<"Map::setGroundHeight(("
+                       <<p.X<<","<<p.Y
+                       <<"), "<<y<<")"<<std::endl;*/
+       v2s16 sectorpos = getNodeSectorPos(p);
+       MapSector * sref = getSectorNoGenerate(sectorpos);
+       v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
+       //sref->mutex.Lock();
+       sref->setGroundHeight(relpos, y);
+       //sref->mutex.Unlock();
+}
+
+bool Map::isNodeUnderground(v3s16 p)
+{
+       v3s16 blockpos = getNodeBlockPos(p);
+       try{
+               MapBlock * block = getBlockNoCreate(blockpos);
+               return block->getIsUnderground();
+       }
+       catch(InvalidPositionException &e)
+       {
+               return false;
+       }
+}
+
+#ifdef LKJnb
+//TODO: Remove: Not used.
+/*
+       Goes recursively through the neighbours of the node.
+
+       Alters only transparent nodes.
+
+       If the lighting of the neighbour is lower than the lighting of
+       the node was (before changing it to 0 at the step before), the
+       lighting of the neighbour is set to 0 and then the same stuff
+       repeats for the neighbour.
+
+       Some things are made strangely to make it as fast as possible.
+
+       Usage: (for clearing all possible spreaded light of a lamp)
+       NOTE: This is outdated
+               core::list<v3s16> light_sources;
+               core::map<v3s16, MapBlock*> modified_blocks;
+               u8 oldlight = node_at_pos.light;
+               node_at_pos.setLight(0);
+               unLightNeighbors(pos, oldlight, light_sources, modified_blocks);
+*/
+void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
+               core::map<v3s16, bool> & light_sources,
+               core::map<v3s16, MapBlock*>  & modified_blocks)
+{
+       v3s16 dirs[6] = {
+               v3s16(0,0,1), // back
+               v3s16(0,1,0), // top
+               v3s16(1,0,0), // right
+               v3s16(0,0,-1), // front
+               v3s16(0,-1,0), // bottom
+               v3s16(-1,0,0), // left
+       };
+       
+       /*
+               Initialize block cache
+       */
+       v3s16 blockpos_last;
+       MapBlock *block = NULL;
+       // Cache this a bit, too
+       bool block_checked_in_modified = false;
+       
+       // Loop through 6 neighbors
+       for(u16 i=0; i<6; i++){
+               // Get the position of the neighbor node
+               v3s16 n2pos = pos + dirs[i];
+               
+               // Get the block where the node is located
+               v3s16 blockpos = getNodeBlockPos(n2pos);
+
+               // Only fetch a new block if the block position has changed
+               try{
+                       if(block == NULL || blockpos != blockpos_last)
+                       {
+                               block = getBlockNoCreate(blockpos);
+                               blockpos_last = blockpos;
+                               
+                               block_checked_in_modified = false;
+                               //blockchangecount++;
+                       }
+               }
+               catch(InvalidPositionException &e)
+               {
+                       continue;
+               }
+
+               if(block->isDummy())
+                       continue;
+               
+               // Calculate relative position in block
+               v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
+               // Get node straight from the block
+               MapNode n2 = block->getNode(relpos);
+               
+               /*
+                       If the neighbor is dimmer than what was specified
+                       as oldlight (the light of the previous node)
+               */
+               if(n2.getLight() < oldlight)
+               {
+                       /*
+                               And the neighbor is transparent and it has some light
+                       */
+                       if(n2.light_propagates() && n2.getLight() != 0)
+                       {
+                               /*
+                                       Set light to 0 and recurse.
+                               */
+                               u8 current_light = n2.getLight();
+                               n2.setLight(0);
+                               block->setNode(relpos, n2);
+                               unLightNeighbors(n2pos, current_light,
+                                               light_sources, modified_blocks);
+                               
+                               if(block_checked_in_modified == false)
+                               {
+                                       // If the block is not found in modified_blocks, add.
+                                       if(modified_blocks.find(blockpos) == NULL)
+                                       {
+                                               modified_blocks.insert(blockpos, block);
+                                       }
+                                       block_checked_in_modified = true;
+                               }
+                       }
+               }
+               else{
+                       //light_sources.push_back(n2pos);
+                       light_sources.insert(n2pos, true);
+               }
+       }
+}
+#endif
+
+/*
+       Goes recursively through the neighbours of the node.
+
+       Alters only transparent nodes.
+
+       If the lighting of the neighbour is lower than the lighting of
+       the node was (before changing it to 0 at the step before), the
+       lighting of the neighbour is set to 0 and then the same stuff
+       repeats for the neighbour.
+
+       The ending nodes of the routine are stored in light_sources.
+       This is useful when a light is removed. In such case, this
+       routine can be called for the light node and then again for
+       light_sources to re-light the area without the removed light.
+
+       values of from_nodes are lighting values.
+*/
+void Map::unspreadLight(core::map<v3s16, u8> & from_nodes,
+               core::map<v3s16, bool> & light_sources,
+               core::map<v3s16, MapBlock*>  & modified_blocks)
+{
+       v3s16 dirs[6] = {
+               v3s16(0,0,1), // back
+               v3s16(0,1,0), // top
+               v3s16(1,0,0), // right
+               v3s16(0,0,-1), // front
+               v3s16(0,-1,0), // bottom
+               v3s16(-1,0,0), // left
+       };
+       
+       if(from_nodes.size() == 0)
+               return;
+       
+       u32 blockchangecount = 0;
+
+       core::map<v3s16, u8> unlighted_nodes;
+       core::map<v3s16, u8>::Iterator j;
+       j = from_nodes.getIterator();
+
+       /*
+               Initialize block cache
+       */
+       v3s16 blockpos_last;
+       MapBlock *block = NULL;
+       // Cache this a bit, too
+       bool block_checked_in_modified = false;
+       
+       for(; j.atEnd() == false; j++)
+       {
+               v3s16 pos = j.getNode()->getKey();
+               v3s16 blockpos = getNodeBlockPos(pos);
+               
+               // Only fetch a new block if the block position has changed
+               try{
+                       if(block == NULL || blockpos != blockpos_last){
+                               block = getBlockNoCreate(blockpos);
+                               blockpos_last = blockpos;
+
+                               block_checked_in_modified = false;
+                               blockchangecount++;
+                       }
+               }
+               catch(InvalidPositionException &e)
+               {
+                       continue;
+               }
+
+               if(block->isDummy())
+                       continue;
+
+               // Calculate relative position in block
+               v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
+
+               // Get node straight from the block
+               MapNode n = block->getNode(relpos);
+               
+               u8 oldlight = j.getNode()->getValue();
+               
+               // Loop through 6 neighbors
+               for(u16 i=0; i<6; i++)
+               {
+                       // Get the position of the neighbor node
+                       v3s16 n2pos = pos + dirs[i];
+                       
+                       // Get the block where the node is located
+                       v3s16 blockpos = getNodeBlockPos(n2pos);
+
+                       try
+                       {
+                               // Only fetch a new block if the block position has changed
+                               try{
+                                       if(block == NULL || blockpos != blockpos_last){
+                                               block = getBlockNoCreate(blockpos);
+                                               blockpos_last = blockpos;
+
+                                               block_checked_in_modified = false;
+                                               blockchangecount++;
+                                       }
+                               }
+                               catch(InvalidPositionException &e)
+                               {
+                                       continue;
+                               }
+                               
+                               // Calculate relative position in block
+                               v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
+                               // Get node straight from the block
+                               MapNode n2 = block->getNode(relpos);
+                               
+                               bool changed = false;
+
+                               //TODO: Optimize output by optimizing light_sources?
+
+                               /*
+                                       If the neighbor is dimmer than what was specified
+                                       as oldlight (the light of the previous node)
+                               */
+                               if(n2.getLight() < oldlight)
+                               {
+                                       /*
+                                               And the neighbor is transparent and it has some light
+                                       */
+                                       if(n2.light_propagates() && n2.getLight() != 0)
+                                       {
+                                               /*
+                                                       Set light to 0 and add to queue
+                                               */
+
+                                               u8 current_light = n2.getLight();
+                                               n2.setLight(0);
+                                               block->setNode(relpos, n2);
+
+                                               unlighted_nodes.insert(n2pos, current_light);
+                                               changed = true;
+
+                                               /*
+                                                       Remove from light_sources if it is there
+                                                       NOTE: This doesn't happen nearly at all
+                                               */
+                                               /*if(light_sources.find(n2pos))
+                                               {
+                                                       std::cout<<"Removed from light_sources"<<std::endl;
+                                                       light_sources.remove(n2pos);
+                                               }*/
+                                       }
+                               }
+                               else{
+                                       light_sources.insert(n2pos, true);
+                               }
+
+                               // Add to modified_blocks
+                               if(changed == true && block_checked_in_modified == false)
+                               {
+                                       // If the block is not found in modified_blocks, add.
+                                       if(modified_blocks.find(blockpos) == NULL)
+                                       {
+                                               modified_blocks.insert(blockpos, block);
+                                       }
+                                       block_checked_in_modified = true;
+                               }
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               continue;
+                       }
+               }
+       }
+
+       /*dstream<<"unspreadLight(): Changed block "
+                       <<blockchangecount<<" times"
+                       <<" for "<<from_nodes.size()<<" nodes"
+                       <<std::endl;*/
+       
+       if(unlighted_nodes.size() > 0)
+               unspreadLight(unlighted_nodes, light_sources, modified_blocks);
+}
+
+/*
+       A single-node wrapper of the above
+*/
+void Map::unLightNeighbors(v3s16 pos, u8 lightwas,
+               core::map<v3s16, bool> & light_sources,
+               core::map<v3s16, MapBlock*>  & modified_blocks)
+{
+       core::map<v3s16, u8> from_nodes;
+       from_nodes.insert(pos, lightwas);
+
+       unspreadLight(from_nodes, light_sources, modified_blocks);
+}
+
+/*
+       Lights neighbors of from_nodes, collects all them and then
+       goes on recursively.
+*/
+void Map::spreadLight(core::map<v3s16, bool> & from_nodes,
+               core::map<v3s16, MapBlock*> & modified_blocks)
+{
+       const v3s16 dirs[6] = {
+               v3s16(0,0,1), // back
+               v3s16(0,1,0), // top
+               v3s16(1,0,0), // right
+               v3s16(0,0,-1), // front
+               v3s16(0,-1,0), // bottom
+               v3s16(-1,0,0), // left
+       };
+
+       if(from_nodes.size() == 0)
+               return;
+       
+       u32 blockchangecount = 0;
+
+       core::map<v3s16, bool> lighted_nodes;
+       core::map<v3s16, bool>::Iterator j;
+       j = from_nodes.getIterator();
+
+       /*
+               Initialize block cache
+       */
+       v3s16 blockpos_last;
+       MapBlock *block = NULL;
+       // Cache this a bit, too
+       bool block_checked_in_modified = false;
+       
+       for(; j.atEnd() == false; j++)
+       //for(; j != from_nodes.end(); j++)
+       {
+               v3s16 pos = j.getNode()->getKey();
+               //v3s16 pos = *j;
+               //dstream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
+               v3s16 blockpos = getNodeBlockPos(pos);
+               
+               // Only fetch a new block if the block position has changed
+               try{
+                       if(block == NULL || blockpos != blockpos_last){
+                               block = getBlockNoCreate(blockpos);
+                               blockpos_last = blockpos;
+
+                               block_checked_in_modified = false;
+                               blockchangecount++;
+                       }
+               }
+               catch(InvalidPositionException &e)
+               {
+                       continue;
+               }
+
+               if(block->isDummy())
+                       continue;
+
+               // Calculate relative position in block
+               v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
+
+               // Get node straight from the block
+               MapNode n = block->getNode(relpos);
+
+               u8 oldlight = n.getLight();
+               u8 newlight = diminish_light(oldlight);
+
+               // Loop through 6 neighbors
+               for(u16 i=0; i<6; i++){
+                       // Get the position of the neighbor node
+                       v3s16 n2pos = pos + dirs[i];
+                       
+                       // Get the block where the node is located
+                       v3s16 blockpos = getNodeBlockPos(n2pos);
+
+                       try
+                       {
+                               // Only fetch a new block if the block position has changed
+                               try{
+                                       if(block == NULL || blockpos != blockpos_last){
+                                               block = getBlockNoCreate(blockpos);
+                                               blockpos_last = blockpos;
+
+                                               block_checked_in_modified = false;
+                                               blockchangecount++;
+                                       }
+                               }
+                               catch(InvalidPositionException &e)
+                               {
+                                       continue;
+                               }
+                               
+                               // Calculate relative position in block
+                               v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
+                               // Get node straight from the block
+                               MapNode n2 = block->getNode(relpos);
+                               
+                               bool changed = false;
+                               /*
+                                       If the neighbor is brighter than the current node,
+                                       add to list (it will light up this node on its turn)
+                               */
+                               if(n2.getLight() > undiminish_light(oldlight))
+                               {
+                                       lighted_nodes.insert(n2pos, true);
+                                       //lighted_nodes.push_back(n2pos);
+                                       changed = true;
+                               }
+                               /*
+                                       If the neighbor is dimmer than how much light this node
+                                       would spread on it, add to list
+                               */
+                               if(n2.getLight() < newlight)
+                               {
+                                       if(n2.light_propagates())
+                                       {
+                                               n2.setLight(newlight);
+                                               block->setNode(relpos, n2);
+                                               lighted_nodes.insert(n2pos, true);
+                                               //lighted_nodes.push_back(n2pos);
+                                               changed = true;
+                                       }
+                               }
+
+                               // Add to modified_blocks
+                               if(changed == true && block_checked_in_modified == false)
+                               {
+                                       // If the block is not found in modified_blocks, add.
+                                       if(modified_blocks.find(blockpos) == NULL)
+                                       {
+                                               modified_blocks.insert(blockpos, block);
+                                       }
+                                       block_checked_in_modified = true;
+                               }
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               continue;
+                       }
+               }
+       }
+
+       /*dstream<<"spreadLight(): Changed block "
+                       <<blockchangecount<<" times"
+                       <<" for "<<from_nodes.size()<<" nodes"
+                       <<std::endl;*/
+       
+       if(lighted_nodes.size() > 0)
+               spreadLight(lighted_nodes, modified_blocks);
+}
+
+/*
+       A single-node source variation of the above.
+*/
+void Map::lightNeighbors(v3s16 pos,
+               core::map<v3s16, MapBlock*> & modified_blocks)
+{
+       core::map<v3s16, bool> from_nodes;
+       from_nodes.insert(pos, true);
+       spreadLight(from_nodes, modified_blocks);
+}
+
+v3s16 Map::getBrightestNeighbour(v3s16 p)
+{
+       v3s16 dirs[6] = {
+               v3s16(0,0,1), // back
+               v3s16(0,1,0), // top
+               v3s16(1,0,0), // right
+               v3s16(0,0,-1), // front
+               v3s16(0,-1,0), // bottom
+               v3s16(-1,0,0), // left
+       };
+       
+       u8 brightest_light = 0;
+       v3s16 brightest_pos(0,0,0);
+       bool found_something = false;
+
+       // Loop through 6 neighbors
+       for(u16 i=0; i<6; i++){
+               // Get the position of the neighbor node
+               v3s16 n2pos = p + dirs[i];
+               MapNode n2;
+               try{
+                       n2 = getNode(n2pos);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       continue;
+               }
+               if(n2.getLight() > brightest_light || found_something == false){
+                       brightest_light = n2.getLight();
+                       brightest_pos = n2pos;
+                       found_something = true;
+               }
+       }
+
+       if(found_something == false)
+               throw InvalidPositionException();
+               
+       return brightest_pos;
+}
+
+/*
+       Propagates sunlight down from a node.
+       Starting point gets sunlight.
+
+       Returns the lowest y value of where the sunlight went.
+*/
+s16 Map::propagateSunlight(v3s16 start,
+               core::map<v3s16, MapBlock*> & modified_blocks)
+{
+       s16 y = start.Y;
+       for(; ; y--)
+       {
+               v3s16 pos(start.X, y, start.Z);
+               
+               v3s16 blockpos = getNodeBlockPos(pos);
+               MapBlock *block;
+               try{
+                       block = getBlockNoCreate(blockpos);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       break;
+               }
+
+               v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE;
+               MapNode n = block->getNode(relpos);
+
+               if(n.sunlight_propagates())
+               {
+                       n.setLight(LIGHT_SUN);
+                       block->setNode(relpos, n);
+
+                       modified_blocks.insert(blockpos, block);
+               }
+               else{
+                       break;
+               }
+       }
+       return y + 1;
+}
+
+void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
+               core::map<v3s16, MapBlock*> & modified_blocks)
+{
+       /*m_dout<<DTIME<<"Map::updateLighting(): "
+                       <<a_blocks.getSize()<<" blocks... ";*/
+       
+       // For debugging
+       bool debug=false;
+       u32 count_was = modified_blocks.size();
+
+       /*core::list<MapBlock *>::Iterator i = a_blocks.begin();
+       for(; i != a_blocks.end(); i++)
+       {
+               MapBlock *block = *i;*/
+
+       core::map<v3s16, bool> light_sources;
+       
+       core::map<v3s16, u8> unlight_from;
+               
+       core::map<v3s16, MapBlock*>::Iterator i;
+       i = a_blocks.getIterator();
+       for(; i.atEnd() == false; i++)
+       {
+               MapBlock *block = i.getNode()->getValue();
+               
+               for(;;)
+               {
+                       // Don't bother with dummy blocks.
+                       if(block->isDummy())
+                               break;
+               
+                       v3s16 pos = block->getPos();
+                       modified_blocks.insert(pos, block);
+
+                       /*
+                               Clear all light from block
+                       */
+                       for(s16 z=0; z<MAP_BLOCKSIZE; z++)
+                       for(s16 x=0; x<MAP_BLOCKSIZE; x++)
+                       for(s16 y=0; y<MAP_BLOCKSIZE; y++)
+                       {
+                               
+                               try{
+                                       v3s16 p(x,y,z);
+                                       MapNode n = block->getNode(v3s16(x,y,z));
+                                       u8 oldlight = n.getLight();
+                                       n.setLight(0);
+                                       block->setNode(v3s16(x,y,z), n);
+                                       
+                                       // Collect borders for unlighting
+                                       if(x==0 || x == MAP_BLOCKSIZE-1
+                                                       || y==0 || y == MAP_BLOCKSIZE-1
+                                                       || z==0 || z == MAP_BLOCKSIZE-1)
+                                       {
+                                               v3s16 p_map = p + v3s16(
+                                                               MAP_BLOCKSIZE*pos.X,
+                                                               MAP_BLOCKSIZE*pos.Y,
+                                                               MAP_BLOCKSIZE*pos.Z);
+                                               unlight_from.insert(p_map, oldlight);
+                                       }
+                               }
+                               catch(InvalidPositionException &e)
+                               {
+                                       /*
+                                               This would happen when dealing with a
+                                               dummy block.
+                                       */
+                                       //assert(0);
+                                       dstream<<"updateLighting(): InvalidPositionException"
+                                                       <<std::endl;
+                               }
+                       }
+                       
+                       bool bottom_valid = block->propagateSunlight(light_sources);
+
+                       // If bottom is valid, we're done.
+                       if(bottom_valid)
+                               break;
+                               
+                       /*dstream<<"Bottom for sunlight-propagated block ("
+                                       <<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
+                                       <<std::endl;*/
+
+                       // Else get the block below and loop to it
+
+                       pos.Y--;
+                       try{
+                               block = getBlockNoCreate(pos);
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               assert(0);
+                       }
+                       
+               }
+       }
+       
+       {
+               //TimeTaker timer("unspreadLight", g_device);
+               unspreadLight(unlight_from, light_sources, modified_blocks);
+       }
+       
+       if(debug)
+       {
+               u32 diff = modified_blocks.size() - count_was;
+               count_was = modified_blocks.size();
+               dstream<<"unspreadLight modified "<<diff<<std::endl;
+       }
+
+       // TODO: Spread light from propagated sunlight?
+       // Yes, add it to light_sources... somehow.
+       // It has to be added at somewhere above, in the loop.
+       // TODO
+
+       {
+               //TimeTaker timer("spreadLight", g_device);
+               spreadLight(light_sources, modified_blocks);
+       }
+       
+       if(debug)
+       {
+               u32 diff = modified_blocks.size() - count_was;
+               count_was = modified_blocks.size();
+               dstream<<"spreadLight modified "<<diff<<std::endl;
+       }
+
+       //m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
+}
+
+/*
+       This is called after changing a node from transparent to opaque.
+       The lighting value of the node should be left as-is after changing
+       other values. This sets the lighting value to 0.
+*/
+/*void Map::nodeAddedUpdate(v3s16 p, u8 lightwas,
+               core::map<v3s16, MapBlock*> &modified_blocks)*/
+void Map::addNodeAndUpdate(v3s16 p, MapNode n,
+               core::map<v3s16, MapBlock*> &modified_blocks)
+{
+       /*PrintInfo(m_dout);
+       m_dout<<DTIME<<"Map::nodeAddedUpdate(): p=("
+                       <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+       u8 lightwas = getNode(p).getLight();
+
+       //core::list<v3s16> light_sources;
+       core::map<v3s16, bool> light_sources;
+       //MapNode n = getNode(p);
+
+       /*
+               From this node to nodes underneath:
+               If lighting is sunlight (1.0), unlight neighbours and
+               set lighting to 0.
+               Else discontinue.
+       */
+
+       bool node_under_sunlight = true;
+       
+       v3s16 toppos = p + v3s16(0,1,0);
+
+       /*
+               If there is a node at top and it doesn't have sunlight,
+               there has not been any sunlight going down.
+
+               Otherwise there probably is.
+       */
+       try{
+               MapNode topnode = getNode(toppos);
+
+               if(topnode.getLight() != LIGHT_SUN)
+                       node_under_sunlight = false;
+       }
+       catch(InvalidPositionException &e)
+       {
+       }
+
+       // Add the block of the added node to modified_blocks
+       v3s16 blockpos = getNodeBlockPos(p);
+       MapBlock * block = getBlockNoCreate(blockpos);
+       assert(block != NULL);
+       modified_blocks.insert(blockpos, block);
+       
+       if(isValidPosition(p) == false)
+               throw;
+               
+       // Unlight neighbours of node.
+       // This means setting light of all consequent dimmer nodes
+       // to 0.
+       // This also collects the nodes at the border which will spread
+       // light again into this.
+       unLightNeighbors(p, lightwas, light_sources, modified_blocks);
+
+       n.setLight(0);
+       setNode(p, n);
+       
+       /*
+               If node is under sunlight, take all sunlighted nodes under
+               it and clear light from them and from where the light has
+               been spread.
+       */
+       if(node_under_sunlight)
+       {
+               s16 y = p.Y - 1;
+               for(;; y--){
+                       //m_dout<<DTIME<<"y="<<y<<std::endl;
+                       v3s16 n2pos(p.X, y, p.Z);
+                       
+                       MapNode n2;
+                       try{
+                               n2 = getNode(n2pos);
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               break;
+                       }
+
+                       if(n2.getLight() == LIGHT_SUN)
+                       {
+                               //m_dout<<DTIME<<"doing"<<std::endl;
+                               unLightNeighbors(n2pos, n2.getLight(), light_sources, modified_blocks);
+                               n2.setLight(0);
+                               setNode(n2pos, n2);
+                       }
+                       else
+                               break;
+               }
+       }
+       
+       /*
+               Spread light from all nodes that might be capable of doing so
+               TODO: Convert to spreadLight
+       */
+       spreadLight(light_sources, modified_blocks);
+}
+
+/*
+*/
+void Map::removeNodeAndUpdate(v3s16 p,
+               core::map<v3s16, MapBlock*> &modified_blocks)
+{
+       /*PrintInfo(m_dout);
+       m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
+                       <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+       
+       bool node_under_sunlight = true;
+       
+       v3s16 toppos = p + v3s16(0,1,0);
+       
+       /*
+               If there is a node at top and it doesn't have sunlight,
+               there will be no sunlight going down.
+       */
+       try{
+               MapNode topnode = getNode(toppos);
+
+               if(topnode.getLight() != LIGHT_SUN)
+                       node_under_sunlight = false;
+       }
+       catch(InvalidPositionException &e)
+       {
+       }
+
+       /*
+               Unlight neighbors (in case the node is a light source)
+       */
+       //core::list<v3s16> light_sources;
+       core::map<v3s16, bool> light_sources;
+       unLightNeighbors(p, getNode(p).getLight(),
+                       light_sources, modified_blocks);
+
+       /*
+               Remove the node
+       */
+       MapNode n;
+       n.d = MATERIAL_AIR;
+       n.setLight(0);
+       setNode(p, n);
+       
+       /*
+               Recalculate lighting
+       */
+       spreadLight(light_sources, modified_blocks);
+
+       // Add the block of the removed node to modified_blocks
+       v3s16 blockpos = getNodeBlockPos(p);
+       MapBlock * block = getBlockNoCreate(blockpos);
+       assert(block != NULL);
+       modified_blocks.insert(blockpos, block);
+
+       /*
+               If the removed node was under sunlight, propagate the
+               sunlight down from it and then light all neighbors
+               of the propagated blocks.
+       */
+       if(node_under_sunlight)
+       {
+               s16 ybottom = propagateSunlight(p, modified_blocks);
+               /*m_dout<<DTIME<<"Node was under sunlight. "
+                               "Propagating sunlight";
+               m_dout<<DTIME<<" -> ybottom="<<ybottom<<std::endl;*/
+               s16 y = p.Y;
+               for(; y >= ybottom; y--)
+               {
+                       v3s16 p2(p.X, y, p.Z);
+                       /*m_dout<<DTIME<<"lighting neighbors of node ("
+                                       <<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
+                                       <<std::endl;*/
+                       lightNeighbors(p2, modified_blocks);
+               }
+       }
+       else
+       {
+               // Set the lighting of this node to 0
+               try{
+                       MapNode n = getNode(p);
+                       n.setLight(0);
+                       setNode(p, n);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       throw;
+               }
+       }
+
+       // Get the brightest neighbour node and propagate light from it
+       v3s16 n2p = getBrightestNeighbour(p);
+       try{
+               MapNode n2 = getNode(n2p);
+               lightNeighbors(n2p, modified_blocks);
+       }
+       catch(InvalidPositionException &e)
+       {
+       }
+}
+
+void Map::updateMeshes(v3s16 blockpos)
+{
+       assert(mapType() == MAPTYPE_CLIENT);
+
+       try{
+               v3s16 p = blockpos + v3s16(0,0,0);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh();
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(-1,0,0);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh();
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(0,-1,0);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh();
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(0,0,-1);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh();
+       }
+       catch(InvalidPositionException &e){}
+}
+
+/*
+       Updates usage timers
+*/
+void Map::timerUpdate(float dtime)
+{
+       JMutexAutoLock lock(m_sector_mutex);
+
+       core::map<v2s16, MapSector*>::Iterator si;
+
+       si = m_sectors.getIterator();
+       for(; si.atEnd() == false; si++)
+       {
+               MapSector *sector = si.getNode()->getValue();
+               sector->usage_timer += dtime;
+       }
+}
+
+void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
+{
+       core::list<v2s16>::Iterator j;
+       for(j=list.begin(); j!=list.end(); j++)
+       {
+               MapSector *sector = m_sectors[*j];
+               if(only_blocks)
+               {
+                       sector->deleteBlocks();
+               }
+               else
+               {
+                       /*
+                               If sector is in sector cache, remove it from there
+                       */
+                       if(m_sector_cache == sector)
+                       {
+                               m_sector_cache = NULL;
+                       }
+                       /*
+                               Remove from map and delete
+                       */
+                       m_sectors.remove(*j);
+                       delete sector;
+               }
+       }
+}
+
+u32 Map::deleteUnusedSectors(float timeout, bool only_blocks,
+               core::list<v3s16> *deleted_blocks)
+{
+       JMutexAutoLock lock(m_sector_mutex);
+
+       core::list<v2s16> sector_deletion_queue;
+       core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+       for(; i.atEnd() == false; i++)
+       {
+               MapSector *sector = i.getNode()->getValue();
+               /*
+                       Delete sector from memory if it hasn't been used in a long time
+               */
+               if(sector->usage_timer > timeout)
+               {
+                       sector_deletion_queue.push_back(i.getNode()->getKey());
+                       
+                       if(deleted_blocks != NULL)
+                       {
+                               // Collect positions of blocks of sector
+                               MapSector *sector = i.getNode()->getValue();
+                               core::list<MapBlock*> blocks;
+                               sector->getBlocks(blocks);
+                               for(core::list<MapBlock*>::Iterator i = blocks.begin();
+                                               i != blocks.end(); i++)
+                               {
+                                       deleted_blocks->push_back((*i)->getPos());
+                               }
+                       }
+               }
+       }
+       deleteSectors(sector_deletion_queue, only_blocks);
+       return sector_deletion_queue.getSize();
+}
+
+void Map::PrintInfo(std::ostream &out)
+{
+       out<<"Map: ";
+}
+
+/*
+       ServerMap
+*/
+
+ServerMap::ServerMap(std::string savedir, MapgenParams params):
+       Map(dout_server),
+       m_heightmap(NULL)
+{
+       m_savedir = savedir;
+       m_map_saving_enabled = false;
+       
+       try
+       {
+               // If directory exists, check contents and load if possible
+               if(fs::PathExists(m_savedir))
+               {
+                       // If directory is empty, it is safe to save into it.
+                       if(fs::GetDirListing(m_savedir).size() == 0)
+                       {
+                               dstream<<DTIME<<"Server: Empty save directory is valid."
+                                               <<std::endl;
+                               m_map_saving_enabled = true;
+                       }
+                       else
+                       {
+                               // Load master heightmap
+                               loadMasterHeightmap();
+                               
+                               // Load sector (0,0) and throw and exception on fail
+                               if(loadSectorFull(v2s16(0,0)) == false)
+                                       throw LoadError("Failed to load sector (0,0)");
+
+                               dstream<<DTIME<<"Server: Successfully loaded master "
+                                               "heightmap and sector (0,0) from "<<savedir<<
+                                               ", assuming valid save directory."
+                                               <<std::endl;
+
+                               m_map_saving_enabled = true;
+                               // Map loaded, not creating new one
+                               return;
+                       }
+               }
+               // If directory doesn't exist, it is safe to save to it
+               else{
+                       m_map_saving_enabled = true;
+               }
+       }
+       catch(std::exception &e)
+       {
+               dstream<<DTIME<<"Server: Failed to load map from "<<savedir
+                               <<", exception: "<<e.what()<<std::endl;
+               dstream<<DTIME<<"Please remove the map or fix it."<<std::endl;
+               dstream<<DTIME<<"WARNING: Map saving will be disabled."<<std::endl;
+       }
+
+       dstream<<DTIME<<"Initializing new map."<<std::endl;
+
+       ValueGenerator *maxgen =
+                       ValueGenerator::deSerialize(params.height_randmax);
+       ValueGenerator *factorgen =
+                       ValueGenerator::deSerialize(params.height_randfactor);
+       ValueGenerator *basegen =
+                       ValueGenerator::deSerialize(params.height_base);
+       m_heightmap = new UnlimitedHeightmap
+                       (params.heightmap_blocksize, maxgen, factorgen, basegen);
+       
+       // Create zero sector
+       emergeSector(v2s16(0,0));
+
+       // Initially write whole map
+       save(false);
+}
+
+ServerMap::~ServerMap()
+{
+       try
+       {
+               if(m_map_saving_enabled)
+               {
+                       //save(false);
+                       // Save only changed parts
+                       save(true);
+                       dstream<<DTIME<<"Server: saved map to "<<m_savedir<<std::endl;
+               }
+               else
+               {
+                       dstream<<DTIME<<"Server: map not saved"<<std::endl;
+               }
+       }
+       catch(std::exception &e)
+       {
+               dstream<<DTIME<<"Server: Failed to save map to "<<m_savedir
+                               <<", exception: "<<e.what()<<std::endl;
+       }
+       
+       if(m_heightmap != NULL)
+               delete m_heightmap;
+}
+
+MapSector * ServerMap::emergeSector(v2s16 p2d)
+{
+       DSTACK("%s: p2d=(%d,%d)",
+                       __FUNCTION_NAME,
+                       p2d.X, p2d.Y);
+       // Check that it doesn't exist already
+       try{
+               return getSectorNoGenerate(p2d);
+       }
+       catch(InvalidPositionException &e)
+       {
+       }
+       
+       /*
+               Try to load the sector from disk.
+       */
+       if(loadSectorFull(p2d) == true)
+       {
+               return getSectorNoGenerate(p2d);
+       }
+
+       /*
+               If there is no master heightmap, throw.
+       */
+       if(m_heightmap == NULL)
+       {
+               throw InvalidPositionException("emergeSector(): no heightmap");
+       }
+
+       /*
+               Do not generate over-limit
+       */
+       if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+       || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+       || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+       || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
+               throw InvalidPositionException("emergeSector(): pos. over limit");
+
+       /*
+               Generate sector and heightmaps
+       */
+       
+       // Number of heightmaps in sector in each direction
+       u16 hm_split = SECTOR_HEIGHTMAP_SPLIT;
+
+       // Heightmap side width
+       s16 hm_d = MAP_BLOCKSIZE / hm_split;
+
+       ServerMapSector *sector = new ServerMapSector(this, p2d, hm_split);
+
+       /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")"
+                       " heightmaps and objects"<<std::endl;*/
+       
+       // Loop through sub-heightmaps
+       for(s16 y=0; y<hm_split; y++)
+       for(s16 x=0; x<hm_split; x++)
+       {
+               v2s16 p_in_sector = v2s16(x,y);
+               v2s16 mhm_p = p2d * hm_split + p_in_sector;
+               f32 corners[4] = {
+                       m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
+                       m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
+                       m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
+                       m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
+               };
+
+               /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
+                               <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
+                               <<std::endl;*/
+
+               FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
+                               mhm_p, hm_d);
+               sector->setHeightmap(p_in_sector, hm);
+
+               //TODO: Make these values configurable
+               hm->generateContinued(1.0, 0.2, corners);
+               //hm->generateContinued(2.0, 0.2, corners);
+
+               //hm->print();
+               
+       }
+
+       /*
+               Generate objects
+       */
+       
+       core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
+       sector->setObjects(objects);
+       
+       v2s16 mhm_p = p2d * hm_split;
+       f32 corners[4] = {
+               m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split),
+               m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)*hm_split),
+               m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)*hm_split),
+               m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)*hm_split),
+       };
+       
+       float avgheight = (corners[0]+corners[1]+corners[2]+corners[3])/4.0;
+       float avgslope = 0.0;
+       avgslope += fabs(avgheight - corners[0]);
+       avgslope += fabs(avgheight - corners[1]);
+       avgslope += fabs(avgheight - corners[2]);
+       avgslope += fabs(avgheight - corners[3]);
+       avgslope /= 4.0;
+       avgslope /= MAP_BLOCKSIZE;
+       //dstream<<"avgslope="<<avgslope<<std::endl;
+
+       float pitness = 0.0;
+       v2f32 a;
+       a = m_heightmap->getSlope(p2d+v2s16(0,0));
+       pitness += -a.X;
+       pitness += -a.Y;
+       a = m_heightmap->getSlope(p2d+v2s16(0,1));
+       pitness += -a.X;
+       pitness += a.Y;
+       a = m_heightmap->getSlope(p2d+v2s16(1,1));
+       pitness += a.X;
+       pitness += a.Y;
+       a = m_heightmap->getSlope(p2d+v2s16(1,0));
+       pitness += a.X;
+       pitness += -a.Y;
+       pitness /= 4.0;
+       pitness /= MAP_BLOCKSIZE;
+       //dstream<<"pitness="<<pitness<<std::endl;
+       
+       /*
+               Plant some trees if there is not much slope
+       */
+       {
+               // Avgslope is the derivative of a hill
+               float t = avgslope * avgslope;
+               float a = MAP_BLOCKSIZE * 2;
+               u32 tree_max;
+               if(t > 0.03)
+                       tree_max = a / (t/0.03);
+               else
+                       tree_max = a;
+               u32 count = (rand()%(tree_max+1));
+               //u32 count = tree_max;
+               for(u32 i=0; i<count; i++)
+               {
+                       s16 x = (rand()%(MAP_BLOCKSIZE-2))+1;
+                       s16 z = (rand()%(MAP_BLOCKSIZE-2))+1;
+                       s16 y = sector->getGroundHeight(v2s16(x,z))+1;
+                       if(y < WATER_LEVEL)
+                               continue;
+                       objects->insert(v3s16(x, y, z),
+                                       SECTOR_OBJECT_TREE_1);
+               }
+       }
+       {
+               // Pitness usually goes at around -0.5...0.5
+               u32 bush_max = 0;
+               u32 a = MAP_BLOCKSIZE * 3;
+               if(pitness > 0)
+                       bush_max = (pitness*a*4);
+               if(bush_max > a)
+                       bush_max = a;
+               u32 count = (rand()%(bush_max+1));
+               for(u32 i=0; i<count; i++)
+               {
+                       s16 x = rand()%(MAP_BLOCKSIZE-0)+0;
+                       s16 z = rand()%(MAP_BLOCKSIZE-0)+0;
+                       s16 y = sector->getGroundHeight(v2s16(x,z))+1;
+                       if(y < WATER_LEVEL)
+                               continue;
+                       objects->insert(v3s16(x, y, z),
+                                       SECTOR_OBJECT_BUSH_1);
+               }
+       }
+
+       /*
+               Insert to container
+       */
+       JMutexAutoLock lock(m_sector_mutex);
+       m_sectors.insert(p2d, sector);
+       
+       return sector;
+}
+
+MapBlock * ServerMap::emergeBlock(
+               v3s16 p,
+               bool only_from_disk,
+               core::map<v3s16, MapBlock*> &changed_blocks,
+               core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
+)
+{
+       DSTACK("%s: p=(%d,%d,%d), only_from_disk=%d",
+                       __FUNCTION_NAME,
+                       p.X, p.Y, p.Z, only_from_disk);
+                       
+       /*dstream<<"ServerMap::emergeBlock(): "
+                       <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                       <<", only_from_disk="<<only_from_disk<<std::endl;*/
+       v2s16 p2d(p.X, p.Z);
+       s16 block_y = p.Y;
+       /*
+               This will create or load a sector if not found in memory.
+               If block exists on disk, it will be loaded.
+
+               NOTE: On old save formats, this will be slow, as it generates
+                     lighting on blocks for them.
+       */
+       ServerMapSector *sector = (ServerMapSector*)emergeSector(p2d);
+       assert(sector->getId() == MAPSECTOR_SERVER);
+
+       // Try to get a block from the sector
+       MapBlock *block = NULL;
+       bool not_on_disk = false;
+       try{
+               block = sector->getBlockNoCreate(block_y);
+               if(block->isDummy() == true)
+                       not_on_disk = true;
+               else
+                       return block;
+       }
+       catch(InvalidPositionException &e)
+       {
+               not_on_disk = true;
+       }
+       
+       /*
+               If block was not found on disk and not going to generate a
+               new one, make sure there is a dummy block in place.
+       */
+       if(not_on_disk && only_from_disk)
+       {
+               if(block == NULL)
+               {
+                       // Create dummy block
+                       block = new MapBlock(this, p, true);
+
+                       // Add block to sector
+                       sector->insertBlock(block);
+               }
+               // Done.
+               return block;
+       }
+
+       //dstream<<"Not found on disk, generating."<<std::endl;
+
+       /*
+               Do not generate over-limit
+       */
+       if(blockpos_over_limit(p))
+               throw InvalidPositionException("emergeBlock(): pos. over limit");
+
+       /*
+               OK; Not found.
+
+               Go on generating the block.
+
+               TODO: If a dungeon gets generated so that it's side gets
+                     revealed to the outside air, the lighting should be
+                         recalculated.
+       */
+       
+       /*
+               If block doesn't exist, create one.
+               If it exists, it is a dummy. In that case unDummify() it.
+       */
+       if(block == NULL)
+       {
+               block = sector->createBlankBlockNoInsert(block_y);
+       }
+       else
+       {
+               // Remove the block so that nobody can get a half-generated one.
+               sector->removeBlock(block);
+               // Allocate the block to be a proper one.
+               block->unDummify();
+       }
+
+       // Randomize a bit. This makes dungeons.
+       bool low_block_is_empty = false;
+       if(rand() % 4 == 0)
+               low_block_is_empty = true;
+       
+       // This is the basic material of what the visible flat ground
+       // will consist of
+       u8 material = MATERIAL_GRASS;
+       
+       s32 lowest_ground_y = 32767;
+       
+       // DEBUG
+       //sector->printHeightmaps();
+
+       for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+       for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+       {
+               //dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl;
+               float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
+
+               assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
+               
+               s16 surface_y = surface_y_f;
+               //avg_ground_y += surface_y;
+               if(surface_y < lowest_ground_y)
+                       lowest_ground_y = surface_y;
+
+               s32 surface_depth = 0;
+               
+               float slope = sector->getSlope(v2s16(x0,z0)).getLength();
+               
+               float min_slope = 0.45;
+               float max_slope = 0.85;
+               float min_slope_depth = 5.0;
+               float max_slope_depth = 0;
+               if(slope < min_slope)
+                       surface_depth = min_slope_depth;
+               else if(slope > max_slope)
+                       surface_depth = max_slope_depth;
+               else
+                       surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
+
+               for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++){
+                       s16 real_y = block_y * MAP_BLOCKSIZE + y0;
+                       MapNode n;
+                       /*
+                               Calculate lighting
+                               
+                               FIXME: If there are some man-made structures above the
+                               newly created block, they won't be taken into account.
+                       */
+                       if(real_y > surface_y)
+                               n.setLight(LIGHT_SUN);
+                       /*
+                               Calculate material
+                       */
+                       // If node is very low
+                       if(real_y <= surface_y - 10){
+                               // Create dungeons
+                               if(low_block_is_empty){
+                                       n.d = MATERIAL_AIR;
+                               }
+                               else{
+                                       n.d = MATERIAL_STONE;
+                               }
+                       }
+                       // If node is under surface level
+                       else if(real_y <= surface_y - surface_depth)
+                               n.d = MATERIAL_STONE;
+                       // If node is at or under heightmap y
+                       else if(real_y <= surface_y)
+                               n.d = material;
+                       // If node is over heightmap y
+                       else{
+                               // If under water level, it's water
+                               if(real_y < WATER_LEVEL)
+                               {
+                                       n.d = MATERIAL_WATER;
+                                       n.setLight(diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
+                               }
+                               // else air
+                               else
+                                       n.d = MATERIAL_AIR;
+                       }
+                       block->setNode(v3s16(x0,y0,z0), n);
+               }
+       }
+
+       /*
+               Calculate is_underground
+       */
+       // Probably underground if the highest part of block is under lowest
+       // ground height
+       bool is_underground = (block_y+1) * MAP_BLOCKSIZE < lowest_ground_y;
+       block->setIsUnderground(is_underground);
+
+       /*
+               Add some minerals
+       */
+
+       if(is_underground && low_block_is_empty == false)
+       {
+               s16 underground_level = lowest_ground_y/MAP_BLOCKSIZE - block_y;
+               for(s16 i=0; i<underground_level*3; i++)
+               {
+                       if(rand()%2 == 0)
+                       {
+                               v3s16 cp(
+                                       /*(rand()%(MAP_BLOCKSIZE-4))+2,
+                                       (rand()%(MAP_BLOCKSIZE-4))+2,
+                                       (rand()%(MAP_BLOCKSIZE-4))+2*/
+                                       (rand()%(MAP_BLOCKSIZE-2))+1,
+                                       (rand()%(MAP_BLOCKSIZE-2))+1,
+                                       (rand()%(MAP_BLOCKSIZE-2))+1
+                               );
+
+                               MapNode n;
+                               n.d = MATERIAL_MESE;
+                               
+                               if(rand()%8 == 0)
+                                       block->setNode(cp, n);
+
+                               for(u16 i=0; i<26; i++)
+                               {
+                                       if(rand()%8 == 0)
+                                               block->setNode(cp+g_26dirs[i], n);
+                               }
+                       }
+               }
+       }
+       
+       /*
+               Create a few rats in empty blocks underground
+       */
+       if(is_underground && low_block_is_empty == true)
+       {
+               //for(u16 i=0; i<2; i++)
+               {
+                       v3s16 pos(8, 1, 8);
+                       RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
+                       block->addObject(obj);
+               }
+       }
+       
+       /*
+               TODO: REMOVE
+               DEBUG
+               Add some objects to the block for testing.
+       */
+       /*if(p == v3s16(0,0,0))
+       {
+               //TestObject *obj = new TestObject(NULL, -1, v3f(BS*8,BS*8,BS*8));
+               Test2Object *obj = new Test2Object(NULL, -1, v3f(BS*8,BS*15,BS*8));
+               block->addObject(obj);
+       }*/
+       
+       /*
+       {
+               v3s16 pos(8, 11, 8);
+               SignObject *obj = new SignObject(NULL, -1, intToFloat(pos));
+               obj->setText("Moicka");
+               obj->setYaw(45);
+               block->addObject(obj);
+       }
+
+       {
+               v3s16 pos(8, 11, 8);
+               RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
+               block->addObject(obj);
+       }
+       */
+
+       /*
+               Add block to sector.
+       */
+       sector->insertBlock(block);
+       
+       // An y-wise container if changed blocks
+       core::map<s16, MapBlock*> changed_blocks_sector;
+
+       /*
+               Check if any sector's objects can be placed now.
+               If so, place them.
+       */
+       core::map<v3s16, u8> *objects = sector->getObjects();
+       core::list<v3s16> objects_to_remove;
+       for(core::map<v3s16, u8>::Iterator i = objects->getIterator();
+                       i.atEnd() == false; i++)
+       {
+               v3s16 p = i.getNode()->getKey();
+               u8 d = i.getNode()->getValue();
+
+               //v3s16 p = p_sector - v3s16(0, block_y*MAP_BLOCKSIZE, 0);
+               
+               try
+               {
+
+               if(d == SECTOR_OBJECT_TEST)
+               {
+                       if(sector->isValidArea(p + v3s16(0,0,0),
+                                       p + v3s16(0,0,0), &changed_blocks_sector))
+                       {
+                               MapNode n;
+                               n.d = MATERIAL_LIGHT;
+                               sector->setNode(p, n);
+                               objects_to_remove.push_back(p);
+                       }
+               }
+               else if(d == SECTOR_OBJECT_TREE_1)
+               {
+                       v3s16 p_min = p + v3s16(-1,0,-1);
+                       v3s16 p_max = p + v3s16(1,4,1);
+                       if(sector->isValidArea(p_min, p_max,
+                                       &changed_blocks_sector))
+                       {
+                               MapNode n;
+                               n.d = MATERIAL_TREE;
+                               sector->setNode(p+v3s16(0,0,0), n);
+                               sector->setNode(p+v3s16(0,1,0), n);
+                               sector->setNode(p+v3s16(0,2,0), n);
+                               sector->setNode(p+v3s16(0,3,0), n);
+
+                               n.d = MATERIAL_LEAVES;
+
+                               sector->setNode(p+v3s16(0,4,0), n);
+                               
+                               sector->setNode(p+v3s16(-1,4,0), n);
+                               sector->setNode(p+v3s16(1,4,0), n);
+                               sector->setNode(p+v3s16(0,4,-1), n);
+                               sector->setNode(p+v3s16(0,4,1), n);
+                               sector->setNode(p+v3s16(1,4,1), n);
+                               sector->setNode(p+v3s16(-1,4,1), n);
+                               sector->setNode(p+v3s16(-1,4,-1), n);
+                               sector->setNode(p+v3s16(1,4,-1), n);
+
+                               sector->setNode(p+v3s16(-1,3,0), n);
+                               sector->setNode(p+v3s16(1,3,0), n);
+                               sector->setNode(p+v3s16(0,3,-1), n);
+                               sector->setNode(p+v3s16(0,3,1), n);
+                               sector->setNode(p+v3s16(1,3,1), n);
+                               sector->setNode(p+v3s16(-1,3,1), n);
+                               sector->setNode(p+v3s16(-1,3,-1), n);
+                               sector->setNode(p+v3s16(1,3,-1), n);
+                               
+                               objects_to_remove.push_back(p);
+                               
+                               // Lighting has to be recalculated for this one.
+                               sector->getBlocksInArea(p_min, p_max, 
+                                               lighting_invalidated_blocks);
+                       }
+               }
+               else if(d == SECTOR_OBJECT_BUSH_1)
+               {
+                       if(sector->isValidArea(p + v3s16(0,0,0),
+                                       p + v3s16(0,0,0), &changed_blocks_sector))
+                       {
+                               MapNode n;
+                               n.d = MATERIAL_LEAVES;
+                               sector->setNode(p+v3s16(0,0,0), n);
+                               
+                               objects_to_remove.push_back(p);
+                       }
+               }
+               else
+               {
+                       dstream<<"ServerMap::emergeBlock(): "
+                                       "Invalid heightmap object"
+                                       <<std::endl;
+               }
+
+               }//try
+               catch(InvalidPositionException &e)
+               {
+                       dstream<<"WARNING: "<<__FUNCTION_NAME
+                                       <<": while inserting object "<<(int)d
+                                       <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                                       <<" InvalidPositionException.what()="
+                                       <<e.what()<<std::endl;
+                       // This is not too fatal and seems to happen sometimes.
+                       assert(0);
+               }
+       }
+
+       for(core::list<v3s16>::Iterator i = objects_to_remove.begin();
+                       i != objects_to_remove.end(); i++)
+       {
+               objects->remove(*i);
+       }
+
+       for(core::map<s16, MapBlock*>::Iterator
+                       i = changed_blocks_sector.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               MapBlock *block = i.getNode()->getValue();
+
+               changed_blocks.insert(block->getPos(), block);
+       }
+
+       return block;
+}
+
+void ServerMap::createDir(std::string path)
+{
+       if(fs::CreateDir(path) == false)
+       {
+               m_dout<<DTIME<<"ServerMap: Failed to create directory "
+                               <<"\""<<path<<"\""<<std::endl;
+               throw BaseException("ServerMap failed to create directory");
+       }
+}
+
+std::string ServerMap::getSectorSubDir(v2s16 pos)
+{
+       char cc[9];
+       snprintf(cc, 9, "%.4x%.4x",
+                       (unsigned int)pos.X&0xffff,
+                       (unsigned int)pos.Y&0xffff);
+
+       return std::string(cc);
+}
+
+std::string ServerMap::getSectorDir(v2s16 pos)
+{
+       return m_savedir + "/sectors/" + getSectorSubDir(pos);
+}
+
+v2s16 ServerMap::getSectorPos(std::string dirname)
+{
+       if(dirname.size() != 8)
+               throw InvalidFilenameException("Invalid sector directory name");
+       unsigned int x, y;
+       int r = sscanf(dirname.c_str(), "%4x%4x", &x, &y);
+       if(r != 2)
+               throw InvalidFilenameException("Invalid sector directory name");
+       v2s16 pos((s16)x, (s16)y);
+       return pos;
+}
+
+v3s16 ServerMap::getBlockPos(std::string sectordir, std::string blockfile)
+{
+       v2s16 p2d = getSectorPos(sectordir);
+
+       if(blockfile.size() != 4){
+               throw InvalidFilenameException("Invalid block filename");
+       }
+       unsigned int y;
+       int r = sscanf(blockfile.c_str(), "%4x", &y);
+       if(r != 1)
+               throw InvalidFilenameException("Invalid block filename");
+       return v3s16(p2d.X, y, p2d.Y);
+}
+
+// Debug helpers
+#define ENABLE_SECTOR_SAVING 1
+#define ENABLE_SECTOR_LOADING 1
+#define ENABLE_BLOCK_SAVING 1
+#define ENABLE_BLOCK_LOADING 1
+
+void ServerMap::save(bool only_changed)
+{
+       DSTACK(__FUNCTION_NAME);
+       if(m_map_saving_enabled == false)
+       {
+               dstream<<DTIME<<"WARNING: Not saving map, saving disabled."<<std::endl;
+               return;
+       }
+       
+       if(only_changed == false)
+               dstream<<DTIME<<"ServerMap: Saving whole map, this can take time."
+                               <<std::endl;
+       
+       saveMasterHeightmap();
+       
+       u32 sector_meta_count = 0;
+       u32 block_count = 0;
+       
+       { //sectorlock
+       JMutexAutoLock lock(m_sector_mutex);
+       
+       core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+       for(; i.atEnd() == false; i++)
+       {
+               ServerMapSector *sector = (ServerMapSector*)i.getNode()->getValue();
+               assert(sector->getId() == MAPSECTOR_SERVER);
+               
+               if(ENABLE_SECTOR_SAVING)
+               {
+                       if(sector->differs_from_disk || only_changed == false)
+                       {
+                               saveSectorMeta(sector);
+                               sector_meta_count++;
+                       }
+               }
+               if(ENABLE_BLOCK_SAVING)
+               {
+                       core::list<MapBlock*> blocks;
+                       sector->getBlocks(blocks);
+                       core::list<MapBlock*>::Iterator j;
+                       for(j=blocks.begin(); j!=blocks.end(); j++)
+                       {
+                               MapBlock *block = *j;
+                               if(block->getChangedFlag() || only_changed == false)
+                               {
+                                       saveBlock(block);
+                                       block_count++;
+                               }
+                       }
+               }
+       }
+
+       }//sectorlock
+       
+       u32 deleted_count = 0;
+       deleted_count = deleteUnusedSectors
+                       (SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT);
+       
+       /*
+               Only print if something happened or saved whole map
+       */
+       if(only_changed == false || sector_meta_count != 0
+                       || block_count != 0 || deleted_count != 0)
+       {
+               dstream<<DTIME<<"ServerMap: Written: "
+                               <<sector_meta_count<<" sector metadata files, "
+                               <<block_count<<" block files, "
+                               <<deleted_count<<" sectors unloaded from memory."
+                               <<std::endl;
+       }
+}
+
+void ServerMap::loadAll()
+{
+       DSTACK(__FUNCTION_NAME);
+       dstream<<DTIME<<"ServerMap: Loading map..."<<std::endl;
+
+       loadMasterHeightmap();
+
+       std::vector<fs::DirListNode> list = fs::GetDirListing(m_savedir+"/sectors/");
+
+       dstream<<DTIME<<"There are "<<list.size()<<" sectors."<<std::endl;
+       
+       JMutexAutoLock lock(m_sector_mutex);
+       
+       s32 counter = 0;
+       s32 printed_counter = -100000;
+       s32 count = list.size();
+
+       std::vector<fs::DirListNode>::iterator i;
+       for(i=list.begin(); i!=list.end(); i++)
+       {
+               if(counter > printed_counter + 10)
+               {
+                       dstream<<DTIME<<counter<<"/"<<count<<std::endl;
+                       printed_counter = counter;
+               }
+               counter++;
+
+               MapSector *sector = NULL;
+
+               // We want directories
+               if(i->dir == false)
+                       continue;
+               try{
+                       sector = loadSectorMeta(i->name);
+               }
+               catch(InvalidFilenameException &e)
+               {
+                       // This catches unknown crap in directory
+               }
+               
+               if(ENABLE_BLOCK_LOADING)
+               {
+                       std::vector<fs::DirListNode> list2 = fs::GetDirListing
+                                       (m_savedir+"/sectors/"+i->name);
+                       std::vector<fs::DirListNode>::iterator i2;
+                       for(i2=list2.begin(); i2!=list2.end(); i2++)
+                       {
+                               // We want files
+                               if(i2->dir)
+                                       continue;
+                               try{
+                                       loadBlock(i->name, i2->name, sector);
+                               }
+                               catch(InvalidFilenameException &e)
+                               {
+                                       // This catches unknown crap in directory
+                               }
+                       }
+               }
+       }
+       dstream<<DTIME<<"ServerMap: Map loaded."<<std::endl;
+}
+
+void ServerMap::saveMasterHeightmap()
+{
+       DSTACK(__FUNCTION_NAME);
+       createDir(m_savedir);
+       
+       std::string fullpath = m_savedir + "/master_heightmap";
+       std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+       if(o.good() == false)
+               throw FileNotGoodException("Cannot open master heightmap");
+       
+       // Format used for writing
+       u8 version = SER_FMT_VER_HIGHEST;
+
+#if 0
+       SharedBuffer<u8> hmdata = m_heightmap->serialize(version);
+       /*
+               [0] u8 serialization version
+               [1] X master heightmap
+       */
+       u32 fullsize = 1 + hmdata.getSize();
+       SharedBuffer<u8> data(fullsize);
+
+       data[0] = version;
+       memcpy(&data[1], *hmdata, hmdata.getSize());
+
+       o.write((const char*)*data, fullsize);
+#endif
+       
+       m_heightmap->serialize(o, version);
+}
+
+void ServerMap::loadMasterHeightmap()
+{
+       DSTACK(__FUNCTION_NAME);
+       std::string fullpath = m_savedir + "/master_heightmap";
+       std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+       if(is.good() == false)
+               throw FileNotGoodException("Cannot open master heightmap");
+       
+       if(m_heightmap != NULL)
+               delete m_heightmap;
+               
+       m_heightmap = UnlimitedHeightmap::deSerialize(is);
+}
+
+void ServerMap::saveSectorMeta(ServerMapSector *sector)
+{
+       DSTACK(__FUNCTION_NAME);
+       // Format used for writing
+       u8 version = SER_FMT_VER_HIGHEST;
+       // Get destination
+       v2s16 pos = sector->getPos();
+       createDir(m_savedir);
+       createDir(m_savedir+"/sectors");
+       std::string dir = getSectorDir(pos);
+       createDir(dir);
+       
+       std::string fullpath = dir + "/heightmap";
+       std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+       if(o.good() == false)
+               throw FileNotGoodException("Cannot open master heightmap");
+
+       sector->serialize(o, version);
+       
+       sector->differs_from_disk = false;
+}
+
+MapSector* ServerMap::loadSectorMeta(std::string dirname)
+{
+       DSTACK(__FUNCTION_NAME);
+       // Get destination
+       v2s16 p2d = getSectorPos(dirname);
+       std::string dir = m_savedir + "/sectors/" + dirname;
+       
+       std::string fullpath = dir + "/heightmap";
+       std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+       if(is.good() == false)
+               throw FileNotGoodException("Cannot open sector heightmap");
+
+       ServerMapSector *sector = ServerMapSector::deSerialize
+                       (is, this, p2d, &m_hwrapper, m_sectors);
+       
+       sector->differs_from_disk = false;
+
+       return sector;
+}
+
+bool ServerMap::loadSectorFull(v2s16 p2d)
+{
+       DSTACK(__FUNCTION_NAME);
+       std::string sectorsubdir = getSectorSubDir(p2d);
+
+       MapSector *sector = NULL;
+
+       JMutexAutoLock lock(m_sector_mutex);
+
+       try{
+               sector = loadSectorMeta(sectorsubdir);
+       }
+       catch(InvalidFilenameException &e)
+       {
+               return false;
+       }
+       catch(FileNotGoodException &e)
+       {
+               return false;
+       }
+       catch(std::exception &e)
+       {
+               return false;
+       }
+
+       if(ENABLE_BLOCK_LOADING)
+       {
+               std::vector<fs::DirListNode> list2 = fs::GetDirListing
+                               (m_savedir+"/sectors/"+sectorsubdir);
+               std::vector<fs::DirListNode>::iterator i2;
+               for(i2=list2.begin(); i2!=list2.end(); i2++)
+               {
+                       // We want files
+                       if(i2->dir)
+                               continue;
+                       try{
+                               loadBlock(sectorsubdir, i2->name, sector);
+                       }
+                       catch(InvalidFilenameException &e)
+                       {
+                               // This catches unknown crap in directory
+                       }
+               }
+       }
+       return true;
+}
+
+#if 0
+bool ServerMap::deFlushSector(v2s16 p2d)
+{
+       DSTACK(__FUNCTION_NAME);
+       // See if it already exists in memory
+       try{
+               MapSector *sector = getSectorNoGenerate(p2d);
+               return true;
+       }
+       catch(InvalidPositionException &e)
+       {
+               /*
+                       Try to load the sector from disk.
+               */
+               if(loadSectorFull(p2d) == true)
+               {
+                       return true;
+               }
+       }
+       return false;
+}
+#endif
+
+void ServerMap::saveBlock(MapBlock *block)
+{
+       DSTACK(__FUNCTION_NAME);
+       /*
+               Dummy blocks are not written
+       */
+       if(block->isDummy())
+       {
+               /*v3s16 p = block->getPos();
+               dstream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block "
+                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+               return;
+       }
+
+       // Format used for writing
+       u8 version = SER_FMT_VER_HIGHEST;
+       // Get destination
+       v3s16 p3d = block->getPos();
+       v2s16 p2d(p3d.X, p3d.Z);
+       createDir(m_savedir);
+       createDir(m_savedir+"/sectors");
+       std::string dir = getSectorDir(p2d);
+       createDir(dir);
+       
+       // Block file is map/sectors/xxxxxxxx/xxxx
+       char cc[5];
+       snprintf(cc, 5, "%.4x", (unsigned int)p3d.Y&0xffff);
+       std::string fullpath = dir + "/" + cc;
+       std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+       if(o.good() == false)
+               throw FileNotGoodException("Cannot open block data");
+
+       /*
+               [0] u8 serialization version
+               [1] data
+       */
+       o.write((char*)&version, 1);
+       
+       block->serialize(o, version);
+
+       /*
+               Versions up from 9 have block objects.
+       */
+       if(version >= 9)
+       {
+               block->serializeObjects(o, version);
+       }
+       
+       // We just wrote it to the disk
+       block->resetChangedFlag();
+}
+
+void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSector *sector)
+{
+       DSTACK(__FUNCTION_NAME);
+       // Block file is map/sectors/xxxxxxxx/xxxx
+       std::string fullpath = m_savedir+"/sectors/"+sectordir+"/"+blockfile;
+       std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+       if(is.good() == false)
+               throw FileNotGoodException("Cannot open block file");
+
+       v3s16 p3d = getBlockPos(sectordir, blockfile);
+       v2s16 p2d(p3d.X, p3d.Z);
+       
+       assert(sector->getPos() == p2d);
+       
+       u8 version = SER_FMT_VER_INVALID;
+       is.read((char*)&version, 1);
+
+       /*u32 block_size = MapBlock::serializedLength(version);
+       SharedBuffer<u8> data(block_size);
+       is.read((char*)*data, block_size);*/
+
+       // This will always return a sector because we're the server
+       //MapSector *sector = emergeSector(p2d);
+
+       MapBlock *block = NULL;
+       bool created_new = false;
+       try{
+               block = sector->getBlockNoCreate(p3d.Y);
+       }
+       catch(InvalidPositionException &e)
+       {
+               block = sector->createBlankBlockNoInsert(p3d.Y);
+               created_new = true;
+       }
+
+       block->deSerialize(is, version);
+       
+       /*
+               Versions up from 9 have block objects.
+       */
+       if(version >= 9)
+       {
+               block->updateObjects(is, version, NULL);
+       }
+
+       if(created_new)
+               sector->insertBlock(block);
+       
+       /*
+               Convert old formats to new and save
+       */
+
+       if(version == 0 || version == 1)
+       {
+               dstream<<"Block ("<<p3d.X<<","<<p3d.Y<<","<<p3d.Z<<")"
+                               " is in old format. Updating lighting and saving"
+                               " modified blocks in new format."<<std::endl;
+
+               // Old version has zero lighting, update it
+               core::map<v3s16, MapBlock*> blocks_changed;
+               blocks_changed.insert(block->getPos(), block);
+               core::map<v3s16, MapBlock*> modified_blocks;
+               updateLighting(blocks_changed, modified_blocks);
+               
+               // Close input file
+               is.close();
+               
+               // Save modified blocks
+               core::map<v3s16, MapBlock * >::Iterator i = modified_blocks.getIterator();
+               for(; i.atEnd() == false; i++)
+               {
+                       MapBlock *b2 = i.getNode()->getValue();
+                       saveBlock(b2);
+               }
+       }
+       // Save blocks in new format
+       else if(version < SER_FMT_VER_HIGHEST)
+       {
+               saveBlock(block);
+       }
+       
+       // We just loaded it from the disk, so it's up-to-date.
+       block->resetChangedFlag();
+}
+
+// Gets from master heightmap
+void ServerMap::getSectorCorners(v2s16 p2d, s16 *corners)
+{
+       assert(m_heightmap != NULL);
+       /*
+               Corner definition:
+               v2s16(0,0),
+               v2s16(1,0),
+               v2s16(1,1),
+               v2s16(0,1),
+       */
+       corners[0] = m_heightmap->getGroundHeight
+                       ((p2d+v2s16(0,0))*SECTOR_HEIGHTMAP_SPLIT);
+       corners[1] = m_heightmap->getGroundHeight
+                       ((p2d+v2s16(1,0))*SECTOR_HEIGHTMAP_SPLIT);
+       corners[2] = m_heightmap->getGroundHeight
+                       ((p2d+v2s16(1,1))*SECTOR_HEIGHTMAP_SPLIT);
+       corners[3] = m_heightmap->getGroundHeight
+                       ((p2d+v2s16(0,1))*SECTOR_HEIGHTMAP_SPLIT);
+}
+
+void ServerMap::PrintInfo(std::ostream &out)
+{
+       out<<"ServerMap: ";
+}
+
+/*
+       ClientMap
+*/
+
+ClientMap::ClientMap(
+               Client *client,
+               video::SMaterial *materials,
+               scene::ISceneNode* parent,
+               scene::ISceneManager* mgr,
+               s32 id
+):
+       Map(dout_client),
+       scene::ISceneNode(parent, mgr, id),
+       m_client(client),
+       m_materials(materials),
+       mesh(NULL)
+{
+       /*m_box = core::aabbox3d<f32>(0,0,0,
+                       map->getW()*BS, map->getH()*BS, map->getD()*BS);*/
+       /*m_box = core::aabbox3d<f32>(0,0,0,
+                       map->getSizeNodes().X * BS,
+                       map->getSizeNodes().Y * BS,
+                       map->getSizeNodes().Z * BS);*/
+       m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000,
+                       BS*1000000,BS*1000000,BS*1000000);
+
+       mesh_mutex.Init();
+}
+
+ClientMap::~ClientMap()
+{
+       JMutexAutoLock lock(mesh_mutex);
+       
+       if(mesh != NULL)
+       {
+               mesh->drop();
+               mesh = NULL;
+       }
+}
+
+MapSector * ClientMap::emergeSector(v2s16 p2d)
+{
+       DSTACK(__FUNCTION_NAME);
+       // Check that it doesn't exist already
+       try{
+               return getSectorNoGenerate(p2d);
+       }
+       catch(InvalidPositionException &e)
+       {
+       }
+       
+       // Create a sector with no heightmaps
+       ClientMapSector *sector = new ClientMapSector(this, p2d);
+       
+       {
+               JMutexAutoLock lock(m_sector_mutex);
+               m_sectors.insert(p2d, sector);
+       }
+       
+       return sector;
+}
+
+void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
+{
+       DSTACK(__FUNCTION_NAME);
+       ClientMapSector *sector = NULL;
+
+       JMutexAutoLock lock(m_sector_mutex);
+       
+       core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d);
+
+       if(n != NULL)
+       {
+               sector = (ClientMapSector*)n->getValue();
+               assert(sector->getId() == MAPSECTOR_CLIENT);
+       }
+       else
+       {
+               sector = new ClientMapSector(this, p2d);
+               {
+                       JMutexAutoLock lock(m_sector_mutex);
+                       m_sectors.insert(p2d, sector);
+               }
+       }
+
+       sector->deSerialize(is);
+}
+
+void ClientMap::renderMap(video::IVideoDriver* driver,
+       video::SMaterial *materials, s32 pass)
+{
+       //m_dout<<DTIME<<"Rendering map..."<<std::endl;
+       DSTACK(__FUNCTION_NAME);
+
+       bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
+#if 0
+       /*
+               Draw master heightmap mesh
+       */
+       
+       {
+               JMutexAutoLock lock(mesh_mutex);
+               if(mesh != NULL)
+               {
+                       u32 c = mesh->getMeshBufferCount();
+
+                       for(u32 i=0; i<c; i++)
+                       {
+                               scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+                               const video::SMaterial& material = buf->getMaterial();
+                               video::IMaterialRenderer* rnd =
+                                               driver->getMaterialRenderer(material.MaterialType);
+                               bool transparent = (rnd && rnd->isTransparent());
+                               // Render transparent on transparent pass and likewise.
+                               if(transparent == is_transparent_pass)
+                               {
+                                       driver->setMaterial(buf->getMaterial());
+                                       driver->drawMeshBuffer(buf);
+                               }
+                       }
+               }
+       }
+#endif
+
+       /*
+               Get time for measuring timeout.
+               
+               Measuring time is very useful for long delays when the
+               machine is swapping a lot.
+       */
+       int time1 = time(0);
+
+       /*
+               Collect all blocks that are in the view range
+
+               Should not optimize more here as we want to auto-update
+               all changed nodes in viewing range at the next step.
+       */
+
+       s16 viewing_range_nodes;
+       bool viewing_range_all;
+       {
+               JMutexAutoLock lock(g_range_mutex);
+               viewing_range_nodes = g_viewing_range_nodes;
+               viewing_range_all = g_viewing_range_all;
+       }
+
+       m_camera_mutex.Lock();
+       v3f camera_position = m_camera_position;
+       v3f camera_direction = m_camera_direction;
+       m_camera_mutex.Unlock();
+
+       /*
+               Get all blocks and draw all visible ones
+       */
+
+       v3s16 cam_pos_nodes(
+                       camera_position.X / BS,
+                       camera_position.Y / BS,
+                       camera_position.Z / BS);
+
+       v3s16 box_nodes_d = viewing_range_nodes * v3s16(1,1,1);
+
+       v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
+       v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
+
+       // Take a fair amount as we will be dropping more out later
+       v3s16 p_blocks_min(
+                       p_nodes_min.X / MAP_BLOCKSIZE - 1,
+                       p_nodes_min.Y / MAP_BLOCKSIZE - 1,
+                       p_nodes_min.Z / MAP_BLOCKSIZE - 1);
+       v3s16 p_blocks_max(
+                       p_nodes_max.X / MAP_BLOCKSIZE + 1,
+                       p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+                       p_nodes_max.Z / MAP_BLOCKSIZE + 1);
+       
+       u32 vertex_count = 0;
+       
+       core::map<v2s16, MapSector*>::Iterator si;
+
+       //NOTE: The sectors map should be locked but we're not doing it
+       // because it'd cause too much delays
+
+       si = m_sectors.getIterator();
+       for(; si.atEnd() == false; si++)
+       {
+               {
+                       static int timecheck_counter = 0;
+                       timecheck_counter++;
+                       if(timecheck_counter > 50)
+                       {
+                               int time2 = time(0);
+                               if(time2 > time1 + 4)
+                               {
+                                       dstream<<"ClientMap::renderMap(): "
+                                               "Rendering takes ages, returning."
+                                               <<std::endl;
+                                       return;
+                               }
+                       }
+               }
+
+               MapSector *sector = si.getNode()->getValue();
+               v2s16 sp = sector->getPos();
+               
+               if(viewing_range_all == false)
+               {
+                       if(sp.X < p_blocks_min.X
+                       || sp.X > p_blocks_max.X
+                       || sp.Y < p_blocks_min.Z
+                       || sp.Y > p_blocks_max.Z)
+                               continue;
+               }
+
+               core::list< MapBlock * > sectorblocks;
+               sector->getBlocks(sectorblocks);
+               
+               /*
+                       Draw blocks
+               */
+
+               core::list< MapBlock * >::Iterator i;
+               for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
+               {
+                       MapBlock *block = *i;
+
+                       /*
+                               Compare block position to camera position, skip
+                               if not seen on display
+                       */
+                       
+                       v3s16 blockpos_nodes = block->getPosRelative();
+                       
+                       // Block center position
+                       v3f blockpos(
+                                       ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
+                                       ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
+                                       ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
+                       );
+
+                       // Block position relative to camera
+                       v3f blockpos_relative = blockpos - camera_position;
+
+                       // Distance in camera direction (+=front, -=back)
+                       f32 dforward = blockpos_relative.dotProduct(camera_direction);
+
+                       // Total distance
+                       f32 d = blockpos_relative.getLength();
+                       
+                       if(viewing_range_all == false)
+                       {
+                               // If block is far away, don't draw it
+                               if(d > viewing_range_nodes * BS)
+                                       continue;
+                       }
+                       
+                       // Maximum radius of a block
+                       f32 block_max_radius = 0.5*1.44*1.44*MAP_BLOCKSIZE*BS;
+                       
+                       // If block is (nearly) touching the camera, don't
+                       // bother validating further (that is, render it anyway)
+                       if(d > block_max_radius * 1.5)
+                       {
+                               // Cosine of the angle between the camera direction
+                               // and the block direction (camera_direction is an unit vector)
+                               f32 cosangle = dforward / d;
+                               
+                               // Compensate for the size of the block
+                               // (as the block has to be shown even if it's a bit off FOV)
+                               // This is an estimate.
+                               cosangle += block_max_radius / dforward;
+
+                               // If block is not in the field of view, skip it
+                               //if(cosangle < cos(FOV_ANGLE/2))
+                               if(cosangle < cos(FOV_ANGLE/2. * 4./3.))
+                                       continue;
+                       }
+                       
+                       /*
+                               Draw the faces of the block
+                       */
+                       
+                       {
+                               JMutexAutoLock lock(block->mesh_mutex);
+
+                               // Cancel if block has no mesh
+                               if(block->mesh == NULL)
+                                       continue;
+
+                               u32 c = block->mesh->getMeshBufferCount();
+
+                               for(u32 i=0; i<c; i++)
+                               {
+                                       scene::IMeshBuffer *buf = block->mesh->getMeshBuffer(i);
+                                       const video::SMaterial& material = buf->getMaterial();
+                                       video::IMaterialRenderer* rnd =
+                                                       driver->getMaterialRenderer(material.MaterialType);
+                                       bool transparent = (rnd && rnd->isTransparent());
+                                       // Render transparent on transparent pass and likewise.
+                                       if(transparent == is_transparent_pass)
+                                       {
+                                               driver->setMaterial(buf->getMaterial());
+                                               driver->drawMeshBuffer(buf);
+                                               vertex_count += buf->getVertexCount();
+                                       }
+                               }
+                       }
+               } // foreach sectorblocks
+       }
+
+       /*dstream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
+                       <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
+}
+
+void ClientMap::updateMesh()
+{
+#if 0
+       DSTACK(__FUNCTION_NAME);
+       //TODO
+       /*
+               Check what sectors don't draw anything useful at ground level
+               and create a mesh of the rough heightmap at those positions.
+       */
+
+       m_camera_mutex.Lock();
+       v3f camera_position = m_camera_position;
+       v3f camera_direction = m_camera_direction;
+       m_camera_mutex.Unlock();
+
+       v3s16 cam_pos_nodes(
+                       camera_position.X / BS,
+                       camera_position.Y / BS,
+                       camera_position.Z / BS);
+
+       v3s16 box_nodes_d = HEIGHTMAP_RANGE_NODES * v3s16(1,1,1);
+
+       v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
+       v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
+
+       // Take a fair amount as we will be dropping more out later
+       v3s16 p_blocks_min(
+                       p_nodes_min.X / MAP_BLOCKSIZE - 1,
+                       p_nodes_min.Y / MAP_BLOCKSIZE - 1,
+                       p_nodes_min.Z / MAP_BLOCKSIZE - 1);
+       v3s16 p_blocks_max(
+                       p_nodes_max.X / MAP_BLOCKSIZE + 1,
+                       p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+                       p_nodes_max.Z / MAP_BLOCKSIZE + 1);
+       
+       /*
+               Initialize new mesh
+       */
+       
+       scene::SMesh *mesh_new = new scene::SMesh();
+       //scene::IMeshBuffer *buf = NULL;
+       scene::SMeshBuffer *buf = NULL;
+
+       u8 material_in_use = 0;
+
+       /*
+               Loop through sectors
+       */
+       
+       for(core::map<v2s16, MapSector*>::Iterator
+                       si = m_sectors.getIterator();
+                       si.atEnd() == false; si++)
+       {
+               MapSector *sector = si.getNode()->getValue();
+               
+               if(sector->getId() != MAPSECTOR_CLIENT)
+               {
+                       dstream<<"WARNING: Client has a non-client sector"
+                                       <<std::endl;
+                       continue;
+               }
+               
+               ClientMapSector *cs = (ClientMapSector*)sector;
+
+               v2s16 sp = sector->getPos();
+
+               if(sp.X < p_blocks_min.X
+               || sp.X > p_blocks_max.X
+               || sp.Y < p_blocks_min.Z
+               || sp.Y > p_blocks_max.Z)
+                       continue;
+               
+               /*
+                       Get some ground level info
+               */
+               
+               s16 a = -5;
+
+               s16 cn[4] = 
+               {
+                       cs->getCorner(0)+a,
+                       cs->getCorner(1)+a,
+                       cs->getCorner(2)+a,
+                       cs->getCorner(3)+a,
+               };
+               s16 cn_avg = (cn[0]+cn[1]+cn[2]+cn[3])/4;
+               s16 cn_min = 32767;
+               s16 cn_max = -32768;
+               for(s16 i=0; i<4; i++)
+               {
+                       if(cn[i] < cn_min)
+                               cn_min = cn[i];
+                       if(cn[i] > cn_max)
+                               cn_max = cn[i];
+               }
+               s16 cn_slope = cn_max - cn_min;
+               
+               /*
+                       Generate this part of the heightmap mesh
+               */
+
+               u8 material;
+               if(cn_avg + MAP_BLOCKSIZE/4 <= WATER_LEVEL)
+                       material = 0;
+               else if(cn_slope <= MAP_BLOCKSIZE)
+                       material = 1;
+               else
+                       material = 2;
+
+               if(material != material_in_use || buf == NULL)
+               {
+                       // Try to get a meshbuffer associated with the material
+                       buf = (scene::SMeshBuffer*)mesh_new->getMeshBuffer
+                                       (g_mesh_materials[material]);
+                       // If not found, create one
+                       if(buf == NULL)
+                       {
+                               // This is a "Standard MeshBuffer",
+                               // it's a typedeffed CMeshBuffer<video::S3DVertex>
+                               buf = new scene::SMeshBuffer();
+
+                               // Set material
+                               buf->Material = g_mesh_materials[material];
+                               // Use VBO
+                               //buf->setHardwareMappingHint(scene::EHM_STATIC);
+                               // Add to mesh
+                               mesh_new->addMeshBuffer(buf);
+                               // Mesh grabbed it
+                               buf->drop();
+                       }
+                       material_in_use = material;
+               }
+
+               // Sector side width in floating-point units
+               f32 sd = BS * MAP_BLOCKSIZE;
+               // Sector position in global floating-point units
+               v3f spf = v3f((f32)sp.X, 0, (f32)sp.Y) * sd;
+
+               //video::SColor c(255,255,255,255);
+               u8 cc = 180;
+               video::SColor c(255,cc,cc,cc);
+               
+               video::S3DVertex vertices[4] =
+               {
+                       video::S3DVertex(spf.X,   (f32)BS*cn[0],spf.Z,   0,0,0, c, 0,1),
+                       video::S3DVertex(spf.X+sd,(f32)BS*cn[1],spf.Z,   0,0,0, c, 1,1),
+                       video::S3DVertex(spf.X+sd,(f32)BS*cn[2],spf.Z+sd,0,0,0, c, 1,0),
+                       video::S3DVertex(spf.X,   (f32)BS*cn[3],spf.Z+sd,0,0,0, c, 0,0),
+               };
+               u16 indices[] = {0,1,2,2,3,0};
+               
+               buf->append(vertices, 4, indices, 6);
+       }
+       
+       // Set VBO on
+       //mesh_new->setHardwareMappingHint(scene::EHM_STATIC);
+
+       /*
+               Replace the mesh
+       */
+
+       mesh_mutex.Lock();
+
+       scene::SMesh *mesh_old = mesh;
+
+       //DEBUG
+       /*mesh = NULL;
+       mesh_new->drop();*/
+       mesh = mesh_new;
+       
+       mesh_mutex.Unlock();
+
+       if(mesh_old != NULL)
+       {
+               /*dstream<<"mesh_old refcount="<<mesh_old->getReferenceCount()
+                               <<std::endl;
+               scene::IMeshBuffer *buf = mesh_new->getMeshBuffer
+                               (g_materials[MATERIAL_GRASS]);
+               if(buf != NULL)
+                       dstream<<"grass buf refcount="<<buf->getReferenceCount()
+                                       <<std::endl;*/
+
+               mesh_old->drop();
+       }
+       else
+       {
+               dstream<<"WARNING: There was no old master heightmap mesh"<<std::endl;
+       }
+#endif
+}
+
+void ClientMap::PrintInfo(std::ostream &out)
+{
+       out<<"ClientMap: ";
+}
+
+
diff --git a/src/map.h b/src/map.h
new file mode 100644 (file)
index 0000000..ca3b008
--- /dev/null
+++ b/src/map.h
@@ -0,0 +1,430 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAP_HEADER
+#define MAP_HEADER
+
+#include <jmutex.h>
+#include <jthread.h>
+#include <iostream>
+#include <malloc.h>
+
+#ifdef _WIN32
+       #include <windows.h>
+       #define sleep_s(x) Sleep((x*1000))
+#else
+       #include <unistd.h>
+       #define sleep_s(x) sleep(x)
+#endif
+
+#include "common_irrlicht.h"
+#include "heightmap.h"
+#include "loadstatus.h"
+#include "mapnode.h"
+#include "mapblock.h"
+#include "mapsector.h"
+#include "constants.h"
+
+class InvalidFilenameException : public BaseException
+{
+public:
+       InvalidFilenameException(const char *s):
+               BaseException(s)
+       {}
+};
+
+#define MAPTYPE_BASE 0
+#define MAPTYPE_SERVER 1
+#define MAPTYPE_CLIENT 2
+
+class Map : public NodeContainer, public Heightmappish
+{
+protected:
+
+       std::ostream &m_dout;
+
+       core::map<v2s16, MapSector*> m_sectors;
+       JMutex m_sector_mutex;
+
+       v3f m_camera_position;
+       v3f m_camera_direction;
+       JMutex m_camera_mutex;
+
+       // Be sure to set this to NULL when the cached sector is deleted 
+       MapSector *m_sector_cache;
+       v2s16 m_sector_cache_p;
+
+       WrapperHeightmap m_hwrapper;
+
+public:
+
+       v3s16 drawoffset; // for drawbox()
+
+       Map(std::ostream &dout);
+       virtual ~Map();
+
+       virtual u16 nodeContainerId() const
+       {
+               return NODECONTAINER_ID_MAP;
+       }
+
+       virtual s32 mapType() const
+       {
+               return MAPTYPE_BASE;
+       }
+
+       void updateCamera(v3f pos, v3f dir)
+       {
+               JMutexAutoLock lock(m_camera_mutex);
+               m_camera_position = pos;
+               m_camera_direction = dir;
+       }
+
+       /*void StartUpdater()
+       {
+               updater.Start();
+       }
+
+       void StopUpdater()
+       {
+               updater.setRun(false);
+               while(updater.IsRunning())
+                       sleep_s(1);
+       }
+
+       bool UpdaterIsRunning()
+       {
+               return updater.IsRunning();
+       }*/
+
+       static core::aabbox3d<f32> getNodeBox(v3s16 p)
+       {
+               return core::aabbox3d<f32>(
+                       (float)p.X * BS - 0.5*BS,
+                       (float)p.Y * BS - 0.5*BS,
+                       (float)p.Z * BS - 0.5*BS,
+                       (float)p.X * BS + 0.5*BS,
+                       (float)p.Y * BS + 0.5*BS,
+                       (float)p.Z * BS + 0.5*BS
+               );
+       }
+
+       //bool sectorExists(v2s16 p);
+       MapSector * getSectorNoGenerate(v2s16 p2d);
+       /*
+               This is overloaded by ClientMap and ServerMap to allow
+               their differing fetch methods.
+       */
+       virtual MapSector * emergeSector(v2s16 p) = 0;
+       
+       // Returns InvalidPositionException if not found
+       MapBlock * getBlockNoCreate(v3s16 p);
+       //virtual MapBlock * getBlock(v3s16 p, bool generate=true);
+       
+       // Returns InvalidPositionException if not found
+       f32 getGroundHeight(v2s16 p, bool generate=false);
+       void setGroundHeight(v2s16 p, f32 y, bool generate=false);
+
+       // Returns InvalidPositionException if not found
+       bool isNodeUnderground(v3s16 p);
+       
+       // virtual from NodeContainer
+       bool isValidPosition(v3s16 p)
+       {
+               v3s16 blockpos = getNodeBlockPos(p);
+               MapBlock *blockref;
+               try{
+                       blockref = getBlockNoCreate(blockpos);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       return false;
+               }
+               return true;
+               /*v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+               bool is_valid = blockref->isValidPosition(relpos);
+               return is_valid;*/
+       }
+       
+       // virtual from NodeContainer
+       MapNode getNode(v3s16 p)
+       {
+               v3s16 blockpos = getNodeBlockPos(p);
+               MapBlock * blockref = getBlockNoCreate(blockpos);
+               v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+
+               return blockref->getNode(relpos);
+       }
+
+       // virtual from NodeContainer
+       void setNode(v3s16 p, MapNode & n)
+       {
+               v3s16 blockpos = getNodeBlockPos(p);
+               MapBlock * blockref = getBlockNoCreate(blockpos);
+               v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+               blockref->setNode(relpos, n);
+       }
+
+       /*MapNode getNodeGenerate(v3s16 p)
+       {
+               v3s16 blockpos = getNodeBlockPos(p);
+               MapBlock * blockref = getBlock(blockpos);
+               v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+
+               return blockref->getNode(relpos);
+       }*/
+
+       /*void setNodeGenerate(v3s16 p, MapNode & n)
+       {
+               v3s16 blockpos = getNodeBlockPos(p);
+               MapBlock * blockref = getBlock(blockpos);
+               v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+               blockref->setNode(relpos, n);
+       }*/
+
+       void unspreadLight(core::map<v3s16, u8> & from_nodes,
+                       core::map<v3s16, bool> & light_sources,
+                       core::map<v3s16, MapBlock*> & modified_blocks);
+
+       void unLightNeighbors(v3s16 pos, u8 lightwas,
+                       core::map<v3s16, bool> & light_sources,
+                       core::map<v3s16, MapBlock*> & modified_blocks);
+       
+       void spreadLight(core::map<v3s16, bool> & from_nodes,
+                       core::map<v3s16, MapBlock*> & modified_blocks);
+       
+       void lightNeighbors(v3s16 pos,
+                       core::map<v3s16, MapBlock*> & modified_blocks);
+
+       v3s16 getBrightestNeighbour(v3s16 p);
+
+       s16 propagateSunlight(v3s16 start,
+                       core::map<v3s16, MapBlock*> & modified_blocks);
+       
+       void updateLighting(core::map<v3s16, MapBlock*>  & a_blocks,
+                       core::map<v3s16, MapBlock*> & modified_blocks);
+                       
+       /*
+               These handle lighting but not faces.
+       */
+       void addNodeAndUpdate(v3s16 p, MapNode n,
+                       core::map<v3s16, MapBlock*> &modified_blocks);
+       void removeNodeAndUpdate(v3s16 p,
+                       core::map<v3s16, MapBlock*> &modified_blocks);
+       
+       /*
+               Updates the faces of the given block and blocks on the
+               leading edge.
+       */
+       void updateMeshes(v3s16 blockpos);
+
+       //core::aabbox3d<s16> getDisplayedBlockArea();
+
+       //bool updateChangedVisibleArea();
+       
+       virtual void save(bool only_changed){assert(0);};
+
+       /*
+               Updates usage timers
+       */
+       void timerUpdate(float dtime);
+       
+       // Takes cache into account
+       // sector mutex should be locked when calling
+       void deleteSectors(core::list<v2s16> &list, bool only_blocks);
+       
+       // Returns count of deleted sectors
+       u32 deleteUnusedSectors(float timeout, bool only_blocks=false,
+                       core::list<v3s16> *deleted_blocks=NULL);
+
+       // For debug printing
+       virtual void PrintInfo(std::ostream &out);
+};
+
+struct MapgenParams
+{
+       MapgenParams()
+       {
+               heightmap_blocksize = 64;
+               height_randmax = "constant 70.0";
+               height_randfactor = "constant 0.6";
+               height_base = "linear 0 80 0";
+               plants_amount = "1.0";
+       }
+       s16 heightmap_blocksize;
+       std::string height_randmax;
+       std::string height_randfactor;
+       std::string height_base;
+       std::string plants_amount;
+};
+
+class ServerMap : public Map
+{
+public:
+       /*
+               savedir: directory to which map data should be saved
+       */
+       ServerMap(std::string savedir, MapgenParams params);
+       ~ServerMap();
+
+       s32 mapType() const
+       {
+               return MAPTYPE_SERVER;
+       }
+
+       /*
+               Forcefully get a sector from somewhere
+       */
+       MapSector * emergeSector(v2s16 p);
+       /*
+               Forcefully get a block from somewhere.
+
+               Exceptions:
+               - InvalidPositionException: possible if only_from_disk==true
+               
+               changed_blocks:
+               - All already existing blocks that were modified are added.
+                       - If found on disk, nothing will be added.
+                       - If generated, the new block will not be included.
+
+               lighting_invalidated_blocks:
+               - All blocks that have heavy-to-calculate lighting changes
+                 are added.
+                       - updateLighting() should be called for these.
+               
+               - A block that is in changed_blocks may not be in
+                 lighting_invalidated_blocks.
+       */
+       MapBlock * emergeBlock(
+                       v3s16 p,
+                       bool only_from_disk,
+                       core::map<v3s16, MapBlock*> &changed_blocks,
+                       core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
+       );
+
+       void createDir(std::string path);
+       void createSaveDir();
+       // returns something like "xxxxxxxx"
+       std::string getSectorSubDir(v2s16 pos);
+       // returns something like "map/sectors/xxxxxxxx"
+       std::string getSectorDir(v2s16 pos);
+       std::string createSectorDir(v2s16 pos);
+       // dirname: final directory name
+       v2s16 getSectorPos(std::string dirname);
+       v3s16 getBlockPos(std::string sectordir, std::string blockfile);
+
+       void save(bool only_changed);
+       void loadAll();
+
+       void saveMasterHeightmap();
+       void loadMasterHeightmap();
+
+       // The sector mutex should be locked when calling most of these
+       
+       // This only saves sector-specific data such as the heightmap
+       // (no MapBlocks)
+       void saveSectorMeta(ServerMapSector *sector);
+       MapSector* loadSectorMeta(std::string dirname);
+       
+       // Full load of a sector including all blocks.
+       // returns true on success, false on failure.
+       bool loadSectorFull(v2s16 p2d);
+       // If sector is not found in memory, try to load it from disk.
+       // Returns true if sector now resides in memory
+       //bool deFlushSector(v2s16 p2d);
+       
+       void saveBlock(MapBlock *block);
+       // This will generate a sector with getSector if not found.
+       void loadBlock(std::string sectordir, std::string blockfile, MapSector *sector);
+
+       // Gets from master heightmap
+       void getSectorCorners(v2s16 p2d, s16 *corners);
+
+       // For debug printing
+       virtual void PrintInfo(std::ostream &out);
+
+private:
+       UnlimitedHeightmap *m_heightmap;
+       std::string m_savedir;
+       bool m_map_saving_enabled;
+};
+
+class Client;
+
+class ClientMap : public Map, public scene::ISceneNode
+{
+public:
+       ClientMap(
+                       Client *client,
+                       video::SMaterial *materials,
+                       scene::ISceneNode* parent,
+                       scene::ISceneManager* mgr,
+                       s32 id
+       );
+
+       ~ClientMap();
+
+       s32 mapType() const
+       {
+               return MAPTYPE_CLIENT;
+       }
+
+       /*
+               Forcefully get a sector from somewhere
+       */
+       MapSector * emergeSector(v2s16 p);
+
+       void deSerializeSector(v2s16 p2d, std::istream &is);
+
+       /*
+               ISceneNode methods
+       */
+
+       virtual void OnRegisterSceneNode()
+       {
+               if(IsVisible)
+               {
+                       //SceneManager->registerNodeForRendering(this, scene::ESNRP_SKY_BOX);
+                       SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
+                       SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
+               }
+
+               ISceneNode::OnRegisterSceneNode();
+       }
+
+       virtual void render()
+       {
+               video::IVideoDriver* driver = SceneManager->getVideoDriver();
+               driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
+               renderMap(driver, m_materials, SceneManager->getSceneNodeRenderPass());
+       }
+       
+       virtual const core::aabbox3d<f32>& getBoundingBox() const
+       {
+               return m_box;
+       }
+
+       void renderMap(video::IVideoDriver* driver,
+               video::SMaterial *materials, s32 pass);
+
+       // Update master heightmap mesh
+       void updateMesh();
+
+       // For debug printing
+       virtual void PrintInfo(std::ostream &out);
+       
+private:
+       Client *m_client;
+       
+       video::SMaterial *m_materials;
+
+       core::aabbox3d<f32> m_box;
+       
+       // This is the master heightmap mesh
+       scene::SMesh *mesh;
+       JMutex mesh_mutex;
+};
+
+#endif
+
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
new file mode 100644 (file)
index 0000000..2556100
--- /dev/null
@@ -0,0 +1,698 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "mapblock.h"
+#include "map.h"
+// For g_materials
+#include "main.h"
+#include "light.h"
+#include <sstream>
+
+
+/*
+       MapBlock
+*/
+
+bool MapBlock::isValidPositionParent(v3s16 p)
+{
+       if(isValidPosition(p))
+       {
+               return true;
+       }
+       else{
+               return m_parent->isValidPosition(getPosRelative() + p);
+       }
+}
+
+MapNode MapBlock::getNodeParent(v3s16 p)
+{
+       if(isValidPosition(p) == false)
+       {
+               return m_parent->getNode(getPosRelative() + p);
+       }
+       else
+       {
+               if(data == NULL)
+                       throw InvalidPositionException();
+               return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
+       }
+}
+
+void MapBlock::setNodeParent(v3s16 p, MapNode & n)
+{
+       if(isValidPosition(p) == false)
+       {
+               m_parent->setNode(getPosRelative() + p, n);
+       }
+       else
+       {
+               if(data == NULL)
+                       throw InvalidPositionException();
+               data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
+       }
+}
+
+FastFace * MapBlock::makeFastFace(u8 material, u8 light, v3f p,
+               v3f dir, v3f scale, v3f posRelative_f)
+{
+       FastFace *f = new FastFace;
+       
+       // Position is at the center of the cube.
+       v3f pos = p * BS;
+       posRelative_f *= BS;
+
+       v3f vertex_pos[4];
+       // If looking towards z+, this is the face that is behind
+       // the center point, facing towards z+.
+       vertex_pos[0] = v3f( BS/2,-BS/2,BS/2);
+       vertex_pos[1] = v3f(-BS/2,-BS/2,BS/2);
+       vertex_pos[2] = v3f(-BS/2, BS/2,BS/2);
+       vertex_pos[3] = v3f( BS/2, BS/2,BS/2);
+       
+       /*
+               TODO: Rotate it the right way (one side comes upside down)
+       */
+       core::CMatrix4<f32> m;
+       m.buildRotateFromTo(v3f(0,0,1), dir);
+       
+       for(u16 i=0; i<4; i++){
+               m.rotateVect(vertex_pos[i]);
+               vertex_pos[i].X *= scale.X;
+               vertex_pos[i].Y *= scale.Y;
+               vertex_pos[i].Z *= scale.Z;
+               vertex_pos[i] += pos + posRelative_f;
+       }
+
+       f32 abs_scale = 1.;
+       if     (scale.X < 0.999 || scale.X > 1.001) abs_scale = scale.X;
+       else if(scale.Y < 0.999 || scale.Y > 1.001) abs_scale = scale.Y;
+       else if(scale.Z < 0.999 || scale.Z > 1.001) abs_scale = scale.Z;
+
+       v3f zerovector = v3f(0,0,0);
+       
+       u8 li = decode_light(light);
+       //u8 li = 150;
+
+       u8 alpha = 255;
+
+       if(material == MATERIAL_WATER)
+       {
+               alpha = 128;
+       }
+
+       video::SColor c = video::SColor(alpha,li,li,li);
+
+       /*f->vertices[0] = video::S3DVertex(vertex_pos[0], zerovector, c,
+                       core::vector2d<f32>(0,1));
+       f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c,
+                       core::vector2d<f32>(abs_scale,1));
+       f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c,
+                       core::vector2d<f32>(abs_scale,0));
+       f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c,
+                       core::vector2d<f32>(0,0));*/
+       f->vertices[0] = video::S3DVertex(vertex_pos[0], zerovector, c,
+                       core::vector2d<f32>(0,1));
+       f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c,
+                       core::vector2d<f32>(abs_scale,1));
+       f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c,
+                       core::vector2d<f32>(abs_scale,0));
+       f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c,
+                       core::vector2d<f32>(0,0));
+
+       f->material = material;
+
+       return f;
+}
+       
+/*
+       Parameters must consist of air and !air.
+       Order doesn't matter.
+
+       If either of the nodes doesn't exist, light is 0.
+*/
+u8 MapBlock::getFaceLight(v3s16 p, v3s16 face_dir)
+{
+       try{
+               MapNode n = getNodeParent(p);
+               MapNode n2 = getNodeParent(p + face_dir);
+               u8 light;
+               if(n.solidness() < n2.solidness())
+                       light = n.getLight();
+               else
+                       light = n2.getLight();
+
+               // Make some nice difference to different sides
+               if(face_dir.X == 1 || face_dir.Z == 1 || face_dir.Y == -1)
+                       light = diminish_light(diminish_light(light));
+               else if(face_dir.X == -1 || face_dir.Z == -1)
+                       light = diminish_light(light);
+
+               return light;
+       }
+       catch(InvalidPositionException &e)
+       {
+               return 0;
+       }
+}
+
+/*
+       Gets node material from any place relative to block.
+       Returns MATERIAL_AIR if doesn't exist.
+*/
+u8 MapBlock::getNodeMaterial(v3s16 p)
+{
+       try{
+               MapNode n = getNodeParent(p);
+               return n.d;
+       }
+       catch(InvalidPositionException &e)
+       {
+               return MATERIAL_IGNORE;
+       }
+}
+
+/*
+       startpos:
+       translate_dir: unit vector with only one of x, y or z
+       face_dir: unit vector with only one of x, y or z
+*/
+void MapBlock::updateFastFaceRow(v3s16 startpos,
+               u16 length,
+               v3s16 translate_dir,
+               v3s16 face_dir,
+               core::list<FastFace*> &dest)
+{
+       /*
+               Precalculate some variables
+       */
+       v3f translate_dir_f(translate_dir.X, translate_dir.Y,
+                       translate_dir.Z); // floating point conversion
+       v3f face_dir_f(face_dir.X, face_dir.Y,
+                       face_dir.Z); // floating point conversion
+       v3f posRelative_f(getPosRelative().X, getPosRelative().Y,
+                       getPosRelative().Z); // floating point conversion
+
+       v3s16 p = startpos;
+       /*
+               The light in the air lights the surface is taken from
+               the node that is air.
+       */
+       u8 light = getFaceLight(p, face_dir);
+       
+       u16 continuous_materials_count = 0;
+       
+       u8 material0 = getNodeMaterial(p);
+       u8 material1 = getNodeMaterial(p + face_dir);
+               
+       for(u16 j=0; j<length; j++)
+       {
+               bool next_is_different = true;
+               
+               v3s16 p_next;
+               u8 material0_next = 0;
+               u8 material1_next = 0;
+               u8 light_next = 0;
+
+               if(j != length - 1){
+                       p_next = p + translate_dir;
+                       material0_next = getNodeMaterial(p_next);
+                       material1_next = getNodeMaterial(p_next + face_dir);
+                       light_next = getFaceLight(p_next, face_dir);
+
+                       if(material0_next == material0
+                                       && material1_next == material1
+                                       && light_next == light)
+                       {
+                               next_is_different = false;
+                       }
+               }
+
+               continuous_materials_count++;
+               
+               if(next_is_different)
+               {
+                       /*
+                               Create a face if there should be one
+                       */
+                       u8 mf = face_materials(material0, material1);
+                       
+                       if(mf != 0)
+                       {
+                               // Floating point conversion of the position vector
+                               v3f pf(p.X, p.Y, p.Z);
+                               // Center point of face (kind of)
+                               v3f sp = pf - ((f32)continuous_materials_count / 2. - 0.5) * translate_dir_f;
+                               v3f scale(1,1,1);
+                               if(translate_dir.X != 0){
+                                       scale.X = continuous_materials_count;
+                               }
+                               if(translate_dir.Y != 0){
+                                       scale.Y = continuous_materials_count;
+                               }
+                               if(translate_dir.Z != 0){
+                                       scale.Z = continuous_materials_count;
+                               }
+                               
+                               FastFace *f;
+
+                               // If node at sp (material0) is more solid
+                               if(mf == 1)
+                               {
+                                       f = makeFastFace(material0, light,
+                                                       sp, face_dir_f, scale,
+                                                       posRelative_f);
+                               }
+                               // If node at sp is less solid (mf == 2)
+                               else
+                               {
+                                       f = makeFastFace(material1, light,
+                                                       sp+face_dir_f, -1*face_dir_f, scale,
+                                                       posRelative_f);
+                               }
+                               dest.push_back(f);
+                       }
+
+                       continuous_materials_count = 0;
+                       material0 = material0_next;
+                       material1 = material1_next;
+                       light = light_next;
+               }
+               
+               p = p_next;
+       }
+}
+
+void MapBlock::updateMesh()
+{
+       /*v3s16 p = getPosRelative();
+       std::cout<<"MapBlock("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                       <<"::updateMesh(): ";*/
+                       //<<"::updateMesh()"<<std::endl;
+       
+       /*
+               TODO: Change this to directly generate the mesh (and get rid
+                     of FastFaces)
+       */
+
+       core::list<FastFace*> *fastfaces_new = new core::list<FastFace*>;
+       
+       /*
+               We are including the faces of the trailing edges of the block.
+               This means that when something changes, the caller must
+               also update the meshes of the blocks at the leading edges.
+       */
+
+       /*
+               Go through every y,z and get top faces in rows of x+
+       */
+       for(s16 y=0; y<MAP_BLOCKSIZE; y++){
+       //for(s16 y=-1; y<MAP_BLOCKSIZE; y++){
+               for(s16 z=0; z<MAP_BLOCKSIZE; z++){
+                       updateFastFaceRow(v3s16(0,y,z), MAP_BLOCKSIZE,
+                                       v3s16(1,0,0),
+                                       v3s16(0,1,0),
+                                       *fastfaces_new);
+               }
+       }
+       /*
+               Go through every x,y and get right faces in rows of z+
+       */
+       for(s16 x=0; x<MAP_BLOCKSIZE; x++){
+       //for(s16 x=-1; x<MAP_BLOCKSIZE; x++){
+               for(s16 y=0; y<MAP_BLOCKSIZE; y++){
+                       updateFastFaceRow(v3s16(x,y,0), MAP_BLOCKSIZE,
+                                       v3s16(0,0,1),
+                                       v3s16(1,0,0),
+                                       *fastfaces_new);
+               }
+       }
+       /*
+               Go through every y,z and get back faces in rows of x+
+       */
+       for(s16 z=0; z<MAP_BLOCKSIZE; z++){
+       //for(s16 z=-1; z<MAP_BLOCKSIZE; z++){
+               for(s16 y=0; y<MAP_BLOCKSIZE; y++){
+                       updateFastFaceRow(v3s16(0,y,z), MAP_BLOCKSIZE,
+                                       v3s16(1,0,0),
+                                       v3s16(0,0,1),
+                                       *fastfaces_new);
+               }
+       }
+
+       scene::SMesh *mesh_new = NULL;
+       
+       if(fastfaces_new->getSize() > 0)
+       {
+               mesh_new = new scene::SMesh();
+               scene::IMeshBuffer *buf = NULL;
+
+               core::list<FastFace*>::Iterator i = fastfaces_new->begin();
+
+               // MATERIAL_AIR shouldn't be used by any face
+               u8 material_in_use = MATERIAL_AIR;
+
+               for(; i != fastfaces_new->end(); i++)
+               {
+                       FastFace *f = *i;
+                       
+                       if(f->material != material_in_use || buf == NULL)
+                       {
+                               // Try to get a meshbuffer associated with the material
+                               buf = mesh_new->getMeshBuffer(g_materials[f->material]);
+                               // If not found, create one
+                               if(buf == NULL)
+                               {
+                                       // This is a "Standard MeshBuffer",
+                                       // it's a typedeffed CMeshBuffer<video::S3DVertex>
+                                       buf = new scene::SMeshBuffer();
+                                       // Set material
+                                       ((scene::SMeshBuffer*)buf)->Material = g_materials[f->material];
+                                       // Use VBO
+                                       //buf->setHardwareMappingHint(scene::EHM_STATIC);
+                                       // Add to mesh
+                                       mesh_new->addMeshBuffer(buf);
+                                       // Mesh grabbed it
+                                       buf->drop();
+                               }
+                               material_in_use = f->material;
+                       }
+
+                       u16 indices[] = {0,1,2,2,3,0};
+                       buf->append(f->vertices, 4, indices, 6);
+               }
+
+               // Use VBO for mesh (this just would set this for ever buffer)
+               //mesh_new->setHardwareMappingHint(scene::EHM_STATIC);
+               
+               /*std::cout<<"MapBlock has "<<fastfaces_new->getSize()<<" faces "
+                               <<"and uses "<<mesh_new->getMeshBufferCount()
+                               <<" materials"<<std::endl;*/
+       }
+
+       // TODO: Get rid of the FastFace stage
+       core::list<FastFace*>::Iterator i;
+       i = fastfaces_new->begin();
+       for(; i != fastfaces_new->end(); i++)
+       {
+               delete *i;
+       }
+       fastfaces_new->clear();
+       delete fastfaces_new;
+
+       /*
+               Replace the mesh
+       */
+
+       mesh_mutex.Lock();
+
+       scene::SMesh *mesh_old = mesh;
+
+       mesh = mesh_new;
+       
+       if(mesh_old != NULL)
+       {
+               // Remove hardware buffers of meshbuffers of mesh
+               // NOTE: No way, this runs in a different thread and everything
+               /*u32 c = mesh_old->getMeshBufferCount();
+               for(u32 i=0; i<c; i++)
+               {
+                       IMeshBuffer *buf = mesh_old->getMeshBuffer(i);
+               }*/
+               // Drop the mesh
+               mesh_old->drop();
+               //delete mesh_old;
+       }
+
+       mesh_mutex.Unlock();
+       
+       //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
+}
+
+/*
+       Propagates sunlight down through the block.
+       Doesn't modify nodes that are not affected by sunlight.
+       
+       Returns false if sunlight at bottom block is invalid
+       Returns true if bottom block doesn't exist.
+
+       If there is a block above, continues from it.
+       If there is no block above, assumes there is sunlight, unless
+       is_underground is set.
+
+       At the moment, all sunlighted nodes are added to light_sources.
+       TODO: This could be optimized.
+*/
+bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources)
+{
+       // Whether the sunlight at the top of the bottom block is valid
+       bool block_below_is_valid = true;
+       
+       v3s16 pos_relative = getPosRelative();
+       
+       for(s16 x=0; x<MAP_BLOCKSIZE; x++)
+       {
+               for(s16 z=0; z<MAP_BLOCKSIZE; z++)
+               {
+                       bool no_sunlight = false;
+                       bool no_top_block = false;
+                       // Check if node above block has sunlight
+                       try{
+                               MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
+                               if(n.getLight() != LIGHT_SUN)
+                               {
+                                       /*if(is_underground)
+                                       {
+                                               no_sunlight = true;
+                                       }*/
+                                       no_sunlight = true;
+                               }
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               no_top_block = true;
+                               
+                               // TODO: This makes over-ground roofed places sunlighted
+                               // Assume sunlight, unless is_underground==true
+                               if(is_underground)
+                               {
+                                       no_sunlight = true;
+                               }
+                               
+                               // TODO: There has to be some way to allow this behaviour
+                               // As of now, it just makes everything dark.
+                               // No sunlight here
+                               //no_sunlight = true;
+                       }
+
+                       /*std::cout<<"("<<x<<","<<z<<"): "
+                                       <<"no_top_block="<<no_top_block
+                                       <<", is_underground="<<is_underground
+                                       <<", no_sunlight="<<no_sunlight
+                                       <<std::endl;*/
+               
+                       s16 y = MAP_BLOCKSIZE-1;
+                       
+                       if(no_sunlight == false)
+                       {
+                               // Continue spreading sunlight downwards through transparent
+                               // nodes
+                               for(; y >= 0; y--)
+                               {
+                                       v3s16 pos(x, y, z);
+                                       
+                                       MapNode &n = getNodeRef(pos);
+
+                                       if(n.sunlight_propagates())
+                                       {
+                                               n.setLight(LIGHT_SUN);
+
+                                               light_sources.insert(pos_relative + pos, true);
+                                       }
+                                       else{
+                                               break;
+                                       }
+                               }
+                       }
+
+                       bool sunlight_should_go_down = (y==-1);
+
+                       // Fill rest with black (only transparent ones)
+                       for(; y >= 0; y--){
+                               v3s16 pos(x, y, z);
+                               
+                               MapNode &n = getNodeRef(pos);
+
+                               if(n.light_propagates())
+                               {
+                                       n.setLight(0);
+                               }
+                               else{
+                                       break;
+                               }
+                       }
+
+                       /*
+                               If the block below hasn't already been marked invalid:
+
+                               Check if the node below the block has proper sunlight at top.
+                               If not, the block below is invalid.
+                               
+                               Ignore non-transparent nodes as they always have no light
+                       */
+                       try
+                       {
+                       if(block_below_is_valid)
+                       {
+                               MapNode n = getNodeParent(v3s16(x, -1, z));
+                               if(n.light_propagates())
+                               {
+                                       if(n.getLight() == LIGHT_SUN
+                                                       && sunlight_should_go_down == false)
+                                               block_below_is_valid = false;
+                                       else if(n.getLight() != LIGHT_SUN
+                                                       && sunlight_should_go_down == true)
+                                               block_below_is_valid = false;
+                               }
+                       }//if
+                       }//try
+                       catch(InvalidPositionException &e)
+                       {
+                               /*std::cout<<"InvalidBlockException for bottom block node"
+                                               <<std::endl;*/
+                               // Just no block below, no need to panic.
+                       }
+               }
+       }
+
+       return block_below_is_valid;
+}
+
+/*
+       Serialization
+*/
+
+void MapBlock::serialize(std::ostream &os, u8 version)
+{
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: MapBlock format not supported");
+       
+       if(data == NULL)
+       {
+               throw SerializationError("ERROR: Not writing dummy block.");
+       }
+       
+       // These have no compression
+       if(version <= 3 || version == 5 || version == 6)
+       {
+               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+               
+               u32 buflen = 1 + nodecount * MapNode::serializedLength(version);
+               SharedBuffer<u8> dest(buflen);
+
+               dest[0] = is_underground;
+               for(u32 i=0; i<nodecount; i++)
+               {
+                       u32 s = 1 + i * MapNode::serializedLength(version);
+                       data[i].serialize(&dest[s], version);
+               }
+               
+               os.write((char*)*dest, dest.getSize());
+       }
+       // All otherversions
+       else
+       {
+               /*
+                       With compression.
+                       Compress the materials and the params separately.
+               */
+               
+               // First byte
+               os.write((char*)&is_underground, 1);
+
+               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+
+               // Get and compress materials
+               SharedBuffer<u8> materialdata(nodecount);
+               for(u32 i=0; i<nodecount; i++)
+               {
+                       materialdata[i] = data[i].d;
+               }
+               compress(materialdata, os, version);
+
+               // Get and compress params
+               SharedBuffer<u8> paramdata(nodecount);
+               for(u32 i=0; i<nodecount; i++)
+               {
+                       paramdata[i] = data[i].param;
+               }
+               compress(paramdata, os, version);
+       }
+}
+
+void MapBlock::deSerialize(std::istream &is, u8 version)
+{
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: MapBlock format not supported");
+
+       // These have no compression
+       if(version <= 3 || version == 5 || version == 6)
+       {
+               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+               char tmp;
+               is.read(&tmp, 1);
+               if(is.gcount() != 1)
+                       throw SerializationError
+                                       ("MapBlock::deSerialize: no enough input data");
+               is_underground = tmp;
+               for(u32 i=0; i<nodecount; i++)
+               {
+                       s32 len = MapNode::serializedLength(version);
+                       SharedBuffer<u8> d(len);
+                       is.read((char*)*d, len);
+                       if(is.gcount() != len)
+                               throw SerializationError
+                                               ("MapBlock::deSerialize: no enough input data");
+                       data[i].deSerialize(*d, version);
+               }
+       }
+       // All other versions
+       else
+       {
+               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+
+               u8 t8;
+               is.read((char*)&t8, 1);
+               is_underground = t8;
+
+               {
+                       // Uncompress and set material data
+                       std::ostringstream os(std::ios_base::binary);
+                       decompress(is, os, version);
+                       std::string s = os.str();
+                       if(s.size() != nodecount)
+                               throw SerializationError
+                                               ("MapBlock::deSerialize: invalid format");
+                       for(u32 i=0; i<s.size(); i++)
+                       {
+                               data[i].d = s[i];
+                       }
+               }
+               {
+                       // Uncompress and set param data
+                       std::ostringstream os(std::ios_base::binary);
+                       decompress(is, os, version);
+                       std::string s = os.str();
+                       if(s.size() != nodecount)
+                               throw SerializationError
+                                               ("MapBlock::deSerialize: invalid format");
+                       for(u32 i=0; i<s.size(); i++)
+                       {
+                               data[i].param = s[i];
+                       }
+               }
+       }
+}
+
+
+//END
diff --git a/src/mapblock.h b/src/mapblock.h
new file mode 100644 (file)
index 0000000..9fcfa07
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAPBLOCK_HEADER
+#define MAPBLOCK_HEADER
+
+#include <jmutex.h>
+#include <jmutexautolock.h>
+#include <exception>
+#include "debug.h"
+#include "common_irrlicht.h"
+#include "mapnode.h"
+#include "exceptions.h"
+#include "serialization.h"
+#include "constants.h"
+#include "mapblockobject.h"
+
+#define MAP_BLOCKSIZE 16
+
+// Named by looking towards z+
+enum{
+       FACE_BACK=0,
+       FACE_TOP,
+       FACE_RIGHT,
+       FACE_FRONT,
+       FACE_BOTTOM,
+       FACE_LEFT
+};
+
+struct FastFace
+{
+       u8 material;
+       video::S3DVertex vertices[4]; // Precalculated vertices
+};
+
+enum
+{
+       NODECONTAINER_ID_MAPBLOCK,
+       NODECONTAINER_ID_MAPSECTOR,
+       NODECONTAINER_ID_MAP
+};
+
+class NodeContainer
+{
+public:
+       virtual bool isValidPosition(v3s16 p) = 0;
+       virtual MapNode getNode(v3s16 p) = 0;
+       virtual void setNode(v3s16 p, MapNode & n) = 0;
+       virtual u16 nodeContainerId() const = 0;
+};
+
+class MapBlock : public NodeContainer
+{
+private:
+
+       NodeContainer *m_parent;
+       // Position in blocks on parent
+       v3s16 m_pos;
+       /*
+               If NULL, block is a dummy block.
+               Dummy blocks are used for caching not-found-on-disk blocks.
+       */
+       MapNode * data;
+       /*
+               - On the client, this is used for checking whether to
+                 recalculate the face cache. (Is it anymore?)
+               - On the server, this is used for telling whether the
+                 block has been changed from the one on disk.
+       */
+       bool changed;
+       /*
+               Used for some initial lighting stuff.
+               At least /has been/ used. 8)
+       */
+       bool is_underground;
+       
+       MapBlockObjectList m_objects;
+       
+public:
+
+       /*
+               This used by Server's block creation stuff for not sending
+               blocks that are waiting a lighting update.
+
+               If true, the block needs some work by the one who set this
+               to true.
+
+               While true, nobody else should touch the block.
+       */
+       //bool is_incomplete;
+       
+       scene::SMesh *mesh;
+       JMutex mesh_mutex;
+
+       MapBlock(NodeContainer *parent, v3s16 pos, bool dummy=false):
+                       m_parent(parent),
+                       m_pos(pos),
+                       changed(true),
+                       is_underground(false),
+                       m_objects(this)
+                       //is_incomplete(false)
+       {
+               data = NULL;
+               if(dummy == false)
+                       reallocate();
+               mesh_mutex.Init();
+               mesh = NULL;
+       }
+
+       ~MapBlock()
+       {
+               {
+                       JMutexAutoLock lock(mesh_mutex);
+                       
+                       if(mesh != NULL)
+                       {
+                               mesh->drop();
+                               mesh = NULL;
+                       }
+               }
+
+               if(data)
+                       delete[] data;
+       }
+       
+       virtual u16 nodeContainerId() const
+       {
+               return NODECONTAINER_ID_MAPBLOCK;
+       }
+
+       NodeContainer * getParent()
+       {
+               return m_parent;
+       }
+
+       bool isDummy()
+       {
+               return (data == NULL);
+       }
+
+       void unDummify()
+       {
+               assert(isDummy());
+               reallocate();
+       }
+       
+       bool getChangedFlag()
+       {
+               return changed;
+       }
+
+       void resetChangedFlag()
+       {
+               changed = false;
+       }
+
+       void setChangedFlag()
+       {
+               changed = true;
+       }
+
+       v3s16 getPos()
+       {
+               return m_pos;
+       }
+               
+       v3s16 getPosRelative()
+       {
+               return m_pos * MAP_BLOCKSIZE;
+       }
+               
+       bool getIsUnderground()
+       {
+               return is_underground;
+       }
+
+       void setIsUnderground(bool a_is_underground)
+       {
+               is_underground = a_is_underground;
+               setChangedFlag();
+       }
+
+       core::aabbox3d<s16> getBox()
+       {
+               return core::aabbox3d<s16>(getPosRelative(),
+                               getPosRelative()
+                               + v3s16(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE)
+                               - v3s16(1,1,1));
+       }
+       
+       void reallocate()
+       {
+               if(data != NULL)
+                       delete[] data;
+               u32 l = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE;
+               data = new MapNode[l];
+               for(u32 i=0; i<l; i++){
+                       data[i] = MapNode();
+               }
+               setChangedFlag();
+       }
+
+       bool isValidPosition(v3s16 p)
+       {
+               if(data == NULL)
+                       return false;
+               return (p.X >= 0 && p.X < MAP_BLOCKSIZE
+                               && p.Y >= 0 && p.Y < MAP_BLOCKSIZE
+                               && p.Z >= 0 && p.Z < MAP_BLOCKSIZE);
+       }
+
+       /*
+               Regular MapNode get-setters
+       */
+       
+       MapNode getNode(s16 x, s16 y, s16 z)
+       {
+               if(data == NULL)
+                       throw InvalidPositionException();
+               if(x < 0 || x >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               if(y < 0 || y >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               if(z < 0 || z >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               return data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x];
+       }
+       
+       MapNode getNode(v3s16 p)
+       {
+               return getNode(p.X, p.Y, p.Z);
+       }
+       
+       void setNode(s16 x, s16 y, s16 z, MapNode & n)
+       {
+               if(data == NULL)
+                       throw InvalidPositionException();
+               if(x < 0 || x >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               if(y < 0 || y >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               if(z < 0 || z >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x] = n;
+               setChangedFlag();
+       }
+       
+       void setNode(v3s16 p, MapNode & n)
+       {
+               setNode(p.X, p.Y, p.Z, n);
+       }
+
+       /*
+               These functions consult the parent container if the position
+               is not valid on this MapBlock.
+       */
+       bool isValidPositionParent(v3s16 p);
+       MapNode getNodeParent(v3s16 p);
+       void setNodeParent(v3s16 p, MapNode & n);
+
+       void drawbox(s16 x0, s16 y0, s16 z0, s16 w, s16 h, s16 d, MapNode node)
+       {
+               for(u16 z=0; z<d; z++)
+                       for(u16 y=0; y<h; y++)
+                               for(u16 x=0; x<w; x++)
+                                       setNode(x0+x, y0+y, z0+z, node);
+       }
+
+       static FastFace * makeFastFace(u8 material, u8 light, v3f p,
+                       v3f dir, v3f scale, v3f posRelative_f);
+       
+       u8 getFaceLight(v3s16 p, v3s16 face_dir);
+       
+       /*
+               Gets node material from any place relative to block.
+               Returns MATERIAL_AIR if doesn't exist.
+       */
+       u8 getNodeMaterial(v3s16 p);
+
+       /*
+               startpos:
+               translate_dir: unit vector with only one of x, y or z
+               face_dir: unit vector with only one of x, y or z
+       */
+       void updateFastFaceRow(v3s16 startpos,
+                       u16 length,
+                       v3s16 translate_dir,
+                       v3s16 face_dir,
+                       core::list<FastFace*> &dest);
+
+       void updateMesh();
+
+       bool propagateSunlight(core::map<v3s16, bool> & light_sources);
+       
+       // Doesn't write version by itself
+       void serialize(std::ostream &os, u8 version);
+
+       void deSerialize(std::istream &is, u8 version);
+       
+       void serializeObjects(std::ostream &os, u8 version)
+       {
+               m_objects.serialize(os, version);
+       }
+       // If smgr!=NULL, new objects are added to the scene
+       void updateObjects(std::istream &is, u8 version,
+                       scene::ISceneManager *smgr)
+       {
+               m_objects.update(is, version, smgr);
+
+               setChangedFlag();
+       }
+       void clearObjects()
+       {
+               m_objects.clear();
+
+               setChangedFlag();
+       }
+       void addObject(MapBlockObject *object)
+                       throw(ContainerFullException, AlreadyExistsException)
+       {
+               m_objects.add(object);
+
+               setChangedFlag();
+       }
+       void removeObject(s16 id)
+       {
+               m_objects.remove(id);
+
+               setChangedFlag();
+       }
+       MapBlockObject * getObject(s16 id)
+       {
+               return m_objects.get(id);
+       }
+       JMutexAutoLock * getObjectLock()
+       {
+               return m_objects.getLock();
+       }
+       void stepObjects(float dtime, bool server)
+       {
+               m_objects.step(dtime, server);
+
+               setChangedFlag();
+       }
+
+       /*void wrapObject(MapBlockObject *object)
+       {
+               m_objects.wrapObject(object);
+
+               setChangedFlag();
+       }*/
+
+       // origin is relative to block
+       void getObjects(v3f origin, f32 max_d,
+                       core::array<DistanceSortedObject> &dest)
+       {
+               m_objects.getObjects(origin, max_d, dest);
+       }
+
+private:
+
+       /*
+               Used only internally, because changes can't be tracked
+       */
+
+       MapNode & getNodeRef(s16 x, s16 y, s16 z)
+       {
+               if(x < 0 || x >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               if(y < 0 || y >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               if(z < 0 || z >= MAP_BLOCKSIZE) throw InvalidPositionException();
+               return data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x];
+       }
+       MapNode & getNodeRef(v3s16 &p)
+       {
+               return getNodeRef(p.X, p.Y, p.Z);
+       }
+};
+
+inline bool blockpos_over_limit(v3s16 p)
+{
+       return
+         (p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+       || p.X >  MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+       || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+       || p.Y >  MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+       || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+       || p.Z >  MAP_GENERATION_LIMIT / MAP_BLOCKSIZE);
+}
+
+/*
+       Returns the position of the block where the node is located
+*/
+inline v3s16 getNodeBlockPos(v3s16 p)
+{
+       return getContainerPos(p, MAP_BLOCKSIZE);
+}
+
+inline v2s16 getNodeSectorPos(v2s16 p)
+{
+       return getContainerPos(p, MAP_BLOCKSIZE);
+}
+
+inline s16 getNodeBlockY(s16 y)
+{
+       return getContainerPos(y, MAP_BLOCKSIZE);
+}
+
+#endif
+
diff --git a/src/mapblockobject.cpp b/src/mapblockobject.cpp
new file mode 100644 (file)
index 0000000..985a01d
--- /dev/null
@@ -0,0 +1,641 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "mapblockobject.h"
+#include "mapblock.h"
+// Only for ::getNodeBox, TODO: Get rid of this
+#include "map.h"
+
+/*
+       MapBlockObject
+*/
+
+// This is here because it uses the MapBlock
+v3f MapBlockObject::getAbsolutePos()
+{
+       if(m_block == NULL)
+               return m_pos;
+       
+       // getPosRelative gets nodepos relative to map origin
+       v3f blockpos = intToFloat(m_block->getPosRelative());
+       return blockpos + m_pos;
+}
+
+void MapBlockObject::setBlockChanged()
+{
+       if(m_block)
+               m_block->setChangedFlag();
+}
+
+/*
+       MovingObject
+*/
+void MovingObject::move(float dtime, v3f acceleration)
+{
+       //m_pos += dtime * 3.0;
+
+       v3s16 oldpos_i = floatToInt(m_pos);
+       
+       if(m_block->isValidPosition(oldpos_i) == false)
+       {
+               // Should have wrapped, cancelling further movement.
+               return;
+       }
+
+       // No collisions if there is no collision box
+       if(m_collision_box == NULL)
+       {
+               m_speed += dtime * acceleration;
+               m_pos += m_speed * dtime;
+               return;
+       }
+
+       v3f position = m_pos;
+       v3f oldpos = position;
+
+       /*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
+                       <<oldpos_i.Z<<")"<<std::endl;*/
+
+       // Maximum time increment (for collision detection etc)
+       // Allow 0.1 blocks per increment
+       // time = distance / speed
+       // NOTE: In the loop below collisions are detected at 0.15*BS radius
+       float speedlength = m_speed.getLength();
+       f32 dtime_max_increment;
+       if(fabs(speedlength) > 0.001)
+               dtime_max_increment = 0.1*BS / speedlength;
+       else
+               dtime_max_increment = 0.5;
+       
+       m_touching_ground = false;
+               
+       u32 loopcount = 0;
+       do
+       {
+               loopcount++;
+
+               f32 dtime_part;
+               if(dtime > dtime_max_increment)
+                       dtime_part = dtime_max_increment;
+               else
+                       dtime_part = dtime;
+               dtime -= dtime_part;
+
+               // Begin of dtime limited code
+               
+               m_speed += acceleration * dtime_part;
+               position += m_speed * dtime_part;
+
+               /*
+                       Collision detection
+               */
+               
+               v3s16 pos_i = floatToInt(position);
+               
+               // The loop length is limited to the object moving a distance
+               f32 d = (float)BS * 0.15;
+
+               core::aabbox3d<f32> objectbox(
+                               m_collision_box->MinEdge + position,
+                               m_collision_box->MaxEdge + position
+               );
+               
+               core::aabbox3d<f32> objectbox_old(
+                               m_collision_box->MinEdge + oldpos,
+                               m_collision_box->MaxEdge + oldpos
+               );
+               
+               //TODO: Get these ranges from somewhere
+               for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
+               for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
+               for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
+               {
+                       try{
+                               if(m_block->getNodeParent(v3s16(x,y,z)).d == MATERIAL_AIR){
+                                       continue;
+                               }
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               // Doing nothing here will block the player from
+                               // walking over map borders
+                       }
+
+                       core::aabbox3d<f32> nodebox = Map::getNodeBox(
+                                       v3s16(x,y,z));
+                       
+                       // See if the player is touching ground
+                       if(
+                                       fabs(nodebox.MaxEdge.Y-objectbox.MinEdge.Y) < d
+                                       && nodebox.MaxEdge.X-d > objectbox.MinEdge.X
+                                       && nodebox.MinEdge.X+d < objectbox.MaxEdge.X
+                                       && nodebox.MaxEdge.Z-d > objectbox.MinEdge.Z
+                                       && nodebox.MinEdge.Z+d < objectbox.MaxEdge.Z
+                       ){
+                               m_touching_ground = true;
+                       }
+                       
+                       if(objectbox.intersectsWithBox(nodebox))
+                       {
+                                       
+               v3f dirs[3] = {
+                       v3f(0,0,1), // back
+                       v3f(0,1,0), // top
+                       v3f(1,0,0), // right
+               };
+               for(u16 i=0; i<3; i++)
+               {
+                       f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
+                       f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
+                       f32 playermax = objectbox.MaxEdge.dotProduct(dirs[i]);
+                       f32 playermin = objectbox.MinEdge.dotProduct(dirs[i]);
+                       f32 playermax_old = objectbox_old.MaxEdge.dotProduct(dirs[i]);
+                       f32 playermin_old = objectbox_old.MinEdge.dotProduct(dirs[i]);
+
+                       bool main_edge_collides = 
+                               ((nodemax > playermin && nodemax <= playermin_old + d
+                                       && m_speed.dotProduct(dirs[i]) < 0)
+                               ||
+                               (nodemin < playermax && nodemin >= playermax_old - d
+                                       && m_speed.dotProduct(dirs[i]) > 0));
+
+                       bool other_edges_collide = true;
+                       for(u16 j=0; j<3; j++)
+                       {
+                               if(j == i)
+                                       continue;
+                               f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
+                               f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
+                               f32 playermax = objectbox.MaxEdge.dotProduct(dirs[j]);
+                               f32 playermin = objectbox.MinEdge.dotProduct(dirs[j]);
+                               if(!(nodemax - d > playermin && nodemin + d < playermax))
+                               {
+                                       other_edges_collide = false;
+                                       break;
+                               }
+                       }
+                       
+                       if(main_edge_collides && other_edges_collide)
+                       {
+                               m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
+                               position -= position.dotProduct(dirs[i]) * dirs[i];
+                               position += oldpos.dotProduct(dirs[i]) * dirs[i];
+                       }
+               
+               }
+               
+                       } // if(objectbox.intersectsWithBox(nodebox))
+               } // for y
+
+       } // End of dtime limited loop
+       while(dtime > 0.001);
+
+       m_pos = position;
+}
+
+/*
+       MapBlockObjectList
+*/
+
+MapBlockObjectList::MapBlockObjectList(MapBlock *block):
+       m_block(block)
+{
+       m_mutex.Init();
+}
+
+MapBlockObjectList::~MapBlockObjectList()
+{
+       clear();
+}
+
+/*
+       The serialization format:
+       [0] u16 number of entries
+       [2] entries (id, typeId, parameters)
+*/
+
+void MapBlockObjectList::serialize(std::ostream &os, u8 version)
+{
+       JMutexAutoLock lock(m_mutex);
+
+       u8 buf[2];
+       writeU16(buf, m_objects.size());
+       os.write((char*)buf, 2);
+
+       for(core::map<s16, MapBlockObject*>::Iterator
+                       i = m_objects.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               i.getNode()->getValue()->serialize(os, version);
+       }
+}
+
+void MapBlockObjectList::update(std::istream &is, u8 version,
+               scene::ISceneManager *smgr)
+{
+       JMutexAutoLock lock(m_mutex);
+
+       /*
+               Collect all existing ids to a set.
+
+               As things are updated, they are removed from this.
+
+               All remaining ones are deleted.
+       */
+       core::map<s16, bool> ids_to_delete;
+       for(core::map<s16, MapBlockObject*>::Iterator
+                       i = m_objects.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               ids_to_delete.insert(i.getNode()->getKey(), true);
+       }
+       
+       u8 buf[6];
+       
+       is.read((char*)buf, 2);
+       u16 count = readU16(buf);
+
+       for(u16 i=0; i<count; i++)
+       {
+               // Read id
+               is.read((char*)buf, 2);
+               s16 id = readS16(buf);
+               
+               // Read position
+               // stored as x1000/BS v3s16
+               is.read((char*)buf, 6);
+               v3s16 pos_i = readV3S16(buf);
+               v3f pos((f32)pos_i.X/1000*BS,
+                               (f32)pos_i.Y/1000*BS,
+                               (f32)pos_i.Z/1000*BS);
+
+               // Read typeId
+               is.read((char*)buf, 2);
+               u16 type_id = readU16(buf);
+               
+               bool create_new = false;
+
+               // Find an object with the id
+               core::map<s16, MapBlockObject*>::Node *n;
+               n = m_objects.find(id);
+               // If no entry is found for id
+               if(n == NULL)
+               {
+                       // Insert dummy pointer node
+                       m_objects.insert(id, NULL);
+                       // Get node
+                       n = m_objects.find(id);
+                       // A new object will be created at this node
+                       create_new = true;
+               }
+               // If type_id differs
+               else if(n->getValue()->getTypeId() != type_id)
+               {
+                       // Delete old object
+                       delete n->getValue();
+                       // A new object will be created at this node
+                       create_new = true;
+               }
+
+               MapBlockObject *obj = NULL;
+
+               if(create_new)
+               {
+                       /*dstream<<"MapBlockObjectList adding new object"
+                                       " id="<<id
+                                       <<std::endl;*/
+
+                       if(type_id == MAPBLOCKOBJECT_TYPE_TEST)
+                       {
+                               // The constructors of objects shouldn't need
+                               // any more parameters than this.
+                               obj = new TestObject(m_block, id, pos);
+                       }
+                       else if(type_id == MAPBLOCKOBJECT_TYPE_TEST2)
+                       {
+                               obj = new Test2Object(m_block, id, pos);
+                       }
+                       else if(type_id == MAPBLOCKOBJECT_TYPE_SIGN)
+                       {
+                               obj = new SignObject(m_block, id, pos);
+                       }
+                       else if(type_id == MAPBLOCKOBJECT_TYPE_RAT)
+                       {
+                               obj = new RatObject(m_block, id, pos);
+                       }
+                       else
+                       {
+                               throw SerializationError
+                               ("MapBlockObjectList::update(): Unknown MapBlockObject type");
+                       }
+
+                       if(smgr != NULL)
+                               obj->addToScene(smgr);
+
+                       n->setValue(obj);
+               }
+               else
+               {
+                       obj = n->getValue();
+                       obj->updatePos(pos);
+               }
+
+               // Now there is an object in obj.
+               // Update it.
+               
+               obj->update(is, version);
+               
+               // Remove from deletion list
+               if(ids_to_delete.find(id) != NULL)
+                       ids_to_delete.remove(id);
+       }
+
+       // Delete all objects whose ids_to_delete remain in ids_to_delete
+       for(core::map<s16, bool>::Iterator
+                       i = ids_to_delete.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               s16 id = i.getNode()->getKey();
+
+               /*dstream<<"MapBlockObjectList deleting object"
+                               " id="<<id
+                               <<std::endl;*/
+
+               MapBlockObject *obj = m_objects[id];
+               obj->removeFromScene();
+               delete obj;
+               m_objects.remove(id);
+       }
+}
+
+s16 MapBlockObjectList::getFreeId() throw(ContainerFullException)
+{
+       s16 id = 0;
+       for(;;)
+       {
+               if(m_objects.find(id) == NULL)
+                       return id;
+               if(id == 32767)
+                       throw ContainerFullException
+                                       ("MapBlockObjectList doesn't fit more objects");
+               id++;
+       }
+}
+
+void MapBlockObjectList::add(MapBlockObject *object)
+               throw(ContainerFullException, AlreadyExistsException)
+{
+       if(object == NULL)
+       {
+               dstream<<"MapBlockObjectList::add(): NULL object"<<std::endl;
+               return;
+       }
+
+       JMutexAutoLock lock(m_mutex);
+
+       // Create unique id if id==-1
+       if(object->m_id == -1)
+       {
+               object->m_id = getFreeId();
+       }
+
+       if(m_objects.find(object->m_id) != NULL)
+       {
+               dstream<<"MapBlockObjectList::add(): "
+                               "object with same id already exists"<<std::endl;
+               throw AlreadyExistsException
+                               ("MapBlockObjectList already has given id");
+       }
+       
+       object->m_block = m_block;
+       
+       /*v3f p = object->m_pos;
+       dstream<<"MapBlockObjectList::add(): "
+                       <<"m_block->getPos()=("
+                       <<m_block->getPos().X<<","
+                       <<m_block->getPos().Y<<","
+                       <<m_block->getPos().Z<<")"
+                       <<" inserting object with id="<<object->m_id
+                       <<" pos="
+                       <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                       <<std::endl;*/
+       
+       m_objects.insert(object->m_id, object);
+}
+
+void MapBlockObjectList::clear()
+{
+       JMutexAutoLock lock(m_mutex);
+
+       for(core::map<s16, MapBlockObject*>::Iterator
+                       i = m_objects.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               MapBlockObject *obj = i.getNode()->getValue();
+               //FIXME: This really shouldn't be NULL at any time,
+               //       but this condition was added because it was.
+               if(obj != NULL)
+               {
+                       obj->removeFromScene();
+                       delete obj;
+               }
+       }
+
+       m_objects.clear();
+}
+
+void MapBlockObjectList::remove(s16 id)
+{
+       JMutexAutoLock lock(m_mutex);
+
+       core::map<s16, MapBlockObject*>::Node *n;
+       n = m_objects.find(id);
+       if(n == NULL)
+               return;
+       
+       n->getValue()->removeFromScene();
+       delete n->getValue();
+       m_objects.remove(id);
+}
+
+MapBlockObject * MapBlockObjectList::get(s16 id)
+{
+       core::map<s16, MapBlockObject*>::Node *n;
+       n = m_objects.find(id);
+       if(n == NULL)
+               return NULL;
+       else
+               return n->getValue();
+}
+
+void MapBlockObjectList::step(float dtime, bool server)
+{
+       JMutexAutoLock lock(m_mutex);
+
+       core::map<s16, bool> ids_to_delete;
+
+       for(core::map<s16, MapBlockObject*>::Iterator
+                       i = m_objects.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               MapBlockObject *obj = i.getNode()->getValue();
+               
+               if(server)
+               {
+                       bool to_delete = obj->serverStep(dtime);
+
+                       if(to_delete)
+                               ids_to_delete.insert(obj->m_id, true);
+               }
+               else
+               {
+                       obj->clientStep(dtime);
+               }
+       }
+
+       // Delete objects in delete queue
+       for(core::map<s16, bool>::Iterator
+                       i = ids_to_delete.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               s16 id = i.getNode()->getKey();
+
+               MapBlockObject *obj = m_objects[id];
+               obj->removeFromScene();
+               delete obj;
+               m_objects.remove(id);
+       }
+       
+       /*
+               Wrap objects on server
+       */
+
+       if(server == false)
+               return;
+
+       for(core::map<s16, MapBlockObject*>::Iterator
+                       i = m_objects.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               MapBlockObject *obj = i.getNode()->getValue();
+
+               v3s16 pos_i = floatToInt(obj->m_pos);
+
+               if(m_block->isValidPosition(pos_i))
+               {
+                       // No wrap
+                       continue;
+               }
+
+               bool impossible = wrapObject(obj);
+
+               if(impossible)
+               {
+                       // No wrap
+                       continue;
+               }
+
+               // Restart find
+               i = m_objects.getIterator();
+       }
+}
+
+bool MapBlockObjectList::wrapObject(MapBlockObject *object)
+{
+       // No lock here; this is called so that the lock is already locked.
+       //JMutexAutoLock lock(m_mutex);
+
+       assert(object->m_block == m_block);
+       assert(m_objects.find(object->m_id) != NULL);
+       assert(m_objects[object->m_id] == object);
+
+       NodeContainer *parentcontainer = m_block->getParent();
+       // This will only work if the parent is the map
+       if(parentcontainer->nodeContainerId() != NODECONTAINER_ID_MAP)
+       {
+               dstream<<"WARNING: Wrapping object not possible: "
+                               "MapBlock's parent is not map"<<std::endl;
+               return true;
+       }
+       // OK, we have the map!
+       Map *map = (Map*)parentcontainer;
+       
+       // Calculate blockpos on map
+       v3s16 oldblock_pos_i_on_map = m_block->getPosRelative();
+       v3f pos_f_on_oldblock = object->m_pos;
+       v3s16 pos_i_on_oldblock = floatToInt(pos_f_on_oldblock);
+       v3s16 pos_i_on_map = pos_i_on_oldblock + oldblock_pos_i_on_map;
+       v3s16 pos_blocks_on_map = getNodeBlockPos(pos_i_on_map);
+
+       // Get new block
+       MapBlock *newblock;
+       try{
+               newblock = map->getBlockNoCreate(pos_blocks_on_map);
+       }
+       catch(InvalidPositionException &e)
+       {
+               // Couldn't find block -> not wrapping
+               /*dstream<<"WARNING: Wrapping object not possible: "
+                               <<"could not find new block"
+                               <<"("<<pos_blocks_on_map.X
+                               <<","<<pos_blocks_on_map.Y
+                               <<","<<pos_blocks_on_map.Z
+                               <<")"<<std::endl;*/
+               /*dstream<<"pos_f_on_oldblock=("
+                               <<pos_f_on_oldblock.X<<","
+                               <<pos_f_on_oldblock.Y<<","
+                               <<pos_f_on_oldblock.Z<<")"
+                               <<std::endl;*/
+               return true;
+       }
+
+       if(newblock == m_block)
+       {
+               dstream<<"WARNING: Wrapping object not possible: "
+                               "newblock == oldblock"<<std::endl;
+               return true;
+       }
+       
+       // Calculate position on new block
+       v3f oldblock_pos_f_on_map = intToFloat(oldblock_pos_i_on_map);
+       v3s16 newblock_pos_i_on_map = newblock->getPosRelative();
+       v3f newblock_pos_f_on_map = intToFloat(newblock_pos_i_on_map);
+       v3f pos_f_on_newblock = pos_f_on_oldblock
+                       - newblock_pos_f_on_map + oldblock_pos_f_on_map;
+
+       // Remove object from this block
+       m_objects.remove(object->m_id);
+       
+       // Add object to new block
+       object->m_pos = pos_f_on_newblock;
+       object->m_id = -1;
+       object->m_block = NULL;
+       newblock->addObject(object);
+
+       //dstream<<"NOTE: Wrapped object"<<std::endl;
+
+       return false;
+}
+
+void MapBlockObjectList::getObjects(v3f origin, f32 max_d,
+               core::array<DistanceSortedObject> &dest)
+{
+       for(core::map<s16, MapBlockObject*>::Iterator
+                       i = m_objects.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               MapBlockObject *obj = i.getNode()->getValue();
+
+               f32 d = (obj->m_pos - origin).getLength();
+
+               if(d > max_d)
+                       continue;
+
+               DistanceSortedObject dso(obj, d);
+
+               dest.push_back(dso);
+       }
+}
+
+//END
diff --git a/src/mapblockobject.h b/src/mapblockobject.h
new file mode 100644 (file)
index 0000000..1939cc8
--- /dev/null
@@ -0,0 +1,892 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAPBLOCKOBJECT_HEADER
+#define MAPBLOCKOBJECT_HEADER
+
+#include "common_irrlicht.h"
+#include <math.h>
+#include <string>
+#include "serialization.h"
+#include "mapnode.h"
+#include "constants.h"
+
+enum
+{
+       MAPBLOCKOBJECT_TYPE_TEST=0,
+       MAPBLOCKOBJECT_TYPE_TEST2=1,
+       MAPBLOCKOBJECT_TYPE_SIGN=2,
+       MAPBLOCKOBJECT_TYPE_RAT=3,
+};
+
+class MapBlock;
+
+class MapBlockObject
+{
+public:
+       MapBlockObject(MapBlock *block, s16 id, v3f pos):
+               m_collision_box(NULL),
+               m_selection_box(NULL),
+               m_block(block),
+               m_id(id),
+               m_pos(pos)
+       {
+       }
+       virtual ~MapBlockObject()
+       {
+       }
+
+       s16 getId()
+       {
+               return m_id;
+       }
+       MapBlock* getBlock()
+       {
+               return m_block;
+       }
+       
+       // Writes id, pos and typeId
+       void serializeBase(std::ostream &os, u8 version)
+       {
+               u8 buf[6];
+
+               // id
+               writeS16(buf, m_id);
+               os.write((char*)buf, 2);
+               
+               // position
+               // stored as x1000/BS v3s16
+               v3s16 pos_i(m_pos.X*1000/BS, m_pos.Y*1000/BS, m_pos.Z*1000/BS);
+               writeV3S16(buf, pos_i);
+               os.write((char*)buf, 6);
+
+               // typeId
+               writeU16(buf, getTypeId());
+               os.write((char*)buf, 2);
+       }
+       
+       // Get floating point position on map
+       v3f getAbsolutePos();
+
+       void setBlockChanged();
+
+       // Shootline is relative to block
+       bool isSelected(core::line3d<f32> shootline)
+       {
+               if(m_selection_box == NULL)
+                       return false;
+
+               core::aabbox3d<f32> offsetted_box(
+                               m_selection_box->MinEdge + m_pos,
+                               m_selection_box->MaxEdge + m_pos
+               );
+
+               return offsetted_box.intersectsWithLine(shootline);
+       }
+
+       core::aabbox3d<f32> getSelectionBoxOnMap()
+       {
+               v3f absolute_pos = getAbsolutePos();
+
+               core::aabbox3d<f32> box(
+                               m_selection_box->MinEdge + absolute_pos,
+                               m_selection_box->MaxEdge + absolute_pos
+               );
+
+               return box;
+       }
+       
+       /*
+               Implementation interface
+       */
+
+       virtual u16 getTypeId() const = 0;
+       // Shall call serializeBase and then write the parameters
+       virtual void serialize(std::ostream &os, u8 version) = 0;
+       // Shall read parameters from stream
+       virtual void update(std::istream &is, u8 version) = 0;
+
+       virtual std::string getInventoryString() { return "None"; }
+       
+       // Reimplementation shall call this.
+       virtual void updatePos(v3f pos)
+       {
+               m_pos = pos;
+       }
+       
+       // Shall move the object around, modify it and possibly delete it.
+       // Typical dtimes are 0.2 and 10000.
+       // A return value of true requests deletion of the object by the caller.
+       // NOTE: Only server calls this.
+       virtual bool serverStep(float dtime) { return false; };
+       // This should do slight animations only or so
+       virtual void clientStep(float dtime) {};
+
+       // NOTE: These functions should do nothing if the asked state is
+       //       same as the current state
+       // Shall add and remove relevant scene nodes for rendering the
+       // object in the game world
+       virtual void addToScene(scene::ISceneManager *smgr) {};
+       // Shall remove stuff from the scene
+       // Should return silently if there is nothing to remove
+       // NOTE: This has to be called before calling destructor
+       virtual void removeFromScene() {};
+
+       virtual std::string infoText() { return ""; }
+       
+       // Shall be left NULL if doesn't collide
+       // Position is relative to m_pos in block
+       core::aabbox3d<f32> * m_collision_box;
+       
+       // Shall be left NULL if can't be selected
+       core::aabbox3d<f32> * m_selection_box;
+
+protected:
+       MapBlock *m_block;
+       // This differentiates the instance of the object
+       // Not same as typeId.
+       s16 m_id;
+       // Position of the object inside the block
+       // Units is node coordinates * BS
+       v3f m_pos;
+
+       friend class MapBlockObjectList;
+};
+
+class TestObject : public MapBlockObject
+{
+public:
+       // The constructor of every MapBlockObject should be like this
+       TestObject(MapBlock *block, s16 id, v3f pos):
+               MapBlockObject(block, id, pos),
+               m_node(NULL)
+       {
+       }
+       virtual ~TestObject()
+       {
+       }
+       
+       /*
+               Implementation interface
+       */
+       virtual u16 getTypeId() const
+       {
+               return MAPBLOCKOBJECT_TYPE_TEST;
+       }
+       virtual void serialize(std::ostream &os, u8 version)
+       {
+               serializeBase(os, version);
+
+               // Write subpos_c * 100
+               u8 buf[2];
+               writeU16(buf, m_subpos_c * 100);
+               os.write((char*)buf, 2);
+       }
+       virtual void update(std::istream &is, u8 version)
+       {
+               // Read subpos_c * 100
+               u8 buf[2];
+               is.read((char*)buf, 2);
+               m_subpos_c = (f32)readU16(buf) / 100;
+               
+               updateNodePos();
+       }
+       virtual bool serverStep(float dtime)
+       {
+               m_subpos_c += dtime * 3.0;
+
+               updateNodePos();
+
+               return false;
+       }
+       virtual void addToScene(scene::ISceneManager *smgr)
+       {
+               if(m_node != NULL)
+                       return;
+               
+               //dstream<<"Adding to scene"<<std::endl;
+
+               video::IVideoDriver* driver = smgr->getVideoDriver();
+               
+               scene::SMesh *mesh = new scene::SMesh();
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               video::SColor c(255,255,255,255);
+               video::S3DVertex vertices[4] =
+               {
+                       video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+                       video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+                       video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0),
+                       video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0),
+               };
+               u16 indices[] = {0,1,2,2,3,0};
+               buf->append(vertices, 4, indices, 6);
+               // Set material
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+               buf->getMaterial().setTexture
+                               (0, driver->getTexture("../data/player.png"));
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+               // Add to mesh
+               mesh->addMeshBuffer(buf);
+               buf->drop();
+               m_node = smgr->addMeshSceneNode(mesh, NULL);
+               mesh->drop();
+               m_node->setPosition(getAbsolutePos());
+       }
+       virtual void removeFromScene()
+       {
+               //dstream<<"Removing from scene"<<std::endl;
+               if(m_node != NULL)
+               {
+                       m_node->remove();
+                       m_node = NULL;
+               }
+       }
+
+       /*
+               Special methods
+       */
+       
+       void updateNodePos()
+       {
+               m_subpos = BS*2.0 * v3f(sin(m_subpos_c), sin(m_subpos_c+1.0), sin(-m_subpos_c));
+               
+               if(m_node != NULL)
+               {
+                       m_node->setPosition(getAbsolutePos() + m_subpos);
+               }
+       }
+       
+protected:
+       scene::IMeshSceneNode *m_node;
+       std::string m_text;
+
+       v3f m_subpos;
+       f32 m_subpos_c;
+};
+
+class MovingObject : public MapBlockObject
+{
+public:
+       // The constructor of every MapBlockObject should be like this
+       MovingObject(MapBlock *block, s16 id, v3f pos):
+               MapBlockObject(block, id, pos),
+               m_speed(0,0,0)
+       {
+               m_touching_ground = false;
+       }
+       virtual ~MovingObject()
+       {
+       }
+       
+       /*
+               Implementation interface
+       */
+       
+       virtual u16 getTypeId() const = 0;
+
+       virtual void serialize(std::ostream &os, u8 version)
+       {
+               serializeBase(os, version);
+
+               u8 buf[6];
+
+               // Write speed
+               // stored as x100/BS v3s16
+               v3s16 speed_i(m_speed.X*100/BS, m_speed.Y*100/BS, m_speed.Z*100/BS);
+               writeV3S16(buf, speed_i);
+               os.write((char*)buf, 6);
+       }
+       virtual void update(std::istream &is, u8 version)
+       {
+               u8 buf[6];
+               
+               // Read speed
+               // stored as x100/BS v3s16
+               is.read((char*)buf, 6);
+               v3s16 speed_i = readV3S16(buf);
+               v3f speed((f32)speed_i.X/100*BS,
+                               (f32)speed_i.Y/100*BS,
+                               (f32)speed_i.Z/100*BS);
+
+               m_speed = speed;
+       }
+
+       virtual bool serverStep(float dtime) { return false; };
+       virtual void clientStep(float dtime) {};
+       
+       virtual void addToScene(scene::ISceneManager *smgr) = 0;
+       virtual void removeFromScene() = 0;
+
+       /*
+               Special methods
+       */
+       
+       // Moves with collision detection
+       void move(float dtime, v3f acceleration);
+       
+protected:
+       v3f m_speed;
+       bool m_touching_ground;
+};
+
+class Test2Object : public MovingObject
+{
+public:
+       // The constructor of every MapBlockObject should be like this
+       Test2Object(MapBlock *block, s16 id, v3f pos):
+               MovingObject(block, id, pos),
+               m_node(NULL)
+       {
+               m_collision_box = new core::aabbox3d<f32>
+                               (-BS*0.3,0,-BS*0.3, BS*0.3,BS*1.7,BS*0.3);
+       }
+       virtual ~Test2Object()
+       {
+               delete m_collision_box;
+       }
+       
+       /*
+               Implementation interface
+       */
+       virtual u16 getTypeId() const
+       {
+               return MAPBLOCKOBJECT_TYPE_TEST2;
+       }
+       virtual void serialize(std::ostream &os, u8 version)
+       {
+               MovingObject::serialize(os, version);
+       }
+       virtual void update(std::istream &is, u8 version)
+       {
+               MovingObject::update(is, version);
+               
+               updateNodePos();
+       }
+
+       virtual bool serverStep(float dtime)
+       {
+               m_speed.X = 2*BS;
+               m_speed.Z = 0;
+
+               if(m_touching_ground)
+               {
+                       static float count = 0;
+                       count -= dtime;
+                       if(count < 0.0)
+                       {
+                               count += 1.0;
+                               m_speed.Y = 6.5*BS;
+                       }
+               }
+
+               move(dtime, v3f(0, -9.81*BS, 0));
+
+               updateNodePos();
+
+               return false;
+       }
+       
+       virtual void clientStep(float dtime)
+       {
+               m_pos += m_speed * dtime;
+
+               updateNodePos();
+       }
+       
+       virtual void addToScene(scene::ISceneManager *smgr)
+       {
+               if(m_node != NULL)
+                       return;
+               
+               //dstream<<"Adding to scene"<<std::endl;
+
+               video::IVideoDriver* driver = smgr->getVideoDriver();
+               
+               scene::SMesh *mesh = new scene::SMesh();
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               video::SColor c(255,255,255,255);
+               video::S3DVertex vertices[4] =
+               {
+                       video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+                       video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+                       video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0),
+                       video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0),
+               };
+               u16 indices[] = {0,1,2,2,3,0};
+               buf->append(vertices, 4, indices, 6);
+               // Set material
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+               buf->getMaterial().setTexture
+                               (0, driver->getTexture("../data/player.png"));
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+               // Add to mesh
+               mesh->addMeshBuffer(buf);
+               buf->drop();
+               m_node = smgr->addMeshSceneNode(mesh, NULL);
+               mesh->drop();
+               m_node->setPosition(getAbsolutePos());
+       }
+       virtual void removeFromScene()
+       {
+               //dstream<<"Removing from scene"<<std::endl;
+               if(m_node != NULL)
+               {
+                       m_node->remove();
+                       m_node = NULL;
+               }
+       }
+
+       /*
+               Special methods
+       */
+       
+       void updateNodePos()
+       {
+               //m_subpos = BS*2.0 * v3f(sin(m_subpos_c), sin(m_subpos_c+1.0), sin(-m_subpos_c));
+               
+               if(m_node != NULL)
+               {
+                       //m_node->setPosition(getAbsolutePos() + m_subpos);
+                       m_node->setPosition(getAbsolutePos());
+               }
+       }
+       
+protected:
+       scene::IMeshSceneNode *m_node;
+};
+
+class RatObject : public MovingObject
+{
+public:
+       RatObject(MapBlock *block, s16 id, v3f pos):
+               MovingObject(block, id, pos),
+               m_node(NULL)
+       {
+               m_collision_box = new core::aabbox3d<f32>
+                               (-BS*0.3,0,-BS*0.3, BS*0.3,BS*0.5,BS*0.3);
+               m_selection_box = new core::aabbox3d<f32>
+                               (-BS*0.3,0,-BS*0.3, BS*0.3,BS*0.5,BS*0.3);
+
+               m_counter1 = 0;
+               m_counter2 = 0;
+       }
+       virtual ~RatObject()
+       {
+               delete m_collision_box;
+               delete m_selection_box;
+       }
+       
+       /*
+               Implementation interface
+       */
+       virtual u16 getTypeId() const
+       {
+               return MAPBLOCKOBJECT_TYPE_RAT;
+       }
+       virtual void serialize(std::ostream &os, u8 version)
+       {
+               MovingObject::serialize(os, version);
+               u8 buf[2];
+
+               // Write yaw * 10
+               writeS16(buf, m_yaw * 10);
+               os.write((char*)buf, 2);
+
+       }
+       virtual void update(std::istream &is, u8 version)
+       {
+               MovingObject::update(is, version);
+               u8 buf[2];
+               
+               // Read yaw * 10
+               is.read((char*)buf, 2);
+               s16 yaw_i = readS16(buf);
+               m_yaw = (f32)yaw_i / 10;
+
+               updateNodePos();
+       }
+
+       virtual bool serverStep(float dtime)
+       {
+               v3f dir(cos(m_yaw/180*PI),0,sin(m_yaw/180*PI));
+
+               f32 speed = 2*BS;
+
+               m_speed.X = speed * dir.X;
+               m_speed.Z = speed * dir.Z;
+
+               if(m_touching_ground && (m_oldpos - m_pos).getLength() < dtime*speed/2)
+               {
+                       m_counter1 -= dtime;
+                       if(m_counter1 < 0.0)
+                       {
+                               m_counter1 += 1.0;
+                               m_speed.Y = 5.0*BS;
+                       }
+               }
+
+               {
+                       m_counter2 -= dtime;
+                       if(m_counter2 < 0.0)
+                       {
+                               m_counter2 += (float)(rand()%100)/100*3.0;
+                               m_yaw += ((float)(rand()%200)-100)/100*180;
+                               m_yaw = wrapDegrees(m_yaw);
+                       }
+               }
+
+               m_oldpos = m_pos;
+
+               //m_yaw += dtime*90;
+
+               move(dtime, v3f(0, -9.81*BS, 0));
+
+               updateNodePos();
+
+               return false;
+       }
+       
+       virtual void clientStep(float dtime)
+       {
+               m_pos += m_speed * dtime;
+
+               updateNodePos();
+       }
+       
+       virtual void addToScene(scene::ISceneManager *smgr)
+       {
+               if(m_node != NULL)
+                       return;
+               
+               video::IVideoDriver* driver = smgr->getVideoDriver();
+               
+               scene::SMesh *mesh = new scene::SMesh();
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               video::SColor c(255,255,255,255);
+               video::S3DVertex vertices[4] =
+               {
+                       video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+                       video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+                       video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 1,0),
+                       video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 0,0),
+               };
+               u16 indices[] = {0,1,2,2,3,0};
+               buf->append(vertices, 4, indices, 6);
+               // Set material
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+               buf->getMaterial().setTexture
+                               (0, driver->getTexture("../data/rat.png"));
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+               // Add to mesh
+               mesh->addMeshBuffer(buf);
+               buf->drop();
+               m_node = smgr->addMeshSceneNode(mesh, NULL);
+               mesh->drop();
+               m_node->setPosition(getAbsolutePos());
+       }
+       virtual void removeFromScene()
+       {
+               if(m_node != NULL)
+               {
+                       m_node->remove();
+                       m_node = NULL;
+               }
+       }
+
+       virtual std::string getInventoryString()
+       {
+               // There must be a space after the name
+               // Or does there?
+               return std::string("Rat ");
+       }
+
+       /*
+               Special methods
+       */
+       
+       void updateNodePos()
+       {
+               if(m_node != NULL)
+               {
+                       m_node->setPosition(getAbsolutePos());
+                       m_node->setRotation(v3f(0, -m_yaw+180, 0));
+               }
+       }
+       
+protected:
+       scene::IMeshSceneNode *m_node;
+       float m_yaw;
+
+       float m_counter1;
+       float m_counter2;
+       v3f m_oldpos;
+};
+
+class SignObject : public MapBlockObject
+{
+public:
+       // The constructor of every MapBlockObject should be like this
+       SignObject(MapBlock *block, s16 id, v3f pos):
+               MapBlockObject(block, id, pos),
+               m_node(NULL)
+       {
+               m_selection_box = new core::aabbox3d<f32>
+                               (-BS*0.4,-BS*0.5,-BS*0.4, BS*0.4,BS*0.5,BS*0.4);
+       }
+       virtual ~SignObject()
+       {
+               delete m_selection_box;
+       }
+       
+       /*
+               Implementation interface
+       */
+       virtual u16 getTypeId() const
+       {
+               return MAPBLOCKOBJECT_TYPE_SIGN;
+       }
+       virtual void serialize(std::ostream &os, u8 version)
+       {
+               serializeBase(os, version);
+               u8 buf[2];
+
+               // Write yaw * 10
+               writeS16(buf, m_yaw * 10);
+               os.write((char*)buf, 2);
+
+               // Write text length
+               writeU16(buf, m_text.size());
+               os.write((char*)buf, 2);
+               
+               // Write text
+               os.write(m_text.c_str(), m_text.size());
+       }
+       virtual void update(std::istream &is, u8 version)
+       {
+               u8 buf[2];
+
+               // Read yaw * 10
+               is.read((char*)buf, 2);
+               s16 yaw_i = readS16(buf);
+               m_yaw = (f32)yaw_i / 10;
+
+               // Read text length
+               is.read((char*)buf, 2);
+               u16 size = readU16(buf);
+
+               // Read text
+               m_text.clear();
+               for(u16 i=0; i<size; i++)
+               {
+                       is.read((char*)buf, 1);
+                       m_text += buf[0];
+               }
+
+               updateSceneNode();
+       }
+       virtual bool serverStep(float dtime)
+       {
+               return false;
+       }
+       virtual void addToScene(scene::ISceneManager *smgr)
+       {
+               if(m_node != NULL)
+                       return;
+               
+               video::IVideoDriver* driver = smgr->getVideoDriver();
+               
+               scene::SMesh *mesh = new scene::SMesh();
+               { // Front
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               video::SColor c(255,255,255,255);
+               video::S3DVertex vertices[4] =
+               {
+                       video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c, 0,1),
+                       video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c, 1,1),
+                       video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 1,0),
+                       video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 0,0),
+               };
+               u16 indices[] = {0,1,2,2,3,0};
+               buf->append(vertices, 4, indices, 6);
+               // Set material
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+               buf->getMaterial().setTexture
+                               (0, driver->getTexture("../data/sign.png"));
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+               // Add to mesh
+               mesh->addMeshBuffer(buf);
+               buf->drop();
+               }
+               { // Back
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               video::SColor c(255,255,255,255);
+               video::S3DVertex vertices[4] =
+               {
+                       video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c, 0,1),
+                       video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c, 1,1),
+                       video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 1,0),
+                       video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 0,0),
+               };
+               u16 indices[] = {0,1,2,2,3,0};
+               buf->append(vertices, 4, indices, 6);
+               // Set material
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+               buf->getMaterial().setTexture
+                               (0, driver->getTexture("../data/sign_back.png"));
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+               // Add to mesh
+               mesh->addMeshBuffer(buf);
+               buf->drop();
+               }
+               m_node = smgr->addMeshSceneNode(mesh, NULL);
+               mesh->drop();
+
+               updateSceneNode();
+       }
+       virtual void removeFromScene()
+       {
+               if(m_node != NULL)
+               {
+                       m_node->remove();
+                       m_node = NULL;
+               }
+       }
+
+       virtual std::string infoText()
+       {
+               return std::string("\"") + m_text + "\"";
+       }
+
+       virtual std::string getInventoryString()
+       {
+               return std::string("Sign ")+m_text;
+       }
+
+       /*
+               Special methods
+       */
+
+       void updateSceneNode()
+       {
+               if(m_node != NULL)
+               {
+                       m_node->setPosition(getAbsolutePos());
+                       m_node->setRotation(v3f(0, m_yaw, 0));
+               }
+       }
+
+       void setText(std::string text)
+       {
+               if(text.size() > SIGN_TEXT_MAX_LENGTH)
+                       text = text.substr(0, SIGN_TEXT_MAX_LENGTH);
+               m_text = text;
+
+               setBlockChanged();
+       }
+
+       void setYaw(f32 yaw)
+       {
+               m_yaw = yaw;
+
+               setBlockChanged();
+       }
+       
+protected:
+       scene::IMeshSceneNode *m_node;
+       std::string m_text;
+       f32 m_yaw;
+};
+
+struct DistanceSortedObject
+{
+       DistanceSortedObject(MapBlockObject *a_obj, f32 a_d)
+       {
+               obj = a_obj;
+               d = a_d;
+       }
+       
+       MapBlockObject *obj;
+       f32 d;
+
+       bool operator < (DistanceSortedObject &other)
+       {
+               return d < other.d;
+       }
+};
+
+class MapBlockObjectList
+{
+public:
+       MapBlockObjectList(MapBlock *block);
+       ~MapBlockObjectList();
+       // Writes the count, id, the type id and the parameters of all objects
+       void serialize(std::ostream &os, u8 version);
+       // Reads ids, type_ids and parameters.
+       // Creates, updates and deletes objects.
+       // If smgr!=NULL, new objects are added to the scene
+       void update(std::istream &is, u8 version, scene::ISceneManager *smgr);
+       // Finds a new unique id
+       s16 getFreeId() throw(ContainerFullException);
+       /*
+               Adds an object.
+               Set id to -1 to have this set it to a suitable one.
+               The block pointer member is set to this block.
+       */
+       void add(MapBlockObject *object)
+                       throw(ContainerFullException, AlreadyExistsException);
+
+       // Deletes and removes all objects
+       void clear();
+
+       /*
+               Removes an object.
+               Ignores inexistent objects
+       */
+       void remove(s16 id);
+       /*
+               References an object.
+               The object will not be valid after step() or of course if
+               it is removed.
+               Grabbing the lock is recommended while processing.
+       */
+       MapBlockObject * get(s16 id);
+
+       // You'll want to grab this in a SharedPtr
+       JMutexAutoLock * getLock()
+       {
+               return new JMutexAutoLock(m_mutex);
+       }
+
+       // Steps all objects and if server==true, removes those that
+       // want to be removed
+       void step(float dtime, bool server);
+
+       // Wraps an object that wants to move onto this block from an another
+       // Returns true if wrapping was impossible
+       bool wrapObject(MapBlockObject *object);
+       
+       // origin is relative to block
+       void getObjects(v3f origin, f32 max_d,
+                       core::array<DistanceSortedObject> &dest);
+
+private:
+       JMutex m_mutex;
+       // Key is id
+       core::map<s16, MapBlockObject*> m_objects;
+       MapBlock *m_block;
+};
+
+
+#endif
+
diff --git a/src/mapnode.h b/src/mapnode.h
new file mode 100644 (file)
index 0000000..68e6691
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAPNODE_HEADER
+#define MAPNODE_HEADER
+
+#include <iostream>
+#include "common_irrlicht.h"
+#include "light.h"
+#include "utility.h"
+#include "exceptions.h"
+#include "serialization.h"
+
+// Size of node in rendering units
+#define BS 10
+
+#define MATERIALS_COUNT 256
+
+// This is completely ignored. It doesn't create faces with anything.
+#define MATERIAL_IGNORE 255
+// This is the common material through which the player can walk
+// and which is transparent to light
+#define MATERIAL_AIR 254
+
+/*
+       Materials-todo:
+
+       GRAVEL
+         - Dynamics of gravel: if there is a drop of more than two
+           blocks on any side, it will drop in there. Is this doable?
+*/
+
+enum Material
+{
+       MATERIAL_STONE=0,
+
+       MATERIAL_GRASS,
+
+       /*
+               For water, the param is water pressure. 0...127.
+               TODO: No, at least the lowest nibble is used for lighting.
+               
+               - Water will be a bit like light, but with different flow
+                 behavior.
+               - Water blocks will fall down if there is empty space below.
+               - If there is water below, the pressure of the block below is
+                 the pressure of the current block + 1, or higher.
+               - If there is any pressure in a horizontally neighboring
+                 block, a water block will try to move away from it.
+               - If there is >=2 of pressure in a block below, water will
+                 try to move upwards.
+               - NOTE: To keep large operations fast, we have to keep a
+                       cache of the water-air-surfaces, just like with light
+       */
+       MATERIAL_WATER,
+
+       MATERIAL_LIGHT,
+
+       MATERIAL_TREE,
+       MATERIAL_LEAVES,
+
+       MATERIAL_GRASS_FOOTSTEPS,
+       
+       MATERIAL_MESE,
+       
+       // This is set to the number of the actual values in this enum
+       USEFUL_MATERIAL_COUNT
+};
+
+/*
+       If true, the material allows light propagation and brightness is stored
+       in param.
+*/
+inline bool light_propagates_material(u8 m)
+{
+       return (m == MATERIAL_AIR || m == MATERIAL_LIGHT || m == MATERIAL_WATER);
+}
+
+/*
+       If true, the material allows lossless sunlight propagation.
+*/
+inline bool sunlight_propagates_material(u8 m)
+{
+       return (m == MATERIAL_AIR);
+}
+
+/*
+       On a node-node surface, the material of the node with higher solidness
+       is used for drawing.
+       0: Invisible
+       1: Transparent
+       2: Opaque
+*/
+inline u8 material_solidness(u8 m)
+{
+       if(m == MATERIAL_AIR)
+               return 0;
+       if(m == MATERIAL_WATER)
+               return 1;
+       return 2;
+}
+
+/*
+       Nodes make a face if materials differ and solidness differs.
+       Return value:
+               0: No face
+               1: Face uses m1's material
+               2: Face uses m2's material
+*/
+inline u8 face_materials(u8 m1, u8 m2)
+{
+       if(m1 == MATERIAL_IGNORE || m2 == MATERIAL_IGNORE)
+               return 0;
+       
+       bool materials_differ = (m1 != m2);
+       bool solidness_differs = (material_solidness(m1) != material_solidness(m2));
+       bool makes_face = materials_differ && solidness_differs;
+
+       if(makes_face == false)
+               return 0;
+
+       if(material_solidness(m1) > material_solidness(m2))
+               return 1;
+       else
+               return 2;
+}
+
+struct MapNode
+{
+       //TODO: block type to differ from material
+       //      (e.g. grass edges or something)
+       // block type
+       u8 d;
+
+       // Removed because light is now stored in param for air
+       // f32 light;
+
+       /*
+               Misc parameter. Initialized to 0.
+               - For light_propagates() blocks, this is light intensity,
+                 stored logarithmically from 0 to LIGHT_MAX.
+                 Sunlight is LIGHT_SUN, which is LIGHT_MAX+1.
+       */
+       s8 param;
+
+       MapNode(const MapNode & n)
+       {
+               *this = n;
+       }
+       
+       MapNode(u8 data=MATERIAL_AIR, u8 a_param=0)
+       {
+               d = data;
+               param = a_param;
+       }
+
+       bool light_propagates()
+       {
+               return light_propagates_material(d);
+       }
+       
+       bool sunlight_propagates()
+       {
+               return sunlight_propagates_material(d);
+       }
+       
+       u8 solidness()
+       {
+               return material_solidness(d);
+       }
+
+       u8 light_source()
+       {
+               /*
+                       Note that a block that isn't light_propagates() can be a light source.
+               */
+               if(d == MATERIAL_LIGHT)
+                       return LIGHT_MAX;
+               
+               return 0;
+       }
+
+       u8 getLight()
+       {
+               // Select the brightest of [light_source, transparent_light]
+               u8 light = 0;
+               if(light_propagates())
+                       light = param & 0x0f;
+               if(light_source() > light)
+                       light = light_source();
+               return light;
+       }
+
+       void setLight(u8 a_light)
+       {
+               // If not transparent, can't set light
+               if(light_propagates() == false)
+                       return;
+               param = a_light;
+       }
+
+       static u32 serializedLength(u8 version)
+       {
+               if(!ser_ver_supported(version))
+                       throw VersionMismatchException("ERROR: MapNode format not supported");
+                       
+               if(version == 0)
+                       return 1;
+               else
+                       return 2;
+       }
+       void serialize(u8 *dest, u8 version)
+       {
+               if(!ser_ver_supported(version))
+                       throw VersionMismatchException("ERROR: MapNode format not supported");
+                       
+               if(version == 0)
+               {
+                       dest[0] = d;
+               }
+               else
+               {
+                       dest[0] = d;
+                       dest[1] = param;
+               }
+       }
+       void deSerialize(u8 *source, u8 version)
+       {
+               if(!ser_ver_supported(version))
+                       throw VersionMismatchException("ERROR: MapNode format not supported");
+                       
+               if(version == 0)
+               {
+                       d = source[0];
+               }
+               else if(version == 1)
+               {
+                       d = source[0];
+                       // This version doesn't support saved lighting
+                       if(light_propagates() || light_source() > 0)
+                               param = 0;
+                       else
+                               param = source[1];
+               }
+               else
+               {
+                       d = source[0];
+                       param = source[1];
+               }
+       }
+};
+
+/*
+       Returns integer position of the node in given
+       floating point position.
+*/
+inline v3s16 floatToInt(v3f p)
+{
+       v3s16 p2(
+               (p.X + (p.X>0 ? BS/2 : -BS/2))/BS,
+               (p.Y + (p.Y>0 ? BS/2 : -BS/2))/BS,
+               (p.Z + (p.Z>0 ? BS/2 : -BS/2))/BS);
+       return p2;
+}
+
+inline v3f intToFloat(v3s16 p)
+{
+       v3f p2(
+               p.X * BS,
+               p.Y * BS,
+               p.Z * BS
+       );
+       return p2;
+}
+
+
+
+#endif
+
diff --git a/src/mapsector.cpp b/src/mapsector.cpp
new file mode 100644 (file)
index 0000000..8dd1c8c
--- /dev/null
@@ -0,0 +1,652 @@
+#include "mapsector.h"
+#include "jmutexautolock.h"
+#include "client.h"
+#include "exceptions.h"
+
+MapSector::MapSector(NodeContainer *parent, v2s16 pos):
+               differs_from_disk(true),
+               usage_timer(0.0),
+               m_parent(parent),
+               m_pos(pos),
+               m_block_cache(NULL)
+{
+       m_mutex.Init();
+       assert(m_mutex.IsInitialized());
+}
+
+MapSector::~MapSector()
+{
+       deleteBlocks();
+}
+
+void MapSector::deleteBlocks()
+{
+       JMutexAutoLock lock(m_mutex);
+
+       // Clear cache
+       m_block_cache = NULL;
+
+       // Delete all
+       core::map<s16, MapBlock*>::Iterator i = m_blocks.getIterator();
+       for(; i.atEnd() == false; i++)
+       {
+               delete i.getNode()->getValue();
+       }
+
+       // Clear container
+       m_blocks.clear();
+}
+
+MapBlock * MapSector::getBlockBuffered(s16 y)
+{
+       MapBlock *block;
+
+       if(m_block_cache != NULL && y == m_block_cache_y){
+               return m_block_cache;
+       }
+       
+       // If block doesn't exist, return NULL
+       core::map<s16, MapBlock*>::Node *n = m_blocks.find(y);
+       if(n == NULL)
+       {
+               block = NULL;
+       }
+       // If block exists, return it
+       else{
+               block = n->getValue();
+       }
+       
+       // Cache the last result
+       m_block_cache_y = y;
+       m_block_cache = block;
+       
+       return block;
+}
+
+MapBlock * MapSector::getBlockNoCreate(s16 y)
+{
+       JMutexAutoLock lock(m_mutex);
+       
+       MapBlock *block = getBlockBuffered(y);
+
+       if(block == NULL)
+               throw InvalidPositionException();
+       
+       return block;
+}
+
+MapBlock * MapSector::createBlankBlockNoInsert(s16 y)
+{
+       // There should not be a block at this position
+       if(getBlockBuffered(y) != NULL)
+               throw AlreadyExistsException("Block already exists");
+
+       v3s16 blockpos_map(m_pos.X, y, m_pos.Y);
+       
+       MapBlock *block = new MapBlock(m_parent, blockpos_map);
+       
+       return block;
+}
+
+MapBlock * MapSector::createBlankBlock(s16 y)
+{
+       JMutexAutoLock lock(m_mutex);
+       
+       MapBlock *block = createBlankBlockNoInsert(y);
+       
+       m_blocks.insert(y, block);
+
+       return block;
+}
+
+void MapSector::insertBlock(MapBlock *block)
+{
+       s16 block_y = block->getPos().Y;
+
+       {
+               JMutexAutoLock lock(m_mutex);
+
+               MapBlock *block2 = getBlockBuffered(block_y);
+               if(block2 != NULL){
+                       throw AlreadyExistsException("Block already exists");
+               }
+
+               v2s16 p2d(block->getPos().X, block->getPos().Z);
+               assert(p2d == m_pos);
+               
+               // Insert into container
+               m_blocks.insert(block_y, block);
+       }
+}
+
+void MapSector::removeBlock(MapBlock *block)
+{
+       s16 block_y = block->getPos().Y;
+
+       JMutexAutoLock lock(m_mutex);
+       
+       // Clear from cache
+       m_block_cache = NULL;
+       
+       // Remove from container
+       m_blocks.remove(block_y);
+}
+
+void MapSector::getBlocks(core::list<MapBlock*> &dest)
+{
+       JMutexAutoLock lock(m_mutex);
+
+       core::list<MapBlock*> ref_list;
+
+       core::map<s16, MapBlock*>::Iterator bi;
+
+       bi = m_blocks.getIterator();
+       for(; bi.atEnd() == false; bi++)
+       {
+               MapBlock *b = bi.getNode()->getValue();
+               dest.push_back(b);
+       }
+}
+
+/*
+       ServerMapSector
+*/
+
+ServerMapSector::ServerMapSector(NodeContainer *parent, v2s16 pos, u16 hm_split):
+               MapSector(parent, pos),
+               m_hm_split(hm_split),
+               m_objects(NULL)
+{
+       // hm_split has to be 1 or 2^x
+       assert(hm_split == 0 || hm_split == 1 || (hm_split & (hm_split-1)) == 0);
+       assert(hm_split * hm_split <= MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT);
+       
+       for(u16 i=0; i<hm_split*hm_split; i++)
+               m_heightmaps[i] = NULL;
+}
+
+ServerMapSector::~ServerMapSector()
+{
+       u16 hm_count = m_hm_split * m_hm_split;
+
+       // Write heightmaps
+       for(u16 i=0; i<hm_count; i++)
+       {
+               if(m_heightmaps[i])
+                       delete m_heightmaps[i];
+       }
+
+       if(m_objects)
+               delete m_objects;
+}
+
+void ServerMapSector::setHeightmap(v2s16 hm_p, FixedHeightmap *hm)
+{
+       assert(isInArea(hm_p, m_hm_split));
+
+       s16 i = hm_p.Y * m_hm_split + hm_p.X;
+       
+       // Don't allow setting already set heightmaps as of now
+       assert(m_heightmaps[i] == NULL);
+       
+       /*std::cout<<"MapSector::setHeightmap for sector "
+                       <<"("<<m_pos.X<<","<<m_pos.Y<<"): "
+                       <<"Setting heightmap "
+                       <<"("<<hm_p.X<<","<<hm_p.Y<<")"
+                       <<" which is i="<<i
+                       <<" to pointer "<<(long long)hm
+                       <<std::endl;*/
+
+       m_heightmaps[i] = hm;
+       
+       differs_from_disk = true;
+}
+
+FixedHeightmap * ServerMapSector::getHeightmap(v2s16 hm_p)
+{
+       assert(isInArea(hm_p, m_hm_split));
+
+       s16 i = hm_p.Y * m_hm_split + hm_p.X;
+       
+       return m_heightmaps[i];
+}
+
+f32 ServerMapSector::getGroundHeight(v2s16 p, bool generate)
+{
+       // If no heightmaps
+       if(m_hm_split == 0)
+       {
+               /*std::cout<<"Sector has no heightmap"
+                               <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+                               <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+                               <<std::endl;*/
+               return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+       }
+       
+       // Side length of heightmap
+       s16 hm_d = MAP_BLOCKSIZE / m_hm_split;
+
+       // Position of selected heightmap
+       v2s16 hm_p = getContainerPos(p, hm_d);
+       if(isInArea(hm_p, m_hm_split) == false)
+       {
+               /*std::cout<<"Sector has no heightmap ("<<hm_p.X<<","<<hm_p.Y<<")"
+                               <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+                               <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+                               <<std::endl;*/
+               return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+       }
+
+       // Selected heightmap
+       FixedHeightmap *hm = m_heightmaps[hm_p.Y * m_hm_split + hm_p.X];
+
+       if(hm == NULL)
+       {
+               /*std::cout<<"Sector heightmap ("<<hm_p.X<<","<<hm_p.Y<<")"
+                               " is NULL"
+                               <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+                               <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+                               <<std::endl;*/
+               return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+       }
+       
+       // Position in selected heighmap
+       v2s16 p_in_hm = p - hm_p * hm_d;
+       if(isInArea(p_in_hm, hm_d+1) == false)
+       {
+               /*std::cout<<"Position ("<<p_in_hm.X<<","<<p_in_hm.Y<<")"
+                               " not in sector heightmap area"
+                               <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+                               <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+                               <<std::endl;*/
+               return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+       }
+       
+       f32 h = hm->getGroundHeight(p_in_hm);
+       
+       /*if(h < GROUNDHEIGHT_VALID_MINVALUE)
+       {
+               std::cout<<"Sector heightmap ("<<hm_p.X<<","<<hm_p.Y<<")"
+                               " returned invalid value"
+                               <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+                               <<" which is ("<<p_in_hm.X<<","<<p_in_hm.Y<<") in heightmap"
+                               <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+                               <<std::endl;
+       }*/
+
+       return h;
+}
+
+void ServerMapSector::setGroundHeight(v2s16 p, f32 y, bool generate)
+{
+       /*
+               NOTE:
+               This causes glitches because the sector cannot be actually
+               modified according to heightmap changes.
+
+               This is useful when generating continued sub-heightmaps
+               inside the sector.
+       */
+
+       // If no heightmaps
+       if(m_hm_split == 0)
+               return;
+       
+       // Side length of heightmap
+       s16 hm_d = MAP_BLOCKSIZE / m_hm_split;
+
+       // Position of selected heightmap
+       v2s16 hm_p = getContainerPos(p, hm_d);
+       if(isInArea(hm_p, m_hm_split) == false)
+               return;
+
+       // Selected heightmap
+       FixedHeightmap *hm = m_heightmaps[hm_p.Y * m_hm_split + hm_p.X];
+
+       if(hm == NULL)
+               return;
+       
+       // Position in selected heighmap
+       v2s16 p_in_hm = p - hm_p * hm_d;
+       if(isInArea(p_in_hm, hm_d) == false)
+               return;
+       
+       hm->setGroundHeight(p_in_hm, y);
+       
+       differs_from_disk = true;
+}
+
+void ServerMapSector::serialize(std::ostream &os, u8 version)
+{
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: MapSector format not supported");
+       
+       /*
+               [0] u8 serialization version
+               + heightmap data
+       */
+       
+       // Server has both of these, no need to support not having them.
+       assert(m_objects != NULL);
+
+       // Write version
+       os.write((char*)&version, 1);
+       
+       /*
+               Serialize heightmap(s)
+       */
+
+       // Version with single heightmap
+       if(version <= 7)
+       {
+               u32 heightmap_size =
+                               FixedHeightmap::serializedLength(version, MAP_BLOCKSIZE);
+               
+               SharedBuffer<u8> data(heightmap_size);
+               m_heightmaps[0]->serialize(*data, version);
+
+               os.write((const char*)*data, heightmap_size);
+               
+               if(version >= 5)
+               {
+                       /*
+                               Write objects
+                       */
+                       
+                       u16 object_count;
+                       if(m_objects->size() > 65535)
+                               object_count = 65535;
+                       else
+                               object_count = m_objects->size();
+
+                       u8 b[2];
+                       writeU16(b, object_count);
+                       os.write((char*)b, 2);
+                       
+                       core::map<v3s16, u8>::Iterator i;
+                       i = m_objects->getIterator();
+                       for(; i.atEnd() == false; i++)
+                       {
+                               v3s16 p = i.getNode()->getKey();
+                               u8 d = i.getNode()->getValue();
+                               u8 b[7];
+                               writeV3S16(&b[0], p);
+                               b[6] = d;
+                               os.write((char*)b, 7);
+                       }
+               }
+       }
+       // Version with multiple heightmaps
+       else
+       {
+               u8 buf[2];
+               
+               if(m_hm_split > 255)
+                       throw SerializationError("Sector has too many heightmaps");
+               
+               // Write heightmap split ratio
+               writeU8(buf, m_hm_split);
+               os.write((char*)buf, 1);
+
+               // If there are heightmaps, write them
+               if(m_hm_split != 0)
+               {
+                       u16 hm_d = MAP_BLOCKSIZE / m_hm_split;
+
+                       u32 hm_size = FixedHeightmap::serializedLength(version, hm_d);
+                       SharedBuffer<u8> data(hm_size);
+                       
+                       u16 hm_count = m_hm_split * m_hm_split;
+
+                       // Write heightmaps
+                       for(u16 i=0; i<hm_count; i++)
+                       {
+                               m_heightmaps[i]->serialize(*data, version);
+                               os.write((const char*)*data, hm_size);
+                       }
+               }
+       
+               /*
+                       Write objects
+               */
+               
+               u16 object_count;
+               if(m_objects->size() > 65535)
+                       object_count = 65535;
+               else
+                       object_count = m_objects->size();
+
+               u8 b[2];
+               writeU16(b, object_count);
+               os.write((char*)b, 2);
+               
+               core::map<v3s16, u8>::Iterator i;
+               i = m_objects->getIterator();
+               for(; i.atEnd() == false; i++)
+               {
+                       v3s16 p = i.getNode()->getKey();
+                       u8 d = i.getNode()->getValue();
+                       u8 b[7];
+                       writeV3S16(&b[0], p);
+                       b[6] = d;
+                       os.write((char*)b, 7);
+               }
+       }
+}
+
+ServerMapSector* ServerMapSector::deSerialize(
+               std::istream &is,
+               NodeContainer *parent,
+               v2s16 p2d,
+               Heightmap *master_hm,
+               core::map<v2s16, MapSector*> & sectors
+       )
+{
+       /*
+               [0] u8 serialization version
+               + heightmap data
+       */
+
+       /*
+               Read stuff
+       */
+       
+       // Read version
+       u8 version = SER_FMT_VER_INVALID;
+       is.read((char*)&version, 1);
+       
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: MapSector format not supported");
+       
+       /*
+               Read heightmap(s)
+       */
+
+       FixedHeightmap *hms[MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT];
+       u16 hm_split = 0;
+       
+       // Version with a single heightmap
+       if(version <= 7)
+       {
+               hm_split = 1;
+
+               u32 hm_size =
+                               FixedHeightmap::serializedLength(version, MAP_BLOCKSIZE);
+               
+               SharedBuffer<u8> data(hm_size);
+               is.read((char*)*data, hm_size);
+
+               hms[0] = new FixedHeightmap(master_hm, p2d, MAP_BLOCKSIZE);
+               hms[0]->deSerialize(*data, version);
+       }
+       // Version with multiple heightmaps
+       else
+       {
+               u8 buf[2];
+
+               // Read split ratio
+               is.read((char*)buf, 1);
+               hm_split = readU8(buf);
+
+               // If there are heightmaps, read them
+               if(hm_split != 0)
+               {
+                       u16 hm_count = hm_split * hm_split;
+
+                       if(hm_count > MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT)
+                               throw SerializationError("Sector has too many heightmaps");
+                       
+                       u16 hm_d = MAP_BLOCKSIZE / hm_split;
+
+                       u32 hm_size = FixedHeightmap::serializedLength(version, hm_d);
+                       
+                       u16 i=0;
+                       for(s16 y=0; y<hm_split; y++)
+                       for(s16 x=0; x<hm_split; x++)
+                       {
+                               SharedBuffer<u8> data(hm_size);
+                               is.read((char*)*data, hm_size);
+
+                               hms[i] = new FixedHeightmap(master_hm, p2d+v2s16(x,y), hm_d);
+                               hms[i]->deSerialize(*data, version);
+                               i++;
+                       }
+               }
+       }
+
+       /*
+               Read objects
+       */
+
+       core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
+
+       if(version >= 5)
+       {
+               u8 b[2];
+               is.read((char*)b, 2);
+               u16 object_count = readU16(b);
+
+               for(u16 i=0; i<object_count; i++)
+               {
+                       u8 b[7];
+                       is.read((char*)b, 7);
+                       v3s16 p = readV3S16(&b[0]);
+                       u8 d = b[6];
+                       objects->insert(p, d);
+               }
+       }
+       
+       /*
+               Get or create sector
+       */
+
+       ServerMapSector *sector = NULL;
+
+       core::map<v2s16, MapSector*>::Node *n = sectors.find(p2d);
+
+       if(n != NULL)
+       {
+               dstream<<"deSerializing existent sectors not supported "
+                               "at the moment, because code hasn't been tested."
+                               <<std::endl;
+               assert(0);
+               // NOTE: At least hm_split mismatch would have to be checked
+               
+               //sector = n->getValue();
+       }
+       else
+       {
+               sector = new ServerMapSector(parent, p2d, hm_split);
+               sectors.insert(p2d, sector);
+       }
+
+       /*
+               Set stuff in sector
+       */
+
+       // Set heightmaps
+       
+       sector->m_hm_split = hm_split;
+
+       u16 hm_count = hm_split * hm_split;
+
+       for(u16 i=0; i<hm_count; i++)
+       {
+               // Set (or change) heightmap
+               FixedHeightmap *oldhm = sector->m_heightmaps[i];
+               sector->m_heightmaps[i] = hms[i];
+               if(oldhm != NULL)
+                       delete oldhm;
+       }
+       
+       // Set (or change) objects
+       core::map<v3s16, u8> *oldfo = sector->m_objects;
+       sector->m_objects = objects;
+       if(oldfo)
+               delete oldfo;
+
+       return sector;
+}
+
+/*
+       ClientMapSector
+*/
+
+ClientMapSector::ClientMapSector(NodeContainer *parent, v2s16 pos):
+               MapSector(parent, pos)
+{
+}
+
+ClientMapSector::~ClientMapSector()
+{
+}
+
+void ClientMapSector::deSerialize(std::istream &is)
+{
+       /*
+               [0] u8 serialization version
+               [1] s16 corners[0]
+               [3] s16 corners[1]
+               [5] s16 corners[2]
+               [7] s16 corners[3]
+               size = 9
+               
+               In which corners are in these positions
+               v2s16(0,0),
+               v2s16(1,0),
+               v2s16(1,1),
+               v2s16(0,1),
+       */
+       
+       // Read version
+       u8 version = SER_FMT_VER_INVALID;
+       is.read((char*)&version, 1);
+       
+       if(!ser_ver_supported(version))
+               throw VersionMismatchException("ERROR: MapSector format not supported");
+       if(version <= 7)
+               throw VersionMismatchException("ERROR: MapSector format not supported");
+       
+       u8 buf[2];
+       
+       // Read corners
+       is.read((char*)buf, 2);
+       s16 c0 = readU16(buf);
+       is.read((char*)buf, 2);
+       s16 c1 = readU16(buf);
+       is.read((char*)buf, 2);
+       s16 c2 = readU16(buf);
+       is.read((char*)buf, 2);
+       s16 c3 = readU16(buf);
+       
+       /*
+               Set stuff in sector
+       */
+       
+       m_corners[0] = c0;
+       m_corners[1] = c1;
+       m_corners[2] = c2;
+       m_corners[3] = c3;
+}
+
+//END
diff --git a/src/mapsector.h b/src/mapsector.h
new file mode 100644 (file)
index 0000000..196a129
--- /dev/null
@@ -0,0 +1,318 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAPSECTOR_HEADER
+#define MAPSECTOR_HEADER
+
+#include <jmutex.h>
+#include "common_irrlicht.h"
+#include "mapblock.h"
+#include "heightmap.h"
+#include "exceptions.h"
+
+/*
+       This is an Y-wise stack of MapBlocks.
+*/
+
+#define WATER_LEVEL (-5)
+
+#define SECTOR_OBJECT_TEST 0
+#define SECTOR_OBJECT_TREE_1 1
+#define SECTOR_OBJECT_BUSH_1 2
+
+#define MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT 4
+
+#define MAPSECTOR_SERVER 0
+#define MAPSECTOR_CLIENT 1
+
+class MapSector: public NodeContainer, public Heightmappish
+{
+public:
+       
+       MapSector(NodeContainer *parent, v2s16 pos);
+       virtual ~MapSector();
+
+       virtual u16 nodeContainerId() const
+       {
+               return NODECONTAINER_ID_MAPSECTOR;
+       }
+
+       virtual u32 getId() const = 0;
+
+       void deleteBlocks();
+
+       v2s16 getPos()
+       {
+               return m_pos;
+       }
+
+       MapBlock * getBlockNoCreate(s16 y);
+       MapBlock * createBlankBlockNoInsert(s16 y);
+       MapBlock * createBlankBlock(s16 y);
+       //MapBlock * getBlock(s16 y, bool generate=true);
+
+       void insertBlock(MapBlock *block);
+       
+       // This is used to remove a dummy from the sector while generating it.
+       // Block is only removed from internal container, not deleted.
+       void removeBlock(MapBlock *block);
+       
+       /*
+               This might not be a thread-safe depending on the day.
+               See the implementation.
+       */
+       void getBlocks(core::list<MapBlock*> &dest);
+       
+       /*
+               If all nodes in area can be accessed, returns true and
+               adds all blocks in area to blocks.
+
+               If all nodes in area cannot be accessed, returns false.
+
+               The implementation of this is quite slow
+
+               if blocks==NULL; it is not accessed at all.
+       */
+       bool isValidArea(v3s16 p_min_nodes, v3s16 p_max_nodes,
+                       core::map<s16, MapBlock*> *blocks)
+       {
+               core::map<s16, MapBlock*> bs;
+               
+               v3s16 p_min = getNodeBlockPos(p_min_nodes);
+               v3s16 p_max = getNodeBlockPos(p_max_nodes);
+               if(p_min.X != 0 || p_min.Z != 0
+                               || p_max.X != 0 || p_max.Z != 0)
+                       return false;
+               v3s16 y;
+               for(s16 y=p_min.Y; y<=p_max.Y; y++)
+               {
+                       try{
+                               MapBlock *block = getBlockNoCreate(y);
+                               if(block->isDummy())
+                                       return false;
+                               if(blocks!=NULL)
+                                       bs[y] = block;
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               return false;
+                       }
+               }
+
+               if(blocks!=NULL)
+               {
+                       for(core::map<s16, MapBlock*>::Iterator i=bs.getIterator();
+                                       i.atEnd()==false; i++)
+                       {
+                               MapBlock *block = i.getNode()->getValue();
+                               s16 y = i.getNode()->getKey();
+                               blocks->insert(y, block);
+                       }
+               }
+               return true;
+       }
+
+       void getBlocksInArea(v3s16 p_min_nodes, v3s16 p_max_nodes,
+                       core::map<v3s16, MapBlock*> &blocks)
+       {
+               v3s16 p_min = getNodeBlockPos(p_min_nodes);
+               v3s16 p_max = getNodeBlockPos(p_max_nodes);
+               v3s16 y;
+               for(s16 y=p_min.Y; y<=p_max.Y; y++)
+               {
+                       try{
+                               MapBlock *block = getBlockNoCreate(y);
+                               blocks.insert(block->getPos(), block);
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                       }
+               }
+       }
+       
+       // virtual from NodeContainer
+       bool isValidPosition(v3s16 p)
+       {
+               v3s16 blockpos = getNodeBlockPos(p);
+
+               if(blockpos.X != 0 || blockpos.Z != 0)
+                       return false;
+
+               MapBlock *blockref;
+               try{
+                       blockref = getBlockNoCreate(blockpos.Y);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       return false;
+               }
+
+               return true;
+       }
+
+       // virtual from NodeContainer
+       MapNode getNode(v3s16 p)
+       {
+               v3s16 blockpos = getNodeBlockPos(p);
+               if(blockpos.X != 0 || blockpos.Z != 0)
+                       throw InvalidPositionException
+                               ("MapSector only allows Y");
+
+               MapBlock * blockref = getBlockNoCreate(blockpos.Y);
+               v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+
+               return blockref->getNode(relpos);
+       }
+       // virtual from NodeContainer
+       void setNode(v3s16 p, MapNode & n)
+       {
+               v3s16 blockpos = getNodeBlockPos(p);
+               if(blockpos.X != 0 || blockpos.Z != 0)
+                       throw InvalidPositionException
+                               ("MapSector only allows Y");
+
+               MapBlock * blockref = getBlockNoCreate(blockpos.Y);
+               v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+               blockref->setNode(relpos, n);
+       }
+
+       virtual f32 getGroundHeight(v2s16 p, bool generate=false)
+       {
+               return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+       }
+       virtual void setGroundHeight(v2s16 p, f32 y, bool generate=false)
+       {
+       }
+       
+       // When true, sector metadata is changed from the one on disk
+       // (sector metadata = all but blocks)
+       // Basically, this should be changed to true in every setter method
+       bool differs_from_disk;
+
+       // Counts seconds from last usage.
+       // Sector can be deleted from memory after some time of inactivity.
+       // NOTE: It has to be made very sure no other thread is accessing
+       //       the sector and it doesn't remain in any cache when
+       //       deleting it.
+       float usage_timer;
+
+protected:
+       
+       // The pile of MapBlocks
+       core::map<s16, MapBlock*> m_blocks;
+       //JMutex m_blocks_mutex; // For public access functions
+
+       NodeContainer *m_parent;
+       // Position on parent (in MapBlock widths)
+       v2s16 m_pos;
+
+       // Be sure to set this to NULL when the cached block is deleted 
+       MapBlock *m_block_cache;
+       s16 m_block_cache_y;
+       
+       // This is used for protecting m_blocks
+       JMutex m_mutex;
+       
+       /*
+               Private methods
+       */
+       MapBlock *getBlockBuffered(s16 y);
+
+};
+
+class ServerMapSector : public MapSector
+{
+public:
+       ServerMapSector(NodeContainer *parent, v2s16 pos, u16 hm_split);
+       ~ServerMapSector();
+       
+       u32 getId() const
+       {
+               return MAPSECTOR_SERVER;
+       }
+
+       void setHeightmap(v2s16 hm_p, FixedHeightmap *hm);
+       FixedHeightmap * getHeightmap(v2s16 hm_p);
+       
+       void printHeightmaps()
+       {
+               for(s16 y=0; y<m_hm_split; y++)
+               for(s16 x=0; x<m_hm_split; x++)
+               {
+                       std::cout<<"Sector "
+                                       <<"("<<m_pos.X<<","<<m_pos.Y<<")"
+                                       " heightmap "
+                                       "("<<x<<","<<y<<"):"
+                                       <<std::endl;
+                       FixedHeightmap *hm = getHeightmap(v2s16(x,y));
+                       hm->print();
+               }
+       }
+       
+       void setObjects(core::map<v3s16, u8> *objects)
+       {
+               m_objects = objects;
+               differs_from_disk = true;
+       }
+
+       core::map<v3s16, u8> * getObjects()
+       {
+               differs_from_disk = true;
+               return m_objects;
+       }
+
+       f32 getGroundHeight(v2s16 p, bool generate=false);
+       void setGroundHeight(v2s16 p, f32 y, bool generate=false);
+
+       /*
+               These functions handle metadata.
+               They do not handle blocks.
+       */
+       void serialize(std::ostream &os, u8 version);
+       
+       static ServerMapSector* deSerialize(
+                       std::istream &is,
+                       NodeContainer *parent,
+                       v2s16 p2d,
+                       Heightmap *master_hm,
+                       core::map<v2s16, MapSector*> & sectors
+               );
+               
+private:
+       // Heightmap(s) for the sector
+       FixedHeightmap *m_heightmaps[MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT];
+       // Sector is split in m_hm_split^2 heightmaps.
+       // Value of 0 means there is no heightmap.
+       u16 m_hm_split;
+       // These are removed when they are drawn to blocks.
+       // - Each is drawn when generating blocks; When the last one of
+       //   the needed blocks is being generated.
+       core::map<v3s16, u8> *m_objects;
+};
+
+class ClientMapSector : public MapSector
+{
+public:
+       ClientMapSector(NodeContainer *parent, v2s16 pos);
+       ~ClientMapSector();
+       
+       u32 getId() const
+       {
+               return MAPSECTOR_CLIENT;
+       }
+
+       void deSerialize(std::istream &is);
+
+       s16 getCorner(u16 i)
+       {
+               return m_corners[i];
+       }
+               
+private:
+       // The ground height of the corners is stored in here
+       s16 m_corners[4];
+};
+       
+#endif
+
diff --git a/src/player.cpp b/src/player.cpp
new file mode 100644 (file)
index 0000000..080de60
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "player.h"
+#include "map.h"
+#include "connection.h"
+#include "constants.h"
+
+Player::Player():
+       touching_ground(false),
+       inventory(PLAYER_INVENTORY_SIZE),
+       peer_id(PEER_ID_NEW),
+       m_speed(0,0,0),
+       m_position(0,0,0)
+{
+       updateName("<not set>");
+}
+
+Player::~Player()
+{
+}
+
+void Player::move(f32 dtime, Map &map)
+{
+       v3f position = getPosition();
+       v3f oldpos = position;
+       v3s16 oldpos_i = floatToInt(oldpos);
+
+       /*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
+                       <<oldpos_i.Z<<")"<<std::endl;*/
+
+       position += m_speed * dtime;
+
+       // Skip collision detection if player is non-local
+       if(isLocal() == false)
+       {
+               setPosition(position);
+               return;
+       }
+
+       /*
+               Collision detection
+       */
+
+       v3s16 pos_i = floatToInt(position);
+       
+       // The frame length is limited to the player going 0.1*BS per call
+       f32 d = (float)BS * 0.15;
+
+#define PLAYER_RADIUS (BS*0.3)
+#define PLAYER_HEIGHT (BS*1.7)
+
+       core::aabbox3d<f32> playerbox(
+               position.X - PLAYER_RADIUS,
+               position.Y - 0.0,
+               position.Z - PLAYER_RADIUS,
+               position.X + PLAYER_RADIUS,
+               position.Y + PLAYER_HEIGHT,
+               position.Z + PLAYER_RADIUS
+       );
+       core::aabbox3d<f32> playerbox_old(
+               oldpos.X - PLAYER_RADIUS,
+               oldpos.Y - 0.0,
+               oldpos.Z - PLAYER_RADIUS,
+               oldpos.X + PLAYER_RADIUS,
+               oldpos.Y + PLAYER_HEIGHT,
+               oldpos.Z + PLAYER_RADIUS
+       );
+
+       //hilightboxes.push_back(playerbox);
+
+       touching_ground = false;
+       
+       /*std::cout<<"Checking collisions for ("
+                       <<oldpos_i.X<<","<<oldpos_i.Y<<","<<oldpos_i.Z
+                       <<") -> ("
+                       <<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z
+                       <<"):"<<std::endl;*/
+
+       for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++){
+               for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++){
+                       for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++){
+                               //std::cout<<"with ("<<x<<","<<y<<","<<z<<"): ";
+                               try{
+                                       if(map.getNode(v3s16(x,y,z)).d == MATERIAL_AIR){
+                                               //std::cout<<"air."<<std::endl;
+                                               continue;
+                                       }
+                               }
+                               catch(InvalidPositionException &e)
+                               {
+                                       // Doing nothing here will block the player from
+                                       // walking over map borders
+                               }
+
+                               core::aabbox3d<f32> nodebox = Map::getNodeBox(
+                                               v3s16(x,y,z));
+                               
+                               // See if the player is touching ground
+                               if(
+                                               fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < d
+                                               && nodebox.MaxEdge.X-d > playerbox.MinEdge.X
+                                               && nodebox.MinEdge.X+d < playerbox.MaxEdge.X
+                                               && nodebox.MaxEdge.Z-d > playerbox.MinEdge.Z
+                                               && nodebox.MinEdge.Z+d < playerbox.MaxEdge.Z
+                               ){
+                                       touching_ground = true;
+                               }
+                               
+                               if(playerbox.intersectsWithBox(nodebox))
+                               {
+                               
+       v3f dirs[3] = {
+               v3f(0,0,1), // back
+               v3f(0,1,0), // top
+               v3f(1,0,0), // right
+       };
+       for(u16 i=0; i<3; i++)
+       {
+               f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
+               f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
+               f32 playermax = playerbox.MaxEdge.dotProduct(dirs[i]);
+               f32 playermin = playerbox.MinEdge.dotProduct(dirs[i]);
+               f32 playermax_old = playerbox_old.MaxEdge.dotProduct(dirs[i]);
+               f32 playermin_old = playerbox_old.MinEdge.dotProduct(dirs[i]);
+
+               bool main_edge_collides = 
+                       ((nodemax > playermin && nodemax <= playermin_old + d
+                               && m_speed.dotProduct(dirs[i]) < 0)
+                       ||
+                       (nodemin < playermax && nodemin >= playermax_old - d
+                               && m_speed.dotProduct(dirs[i]) > 0));
+
+               bool other_edges_collide = true;
+               for(u16 j=0; j<3; j++)
+               {
+                       if(j == i)
+                               continue;
+                       f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
+                       f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
+                       f32 playermax = playerbox.MaxEdge.dotProduct(dirs[j]);
+                       f32 playermin = playerbox.MinEdge.dotProduct(dirs[j]);
+                       if(!(nodemax - d > playermin && nodemin + d < playermax))
+                       {
+                               other_edges_collide = false;
+                               break;
+                       }
+               }
+               
+               if(main_edge_collides && other_edges_collide)
+               {
+                       m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
+                       position -= position.dotProduct(dirs[i]) * dirs[i];
+                       position += oldpos.dotProduct(dirs[i]) * dirs[i];
+               }
+       
+       }
+                               } // if(playerbox.intersectsWithBox(nodebox))
+                       } // for x
+               } // for z
+       } // for y
+
+       setPosition(position);
+}
+
+// Y direction is ignored
+void Player::accelerate(v3f target_speed, f32 max_increase)
+{
+       if(m_speed.X < target_speed.X - max_increase)
+               m_speed.X += max_increase;
+       else if(m_speed.X > target_speed.X + max_increase)
+               m_speed.X -= max_increase;
+       else if(m_speed.X < target_speed.X)
+               m_speed.X = target_speed.X;
+       else if(m_speed.X > target_speed.X)
+               m_speed.X = target_speed.X;
+
+       if(m_speed.Z < target_speed.Z - max_increase)
+               m_speed.Z += max_increase;
+       else if(m_speed.Z > target_speed.Z + max_increase)
+               m_speed.Z -= max_increase;
+       else if(m_speed.Z < target_speed.Z)
+               m_speed.Z = target_speed.Z;
+       else if(m_speed.Z > target_speed.Z)
+               m_speed.Z = target_speed.Z;
+}
+
+/*
+       RemotePlayer
+*/
+
+RemotePlayer::RemotePlayer(
+               scene::ISceneNode* parent,
+               IrrlichtDevice *device,
+               s32 id):
+       scene::ISceneNode(parent, (device==NULL)?NULL:device->getSceneManager(), id),
+       m_text(NULL)
+{
+       m_box = core::aabbox3d<f32>(-BS/2,0,-BS/2,BS/2,BS*2,BS/2);
+       
+       if(parent != NULL && device != NULL)
+       {
+               // ISceneNode stores a member called SceneManager
+               scene::ISceneManager* mgr = SceneManager;
+               video::IVideoDriver* driver = mgr->getVideoDriver();
+               gui::IGUIEnvironment* gui = device->getGUIEnvironment();
+
+               // Add a text node for showing the name
+               wchar_t wname[1] = {0};
+               m_text = mgr->addTextSceneNode(gui->getBuiltInFont(),
+                               wname, video::SColor(255,255,255,255), this);
+               m_text->setPosition(v3f(0, (f32)BS*2.1, 0));
+
+               // Attach a simple mesh to the player for showing an image
+               scene::SMesh *mesh = new scene::SMesh();
+               { // Front
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               video::SColor c(255,255,255,255);
+               video::S3DVertex vertices[4] =
+               {
+                       video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+                       video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+                       video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0),
+                       video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0),
+               };
+               u16 indices[] = {0,1,2,2,3,0};
+               buf->append(vertices, 4, indices, 6);
+               // Set material
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+               buf->getMaterial().setTexture(0, driver->getTexture("../data/player.png"));
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               //buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+               // Add to mesh
+               mesh->addMeshBuffer(buf);
+               buf->drop();
+               }
+               { // Back
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               video::SColor c(255,255,255,255);
+               video::S3DVertex vertices[4] =
+               {
+                       video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+                       video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+                       video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0),
+                       video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0),
+               };
+               u16 indices[] = {0,1,2,2,3,0};
+               buf->append(vertices, 4, indices, 6);
+               // Set material
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+               buf->getMaterial().setTexture(0, driver->getTexture("../data/player_back.png"));
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+               // Add to mesh
+               mesh->addMeshBuffer(buf);
+               buf->drop();
+               }
+               scene::IMeshSceneNode *node = mgr->addMeshSceneNode(mesh, this);
+               mesh->drop();
+               node->setPosition(v3f(0,0,0));
+       }
+}
+
+RemotePlayer::~RemotePlayer()
+{
+       if(SceneManager != NULL)
+               ISceneNode::remove();
+}
+
+void RemotePlayer::updateName(const char *name)
+{
+       Player::updateName(name);
+       if(m_text != NULL)
+       {
+               wchar_t wname[PLAYERNAME_SIZE];
+               mbstowcs(wname, m_name, strlen(m_name)+1);
+               m_text->setText(wname);
+       }
+}
+
+/*
+       LocalPlayer
+*/
+
+LocalPlayer::LocalPlayer()
+{
+}
+
+LocalPlayer::~LocalPlayer()
+{
+}
+
+void LocalPlayer::applyControl(float dtime)
+{
+       // Random constants
+#define WALK_ACCELERATION (4.0 * BS)
+#define WALKSPEED_MAX (4.0 * BS)
+       f32 walk_acceleration = WALK_ACCELERATION;
+       f32 walkspeed_max = WALKSPEED_MAX;
+       
+       setPitch(control.pitch);
+       setYaw(control.yaw);
+       
+       v3f move_direction = v3f(0,0,1);
+       move_direction.rotateXZBy(getYaw());
+       
+       v3f speed = v3f(0,0,0);
+
+       // Superspeed mode
+       bool superspeed = false;
+       if(control.superspeed)
+       {
+               speed += move_direction;
+               superspeed = true;
+       }
+
+       if(control.up)
+       {
+               speed += move_direction;
+       }
+       if(control.down)
+       {
+               speed -= move_direction;
+       }
+       if(control.left)
+       {
+               speed += move_direction.crossProduct(v3f(0,1,0));
+       }
+       if(control.right)
+       {
+               speed += move_direction.crossProduct(v3f(0,-1,0));
+       }
+       if(control.jump)
+       {
+               if(touching_ground){
+                       v3f speed = getSpeed();
+                       speed.Y = 6.5*BS;
+                       setSpeed(speed);
+               }
+       }
+
+       // The speed of the player (Y is ignored)
+       if(superspeed)
+               speed = speed.normalize() * walkspeed_max * 5;
+       else
+               speed = speed.normalize() * walkspeed_max;
+       
+       f32 inc = walk_acceleration * BS * dtime;
+       
+       // Accelerate to target speed with maximum increment
+       accelerate(speed, inc);
+}
+
+
diff --git a/src/player.h b/src/player.h
new file mode 100644 (file)
index 0000000..e692f55
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef PLAYER_HEADER
+#define PLAYER_HEADER
+
+#include "common_irrlicht.h"
+#include "inventory.h"
+
+#define PLAYERNAME_SIZE 20
+
+class Map;
+
+class Player
+{
+public:
+       Player();
+       virtual ~Player();
+
+       void move(f32 dtime, Map &map);
+
+       v3f getSpeed()
+       {
+               return m_speed;
+       }
+
+       void setSpeed(v3f speed)
+       {
+               m_speed = speed;
+       }
+       
+       // Y direction is ignored
+       void accelerate(v3f target_speed, f32 max_increase);
+
+       v3f getPosition()
+       {
+               return m_position;
+       }
+
+       virtual void setPosition(v3f position)
+       {
+               m_position = position;
+       }
+
+       void setPitch(f32 pitch)
+       {
+               m_pitch = pitch;
+       }
+
+       virtual void setYaw(f32 yaw)
+       {
+               m_yaw = yaw;
+       }
+
+       f32 getPitch()
+       {
+               return m_pitch;
+       }
+
+       f32 getYaw()
+       {
+               return m_yaw;
+       }
+
+       virtual void updateName(const char *name)
+       {
+               snprintf(m_name, PLAYERNAME_SIZE, "%s", name);
+       }
+
+       const char * getName()
+       {
+               return m_name;
+       }
+
+       virtual bool isLocal() const = 0;
+
+       bool touching_ground;
+       
+       Inventory inventory;
+
+       u16 peer_id;
+
+protected:
+       char m_name[PLAYERNAME_SIZE];
+       f32 m_pitch;
+       f32 m_yaw;
+       v3f m_speed;
+       v3f m_position;
+};
+
+class RemotePlayer : public Player, public scene::ISceneNode
+{
+public:
+       RemotePlayer(
+               scene::ISceneNode* parent=NULL,
+               IrrlichtDevice *device=NULL,
+               s32 id=0);
+       
+       virtual ~RemotePlayer();
+
+       /*
+               ISceneNode methods
+       */
+
+       virtual void OnRegisterSceneNode()
+       {
+               if (IsVisible)
+                       SceneManager->registerNodeForRendering(this);
+
+               ISceneNode::OnRegisterSceneNode();
+       }
+
+       virtual void render()
+       {
+               // Do nothing
+       }
+       
+       virtual const core::aabbox3d<f32>& getBoundingBox() const
+       {
+               return m_box;
+       }
+
+       void setPosition(v3f position)
+       {
+               Player::setPosition(position);
+               ISceneNode::setPosition(position);
+       }
+
+       virtual void setYaw(f32 yaw)
+       {
+               Player::setYaw(yaw);
+               ISceneNode::setRotation(v3f(0, -yaw, 0));
+       }
+
+       bool isLocal() const
+       {
+               return false;
+       }
+
+       void updateName(const char *name);
+
+private:
+       scene::ITextSceneNode* m_text;
+       core::aabbox3d<f32> m_box;
+};
+
+struct PlayerControl
+{
+       PlayerControl()
+       {
+               up = false;
+               down = false;
+               left = false;
+               right = false;
+               jump = false;
+               superspeed = false;
+               pitch = 0;
+               yaw = 0;
+       }
+       PlayerControl(
+               bool a_up,
+               bool a_down,
+               bool a_left,
+               bool a_right,
+               bool a_jump,
+               bool a_superspeed,
+               float a_pitch,
+               float a_yaw
+       )
+       {
+               up = a_up;
+               down = a_down;
+               left = a_left;
+               right = a_right;
+               jump = a_jump;
+               superspeed = a_superspeed;
+               pitch = a_pitch;
+               yaw = a_yaw;
+       }
+       bool up;
+       bool down;
+       bool left;
+       bool right;
+       bool jump;
+       bool superspeed;
+       float pitch;
+       float yaw;
+};
+
+class LocalPlayer : public Player
+{
+public:
+       LocalPlayer();
+       virtual ~LocalPlayer();
+
+       bool isLocal() const
+       {
+               return true;
+       }
+
+       void applyControl(float dtime);
+       
+       PlayerControl control;
+
+private:
+};
+
+#endif
+
diff --git a/src/porting.h b/src/porting.h
new file mode 100644 (file)
index 0000000..3213ec9
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef PORTING_HEADER
+#define PORTING_HEADER
+
+#ifdef _WIN32
+       #define SWPRINTF_CHARSTRING L"%S"
+#else
+       #define SWPRINTF_CHARSTRING L"%s"
+#endif
+
+#endif
+
diff --git a/src/serialization.cpp b/src/serialization.cpp
new file mode 100644 (file)
index 0000000..72f6755
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "serialization.h"
+#include "utility.h"
+
+void compress(SharedBuffer<u8> data, std::ostream &os, u8 version)
+{
+       if(data.getSize() == 0)
+               return;
+
+       // Write length (u32)
+
+       u8 tmp[4];
+       writeU32(tmp, data.getSize());
+       os.write((char*)tmp, 4);
+       
+       // We will be writing 8-bit pairs of more_count and byte
+       u8 more_count = 0;
+       u8 current_byte = data[0];
+       for(u32 i=1; i<data.getSize(); i++)
+       {
+               if(
+                       data[i] != current_byte
+                       || more_count == 255
+               )
+               {
+                       // write count and byte
+                       os.write((char*)&more_count, 1);
+                       os.write((char*)&current_byte, 1);
+                       more_count = 0;
+                       current_byte = data[i];
+               }
+               else
+               {
+                       more_count++;
+               }
+       }
+       // write count and byte
+       os.write((char*)&more_count, 1);
+       os.write((char*)&current_byte, 1);
+}
+
+void decompress(std::istream &is, std::ostream &os, u8 version)
+{
+       // Read length (u32)
+
+       u8 tmp[4];
+       is.read((char*)tmp, 4);
+       u32 len = readU32(tmp);
+       
+       // We will be reading 8-bit pairs of more_count and byte
+       u32 count = 0;
+       for(;;)
+       {
+               u8 more_count=0;
+               u8 byte=0;
+
+               is.read((char*)&more_count, 1);
+               
+               is.read((char*)&byte, 1);
+
+               if(is.eof())
+                       throw SerializationError("decompress: stream ended halfway");
+
+               for(s32 i=0; i<(u16)more_count+1; i++)
+                       os.write((char*)&byte, 1);
+
+               count += (u16)more_count+1;
+
+               if(count == len)
+                       break;
+       }
+}
+
+
diff --git a/src/serialization.h b/src/serialization.h
new file mode 100644 (file)
index 0000000..fd9b395
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef SERIALIZATION_HEADER
+#define SERIALIZATION_HEADER
+
+#include "common_irrlicht.h"
+#include "exceptions.h"
+#include <iostream>
+#include "utility.h"
+
+/*
+       NOTE: The goal is to increment this so that saved maps will be
+             loadable by any version. Other compatibility is not
+                 maintained.
+       Serialization format versions:
+       0: original networked test with 1-byte nodes
+       1: update with 2-byte nodes
+       2: lighting is transmitted in param
+       3: optional fetching of far blocks
+       4: block compression
+       5: sector objects NOTE: block compression was left accidentally out
+       6: failed attempt at switching block compression on again
+       7: block compression switched on again
+       8: (dev) server-initiated block transfers and all kinds of stuff
+       9: (dev) block objects
+*/
+// This represents an uninitialized or invalid format
+#define SER_FMT_VER_INVALID 255
+// Highest supported serialization version
+#define SER_FMT_VER_HIGHEST 9
+// Lowest supported serialization version
+#define SER_FMT_VER_LOWEST 0
+
+#define ser_ver_supported(v) (v >= SER_FMT_VER_LOWEST && v <= SER_FMT_VER_HIGHEST)
+
+void compress(SharedBuffer<u8> data, std::ostream &os, u8 version);
+void decompress(std::istream &is, std::ostream &os, u8 version);
+
+/*class Serializable
+{
+public:
+       void serialize(std::ostream &os, u8 version) = 0;
+       void deSerialize(std::istream &istr);
+};*/
+
+#endif
+
diff --git a/src/server.cpp b/src/server.cpp
new file mode 100644 (file)
index 0000000..a5f55ab
--- /dev/null
@@ -0,0 +1,2115 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "server.h"
+#include "utility.h"
+#include <iostream>
+#include "clientserver.h"
+#include "map.h"
+#include "jmutexautolock.h"
+#include "main.h"
+#include "constants.h"
+
+void * ServerThread::Thread()
+{
+       ThreadStarted();
+
+       DSTACK(__FUNCTION_NAME);
+
+       while(getRun())
+       {
+               try{
+                       m_server->AsyncRunStep();
+               
+                       //dout_server<<"Running m_server->Receive()"<<std::endl;
+                       m_server->Receive();
+               }
+               catch(con::NoIncomingDataException &e)
+               {
+               }
+#if CATCH_UNHANDLED_EXCEPTIONS
+               /*
+                       This is what has to be done in threads to get suitable debug info
+               */
+               catch(std::exception &e)
+               {
+                       dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
+                                       <<e.what()<<std::endl;
+                       assert(0);
+               }
+#endif
+       }
+       
+
+       return NULL;
+}
+
+void * EmergeThread::Thread()
+{
+       ThreadStarted();
+
+       DSTACK(__FUNCTION_NAME);
+
+       bool debug=false;
+#if CATCH_UNHANDLED_EXCEPTIONS
+       try
+       {
+#endif
+       
+       /*
+               Get block info from queue, emerge them and send them
+               to clients.
+
+               After queue is empty, exit.
+       */
+       while(getRun())
+       {
+               QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
+               if(qptr == NULL)
+                       break;
+               
+               SharedPtr<QueuedBlockEmerge> q(qptr);
+
+               v3s16 &p = q->pos;
+               
+               //derr_server<<"EmergeThread::Thread(): running"<<std::endl;
+               
+               /*
+                       Try to emerge it from somewhere.
+
+                       If it is only wanted as optional, only loading from disk
+                       will be allowed.
+               */
+               
+               /*
+                       Check if any peer wants it as non-optional. In that case it
+                       will be generated.
+
+                       Also decrement the emerge queue count in clients.
+               */
+
+               bool optional = true;
+
+               {
+                       core::map<u16, u8>::Iterator i;
+                       for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++)
+                       {
+                               //u16 peer_id = i.getNode()->getKey();
+
+                               // Check flags
+                               u8 flags = i.getNode()->getValue();
+                               if((flags & TOSERVER_GETBLOCK_FLAG_OPTIONAL) == false)
+                                       optional = false;
+                               
+                       }
+               }
+
+               /*dstream<<"EmergeThread: p="
+                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
+                               <<"optional="<<optional<<std::endl;*/
+               
+               ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
+                       
+               core::map<v3s16, MapBlock*> changed_blocks;
+               core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
+
+               MapBlock *block = NULL;
+               bool got_block = true;
+               core::map<v3s16, MapBlock*> modified_blocks;
+               
+               {//envlock
+
+               JMutexAutoLock envlock(m_server->m_env_mutex);
+
+               //TimeTaker timer("block emerge envlock", g_device);
+                       
+               try{
+                       bool only_from_disk = false;
+                       
+                       if(optional)
+                               only_from_disk = true;
+
+                       block = map.emergeBlock(
+                                       p,
+                                       only_from_disk,
+                                       changed_blocks,
+                                       lighting_invalidated_blocks);
+                       
+                       // If it is a dummy, block was not found on disk
+                       if(block->isDummy())
+                       {
+                               //dstream<<"EmergeThread: Got a dummy block"<<std::endl;
+                               got_block = false;
+                       }
+               }
+               catch(InvalidPositionException &e)
+               {
+                       // Block not found.
+                       // This happens when position is over limit.
+                       got_block = false;
+               }
+               
+               if(got_block)
+               {
+                       if(debug && changed_blocks.size() > 0)
+                       {
+                               dout_server<<DTIME<<"Got changed_blocks: ";
+                               for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
+                                               i.atEnd() == false; i++)
+                               {
+                                       MapBlock *block = i.getNode()->getValue();
+                                       v3s16 p = block->getPos();
+                                       dout_server<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") ";
+                               }
+                               dout_server<<std::endl;
+                       }
+
+                       /*
+                               Collect a list of blocks that have been modified in
+                               addition to the fetched one.
+                       */
+
+                       // Add all the "changed blocks"
+                       for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
+                                       i.atEnd() == false; i++)
+                       {
+                               MapBlock *block = i.getNode()->getValue();
+                               modified_blocks.insert(block->getPos(), block);
+                       }
+                       
+                       //TimeTaker timer("** updateLighting", g_device);
+                       // Update lighting without locking the environment mutex,
+                       // add modified blocks to changed blocks
+                       map.updateLighting(lighting_invalidated_blocks, modified_blocks);
+               }
+               // If we got no block, there should be no invalidated blocks
+               else
+               {
+                       assert(lighting_invalidated_blocks.size() == 0);
+               }
+
+               }//envlock
+
+               /*
+                       Set sent status of modified blocks on clients
+               */
+       
+               // NOTE: Server's clients are also behind the connection mutex
+               JMutexAutoLock lock(m_server->m_con_mutex);
+
+               /*
+                       Add the originally fetched block to the modified list
+               */
+               if(got_block)
+               {
+                       modified_blocks.insert(p, block);
+               }
+               
+               /*
+                       Set the modified blocks unsent for all the clients
+               */
+               
+               for(core::map<u16, RemoteClient*>::Iterator
+                               i = m_server->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);
+                       }
+                       
+                       if(q->peer_ids.find(client->peer_id) != NULL)
+                       {
+                               // Decrement emerge queue count of client
+                               client->BlockEmerged();
+                       }
+               }
+               
+       }
+#if CATCH_UNHANDLED_EXCEPTIONS
+       }//try
+       /*
+               This is what has to be done in threads to get suitable debug info
+       */
+       catch(std::exception &e)
+       {
+               dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
+                               <<e.what()<<std::endl;
+               assert(0);
+       }
+#endif
+
+       return NULL;
+}
+
+void RemoteClient::SendBlocks(Server *server, float dtime)
+{
+       DSTACK(__FUNCTION_NAME);
+       /*
+               Find what blocks to send to the client next, and send them.
+
+               Throttling is based on limiting the amount of blocks "flying"
+               at a given time.
+       */
+
+       // Can't send anything without knowing version
+       if(serialization_version == SER_FMT_VER_INVALID)
+       {
+               dstream<<"RemoteClient::SendBlocks(): Not sending, no version."
+                               <<std::endl;
+               return;
+       }
+
+       {
+               JMutexAutoLock lock(m_blocks_sending_mutex);
+               
+               if(m_blocks_sending.size() >= MAX_SIMULTANEOUS_BLOCK_SENDS)
+               {
+                       //dstream<<"Not sending any blocks, Queue full."<<std::endl;
+                       return;
+               }
+       }
+
+       Player *player = server->m_env.getPlayer(peer_id);
+
+       v3f playerpos = player->getPosition();
+       v3f playerspeed = player->getSpeed();
+
+       v3s16 center_nodepos = floatToInt(playerpos);
+
+       v3s16 center = getNodeBlockPos(center_nodepos);
+
+       /*
+               Find out what block the player is going to next and set
+               center to it.
+
+               Don't react to speeds under the initial value of highest_speed
+       */
+       /*f32 highest_speed = 0.1 * BS;
+       v3s16 dir(0,0,0);
+       if(abs(playerspeed.X) > highest_speed)
+       {
+               highest_speed = playerspeed.X;
+               if(playerspeed.X > 0)
+                       dir = v3s16(1,0,0);
+               else
+                       dir = v3s16(-1,0,0);
+       }
+       if(abs(playerspeed.Y) > highest_speed)
+       {
+               highest_speed = playerspeed.Y;
+               if(playerspeed.Y > 0)
+                       dir = v3s16(0,1,0);
+               else
+                       dir = v3s16(0,-1,0);
+       }
+       if(abs(playerspeed.Z) > highest_speed)
+       {
+               highest_speed = playerspeed.Z;
+               if(playerspeed.Z > 0)
+                       dir = v3s16(0,0,1);
+               else
+                       dir = v3s16(0,0,-1);
+       }
+
+       center += dir;*/
+       
+       /*
+               Calculate the starting value of the block finder radius.
+
+               The radius shall be the last used value minus the
+               maximum moved distance.
+       */
+       /*s16 d_start = m_last_block_find_d;
+       if(max_moved >= d_start)
+       {
+               d_start = 0;
+       }
+       else
+       {
+               d_start -= max_moved;
+       }*/
+       
+       s16 last_nearest_unsent_d;
+       s16 d_start;
+       {
+               JMutexAutoLock lock(m_blocks_sent_mutex);
+               
+               if(m_last_center != center)
+               {
+                       m_nearest_unsent_d = 0;
+                       m_last_center = center;
+               }
+
+               static float reset_counter = 0;
+               reset_counter += dtime;
+               if(reset_counter > 5.0)
+               {
+                       reset_counter = 0;
+                       m_nearest_unsent_d = 0;
+               }
+
+               last_nearest_unsent_d = m_nearest_unsent_d;
+               
+               d_start = m_nearest_unsent_d;
+       }
+
+       u16 maximum_simultaneous_block_sends = MAX_SIMULTANEOUS_BLOCK_SENDS;
+
+       {
+               SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
+               m_time_from_building.m_value += dtime;
+               /*
+                       Check the time from last addNode/removeNode.
+                       Decrease send rate if player is building stuff.
+               */
+               if(m_time_from_building.m_value
+                               < FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)
+               {
+                       maximum_simultaneous_block_sends
+                               = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
+               }
+       }
+
+       // Serialization version used
+       //u8 ser_version = serialization_version;
+
+       //bool has_incomplete_blocks = false;
+       
+       /*
+               TODO: Get this from somewhere
+               TODO: Values more than 7 make placing and removing blocks very
+                     sluggish when the map is being generated. This is
+                         because d is looped every time from 0 to d_max if no
+                         blocks are found for sending.
+       */
+       //s16 d_max = 7;
+       s16 d_max = 8;
+
+       //TODO: Get this from somewhere (probably a bigger value)
+       s16 d_max_gen = 5;
+       
+       //dstream<<"Starting from "<<d_start<<std::endl;
+
+       for(s16 d = d_start; d <= d_max; d++)
+       {
+               //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
+               
+               //if(has_incomplete_blocks == false)
+               {
+                       JMutexAutoLock lock(m_blocks_sent_mutex);
+                       /*
+                               If m_nearest_unsent_d was changed by the EmergeThread
+                               (it can change it to 0 through SetBlockNotSent),
+                               update our d to it.
+                               Else update m_nearest_unsent_d
+                       */
+                       if(m_nearest_unsent_d != last_nearest_unsent_d)
+                       {
+                               d = m_nearest_unsent_d;
+                       }
+                       else
+                       {
+                               m_nearest_unsent_d = d;
+                       }
+                       last_nearest_unsent_d = m_nearest_unsent_d;
+               }
+
+               /*
+                       Get the border/face dot coordinates of a "d-radiused"
+                       box
+               */
+               core::list<v3s16> list;
+               getFacePositions(list, d);
+               
+               core::list<v3s16>::Iterator li;
+               for(li=list.begin(); li!=list.end(); li++)
+               {
+                       v3s16 p = *li + center;
+                       
+                       /*
+                               Send throttling
+                               - Don't allow too many simultaneous transfers
+
+                               Also, don't send blocks that are already flying.
+                       */
+                       {
+                               JMutexAutoLock lock(m_blocks_sending_mutex);
+                               
+                               if(m_blocks_sending.size()
+                                               >= maximum_simultaneous_block_sends)
+                               {
+                                       /*dstream<<"Not sending more blocks. Queue full. "
+                                                       <<m_blocks_sending.size()
+                                                       <<std::endl;*/
+                                       return;
+                               }
+
+                               if(m_blocks_sending.find(p) != NULL)
+                                       continue;
+                       }
+                       
+                       /*
+                               Do not go over-limit
+                       */
+                       if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+                       || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+                       || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+                       || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+                       || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+                       || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
+                               continue;
+
+                       bool generate = d <= d_max_gen;
+               
+                       // Limit the generating area vertically to half
+                       if(abs(p.Y - center.Y) > d_max_gen / 2)
+                               generate = false;
+                       
+                       /*
+                               Don't send already sent blocks
+                       */
+                       {
+                               JMutexAutoLock lock(m_blocks_sent_mutex);
+                               
+                               if(m_blocks_sent.find(p) != NULL)
+                                       continue;
+                       }
+                                       
+                       /*
+                               Check if map has this block
+                       */
+                       MapBlock *block = NULL;
+                       try
+                       {
+                               block = server->m_env.getMap().getBlockNoCreate(p);
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                       }
+                       
+                       bool surely_not_found_on_disk = false;
+                       if(block != NULL)
+                       {
+                               /*if(block->isIncomplete())
+                               {
+                                       has_incomplete_blocks = true;
+                                       continue;
+                               }*/
+
+                               if(block->isDummy())
+                               {
+                                       surely_not_found_on_disk = true;
+                               }
+                       }
+
+                       /*
+                               If block has been marked to not exist on disk (dummy)
+                               and generating new ones is not wanted, skip block. TODO
+                       */
+                       if(generate == false && surely_not_found_on_disk == true)
+                       {
+                               // get next one.
+                               continue;
+                       }
+
+                       /*
+                               Add inexistent block to emerge queue.
+                       */
+                       if(block == NULL || surely_not_found_on_disk)
+                       {
+                               // Block not found.
+                               SharedPtr<JMutexAutoLock> lock
+                                               (m_num_blocks_in_emerge_queue.getLock());
+                               
+                               //TODO: Get value from somewhere
+                               //TODO: Balance between clients
+                               //if(server->m_emerge_queue.size() < 1)
+
+                               // Allow only one block in emerge queue
+                               if(m_num_blocks_in_emerge_queue.m_value == 0)
+                               {
+                                       // Add it to the emerge queue and trigger the thread
+                                       
+                                       u8 flags = 0;
+                                       if(generate == false)
+                                               flags |= TOSERVER_GETBLOCK_FLAG_OPTIONAL;
+                                       
+                                       {
+                                               m_num_blocks_in_emerge_queue.m_value++;
+                                       }
+
+                                       server->m_emerge_queue.addBlock(peer_id, p, flags);
+                                       server->m_emergethread.trigger();
+                               }
+                               
+                               // get next one.
+                               continue;
+                       }
+
+                       /*
+                               Send block
+                       */
+                       
+                       /*dstream<<"RemoteClient::SendBlocks(): d="<<d<<", p="
+                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                               <<" sending queue size: "<<m_blocks_sending.size()<<std::endl;*/
+
+                       server->SendBlockNoLock(peer_id, block, serialization_version);
+                       
+                       /*
+                               Add to history
+                       */
+                       SentBlock(p);
+               }
+       }
+
+       // Don't add anything here. The loop breaks by returning.
+}
+
+void RemoteClient::SendObjectData(
+               Server *server,
+               float dtime,
+               core::map<v3s16, bool> &stepped_blocks
+       )
+{
+       DSTACK(__FUNCTION_NAME);
+
+       // Can't send anything without knowing version
+       if(serialization_version == SER_FMT_VER_INVALID)
+       {
+               dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
+                               <<std::endl;
+               return;
+       }
+
+       /*
+               Send a TOCLIENT_OBJECTDATA packet.
+               Sent as unreliable.
+
+               u16 command
+               u16 number of player positions
+               for each player:
+                       v3s32 position*100
+                       v3s32 speed*100
+                       s32 pitch*100
+                       s32 yaw*100
+               u16 count of blocks
+               for each block:
+                       block objects
+       */
+
+       std::ostringstream os(std::ios_base::binary);
+       u8 buf[12];
+       
+       // Write command
+       writeU16(buf, TOCLIENT_OBJECTDATA);
+       os.write((char*)buf, 2);
+       
+       /*
+               Get and write player data
+       */
+
+       core::list<Player*> players = server->m_env.getPlayers();
+
+       // Write player count
+       u16 playercount = players.size();
+       writeU16(buf, playercount);
+       os.write((char*)buf, 2);
+
+       core::list<Player*>::Iterator i;
+       for(i = players.begin();
+                       i != players.end(); i++)
+       {
+               Player *player = *i;
+
+               v3f pf = player->getPosition();
+               v3f sf = player->getSpeed();
+
+               v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100);
+               v3s32 speed_i   (sf.X*100, sf.Y*100, sf.Z*100);
+               s32   pitch_i   (player->getPitch() * 100);
+               s32   yaw_i     (player->getYaw() * 100);
+               
+               writeU16(buf, player->peer_id);
+               os.write((char*)buf, 2);
+               writeV3S32(buf, position_i);
+               os.write((char*)buf, 12);
+               writeV3S32(buf, speed_i);
+               os.write((char*)buf, 12);
+               writeS32(buf, pitch_i);
+               os.write((char*)buf, 4);
+               writeS32(buf, yaw_i);
+               os.write((char*)buf, 4);
+       }
+       
+       /*
+               Get and write object data
+       */
+
+       /*
+               Get nearby blocks.
+               
+               For making players to be able to build to their nearby
+               environment (building is not possible on blocks that are not
+               in memory):
+               - Set blocks changed
+               - Add blocks to emerge queue if they are not found
+       */
+
+       Player *player = server->m_env.getPlayer(peer_id);
+
+       v3f playerpos = player->getPosition();
+       v3f playerspeed = player->getSpeed();
+
+       v3s16 center_nodepos = floatToInt(playerpos);
+       v3s16 center = getNodeBlockPos(center_nodepos);
+
+       s16 d_max = ACTIVE_OBJECT_D_BLOCKS;
+
+       core::map<v3s16, MapBlock*> blocks;
+
+       for(s16 d = 0; d <= d_max; d++)
+       {
+               core::list<v3s16> list;
+               getFacePositions(list, d);
+               
+               core::list<v3s16>::Iterator li;
+               for(li=list.begin(); li!=list.end(); li++)
+               {
+                       v3s16 p = *li + center;
+
+                       /*
+                               Ignore blocks that haven't been sent to the client
+                       */
+                       {
+                               JMutexAutoLock sentlock(m_blocks_sent_mutex);
+                               if(m_blocks_sent.find(p) == NULL)
+                                       continue;
+                       }
+
+                       try
+                       {
+
+                       // Get block
+                       MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);
+
+                       // Step block if not in stepped_blocks and add to stepped_blocks
+                       if(stepped_blocks.find(p) == NULL)
+                       {
+                               block->stepObjects(dtime, true);
+                               stepped_blocks.insert(p, true);
+                               block->setChangedFlag();
+                       }
+                       
+                       // Add block to queue
+                       blocks.insert(p, block);
+                       
+                       } //try
+                       catch(InvalidPositionException &e)
+                       {
+                               // Not in memory
+                               // Add it to the emerge queue and trigger the thread.
+                               // Fetch the block only if it is on disk.
+                               
+                               // Grab and increment counter
+                               SharedPtr<JMutexAutoLock> lock
+                                               (m_num_blocks_in_emerge_queue.getLock());
+                               m_num_blocks_in_emerge_queue.m_value++;
+                               
+                               // Add to queue as an anonymous fetch from disk
+                               u8 flags = TOSERVER_GETBLOCK_FLAG_OPTIONAL;
+                               server->m_emerge_queue.addBlock(0, p, flags);
+                               server->m_emergethread.trigger();
+                       }
+               }
+       }
+
+       /*
+               Write objects
+       */
+
+       u16 blockcount = blocks.size();
+
+       // Write block count
+       writeU16(buf, blockcount);
+       os.write((char*)buf, 2);
+       
+       for(core::map<v3s16, MapBlock*>::Iterator
+                       i = blocks.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               v3s16 p = i.getNode()->getKey();
+               // Write blockpos
+               writeV3S16(buf, p);
+               os.write((char*)buf, 6);
+               // Write objects
+               MapBlock *block = i.getNode()->getValue();
+               block->serializeObjects(os, serialization_version);
+       }
+       
+       /*
+               Send data
+       */
+       
+       // Make data buffer
+       std::string s = os.str();
+       SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+       // Send as unreliable
+       server->m_con.Send(peer_id, 0, data, false);
+}
+
+void RemoteClient::GotBlock(v3s16 p)
+{
+       JMutexAutoLock lock(m_blocks_sending_mutex);
+       JMutexAutoLock lock2(m_blocks_sent_mutex);
+       if(m_blocks_sending.find(p) != NULL)
+               m_blocks_sending.remove(p);
+       else
+               dstream<<"RemoteClient::GotBlock(): Didn't find in"
+                               " m_blocks_sending"<<std::endl;
+       m_blocks_sent.insert(p, true);
+}
+
+void RemoteClient::SentBlock(v3s16 p)
+{
+       JMutexAutoLock lock(m_blocks_sending_mutex);
+       if(m_blocks_sending.size() > 15)
+       {
+               dstream<<"RemoteClient::SentBlock(): "
+                               <<"m_blocks_sending.size()="
+                               <<m_blocks_sending.size()<<std::endl;
+       }
+       if(m_blocks_sending.find(p) == NULL)
+               m_blocks_sending.insert(p, 0.0);
+       else
+               dstream<<"RemoteClient::SentBlock(): Sent block"
+                               " already in m_blocks_sending"<<std::endl;
+}
+
+void RemoteClient::SetBlockNotSent(v3s16 p)
+{
+       JMutexAutoLock sendinglock(m_blocks_sending_mutex);
+       JMutexAutoLock sentlock(m_blocks_sent_mutex);
+
+       m_nearest_unsent_d = 0;
+       
+       if(m_blocks_sending.find(p) != NULL)
+               m_blocks_sending.remove(p);
+       if(m_blocks_sent.find(p) != NULL)
+               m_blocks_sent.remove(p);
+}
+
+void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
+{
+       JMutexAutoLock sendinglock(m_blocks_sending_mutex);
+       JMutexAutoLock sentlock(m_blocks_sent_mutex);
+
+       m_nearest_unsent_d = 0;
+       
+       for(core::map<v3s16, MapBlock*>::Iterator
+                       i = blocks.getIterator();
+                       i.atEnd()==false; i++)
+       {
+               v3s16 p = i.getNode()->getKey();
+
+               if(m_blocks_sending.find(p) != NULL)
+                       m_blocks_sending.remove(p);
+               if(m_blocks_sent.find(p) != NULL)
+                       m_blocks_sent.remove(p);
+       }
+}
+
+void RemoteClient::BlockEmerged()
+{
+       SharedPtr<JMutexAutoLock> lock(m_num_blocks_in_emerge_queue.getLock());
+       assert(m_num_blocks_in_emerge_queue.m_value > 0);
+       m_num_blocks_in_emerge_queue.m_value--;
+}
+
+/*void RemoteClient::RunSendingTimeouts(float dtime, float timeout)
+{
+       JMutexAutoLock sendinglock(m_blocks_sending_mutex);
+       
+       core::list<v3s16> remove_queue;
+       for(core::map<v3s16, float>::Iterator
+                       i = m_blocks_sending.getIterator();
+                       i.atEnd()==false; i++)
+       {
+               v3s16 p = i.getNode()->getKey();
+               float t = i.getNode()->getValue();
+               t += dtime;
+               i.getNode()->setValue(t);
+
+               if(t > timeout)
+               {
+                       remove_queue.push_back(p);
+               }
+       }
+       for(core::list<v3s16>::Iterator
+                       i = remove_queue.begin();
+                       i != remove_queue.end(); i++)
+       {
+               m_blocks_sending.remove(*i);
+       }
+}*/
+
+/*
+       PlayerInfo
+*/
+
+PlayerInfo::PlayerInfo()
+{
+       name[0] = 0;
+}
+
+void PlayerInfo::PrintLine(std::ostream *s)
+{
+       (*s)<<id<<": \""<<name<<"\" ("
+                       <<position.X<<","<<position.Y
+                       <<","<<position.Z<<") ";
+       address.print(s);
+       (*s)<<" avg_rtt="<<avg_rtt;
+       (*s)<<std::endl;
+}
+
+u32 PIChecksum(core::list<PlayerInfo> &l)
+{
+       core::list<PlayerInfo>::Iterator i;
+       u32 checksum = 1;
+       u32 a = 10;
+       for(i=l.begin(); i!=l.end(); i++)
+       {
+               checksum += a * (i->id+1);
+               checksum ^= 0x435aafcd;
+               a *= 10;
+       }
+       return checksum;
+}
+
+/*
+       Server
+*/
+
+Server::Server(
+               std::string mapsavedir,
+               bool creative_mode,
+               MapgenParams mapgen_params
+       ):
+       m_env(new ServerMap(mapsavedir, mapgen_params), dout_server),
+       m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
+       m_thread(this),
+       m_emergethread(this),
+       m_creative_mode(creative_mode)
+{
+       m_env_mutex.Init();
+       m_con_mutex.Init();
+       m_step_dtime_mutex.Init();
+       m_step_dtime = 0.0;
+}
+
+Server::~Server()
+{
+       // Stop threads
+       stop();
+
+       JMutexAutoLock clientslock(m_con_mutex);
+
+       for(core::map<u16, RemoteClient*>::Iterator
+               i = m_clients.getIterator();
+               i.atEnd() == false; i++)
+       {
+               u16 peer_id = i.getNode()->getKey();
+
+               // Delete player
+               {
+                       JMutexAutoLock envlock(m_env_mutex);
+                       m_env.removePlayer(peer_id);
+               }
+               
+               // Delete client
+               delete i.getNode()->getValue();
+       }
+}
+
+void Server::start(unsigned short port)
+{
+       DSTACK(__FUNCTION_NAME);
+       // Stop thread if already running
+       m_thread.stop();
+       
+       // Initialize connection
+       m_con.setTimeoutMs(50);
+       m_con.Serve(port);
+
+       // Start thread
+       m_thread.setRun(true);
+       m_thread.Start();
+       
+       dout_server<<"Server started on port "<<port<<std::endl;
+}
+
+void Server::stop()
+{
+       DSTACK(__FUNCTION_NAME);
+       // Stop threads (set run=false first so both start stopping)
+       m_thread.setRun(false);
+       m_emergethread.setRun(false);
+       m_thread.stop();
+       m_emergethread.stop();
+       
+       dout_server<<"Server threads stopped"<<std::endl;
+}
+
+void Server::step(float dtime)
+{
+       DSTACK(__FUNCTION_NAME);
+       // Limit a bit
+       if(dtime > 2.0)
+               dtime = 2.0;
+       {
+               JMutexAutoLock lock(m_step_dtime_mutex);
+               m_step_dtime += dtime;
+       }
+}
+
+void Server::AsyncRunStep()
+{
+       DSTACK(__FUNCTION_NAME);
+       float dtime;
+       {
+               JMutexAutoLock lock1(m_step_dtime_mutex);
+               dtime = m_step_dtime;
+               if(dtime < 0.001)
+                       return;
+               m_step_dtime = 0.0;
+       }
+       
+       //dstream<<"Server steps "<<dtime<<std::endl;
+       
+       //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
+       {
+               // Has to be locked for peerAdded/Removed
+               JMutexAutoLock lock1(m_env_mutex);
+               // Process connection's timeouts
+               JMutexAutoLock lock2(m_con_mutex);
+               m_con.RunTimeouts(dtime);
+       }
+       {
+               // Step environment
+               // This also runs Map's timers
+               JMutexAutoLock lock(m_env_mutex);
+               m_env.step(dtime);
+       }
+       
+       /*
+               Do background stuff
+       */
+       
+       // Periodically print some info
+       {
+               static float counter = 0.0;
+               counter += dtime;
+               if(counter >= 30.0)
+               {
+                       counter = 0.0;
+
+                       JMutexAutoLock lock2(m_con_mutex);
+
+                       for(core::map<u16, RemoteClient*>::Iterator
+                               i = m_clients.getIterator();
+                               i.atEnd() == false; i++)
+                       {
+                               //u16 peer_id = i.getNode()->getKey();
+                               RemoteClient *client = i.getNode()->getValue();
+                               client->PrintInfo(std::cout);
+                       }
+               }
+       }
+
+       // Run time- and client- related stuff
+       // NOTE: If you intend to add something here, check that it
+       // doesn't fit in RemoteClient::SendBlocks for exampel.
+       /*{
+               // Clients are behind connection lock
+               JMutexAutoLock lock(m_con_mutex);
+
+               for(core::map<u16, RemoteClient*>::Iterator
+                       i = m_clients.getIterator();
+                       i.atEnd() == false; i++)
+               {
+                       RemoteClient *client = i.getNode()->getValue();
+                       //con::Peer *peer = m_con.GetPeer(client->peer_id);
+                       //client->RunSendingTimeouts(dtime, peer->resend_timeout);
+               }
+       }*/
+
+       // Send blocks to clients
+       SendBlocks(dtime);
+       
+       // Send object positions
+       {
+               static float counter = 0.0;
+               counter += dtime;
+               //TODO: Get value from somewhere
+               if(counter >= 0.1)
+               {
+                       JMutexAutoLock lock1(m_env_mutex);
+                       JMutexAutoLock lock2(m_con_mutex);
+                       SendObjectData(counter);
+
+                       counter = 0.0;
+               }
+       }
+
+       {
+               // Save map
+               static float counter = 0.0;
+               counter += dtime;
+               if(counter >= SERVER_MAP_SAVE_INTERVAL)
+               {
+                       counter = 0.0;
+
+                       JMutexAutoLock lock(m_env_mutex);
+                       // Save only changed parts
+                       m_env.getMap().save(true);
+               }
+       }
+}
+
+void Server::Receive()
+{
+       DSTACK(__FUNCTION_NAME);
+       u32 data_maxsize = 10000;
+       Buffer<u8> data(data_maxsize);
+       u16 peer_id;
+       u32 datasize;
+       try{
+               {
+                       JMutexAutoLock lock(m_con_mutex);
+                       datasize = m_con.Receive(peer_id, *data, data_maxsize);
+               }
+               ProcessData(*data, datasize, peer_id);
+       }
+       catch(con::InvalidIncomingDataException &e)
+       {
+               derr_server<<"Server::Receive(): "
+                               "InvalidIncomingDataException: what()="
+                               <<e.what()<<std::endl;
+       }
+       catch(con::PeerNotFoundException &e)
+       {
+               //NOTE: This is not needed anymore
+               
+               // The peer has been disconnected.
+               // Find the associated player and remove it.
+
+               /*JMutexAutoLock envlock(m_env_mutex);
+
+               dout_server<<"ServerThread: peer_id="<<peer_id
+                               <<" has apparently closed connection. "
+                               <<"Removing player."<<std::endl;
+
+               m_env.removePlayer(peer_id);*/
+       }
+}
+
+void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
+{
+       DSTACK(__FUNCTION_NAME);
+       // Environment is locked first.
+       JMutexAutoLock envlock(m_env_mutex);
+       JMutexAutoLock conlock(m_con_mutex);
+       
+       con::Peer *peer;
+       try{
+               peer = m_con.GetPeer(peer_id);
+       }
+       catch(con::PeerNotFoundException &e)
+       {
+               derr_server<<DTIME<<"Server::ProcessData(): Cancelling: peer "
+                               <<peer_id<<" not found"<<std::endl;
+               return;
+       }
+       
+       //u8 peer_ser_ver = peer->serialization_version;
+       u8 peer_ser_ver = getClient(peer->id)->serialization_version;
+
+       try
+       {
+
+       if(datasize < 2)
+               return;
+
+       ToServerCommand command = (ToServerCommand)readU16(&data[0]);
+       
+       if(command == TOSERVER_INIT)
+       {
+               // [0] u16 TOSERVER_INIT
+               // [2] u8 SER_FMT_VER_HIGHEST
+               // [3] u8[20] player_name
+
+               if(datasize < 3)
+                       return;
+
+               derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
+                               <<peer->id<<std::endl;
+
+               // First byte after command is maximum supported
+               // serialization version
+               u8 client_max = data[2];
+               u8 our_max = SER_FMT_VER_HIGHEST;
+               // Use the highest version supported by both
+               u8 deployed = core::min_(client_max, our_max);
+               // If it's lower than the lowest supported, give up.
+               if(deployed < SER_FMT_VER_LOWEST)
+                       deployed = SER_FMT_VER_INVALID;
+
+               //peer->serialization_version = deployed;
+               getClient(peer->id)->pending_serialization_version = deployed;
+
+               if(deployed == SER_FMT_VER_INVALID)
+               {
+                       derr_server<<DTIME<<"Server: Cannot negotiate "
+                                       "serialization version with peer "
+                                       <<peer_id<<std::endl;
+                       return;
+               }
+
+               /*
+                       Set up player
+               */
+
+               Player *player = m_env.getPlayer(peer_id);
+
+               // Check if player doesn't exist
+               if(player == NULL)
+                       throw con::InvalidIncomingDataException
+                               ("Server::ProcessData(): INIT: Player doesn't exist");
+
+               // update name if it was supplied
+               if(datasize >= 20+3)
+               {
+                       data[20+3-1] = 0;
+                       player->updateName((const char*)&data[3]);
+               }
+
+               // Now answer with a TOCLIENT_INIT
+               
+               SharedBuffer<u8> reply(2+1+6);
+               writeU16(&reply[0], TOCLIENT_INIT);
+               writeU8(&reply[2], deployed);
+               writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0)));
+               // Send as reliable
+               m_con.Send(peer_id, 0, reply, true);
+
+               return;
+       }
+       if(command == TOSERVER_INIT2)
+       {
+               derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
+                               <<peer->id<<std::endl;
+
+
+               getClient(peer->id)->serialization_version
+                               = getClient(peer->id)->pending_serialization_version;
+
+               /*
+                       Send some initialization data
+               */
+               
+               // Send player info to all players
+               SendPlayerInfos();
+
+               // Send inventory to player
+               SendInventory(peer->id);
+
+               return;
+       }
+
+       if(peer_ser_ver == SER_FMT_VER_INVALID)
+       {
+               derr_server<<DTIME<<"Server::ProcessData(): Cancelling: Peer"
+                               " serialization format invalid or not initialized."
+                               " Skipping incoming command="<<command<<std::endl;
+               return;
+       }
+       
+       Player *player = m_env.getPlayer(peer_id);
+
+       if(player == NULL){
+               derr_server<<"Server::ProcessData(): Cancelling: "
+                               "No player for peer_id="<<peer_id
+                               <<std::endl;
+               return;
+       }
+       if(command == TOSERVER_PLAYERPOS)
+       {
+               if(datasize < 2+12+12+4+4)
+                       return;
+       
+               u32 start = 0;
+               v3s32 ps = readV3S32(&data[start+2]);
+               v3s32 ss = readV3S32(&data[start+2+12]);
+               f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0;
+               f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0;
+               v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
+               v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
+               pitch = wrapDegrees(pitch);
+               yaw = wrapDegrees(yaw);
+               player->setPosition(position);
+               player->setSpeed(speed);
+               player->setPitch(pitch);
+               player->setYaw(yaw);
+               
+               /*dout_server<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
+                               <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
+                               <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
+       }
+       else if(command == TOSERVER_GOTBLOCKS)
+       {
+               if(datasize < 2+1)
+                       return;
+               
+               /*
+                       [0] u16 command
+                       [2] u8 count
+                       [3] v3s16 pos_0
+                       [3+6] v3s16 pos_1
+                       ...
+               */
+
+               u16 count = data[2];
+               for(u16 i=0; i<count; i++)
+               {
+                       if((s16)datasize < 2+1+(i+1)*6)
+                               throw con::InvalidIncomingDataException
+                                       ("GOTBLOCKS length is too short");
+                       v3s16 p = readV3S16(&data[2+1+i*6]);
+                       /*dstream<<"Server: GOTBLOCKS ("
+                                       <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+                       RemoteClient *client = getClient(peer_id);
+                       client->GotBlock(p);
+               }
+       }
+       else if(command == TOSERVER_DELETEDBLOCKS)
+       {
+               if(datasize < 2+1)
+                       return;
+               
+               /*
+                       [0] u16 command
+                       [2] u8 count
+                       [3] v3s16 pos_0
+                       [3+6] v3s16 pos_1
+                       ...
+               */
+
+               u16 count = data[2];
+               for(u16 i=0; i<count; i++)
+               {
+                       if((s16)datasize < 2+1+(i+1)*6)
+                               throw con::InvalidIncomingDataException
+                                       ("DELETEDBLOCKS length is too short");
+                       v3s16 p = readV3S16(&data[2+1+i*6]);
+                       /*dstream<<"Server: DELETEDBLOCKS ("
+                                       <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+                       RemoteClient *client = getClient(peer_id);
+                       client->SetBlockNotSent(p);
+               }
+       }
+       else if(command == TOSERVER_CLICK_OBJECT)
+       {
+               if(datasize < 13)
+                       return;
+
+               /*
+                       [0] u16 command
+                       [2] u8 button (0=left, 1=right)
+                       [3] v3s16 block
+                       [9] s16 id
+                       [11] u16 item
+               */
+               u8 button = readU8(&data[2]);
+               v3s16 p;
+               p.X = readS16(&data[3]);
+               p.Y = readS16(&data[5]);
+               p.Z = readS16(&data[7]);
+               s16 id = readS16(&data[9]);
+               //u16 item_i = readU16(&data[11]);
+
+               MapBlock *block = NULL;
+               try
+               {
+                       block = m_env.getMap().getBlockNoCreate(p);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       derr_server<<"PICK_OBJECT block not found"<<std::endl;
+                       return;
+               }
+
+               MapBlockObject *obj = block->getObject(id);
+
+               if(obj == NULL)
+               {
+                       derr_server<<"PICK_OBJECT object not found"<<std::endl;
+                       return;
+               }
+
+               //TODO: Check that object is reasonably close
+               
+               // Left click
+               if(button == 0)
+               {
+                       if(m_creative_mode == false)
+                       {
+                       
+                               // Skip if inventory has no free space
+                               if(player->inventory.getUsedSlots() == player->inventory.getSize())
+                               {
+                                       dout_server<<"Player inventory has no free space"<<std::endl;
+                                       return;
+                               }
+                       
+                               // Add to inventory and send inventory
+                               InventoryItem *item = new MapBlockObjectItem
+                                               (obj->getInventoryString());
+                               player->inventory.addItem(item);
+                               SendInventory(player->peer_id);
+                       }
+
+                       // Remove from block
+                       block->removeObject(id);
+               }
+       }
+       else if(command == TOSERVER_CLICK_GROUND)
+       {
+               if(datasize < 17)
+                       return;
+               /*
+                       length: 17
+                       [0] u16 command
+                       [2] u8 button (0=left, 1=right)
+                       [3] v3s16 nodepos_undersurface
+                       [9] v3s16 nodepos_abovesurface
+                       [15] u16 item
+               */
+               u8 button = readU8(&data[2]);
+               v3s16 p_under;
+               p_under.X = readS16(&data[3]);
+               p_under.Y = readS16(&data[5]);
+               p_under.Z = readS16(&data[7]);
+               v3s16 p_over;
+               p_over.X = readS16(&data[9]);
+               p_over.Y = readS16(&data[11]);
+               p_over.Z = readS16(&data[13]);
+               u16 item_i = readU16(&data[15]);
+
+               //TODO: Check that target is reasonably close
+               
+               /*
+                       Left button digs ground
+               */
+               if(button == 0)
+               {
+
+                       core::map<v3s16, MapBlock*> modified_blocks;
+
+                       u8 material;
+
+                       try
+                       {
+                               // Get material at position
+                               material = m_env.getMap().getNode(p_under).d;
+                               // If it's air, do nothing
+                               if(material == MATERIAL_AIR)
+                               {
+                                       return;
+                               }
+                               // Otherwise remove it
+                               m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               derr_server<<"Server: Ignoring REMOVENODE: Node not found"
+                                               <<std::endl;
+                               return;
+                       }
+                       
+                       // Reset build time counter
+                       getClient(peer->id)->m_time_from_building.set(0.0);
+                       
+                       // Create packet
+                       u32 replysize = 8;
+                       SharedBuffer<u8> reply(replysize);
+                       writeU16(&reply[0], TOCLIENT_REMOVENODE);
+                       writeS16(&reply[2], p_under.X);
+                       writeS16(&reply[4], p_under.Y);
+                       writeS16(&reply[6], p_under.Z);
+                       // Send as reliable
+                       m_con.SendToAll(0, reply, true);
+                       
+                       if(m_creative_mode == false)
+                       {
+                               // Add to inventory and send inventory
+                               InventoryItem *item = new MaterialItem(material, 1);
+                               player->inventory.addItem(item);
+                               SendInventory(player->peer_id);
+                       }
+
+               } // button == 0
+               /*
+                       Right button places blocks and stuff
+               */
+               else if(button == 1)
+               {
+
+                       // Get item
+                       InventoryItem *item = player->inventory.getItem(item_i);
+                       
+                       // If there is no item, it is not possible to add it anywhere
+                       if(item == NULL)
+                               return;
+                       
+                       /*
+                               Handle material items
+                       */
+                       if(std::string("MaterialItem") == item->getName())
+                       {
+                               MaterialItem *mitem = (MaterialItem*)item;
+                               
+                               MapNode n;
+                               n.d = mitem->getMaterial();
+
+                               try{
+                                       // Don't add a node if there isn't air
+                                       MapNode n2 = m_env.getMap().getNode(p_over);
+                                       if(n2.d != MATERIAL_AIR)
+                                               return;
+
+                                       core::map<v3s16, MapBlock*> modified_blocks;
+                                       m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
+                               }
+                               catch(InvalidPositionException &e)
+                               {
+                                       derr_server<<"Server: Ignoring ADDNODE: Node not found"
+                                                       <<std::endl;
+                                       return;
+                               }
+
+                               // Reset build time counter
+                               getClient(peer->id)->m_time_from_building.set(0.0);
+                               
+                               if(m_creative_mode == false)
+                               {
+                                       // Remove from inventory and send inventory
+                                       if(mitem->getCount() == 1)
+                                               player->inventory.deleteItem(item_i);
+                                       else
+                                               mitem->remove(1);
+                                       // Send inventory
+                                       SendInventory(peer_id);
+                               }
+                               
+                               // Create packet
+                               u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
+                               SharedBuffer<u8> reply(replysize);
+                               writeU16(&reply[0], TOCLIENT_ADDNODE);
+                               writeS16(&reply[2], p_over.X);
+                               writeS16(&reply[4], p_over.Y);
+                               writeS16(&reply[6], p_over.Z);
+                               n.serialize(&reply[8], peer_ser_ver);
+                               // Send as reliable
+                               m_con.SendToAll(0, reply, true);
+                       }
+                       /*
+                               Handle block object items
+                       */
+                       else if(std::string("MBOItem") == item->getName())
+                       {
+                               MapBlockObjectItem *oitem = (MapBlockObjectItem*)item;
+
+                               /*dout_server<<"Trying to place a MapBlockObjectItem: "
+                                               "inventorystring=\""
+                                               <<oitem->getInventoryString()
+                                               <<"\""<<std::endl;*/
+
+                               v3s16 blockpos = getNodeBlockPos(p_over);
+
+                               MapBlock *block = NULL;
+                               try
+                               {
+                                       block = m_env.getMap().getBlockNoCreate(blockpos);
+                               }
+                               catch(InvalidPositionException &e)
+                               {
+                                       derr_server<<"Error while placing object: "
+                                                       "block not found"<<std::endl;
+                                       return;
+                               }
+
+                               v3s16 block_pos_i_on_map = block->getPosRelative();
+                               v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+
+                               v3f pos = intToFloat(p_over);
+                               pos -= block_pos_f_on_map;
+                               
+                               /*dout_server<<"pos="
+                                               <<"("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
+                                               <<std::endl;*/
+
+
+                               MapBlockObject *obj = oitem->createObject
+                                               (pos, player->getYaw(), player->getPitch());
+
+                               if(obj == NULL)
+                                       derr_server<<"WARNING: oitem created NULL object"
+                                                       <<std::endl;
+
+                               block->addObject(obj);
+
+                               //dout_server<<"Placed object"<<std::endl;
+
+                               if(m_creative_mode == false)
+                               {
+                                       // Remove from inventory and send inventory
+                                       player->inventory.deleteItem(item_i);
+                                       // Send inventory
+                                       SendInventory(peer_id);
+                               }
+                       }
+
+               } // button == 1
+               /*
+                       Catch invalid buttons
+               */
+               else
+               {
+                       derr_server<<"WARNING: Server: Invalid button "
+                                       <<button<<std::endl;
+               }
+       }
+       else if(command == TOSERVER_RELEASE)
+       {
+               if(datasize < 3)
+                       return;
+               /*
+                       length: 3
+                       [0] u16 command
+                       [2] u8 button
+               */
+               //TODO
+       }
+       else if(command == TOSERVER_SIGNTEXT)
+       {
+               /*
+                       u16 command
+                       v3s16 blockpos
+                       s16 id
+                       u16 textlen
+                       textdata
+               */
+               std::string datastring((char*)&data[2], datasize-2);
+               std::istringstream is(datastring, std::ios_base::binary);
+               u8 buf[6];
+               // Read stuff
+               is.read((char*)buf, 6);
+               v3s16 blockpos = readV3S16(buf);
+               is.read((char*)buf, 2);
+               s16 id = readS16(buf);
+               is.read((char*)buf, 2);
+               u16 textlen = readU16(buf);
+               std::string text;
+               for(u16 i=0; i<textlen; i++)
+               {
+                       is.read((char*)buf, 1);
+                       text += (char)buf[0];
+               }
+
+               MapBlock *block = NULL;
+               try
+               {
+                       block = m_env.getMap().getBlockNoCreate(blockpos);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       derr_server<<"Error while setting sign text: "
+                                       "block not found"<<std::endl;
+                       return;
+               }
+
+               MapBlockObject *obj = block->getObject(id);
+               if(obj == NULL)
+               {
+                       derr_server<<"Error while setting sign text: "
+                                       "object not found"<<std::endl;
+                       return;
+               }
+               
+               if(obj->getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN)
+               {
+                       derr_server<<"Error while setting sign text: "
+                                       "object is not a sign"<<std::endl;
+                       return;
+               }
+
+               ((SignObject*)obj)->setText(text);
+
+               obj->getBlock()->setChangedFlag();
+       }
+       else
+       {
+               derr_server<<"WARNING: Server::ProcessData(): Ignoring "
+                               "unknown command "<<command<<std::endl;
+       }
+       
+       } //try
+       catch(SendFailedException &e)
+       {
+               derr_server<<"Server::ProcessData(): SendFailedException: "
+                               <<"what="<<e.what()
+                               <<std::endl;
+       }
+}
+
+/*void Server::Send(u16 peer_id, u16 channelnum,
+               SharedBuffer<u8> data, bool reliable)
+{
+       JMutexAutoLock lock(m_con_mutex);
+       m_con.Send(peer_id, channelnum, data, reliable);
+}*/
+
+void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
+{
+       DSTACK(__FUNCTION_NAME);
+       /*
+               Create a packet with the block in the right format
+       */
+       
+       std::ostringstream os(std::ios_base::binary);
+       block->serialize(os, ver);
+       std::string s = os.str();
+       SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
+
+       u32 replysize = 8 + blockdata.getSize();
+       SharedBuffer<u8> reply(replysize);
+       v3s16 p = block->getPos();
+       writeU16(&reply[0], TOCLIENT_BLOCKDATA);
+       writeS16(&reply[2], p.X);
+       writeS16(&reply[4], p.Y);
+       writeS16(&reply[6], p.Z);
+       memcpy(&reply[8], *blockdata, blockdata.getSize());
+       
+       /*
+               Send packet
+       */
+       m_con.Send(peer_id, 1, reply, true);
+}
+
+/*void Server::SendBlock(u16 peer_id, MapBlock *block, u8 ver)
+{
+       JMutexAutoLock conlock(m_con_mutex);
+       
+       SendBlockNoLock(peer_id, block, ver);
+}*/
+
+#if 0
+void Server::SendSectorMeta(u16 peer_id, core::list<v2s16> ps, u8 ver)
+{
+       DSTACK(__FUNCTION_NAME);
+       dstream<<"Server sending sector meta of "
+                       <<ps.getSize()<<" sectors"<<std::endl;
+
+       core::list<v2s16>::Iterator i = ps.begin();
+       core::list<v2s16> sendlist;
+       for(;;)
+       {
+               if(sendlist.size() == 255 || i == ps.end())
+               {
+                       if(sendlist.size() == 0)
+                               break;
+                       /*
+                               [0] u16 command
+                               [2] u8 sector count
+                               [3...] v2s16 pos + sector metadata
+                       */
+                       std::ostringstream os(std::ios_base::binary);
+                       u8 buf[4];
+
+                       writeU16(buf, TOCLIENT_SECTORMETA);
+                       os.write((char*)buf, 2);
+
+                       writeU8(buf, sendlist.size());
+                       os.write((char*)buf, 1);
+
+                       for(core::list<v2s16>::Iterator
+                                       j = sendlist.begin();
+                                       j != sendlist.end(); j++)
+                       {
+                               // Write position
+                               writeV2S16(buf, *j);
+                               os.write((char*)buf, 4);
+                               
+                               /*
+                                       Write ClientMapSector metadata
+                               */
+
+                               /*
+                                       [0] u8 serialization version
+                                       [1] s16 corners[0]
+                                       [3] s16 corners[1]
+                                       [5] s16 corners[2]
+                                       [7] s16 corners[3]
+                                       size = 9
+                                       
+                                       In which corners are in these positions
+                                       v2s16(0,0),
+                                       v2s16(1,0),
+                                       v2s16(1,1),
+                                       v2s16(0,1),
+                               */
+
+                               // Write version
+                               writeU8(buf, ver);
+                               os.write((char*)buf, 1);
+
+                               // Write corners
+                               // TODO: Get real values
+                               s16 corners[4];
+                               ((ServerMap&)m_env.getMap()).getSectorCorners(*j, corners);
+
+                               writeS16(buf, corners[0]);
+                               os.write((char*)buf, 2);
+                               writeS16(buf, corners[1]);
+                               os.write((char*)buf, 2);
+                               writeS16(buf, corners[2]);
+                               os.write((char*)buf, 2);
+                               writeS16(buf, corners[3]);
+                               os.write((char*)buf, 2);
+                       }
+
+                       SharedBuffer<u8> data((u8*)os.str().c_str(), os.str().size());
+
+                       /*dstream<<"Server::SendSectorMeta(): sending packet"
+                                       " with "<<sendlist.size()<<" sectors"<<std::endl;*/
+
+                       m_con.Send(peer_id, 1, data, true);
+
+                       if(i == ps.end())
+                               break;
+
+                       sendlist.clear();
+               }
+
+               sendlist.push_back(*i);
+               i++;
+       }
+}
+#endif
+
+core::list<PlayerInfo> Server::getPlayerInfo()
+{
+       DSTACK(__FUNCTION_NAME);
+       JMutexAutoLock envlock(m_env_mutex);
+       JMutexAutoLock conlock(m_con_mutex);
+       
+       core::list<PlayerInfo> list;
+
+       core::list<Player*> players = m_env.getPlayers();
+       
+       core::list<Player*>::Iterator i;
+       for(i = players.begin();
+                       i != players.end(); i++)
+       {
+               PlayerInfo info;
+
+               Player *player = *i;
+               try{
+                       con::Peer *peer = m_con.GetPeer(player->peer_id);
+                       info.id = peer->id;
+                       info.address = peer->address;
+                       info.avg_rtt = peer->avg_rtt;
+               }
+               catch(con::PeerNotFoundException &e)
+               {
+                       // Outdated peer info
+                       info.id = 0;
+                       info.address = Address(0,0,0,0,0);
+                       info.avg_rtt = 0.0;
+               }
+
+               snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
+               info.position = player->getPosition();
+
+               list.push_back(info);
+       }
+
+       return list;
+}
+
+void Server::peerAdded(con::Peer *peer)
+{
+       DSTACK(__FUNCTION_NAME);
+       dout_server<<"Server::peerAdded(): peer->id="
+                       <<peer->id<<std::endl;
+       
+       // Connection is already locked when this is called.
+       //JMutexAutoLock lock(m_con_mutex);
+       
+       // 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
+       {
+               // Already locked when called
+               //JMutexAutoLock envlock(m_env_mutex);
+               
+               Player *player = m_env.getPlayer(peer->id);
+               
+               // The player shouldn't already exist
+               assert(player == NULL);
+
+               player = new RemotePlayer();
+               player->peer_id = peer->id;
+
+               /*
+                       Set player position
+               */
+
+               // Get zero sector (it could have been unloaded to disk)
+               m_env.getMap().emergeSector(v2s16(0,0));
+               // Get ground height at origin
+               f32 groundheight = m_env.getMap().getGroundHeight(v2s16(0,0), true);
+               // The zero sector should have been generated
+               assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
+               // Don't go underwater
+               if(groundheight < WATER_LEVEL)
+                       groundheight = WATER_LEVEL;
+
+               player->setPosition(intToFloat(v3s16(
+                               0,
+                               groundheight + 1,
+                               0
+               )));
+
+               /*
+                       Add player to environment
+               */
+
+               m_env.addPlayer(player);
+
+               /*
+                       Add stuff to inventory
+               */
+               
+               if(m_creative_mode)
+               {
+                       // Give all materials
+                       assert(USEFUL_MATERIAL_COUNT <= PLAYER_INVENTORY_SIZE);
+                       for(u16 i=0; i<USEFUL_MATERIAL_COUNT; i++)
+                       {
+                               InventoryItem *item = new MaterialItem(i, 1);
+                               player->inventory.addItem(item);
+                       }
+                       // Sign
+                       {
+                               InventoryItem *item = new MapBlockObjectItem("Sign Example text");
+                               bool r = player->inventory.addItem(item);
+                               assert(r == true);
+                       }
+                       // Rat
+                       {
+                               InventoryItem *item = new MapBlockObjectItem("Rat");
+                               bool r = player->inventory.addItem(item);
+                               assert(r == true);
+                       }
+               }
+               else
+               {
+                       // Give some lights
+                       {
+                               InventoryItem *item = new MaterialItem(3, 999);
+                               bool r = player->inventory.addItem(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(item);
+                               assert(r == true);
+                       }
+                       /*// and some rats
+                       for(u16 i=0; i<4; i++)
+                       {
+                               InventoryItem *item = new MapBlockObjectItem("Rat");
+                               bool r = player->inventory.addItem(item);
+                               assert(r == true);
+                       }*/
+               }
+       }
+}
+
+void Server::deletingPeer(con::Peer *peer, bool timeout)
+{
+       DSTACK(__FUNCTION_NAME);
+       dout_server<<"Server::deletingPeer(): peer->id="
+                       <<peer->id<<", timeout="<<timeout<<std::endl;
+       
+       // Connection is already locked when this is called.
+       //JMutexAutoLock lock(m_con_mutex);
+
+       // Error check
+       core::map<u16, RemoteClient*>::Node *n;
+       n = m_clients.find(peer->id);
+       // The client should exist
+       assert(n != NULL);
+       
+       // Delete player
+       {
+               // Already locked when called
+               //JMutexAutoLock envlock(m_env_mutex);
+               m_env.removePlayer(peer->id);
+       }
+       
+       // Delete client
+       delete m_clients[peer->id];
+       m_clients.remove(peer->id);
+
+       // Send player info to all clients
+       SendPlayerInfos();
+}
+
+void Server::SendObjectData(float dtime)
+{
+       DSTACK(__FUNCTION_NAME);
+
+       core::map<v3s16, bool> stepped_blocks;
+       
+       for(core::map<u16, RemoteClient*>::Iterator
+               i = m_clients.getIterator();
+               i.atEnd() == false; i++)
+       {
+               u16 peer_id = i.getNode()->getKey();
+               RemoteClient *client = i.getNode()->getValue();
+               assert(client->peer_id == peer_id);
+               
+               if(client->serialization_version == SER_FMT_VER_INVALID)
+                       continue;
+               
+               client->SendObjectData(this, dtime, stepped_blocks);
+       }
+}
+
+void Server::SendPlayerInfos()
+{
+       DSTACK(__FUNCTION_NAME);
+
+       //JMutexAutoLock envlock(m_env_mutex);
+       
+       core::list<Player*> players = m_env.getPlayers();
+       
+       u32 player_count = players.getSize();
+       u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count;
+
+       SharedBuffer<u8> data(datasize);
+       writeU16(&data[0], TOCLIENT_PLAYERINFO);
+       
+       u32 start = 2;
+       core::list<Player*>::Iterator i;
+       for(i = players.begin();
+                       i != players.end(); i++)
+       {
+               Player *player = *i;
+
+               /*dstream<<"Server sending player info for player with "
+                               "peer_id="<<player->peer_id<<std::endl;*/
+               
+               writeU16(&data[start], player->peer_id);
+               snprintf((char*)&data[start+2], PLAYERNAME_SIZE, "%s", player->getName());
+               start += 2+PLAYERNAME_SIZE;
+       }
+
+       //JMutexAutoLock conlock(m_con_mutex);
+
+       // Send as reliable
+       m_con.SendToAll(0, data, true);
+}
+
+void Server::SendInventory(u16 peer_id)
+{
+       DSTACK(__FUNCTION_NAME);
+       
+       //JMutexAutoLock envlock(m_env_mutex);
+       
+       Player* player = m_env.getPlayer(peer_id);
+
+       std::ostringstream os;
+       //os.imbue(std::locale("C"));
+
+       player->inventory.serialize(os);
+
+       std::string s = os.str();
+       
+       SharedBuffer<u8> data(s.size()+2);
+       writeU16(&data[0], TOCLIENT_INVENTORY);
+       memcpy(&data[2], s.c_str(), s.size());
+       
+       //JMutexAutoLock conlock(m_con_mutex);
+
+       // Send as reliable
+       m_con.Send(peer_id, 0, data, true);
+}
+
+void Server::SendBlocks(float dtime)
+{
+       DSTACK(__FUNCTION_NAME);
+       //dstream<<"Server::SendBlocks(): BEGIN"<<std::endl;
+
+       JMutexAutoLock envlock(m_env_mutex);
+       JMutexAutoLock conlock(m_con_mutex);
+
+       for(core::map<u16, RemoteClient*>::Iterator
+               i = m_clients.getIterator();
+               i.atEnd() == false; i++)
+       {
+               RemoteClient *client = i.getNode()->getValue();
+               assert(client->peer_id == i.getNode()->getKey());
+               
+               if(client->serialization_version == SER_FMT_VER_INVALID)
+                       continue;
+
+               //dstream<<"Server::SendBlocks(): sending blocks for client "<<client->peer_id<<std::endl;
+               
+               //u16 peer_id = client->peer_id;
+               client->SendBlocks(this, dtime);
+       }
+
+       //dstream<<"Server::SendBlocks(): END"<<std::endl;
+}
+
+RemoteClient* Server::getClient(u16 peer_id)
+{
+       DSTACK(__FUNCTION_NAME);
+       //JMutexAutoLock lock(m_con_mutex);
+       core::map<u16, RemoteClient*>::Node *n;
+       n = m_clients.find(peer_id);
+       // A client should exist for all peers
+       assert(n != NULL);
+       return n->getValue();
+}
+
+
diff --git a/src/server.h b/src/server.h
new file mode 100644 (file)
index 0000000..5c85633
--- /dev/null
@@ -0,0 +1,388 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef SERVER_HEADER
+#define SERVER_HEADER
+
+#include "connection.h"
+#include "environment.h"
+#include "common_irrlicht.h"
+#include <string>
+
+#ifdef _WIN32
+       #include <windows.h>
+       #define sleep_ms(x) Sleep(x)
+#else
+       #include <unistd.h>
+       #define sleep_ms(x) usleep(x*1000)
+#endif
+
+struct QueuedBlockEmerge
+{
+       v3s16 pos;
+       // key = peer_id, value = flags
+       core::map<u16, u8> peer_ids;
+};
+
+/*
+       This is a thread-safe class.
+*/
+class BlockEmergeQueue
+{
+public:
+       BlockEmergeQueue()
+       {
+               m_mutex.Init();
+       }
+
+       ~BlockEmergeQueue()
+       {
+               JMutexAutoLock lock(m_mutex);
+
+               core::list<QueuedBlockEmerge*>::Iterator i;
+               for(i=m_queue.begin(); i!=m_queue.end(); i++)
+               {
+                       QueuedBlockEmerge *q = *i;
+                       delete q;
+               }
+       }
+       
+       /*
+               peer_id=0 adds with nobody to send to
+       */
+       void addBlock(u16 peer_id, v3s16 pos, u8 flags)
+       {
+               JMutexAutoLock lock(m_mutex);
+
+               if(peer_id != 0)
+               {
+                       /*
+                               Find if block is already in queue.
+                               If it is, update the peer to it and quit.
+                       */
+                       core::list<QueuedBlockEmerge*>::Iterator i;
+                       for(i=m_queue.begin(); i!=m_queue.end(); i++)
+                       {
+                               QueuedBlockEmerge *q = *i;
+                               if(q->pos == pos)
+                               {
+                                       q->peer_ids[peer_id] = flags;
+                                       return;
+                               }
+                       }
+               }
+               
+               /*
+                       Add the block
+               */
+               QueuedBlockEmerge *q = new QueuedBlockEmerge;
+               q->pos = pos;
+               if(peer_id != 0)
+                       q->peer_ids[peer_id] = flags;
+               m_queue.push_back(q);
+       }
+
+       // Returned pointer must be deleted
+       // Returns NULL if queue is empty
+       QueuedBlockEmerge * pop()
+       {
+               JMutexAutoLock lock(m_mutex);
+
+               core::list<QueuedBlockEmerge*>::Iterator i = m_queue.begin();
+               if(i == m_queue.end())
+                       return NULL;
+               QueuedBlockEmerge *q = *i;
+               m_queue.erase(i);
+               return q;
+       }
+
+       u32 size()
+       {
+               JMutexAutoLock lock(m_mutex);
+               return m_queue.size();
+       }
+
+private:
+       core::list<QueuedBlockEmerge*> m_queue;
+       JMutex m_mutex;
+};
+
+class SimpleThread : public JThread
+{
+       bool run;
+       JMutex run_mutex;
+
+public:
+
+       SimpleThread():
+               JThread(),
+               run(true)
+       {
+               run_mutex.Init();
+       }
+
+       virtual ~SimpleThread()
+       {}
+
+       virtual void * Thread() = 0;
+
+       bool getRun()
+       {
+               JMutexAutoLock lock(run_mutex);
+               return run;
+       }
+       void setRun(bool a_run)
+       {
+               JMutexAutoLock lock(run_mutex);
+               run = a_run;
+       }
+
+       void stop()
+       {
+               setRun(false);
+               while(IsRunning())
+                       sleep_ms(100);
+       }
+};
+
+class Server;
+
+class ServerThread : public SimpleThread
+{
+       Server *m_server;
+
+public:
+
+       ServerThread(Server *server):
+               SimpleThread(),
+               m_server(server)
+       {
+       }
+
+       void * Thread();
+};
+
+class EmergeThread : public SimpleThread
+{
+       Server *m_server;
+
+public:
+
+       EmergeThread(Server *server):
+               SimpleThread(),
+               m_server(server)
+       {
+       }
+
+       void * Thread();
+
+       void trigger()
+       {
+               setRun(true);
+               if(IsRunning() == false)
+               {
+                       Start();
+               }
+       }
+};
+
+struct PlayerInfo
+{
+       u16 id;
+       char name[PLAYERNAME_SIZE];
+       v3f position;
+       Address address;
+       float avg_rtt;
+
+       PlayerInfo();
+       void PrintLine(std::ostream *s);
+};
+
+u32 PIChecksum(core::list<PlayerInfo> &l);
+
+class RemoteClient
+{
+public:
+       // peer_id=0 means this client has no associated peer
+       // NOTE: If client is made allowed to exist while peer doesn't,
+       //       this has to be set to 0 when there is no peer.
+       //       Also, the client must be moved to some other container.
+       u16 peer_id;
+       // The serialization version to use with the client
+       u8 serialization_version;
+       // Version is stored in here after INIT before INIT2
+       u8 pending_serialization_version;
+
+       RemoteClient():
+               m_time_from_building(0.0),
+               m_num_blocks_in_emerge_queue(0)
+       {
+               peer_id = 0;
+               serialization_version = SER_FMT_VER_INVALID;
+               pending_serialization_version = SER_FMT_VER_INVALID;
+               m_nearest_unsent_d = 0;
+
+               m_blocks_sent_mutex.Init();
+               m_blocks_sending_mutex.Init();
+       }
+       ~RemoteClient()
+       {
+       }
+       
+       // Connection and environment should be locked when this is called
+       void SendBlocks(Server *server, float dtime);
+
+       // Connection and environment should be locked when this is called
+       // steps() objects of blocks not found in active_blocks, then
+       // adds those blocks to active_blocks
+       void SendObjectData(
+                       Server *server,
+                       float dtime,
+                       core::map<v3s16, bool> &stepped_blocks
+               );
+
+       void GotBlock(v3s16 p);
+
+       void SentBlock(v3s16 p);
+
+       void SetBlockNotSent(v3s16 p);
+       void SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks);
+
+       void BlockEmerged();
+       
+       // Increments timeouts and removes timed-out blocks from list
+       //void RunSendingTimeouts(float dtime, float timeout);
+
+       void PrintInfo(std::ostream &o)
+       {
+               JMutexAutoLock l2(m_blocks_sent_mutex);
+               JMutexAutoLock l3(m_blocks_sending_mutex);
+               o<<"RemoteClient "<<peer_id<<": "
+                               <<"m_num_blocks_in_emerge_queue="
+                               <<m_num_blocks_in_emerge_queue.get()
+                               <<", m_blocks_sent.size()="<<m_blocks_sent.size()
+                               <<", m_blocks_sending.size()="<<m_blocks_sending.size()
+                               <<", m_nearest_unsent_d="<<m_nearest_unsent_d
+                               <<std::endl;
+       }
+
+       // Time from last placing or removing blocks
+       MutexedVariable<float> m_time_from_building;
+       
+private:
+       /*
+               All members that are accessed by many threads should
+               obviously be behind a mutex. The threads include:
+               - main thread (calls step())
+               - server thread (calls AsyncRunStep() and Receive())
+               - emerge thread 
+       */
+       
+       //TODO: core::map<v3s16, MapBlock*> m_active_blocks
+
+       // Number of blocks in the emerge queue that have this client as
+       // a receiver. Used for throttling network usage.
+       MutexedVariable<s16> m_num_blocks_in_emerge_queue;
+
+       /*
+               Blocks that have been sent to client.
+               - These don't have to be sent again.
+               - A block is cleared from here when client says it has
+                 deleted it from it's memory
+               
+               Key is position, value is dummy.
+               No MapBlock* is stored here because the blocks can get deleted.
+       */
+       core::map<v3s16, bool> m_blocks_sent;
+       s16 m_nearest_unsent_d;
+       v3s16 m_last_center;
+       JMutex m_blocks_sent_mutex;
+       /*
+               Blocks that are currently on the line.
+               This is used for throttling the sending of blocks.
+               - The size of this list is limited to some value
+               Block is added when it is sent with BLOCKDATA.
+               Block is removed when GOTBLOCKS is received.
+               Value is time from sending. (not used at the moment)
+       */
+       core::map<v3s16, float> m_blocks_sending;
+       JMutex m_blocks_sending_mutex;
+};
+
+class Server : public con::PeerHandler
+{
+public:
+       /*
+               NOTE: Every public method should be thread-safe
+       */
+       Server(
+                       std::string mapsavedir,
+                       bool creative_mode,
+                       MapgenParams mapgen_params
+               );
+       ~Server();
+       void start(unsigned short port);
+       void stop();
+       void step(float dtime);
+       void AsyncRunStep();
+       void Receive();
+       void ProcessData(u8 *data, u32 datasize, u16 peer_id);
+
+       /*void Send(u16 peer_id, u16 channelnum,
+                       SharedBuffer<u8> data, bool reliable);*/
+
+       // Environment and Connection must be locked when called
+       void SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver);
+       //void SendBlock(u16 peer_id, MapBlock *block, u8 ver);
+       //TODO: Sending of many blocks in a single packet
+       
+       // Environment and Connection must be locked when called
+       //void SendSectorMeta(u16 peer_id, core::list<v2s16> ps, u8 ver);
+
+       core::list<PlayerInfo> getPlayerInfo();
+       
+private:
+
+       // Virtual methods from con::PeerHandler.
+       // As of now, these create and remove clients and players.
+       // TODO: Make it possible to leave players on server.
+       void peerAdded(con::Peer *peer);
+       void deletingPeer(con::Peer *peer, bool timeout);
+       
+       // Envlock and conlock should be locked when calling these
+       void SendObjectData(float dtime);
+       void SendPlayerInfos();
+       void SendInventory(u16 peer_id);
+       // Sends blocks to clients
+       void SendBlocks(float dtime);
+       
+       // When called, connection mutex should be locked
+       RemoteClient* getClient(u16 peer_id);
+       
+       // NOTE: If connection and environment are both to be locked,
+       // environment shall be locked first.
+
+       JMutex m_env_mutex;
+       Environment m_env;
+
+       JMutex m_con_mutex;
+       con::Connection m_con;
+       core::map<u16, RemoteClient*> m_clients; // Behind the con mutex
+
+       float m_step_dtime;
+       JMutex m_step_dtime_mutex;
+
+       ServerThread m_thread;
+       EmergeThread m_emergethread;
+
+       BlockEmergeQueue m_emerge_queue;
+
+       bool m_creative_mode;
+
+       friend class EmergeThread;
+       friend class RemoteClient;
+};
+
+#endif
+
diff --git a/src/socket.cpp b/src/socket.cpp
new file mode 100644 (file)
index 0000000..91b2ef7
--- /dev/null
@@ -0,0 +1,311 @@
+#include "socket.h"
+#include "debug.h"
+#include <stdio.h>
+#include <iostream>
+#include <stdlib.h>
+#include <errno.h>
+
+// Debug printing options
+#define DP 0
+#define DPS ""
+
+bool g_sockets_initialized = false;
+
+void sockets_init()
+{
+#ifdef _WIN32
+       WSADATA WsaData;
+       if(WSAStartup( MAKEWORD(2,2), &WsaData ) != NO_ERROR)
+               throw SocketException("WSAStartup failed");
+#else
+#endif
+       g_sockets_initialized = true;
+}
+
+void sockets_cleanup()
+{
+#ifdef _WIN32
+       WSACleanup();
+#endif
+}
+
+Address::Address()
+{
+}
+
+Address::Address(unsigned int address, unsigned short port)
+{
+       m_address = address;
+       m_port = port;
+}
+
+Address::Address(unsigned int a, unsigned int b,
+               unsigned int c, unsigned int d,
+               unsigned short port)
+{
+       m_address = (a<<24) | (b<<16) | ( c<<8) | d;
+       m_port = port;
+}
+
+bool Address::operator==(Address &address)
+{
+       return (m_address == address.m_address
+                       && m_port == address.m_port);
+}
+
+bool Address::operator!=(Address &address)
+{
+       return !(*this == address);
+}
+
+void Address::Resolve(const char *name)
+{
+       struct addrinfo *resolved;
+       int e = getaddrinfo(name, NULL, NULL, &resolved);
+       if(e != 0)
+               throw ResolveError("");
+       /*
+               FIXME: This is an ugly hack; change the whole class
+               to store the address as sockaddr
+       */
+       struct sockaddr_in *t = (struct sockaddr_in*)resolved->ai_addr;
+       m_address = ntohl(t->sin_addr.s_addr);
+       freeaddrinfo(resolved);
+}
+
+unsigned int Address::getAddress() const
+{
+       return m_address;
+}
+
+unsigned short Address::getPort() const
+{
+       return m_port;
+}
+
+void Address::setAddress(unsigned int address)
+{
+       m_address = address;
+}
+
+void Address::setPort(unsigned short port)
+{
+       m_port = port;
+}
+
+void Address::print(std::ostream *s) const
+{
+       (*s)<<((m_address>>24)&0xff)<<"."
+                       <<((m_address>>16)&0xff)<<"."
+                       <<((m_address>>8)&0xff)<<"."
+                       <<((m_address>>0)&0xff)<<":"
+                       <<m_port;
+}
+
+void Address::print() const
+{
+       print(&dstream);
+}
+
+UDPSocket::UDPSocket()
+{
+       if(g_sockets_initialized == false)
+               throw SocketException("Sockets not initialized");
+       
+    m_handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+       
+       if(DP)
+       dstream<<DPS<<"UDPSocket("<<(int)m_handle<<")::UDPSocket()"<<std::endl;
+       
+    if(m_handle <= 0)
+    {
+               throw SocketException("Failed to create socket");
+    }
+
+/*#ifdef _WIN32
+       DWORD nonblocking = 0;
+       if(ioctlsocket(m_handle, FIONBIO, &nonblocking) != 0)
+       {
+               throw SocketException("Failed set non-blocking mode");
+       }
+#else
+       int nonblocking = 0;
+       if(fcntl(m_handle, F_SETFL, O_NONBLOCK, nonblocking) == -1)
+       {
+               throw SocketException("Failed set non-blocking mode");
+       }
+#endif*/
+
+       setTimeoutMs(0);
+}
+
+UDPSocket::~UDPSocket()
+{
+       if(DP)
+       dstream<<DPS<<"UDPSocket("<<(int)m_handle<<")::~UDPSocket()"<<std::endl;
+
+#ifdef _WIN32
+       closesocket(m_handle);
+#else
+       close(m_handle);
+#endif
+}
+
+void UDPSocket::Bind(unsigned short port)
+{
+       if(DP)
+       dstream<<DPS<<"UDPSocket("<<(int)m_handle
+                       <<")::Bind(): port="<<port<<std::endl;
+
+    sockaddr_in address;
+    address.sin_family = AF_INET;
+    address.sin_addr.s_addr = INADDR_ANY;
+    address.sin_port = htons(port);
+
+    if(bind(m_handle, (const sockaddr*)&address, sizeof(sockaddr_in)) < 0)
+    {
+               dstream<<(int)m_handle<<": Bind failed: "<<strerror(errno)<<std::endl;
+               throw SocketException("Failed to bind socket");
+    }
+}
+
+void UDPSocket::Send(const Address & destination, const void * data, int size)
+{
+       bool dumping_packet = false;
+       if(INTERNET_SIMULATOR)
+               dumping_packet = (rand()%10==0); //easy
+               //dumping_packet = (rand()%4==0); // hard
+
+       if(DP){
+               /*dstream<<DPS<<"UDPSocket("<<(int)m_handle
+                               <<")::Send(): destination=";*/
+               dstream<<DPS;
+               dstream<<(int)m_handle<<" -> ";
+               destination.print();
+               dstream<<", size="<<size<<", data=";
+               for(int i=0; i<size && i<20; i++){
+                       if(i%2==0) printf(" ");
+                       DEBUGPRINT("%.2X", ((int)((const char*)data)[i])&0xff);
+               }
+               if(size>20)
+                       dstream<<"...";
+               if(dumping_packet)
+                       dstream<<" (DUMPED BY INTERNET_SIMULATOR)";
+               dstream<<std::endl;
+       }
+       else if(dumping_packet)
+       {
+               // Lol let's forget it
+               dstream<<"UDPSocket::Send(): "
+                               "INTERNET_SIMULATOR: dumping packet."
+                               <<std::endl;
+       }
+
+       if(dumping_packet)
+               return;
+
+       sockaddr_in address;
+       address.sin_family = AF_INET;
+       address.sin_addr.s_addr = htonl(destination.getAddress());
+       address.sin_port = htons(destination.getPort());
+
+       int sent = sendto(m_handle, (const char*)data, size,
+               0, (sockaddr*)&address, sizeof(sockaddr_in));
+
+    if(sent != size)
+    {
+               throw SendFailedException("Failed to send packet");
+    }
+}
+
+int UDPSocket::Receive(Address & sender, void * data, int size)
+{
+       if(WaitData(m_timeout_ms) == false)
+       {
+               return -1;
+       }
+
+       sockaddr_in address;
+       socklen_t address_len = sizeof(address);
+
+       int received = recvfrom(m_handle, (char*)data,
+                       size, 0, (sockaddr*)&address, &address_len);
+
+       if(received < 0)
+               return -1;
+
+       unsigned int address_ip = ntohl(address.sin_addr.s_addr);
+       unsigned int address_port = ntohs(address.sin_port);
+
+       sender = Address(address_ip, address_port);
+
+       if(DP){
+               //dstream<<DPS<<"UDPSocket("<<(int)m_handle<<")::Receive(): sender=";
+               dstream<<DPS<<(int)m_handle<<" <- ";
+               sender.print();
+               //dstream<<", received="<<received<<std::endl;
+               dstream<<", size="<<received<<", data=";
+               for(int i=0; i<received && i<20; i++){
+                       if(i%2==0) printf(" ");
+                       DEBUGPRINT("%.2X", ((int)((const char*)data)[i])&0xff);
+               }
+               if(received>20)
+                       dstream<<"...";
+               dstream<<std::endl;
+       }
+
+       return received;
+}
+
+int UDPSocket::GetHandle()
+{
+       return m_handle;
+}
+
+void UDPSocket::setTimeoutMs(int timeout_ms)
+{
+       m_timeout_ms = timeout_ms;
+}
+
+bool UDPSocket::WaitData(int timeout_ms)
+{
+       fd_set readset;
+       int result;
+
+       // Initialize the set
+       FD_ZERO(&readset);
+       FD_SET(m_handle, &readset);
+
+       // Initialize time out struct
+       struct timeval tv;
+       tv.tv_sec = 0;
+       tv.tv_usec = timeout_ms * 1000;
+       // select()
+       result = select(m_handle+1, &readset, NULL, NULL, &tv);
+
+       if(result == 0){
+               // Timeout
+               /*dstream<<"Select timed out (timeout_ms="
+                               <<timeout_ms<<")"<<std::endl;*/
+               return false;
+       }
+       else if(result < 0){
+               // Error
+               dstream<<(int)m_handle<<": Select failed: "<<strerror(errno)<<std::endl;
+#ifdef _WIN32
+               dstream<<(int)m_handle<<": WSAGetLastError()="<<WSAGetLastError()<<std::endl;
+#endif
+               throw SocketException("Select failed");
+       }
+       else if(FD_ISSET(m_handle, &readset) == false){
+               // No data
+               //dstream<<"Select reported no data in m_handle"<<std::endl;
+               return false;
+       }
+       
+       // There is data
+       //dstream<<"Select reported data in m_handle"<<std::endl;
+       return true;
+}
+
+
diff --git a/src/socket.h b/src/socket.h
new file mode 100644 (file)
index 0000000..1cbed91
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef SOCKET_HEADER
+#define SOCKET_HEADER
+
+#ifdef _WIN32
+       #define WIN32_LEAN_AND_MEAN
+       #include <windows.h>
+       #include <winsock2.h>
+       #include <ws2tcpip.h>
+       #pragma comment(lib, "wsock32.lib")
+typedef SOCKET socket_t;
+typedef int socklen_t;
+#else
+       #include <sys/socket.h>
+       #include <netinet/in.h>
+       #include <fcntl.h>
+       #include <netdb.h>
+       #include <unistd.h>
+typedef int socket_t;
+#endif
+
+#include <ostream>
+#include "exceptions.h"
+#include "constants.h"
+
+class SocketException : public BaseException
+{
+public:
+       SocketException(const char *s):
+               BaseException(s)
+       {
+       }
+};
+
+class ResolveError : public BaseException
+{
+public:
+       ResolveError(const char *s):
+               BaseException(s)
+       {
+       }
+};
+
+class SendFailedException : public BaseException
+{
+public:
+       SendFailedException(const char *s):
+               BaseException(s)
+       {
+       }
+};
+
+void sockets_init();
+void sockets_cleanup();
+
+class Address
+{
+public:
+       Address();
+       Address(unsigned int address, unsigned short port);
+       Address(unsigned int a, unsigned int b,
+                       unsigned int c, unsigned int d,
+                       unsigned short port);
+       bool operator==(Address &address);
+       bool operator!=(Address &address);
+       void Resolve(const char *name);
+       unsigned int getAddress() const;
+       unsigned short getPort() const;
+       void setAddress(unsigned int address);
+       void setPort(unsigned short port);
+       void print(std::ostream *s) const;
+       void print() const;
+private:
+       unsigned int m_address;
+       unsigned short m_port;
+};
+
+class UDPSocket
+{
+public:
+       UDPSocket();
+       ~UDPSocket();
+       void Bind(unsigned short port);
+       //void Close();
+       //bool IsOpen();
+       void Send(const Address & destination, const void * data, int size);
+       // Returns -1 if there is no data
+       int Receive(Address & sender, void * data, int size);
+       int GetHandle(); // For debugging purposes only
+       void setTimeoutMs(int timeout_ms);
+       // Returns true if there is data, false if timeout occurred
+       bool WaitData(int timeout_ms);
+private:
+       int m_handle;
+       int m_timeout_ms;
+};
+
+#endif
+
diff --git a/src/strfnd.h b/src/strfnd.h
new file mode 100644 (file)
index 0000000..dbbec11
--- /dev/null
@@ -0,0 +1,96 @@
+#ifndef STRFND_HEADER
+#define STRFND_HEADER
+
+#include <string>
+
+std::string trim(std::string str);
+
+class Strfnd{
+    std::string tek;
+    unsigned int p;
+public:
+    void start(std::string niinq){
+        tek = niinq;
+        p=0;
+    }
+    unsigned int where(){
+        return p;
+    }
+    void to(unsigned int i){
+        p = i;
+    }
+    std::string what(){
+        return tek;
+    }
+    std::string next(std::string plop){
+        //std::cout<<"tek=\""<<tek<<"\" plop=\""<<plop<<"\""<<std::endl;
+        size_t n;
+        std::string palautus;
+        if (p < tek.size())
+        {  
+            //std::cout<<"\tp<tek.size()"<<std::endl;
+            if ((n = tek.find(plop, p)) == std::string::npos || plop == "")
+            {  
+                //std::cout<<"\t\tn == string::npos || plop == \"\""<<std::endl;
+                n = tek.size();
+            }
+            else
+            {  
+                //std::cout<<"\t\tn != string::npos"<<std::endl;
+            }
+            palautus = tek.substr(p, n-p);
+            p = n + plop.length();
+        }
+        //else
+            //std::cout<<"\tp>=tek.size()"<<std::endl;
+               //std::cout<<"palautus=\""<<palautus<<"\""<<std::endl;
+        return palautus;
+    }
+    bool atend(){
+        if(p>=tek.size()) return true;
+        return false;
+    }
+    Strfnd(std::string s){
+        start(s);
+    }
+};
+
+inline std::string trim(std::string str)
+{
+    while( 
+            str.length()>0
+            &&
+            (
+             str.substr(0,               1)==" "     ||
+             str.substr(0,               1)=="\t"    ||
+             str.substr(0,               1)=="\r"    ||
+             str.substr(0,               1)=="\n"    ||
+             str.substr(str.length()-1,  1)==" "     ||
+             str.substr(str.length()-1,  1)=="\t"    ||
+             str.substr(str.length()-1,  1)=="\r"    ||
+             str.substr(str.length()-1,  1)=="\n"
+            )
+         )
+    {  
+        if      (str.substr(0,              1)==" ")
+                       str = str.substr(1,str.length()-1);
+        else if (str.substr(0,              1)=="\t")
+                       str = str.substr(1,str.length()-1);
+        else if (str.substr(0,              1)=="\r")
+                       str = str.substr(1,str.length()-1);
+        else if (str.substr(0,              1)=="\n")
+                       str = str.substr(1,str.length()-1);
+        else if (str.substr(str.length()-1, 1)==" ")
+                       str = str.substr(0,str.length()-1);
+        else if (str.substr(str.length()-1, 1)=="\t")
+                       str = str.substr(0,str.length()-1);
+        else if (str.substr(str.length()-1, 1)=="\r")
+                       str = str.substr(0,str.length()-1);
+        else if (str.substr(str.length()-1, 1)=="\n")
+                       str = str.substr(0,str.length()-1);
+    }
+    return str;
+}
+
+#endif
+
diff --git a/src/test.cpp b/src/test.cpp
new file mode 100644 (file)
index 0000000..8cdd844
--- /dev/null
@@ -0,0 +1,920 @@
+#include "test.h"
+#include "common_irrlicht.h"
+
+#include "debug.h"
+#include "map.h"
+#include "player.h"
+#include "main.h"
+#include "heightmap.h"
+#include "socket.h"
+#include "connection.h"
+#include "utility.h"
+#include "serialization.h"
+#include <sstream>
+
+#ifdef _WIN32
+       #include <windows.h>
+       #define sleep_ms(x) Sleep(x)
+#else
+       #include <unistd.h>
+       #define sleep_ms(x) usleep(x*1000)
+#endif
+
+/*
+       Asserts that the exception occurs
+*/
+#define EXCEPTION_CHECK(EType, code)\
+{\
+       bool exception_thrown = false;\
+       try{ code; }\
+       catch(EType &e) { exception_thrown = true; }\
+       assert(exception_thrown);\
+}
+
+struct TestUtilities
+{
+       void Run()
+       {
+               /*dstream<<"wrapDegrees(100.0) = "<<wrapDegrees(100.0)<<std::endl;
+               dstream<<"wrapDegrees(720.5) = "<<wrapDegrees(720.5)<<std::endl;
+               dstream<<"wrapDegrees(-0.5) = "<<wrapDegrees(-0.5)<<std::endl;*/
+               assert(fabs(wrapDegrees(100.0) - 100.0) < 0.001);
+               assert(fabs(wrapDegrees(720.5) - 0.5) < 0.001);
+               assert(fabs(wrapDegrees(-0.5) - (-0.5)) < 0.001);
+               assert(fabs(wrapDegrees(-365.5) - (-5.5)) < 0.001);
+               assert(lowercase("Foo bAR") == "foo bar");
+               assert(is_yes("YeS") == true);
+               assert(is_yes("") == false);
+               assert(is_yes("FAlse") == false);
+       }
+};
+               
+struct TestCompress
+{
+       void Run()
+       {
+               SharedBuffer<u8> fromdata(4);
+               fromdata[0]=1;
+               fromdata[1]=5;
+               fromdata[2]=5;
+               fromdata[3]=1;
+               
+               std::ostringstream os(std::ios_base::binary);
+               compress(fromdata, os, 0);
+
+               std::string str_out = os.str();
+               
+               dstream<<"str_out.size()="<<str_out.size()<<std::endl;
+               dstream<<"TestCompress: 1,5,5,1 -> ";
+               for(u32 i=0; i<str_out.size(); i++)
+               {
+                       dstream<<(u32)str_out[i]<<",";
+               }
+               dstream<<std::endl;
+
+               assert(str_out.size() == 10);
+
+               assert(str_out[0] == 0);
+               assert(str_out[1] == 0);
+               assert(str_out[2] == 0);
+               assert(str_out[3] == 4);
+               assert(str_out[4] == 0);
+               assert(str_out[5] == 1);
+               assert(str_out[6] == 1);
+               assert(str_out[7] == 5);
+               assert(str_out[8] == 0);
+               assert(str_out[9] == 1);
+
+               std::istringstream is(str_out, std::ios_base::binary);
+               std::ostringstream os2(std::ios_base::binary);
+
+               decompress(is, os2, 0);
+               std::string str_out2 = os2.str();
+
+               dstream<<"decompress: ";
+               for(u32 i=0; i<str_out2.size(); i++)
+               {
+                       dstream<<(u32)str_out2[i]<<",";
+               }
+               dstream<<std::endl;
+
+               assert(str_out2.size() == fromdata.getSize());
+
+               for(u32 i=0; i<str_out2.size(); i++)
+               {
+                       assert(str_out2[i] == fromdata[i]);
+               }
+       }
+};
+
+struct TestMapNode
+{
+       void Run()
+       {
+               MapNode n;
+
+               // Default values
+               assert(n.d == MATERIAL_AIR);
+               assert(n.getLight() == 0);
+               
+               // Transparency
+               n.d = MATERIAL_AIR;
+               assert(n.light_propagates() == true);
+               n.d = 0;
+               assert(n.light_propagates() == false);
+       }
+};
+
+struct TestMapBlock
+{
+       class TC : public NodeContainer
+       {
+       public:
+
+               MapNode node;
+               bool position_valid;
+               core::list<v3s16> validity_exceptions;
+
+               TC()
+               {
+                       position_valid = true;
+               }
+
+               virtual bool isValidPosition(v3s16 p)
+               {
+                       //return position_valid ^ (p==position_valid_exception);
+                       bool exception = false;
+                       for(core::list<v3s16>::Iterator i=validity_exceptions.begin();
+                                       i != validity_exceptions.end(); i++)
+                       {
+                               if(p == *i)
+                               {
+                                       exception = true;
+                                       break;
+                               }
+                       }
+                       return exception ? !position_valid : position_valid;
+               }
+
+               virtual MapNode getNode(v3s16 p)
+               {
+                       if(isValidPosition(p) == false)
+                               throw InvalidPositionException();
+                       return node;
+               }
+
+               virtual void setNode(v3s16 p, MapNode & n)
+               {
+                       if(isValidPosition(p) == false)
+                               throw InvalidPositionException();
+               };
+
+               virtual u16 nodeContainerId() const
+               {
+                       return 666;
+               }
+       };
+
+       void Run()
+       {
+               TC parent;
+               
+               MapBlock b(&parent, v3s16(1,1,1));
+               v3s16 relpos(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
+
+               assert(b.getPosRelative() == relpos);
+
+               assert(b.getBox().MinEdge.X == MAP_BLOCKSIZE);
+               assert(b.getBox().MaxEdge.X == MAP_BLOCKSIZE*2-1);
+               assert(b.getBox().MinEdge.Y == MAP_BLOCKSIZE);
+               assert(b.getBox().MaxEdge.Y == MAP_BLOCKSIZE*2-1);
+               assert(b.getBox().MinEdge.Z == MAP_BLOCKSIZE);
+               assert(b.getBox().MaxEdge.Z == MAP_BLOCKSIZE*2-1);
+               
+               assert(b.isValidPosition(v3s16(0,0,0)) == true);
+               assert(b.isValidPosition(v3s16(-1,0,0)) == false);
+               assert(b.isValidPosition(v3s16(-1,-142,-2341)) == false);
+               assert(b.isValidPosition(v3s16(-124,142,2341)) == false);
+               assert(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
+               assert(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE,MAP_BLOCKSIZE-1)) == false);
+
+               /*
+                       TODO: this method should probably be removed
+                       if the block size isn't going to be set variable
+               */
+               /*assert(b.getSizeNodes() == v3s16(MAP_BLOCKSIZE,
+                               MAP_BLOCKSIZE, MAP_BLOCKSIZE));*/
+               
+               // Changed flag should be initially set
+               assert(b.getChangedFlag() == true);
+               b.resetChangedFlag();
+               assert(b.getChangedFlag() == false);
+
+               // All nodes should have been set to
+               // .d=MATERIAL_AIR and .getLight() = 0
+               for(u16 z=0; z<MAP_BLOCKSIZE; z++)
+                       for(u16 y=0; y<MAP_BLOCKSIZE; y++)
+                               for(u16 x=0; x<MAP_BLOCKSIZE; x++){
+                                       assert(b.getNode(v3s16(x,y,z)).d == MATERIAL_AIR);
+                                       assert(b.getNode(v3s16(x,y,z)).getLight() == 0);
+                               }
+               
+               /*
+                       Parent fetch functions
+               */
+               parent.position_valid = false;
+               parent.node.d = 5;
+
+               MapNode n;
+               
+               // Positions in the block should still be valid
+               assert(b.isValidPositionParent(v3s16(0,0,0)) == true);
+               assert(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
+               n = b.getNodeParent(v3s16(0,MAP_BLOCKSIZE-1,0));
+               assert(n.d == MATERIAL_AIR);
+
+               // ...but outside the block they should be invalid
+               assert(b.isValidPositionParent(v3s16(-121,2341,0)) == false);
+               assert(b.isValidPositionParent(v3s16(-1,0,0)) == false);
+               assert(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == false);
+               
+               {
+                       bool exception_thrown = false;
+                       try{
+                               // This should throw an exception
+                               MapNode n = b.getNodeParent(v3s16(0,0,-1));
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               exception_thrown = true;
+                       }
+                       assert(exception_thrown);
+               }
+
+               parent.position_valid = true;
+               // Now the positions outside should be valid
+               assert(b.isValidPositionParent(v3s16(-121,2341,0)) == true);
+               assert(b.isValidPositionParent(v3s16(-1,0,0)) == true);
+               assert(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == true);
+               n = b.getNodeParent(v3s16(0,0,MAP_BLOCKSIZE));
+               assert(n.d == 5);
+
+               /*
+                       Set a node
+               */
+               v3s16 p(1,2,0);
+               n.d = 4;
+               b.setNode(p, n);
+               assert(b.getNode(p).d == 4);
+               assert(b.getNodeMaterial(p) == 4);
+               assert(b.getNodeMaterial(v3s16(-1,-1,0)) == 5);
+               
+               /*
+                       propagateSunlight()
+               */
+               // Set lighting of all nodes to 0
+               for(u16 z=0; z<MAP_BLOCKSIZE; z++){
+                       for(u16 y=0; y<MAP_BLOCKSIZE; y++){
+                               for(u16 x=0; x<MAP_BLOCKSIZE; x++){
+                                       MapNode n = b.getNode(v3s16(x,y,z));
+                                       n.setLight(0);
+                                       b.setNode(v3s16(x,y,z), n);
+                               }
+                       }
+               }
+               {
+                       /*
+                               Check how the block handles being a lonely sky block
+                       */
+                       parent.position_valid = true;
+                       b.setIsUnderground(false);
+                       parent.node.d = MATERIAL_AIR;
+                       parent.node.setLight(LIGHT_SUN);
+                       core::map<v3s16, bool> light_sources;
+                       // The bottom block is invalid, because we have a shadowing node
+                       assert(b.propagateSunlight(light_sources) == false);
+                       assert(b.getNode(v3s16(1,4,0)).getLight() == LIGHT_SUN);
+                       assert(b.getNode(v3s16(1,3,0)).getLight() == LIGHT_SUN);
+                       assert(b.getNode(v3s16(1,2,0)).getLight() == 0);
+                       assert(b.getNode(v3s16(1,1,0)).getLight() == 0);
+                       assert(b.getNode(v3s16(1,0,0)).getLight() == 0);
+                       assert(b.getNode(v3s16(1,2,3)).getLight() == LIGHT_SUN);
+                       assert(b.getFaceLight(p, v3s16(0,1,0)) == LIGHT_SUN);
+                       assert(b.getFaceLight(p, v3s16(0,-1,0)) == 0);
+                       // According to MapBlock::getFaceLight,
+                       // The face on the z+ side should have double-diminished light
+                       assert(b.getFaceLight(p, v3s16(0,0,1)) == diminish_light(diminish_light(LIGHT_MAX)));
+               }
+               /*
+                       Check how the block handles being in between blocks with some non-sunlight
+                       while being underground
+               */
+               {
+                       // Make neighbours to exist and set some non-sunlight to them
+                       parent.position_valid = true;
+                       b.setIsUnderground(true);
+                       parent.node.setLight(LIGHT_MAX/2);
+                       core::map<v3s16, bool> light_sources;
+                       // The block below should be valid because there shouldn't be
+                       // sunlight in there either
+                       assert(b.propagateSunlight(light_sources) == true);
+                       // Should not touch nodes that are not affected (that is, all of them)
+                       //assert(b.getNode(v3s16(1,2,3)).getLight() == LIGHT_SUN);
+                       // Should set light of non-sunlighted blocks to 0.
+                       assert(b.getNode(v3s16(1,2,3)).getLight() == 0);
+               }
+               /*
+                       Set up a situation where:
+                       - There is only air in this block
+                       - There is a valid non-sunlighted block at the bottom, and
+                       - Invalid blocks elsewhere.
+                       - the block is not underground.
+
+                       This should result in bottom block invalidity
+               */
+               {
+                       b.setIsUnderground(false);
+                       // Clear block
+                       for(u16 z=0; z<MAP_BLOCKSIZE; z++){
+                               for(u16 y=0; y<MAP_BLOCKSIZE; y++){
+                                       for(u16 x=0; x<MAP_BLOCKSIZE; x++){
+                                               MapNode n;
+                                               n.d = MATERIAL_AIR;
+                                               n.setLight(0);
+                                               b.setNode(v3s16(x,y,z), n);
+                                       }
+                               }
+                       }
+                       // Make neighbours invalid
+                       parent.position_valid = false;
+                       // Add exceptions to the top of the bottom block
+                       for(u16 x=0; x<MAP_BLOCKSIZE; x++)
+                       for(u16 z=0; z<MAP_BLOCKSIZE; z++)
+                       {
+                               parent.validity_exceptions.push_back(v3s16(MAP_BLOCKSIZE+x, MAP_BLOCKSIZE-1, MAP_BLOCKSIZE+z));
+                       }
+                       // Lighting value for the valid nodes
+                       parent.node.setLight(LIGHT_MAX/2);
+                       core::map<v3s16, bool> light_sources;
+                       // Bottom block is not valid
+                       assert(b.propagateSunlight(light_sources) == false);
+               }
+       }
+};
+
+struct TestMapSector
+{
+       class TC : public NodeContainer
+       {
+       public:
+
+               MapNode node;
+               bool position_valid;
+
+               TC()
+               {
+                       position_valid = true;
+               }
+
+               virtual bool isValidPosition(v3s16 p)
+               {
+                       return position_valid;
+               }
+
+               virtual MapNode getNode(v3s16 p)
+               {
+                       if(position_valid == false)
+                               throw InvalidPositionException();
+                       return node;
+               }
+
+               virtual void setNode(v3s16 p, MapNode & n)
+               {
+                       if(position_valid == false)
+                               throw InvalidPositionException();
+               };
+               
+               virtual u16 nodeContainerId() const
+               {
+                       return 666;
+               }
+       };
+       
+       void Run()
+       {
+               TC parent;
+               parent.position_valid = false;
+               
+               // Create one with no heightmaps
+               ServerMapSector sector(&parent, v2s16(1,1), 0);
+               //ConstantGenerator *dummyheightmap = new ConstantGenerator();
+               //sector->setHeightmap(dummyheightmap);
+               
+               EXCEPTION_CHECK(InvalidPositionException, sector.getBlockNoCreate(0));
+               EXCEPTION_CHECK(InvalidPositionException, sector.getBlockNoCreate(1));
+
+               MapBlock * bref = sector.createBlankBlock(-2);
+               
+               EXCEPTION_CHECK(InvalidPositionException, sector.getBlockNoCreate(0));
+               assert(sector.getBlockNoCreate(-2) == bref);
+               
+               //TODO: Check for AlreadyExistsException
+
+               /*bool exception_thrown = false;
+               try{
+                       sector.getBlock(0);
+               }
+               catch(InvalidPositionException &e){
+                       exception_thrown = true;
+               }
+               assert(exception_thrown);*/
+
+       }
+};
+
+struct TestHeightmap
+{
+       void TestSingleFixed()
+       {
+               const s16 BS1 = 4;
+               OneChildHeightmap hm1(BS1);
+               
+               // Test that it is filled with < GROUNDHEIGHT_VALID_MINVALUE
+               for(s16 y=0; y<=BS1; y++){
+                       for(s16 x=0; x<=BS1; x++){
+                               v2s16 p(x,y);
+                               assert(hm1.m_child.getGroundHeight(p)
+                                       < GROUNDHEIGHT_VALID_MINVALUE);
+                       }
+               }
+
+               hm1.m_child.setGroundHeight(v2s16(1,0), 2.0);
+               //hm1.m_child.print();
+               assert(fabs(hm1.getGroundHeight(v2s16(1,0))-2.0)<0.001);
+               hm1.setGroundHeight(v2s16(0,1), 3.0);
+               assert(fabs(hm1.m_child.getGroundHeight(v2s16(0,1))-3.0)<0.001);
+               
+               // Fill with -1.0
+               for(s16 y=0; y<=BS1; y++){
+                       for(s16 x=0; x<=BS1; x++){
+                               v2s16 p(x,y);
+                               hm1.m_child.setGroundHeight(p, -1.0);
+                       }
+               }
+
+               f32 corners[] = {0.0, 0.0, 1.0, 1.0};
+               hm1.m_child.generateContinued(0.0, 0.0, corners);
+               
+               hm1.m_child.print();
+               assert(fabs(hm1.m_child.getGroundHeight(v2s16(1,0))-0.2)<0.05);
+               assert(fabs(hm1.m_child.getGroundHeight(v2s16(4,3))-0.7)<0.05);
+               assert(fabs(hm1.m_child.getGroundHeight(v2s16(4,4))-1.0)<0.05);
+       }
+
+       void TestUnlimited()
+       {
+               //g_heightmap_debugprint = true;
+               const s16 BS1 = 4;
+               UnlimitedHeightmap hm1(BS1,
+                               new ConstantGenerator(0.0),
+                               new ConstantGenerator(0.0),
+                               new ConstantGenerator(5.0));
+               // Go through it so it generates itself
+               for(s16 y=0; y<=BS1; y++){
+                       for(s16 x=0; x<=BS1; x++){
+                               v2s16 p(x,y);
+                               hm1.getGroundHeight(p);
+                       }
+               }
+               // Print it
+               dstream<<"UnlimitedHeightmap hm1:"<<std::endl;
+               hm1.print();
+               
+               dstream<<"testing UnlimitedHeightmap set/get"<<std::endl;
+               v2s16 p1(0,3);
+               f32 v1(234.01);
+               // Get first heightmap and try setGroundHeight
+               FixedHeightmap * href = hm1.getHeightmap(v2s16(0,0));
+               href->setGroundHeight(p1, v1);
+               // Read from UnlimitedHeightmap
+               assert(fabs(hm1.getGroundHeight(p1)-v1)<0.001);
+       }
+       
+       void Random()
+       {
+               dstream<<"Running random code (get a human to check this)"<<std::endl;
+               dstream<<"rand() values: ";
+               for(u16 i=0; i<5; i++)
+                       dstream<<(u16)rand()<<" ";
+               dstream<<std::endl;
+
+               const s16 BS1 = 8;
+               UnlimitedHeightmap hm1(BS1,
+                               new ConstantGenerator(10.0),
+                               new ConstantGenerator(0.3),
+                               new ConstantGenerator(0.0));
+
+               // Force hm1 to generate a some heightmap
+               hm1.getGroundHeight(v2s16(0,0));
+               hm1.getGroundHeight(v2s16(0,BS1));
+               /*hm1.getGroundHeight(v2s16(BS1,-1));
+               hm1.getGroundHeight(v2s16(BS1-1,-1));*/
+               hm1.print();
+
+               // Get the (0,0) and (1,0) heightmaps
+               /*FixedHeightmap * hr00 = hm1.getHeightmap(v2s16(0,0));
+               FixedHeightmap * hr01 = hm1.getHeightmap(v2s16(1,0));
+               f32 corners[] = {1.0, 1.0, 1.0, 1.0};
+               hr00->generateContinued(0.0, 0.0, corners);
+               hm1.print();*/
+
+               //assert(0);
+       }
+
+       void Run()
+       {
+               //srand(7); // Get constant random
+               srand(time(0)); // Get better random
+
+               TestSingleFixed();
+               TestUnlimited();
+               Random();
+       }
+};
+
+struct TestSocket
+{
+       void Run()
+       {
+               const int port = 30003;
+               UDPSocket socket;
+               socket.Bind(port);
+
+               const char sendbuffer[] = "hello world!";
+               socket.Send(Address(127,0,0,1,port), sendbuffer, sizeof(sendbuffer));
+
+               sleep_ms(50);
+
+               char rcvbuffer[256];
+               memset(rcvbuffer, 0, sizeof(rcvbuffer));
+               Address sender;
+               for(;;)
+               {
+                       int bytes_read = socket.Receive(sender, rcvbuffer, sizeof(rcvbuffer));
+                       if(bytes_read < 0)
+                               break;
+               }
+               //FIXME: This fails on some systems
+               assert(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer))==0);
+               assert(sender.getAddress() == Address(127,0,0,1, 0).getAddress());
+       }
+};
+
+struct TestConnection
+{
+       void TestHelpers()
+       {
+               /*
+                       Test helper functions
+               */
+
+               // Some constants for testing
+               u32 proto_id = 0x12345678;
+               u16 peer_id = 123;
+               u8 channel = 2;
+               SharedBuffer<u8> data1(1);
+               data1[0] = 100;
+               Address a(127,0,0,1, 10);
+               u16 seqnum = 34352;
+
+               con::BufferedPacket p1 = con::makePacket(a, data1,
+                               proto_id, peer_id, channel);
+               /*
+                       We should now have a packet with this data:
+                       Header:
+                               [0] u32 protocol_id
+                               [4] u16 sender_peer_id
+                               [6] u8 channel
+                       Data:
+                               [7] u8 data1[0]
+               */
+               assert(readU32(&p1.data[0]) == proto_id);
+               assert(readU16(&p1.data[4]) == peer_id);
+               assert(readU8(&p1.data[6]) == channel);
+               assert(readU8(&p1.data[7]) == data1[0]);
+               
+               //dstream<<"initial data1[0]="<<((u32)data1[0]&0xff)<<std::endl;
+
+               SharedBuffer<u8> p2 = con::makeReliablePacket(data1, seqnum);
+
+               /*dstream<<"p2.getSize()="<<p2.getSize()<<", data1.getSize()="
+                               <<data1.getSize()<<std::endl;
+               dstream<<"readU8(&p2[3])="<<readU8(&p2[3])
+                               <<" p2[3]="<<((u32)p2[3]&0xff)<<std::endl;
+               dstream<<"data1[0]="<<((u32)data1[0]&0xff)<<std::endl;*/
+
+               assert(p2.getSize() == 3 + data1.getSize());
+               assert(readU8(&p2[0]) == TYPE_RELIABLE);
+               assert(readU16(&p2[1]) == seqnum);
+               assert(readU8(&p2[3]) == data1[0]);
+       }
+
+       struct Handler : public con::PeerHandler
+       {
+               Handler(const char *a_name)
+               {
+                       count = 0;
+                       last_id = 0;
+                       name = a_name;
+               }
+               void peerAdded(con::Peer *peer)
+               {
+                       dstream<<"Handler("<<name<<")::peerAdded(): "
+                                       "id="<<peer->id<<std::endl;
+                       last_id = peer->id;
+                       count++;
+               }
+               void deletingPeer(con::Peer *peer, bool timeout)
+               {
+                       dstream<<"Handler("<<name<<")::deletingPeer(): "
+                                       "id="<<peer->id
+                                       <<", timeout="<<timeout<<std::endl;
+                       last_id = peer->id;
+                       count--;
+               }
+
+               s32 count;
+               u16 last_id;
+               const char *name;
+       };
+
+       void Run()
+       {
+               DSTACK("TestConnection::Run");
+
+               TestHelpers();
+
+               /*
+                       Test some real connections
+               */
+               u32 proto_id = 0xad26846a;
+
+               Handler hand_server("server");
+               Handler hand_client("client");
+               
+               dstream<<"** Creating server Connection"<<std::endl;
+               con::Connection server(proto_id, 512, 5.0, &hand_server);
+               server.Serve(30001);
+               
+               dstream<<"** Creating client Connection"<<std::endl;
+               con::Connection client(proto_id, 512, 5.0, &hand_client);
+
+               assert(hand_server.count == 0);
+               assert(hand_client.count == 0);
+               
+               sleep_ms(50);
+               
+               Address server_address(127,0,0,1, 30001);
+               dstream<<"** running client.Connect()"<<std::endl;
+               client.Connect(server_address);
+
+               sleep_ms(50);
+               
+               // Client should have added server now
+               assert(hand_client.count == 1);
+               assert(hand_client.last_id == 1);
+               // But server should not have added client
+               assert(hand_server.count == 0);
+
+               try
+               {
+                       u16 peer_id;
+                       u8 data[100];
+                       dstream<<"** running server.Receive()"<<std::endl;
+                       u32 size = server.Receive(peer_id, data, 100);
+                       dstream<<"** Server received: peer_id="<<peer_id
+                                       <<", size="<<size
+                                       <<std::endl;
+               }
+               catch(con::NoIncomingDataException &e)
+               {
+                       // No actual data received, but the client has
+                       // probably been connected
+               }
+               
+               // Client should be the same
+               assert(hand_client.count == 1);
+               assert(hand_client.last_id == 1);
+               // Server should have the client
+               assert(hand_server.count == 1);
+               assert(hand_server.last_id == 2);
+               
+               //sleep_ms(50);
+
+               while(client.Connected() == false)
+               {
+                       try
+                       {
+                               u16 peer_id;
+                               u8 data[100];
+                               dstream<<"** running client.Receive()"<<std::endl;
+                               u32 size = client.Receive(peer_id, data, 100);
+                               dstream<<"** Client received: peer_id="<<peer_id
+                                               <<", size="<<size
+                                               <<std::endl;
+                       }
+                       catch(con::NoIncomingDataException &e)
+                       {
+                       }
+                       sleep_ms(50);
+               }
+
+               sleep_ms(50);
+               
+               try
+               {
+                       u16 peer_id;
+                       u8 data[100];
+                       dstream<<"** running server.Receive()"<<std::endl;
+                       u32 size = server.Receive(peer_id, data, 100);
+                       dstream<<"** Server received: peer_id="<<peer_id
+                                       <<", size="<<size
+                                       <<std::endl;
+               }
+               catch(con::NoIncomingDataException &e)
+               {
+               }
+
+               {
+                       /*u8 data[] = "Hello World!";
+                       u32 datasize = sizeof(data);*/
+                       SharedBuffer<u8> data = SharedBufferFromString("Hello World!");
+
+                       dstream<<"** running client.Send()"<<std::endl;
+                       client.Send(PEER_ID_SERVER, 0, data, true);
+
+                       sleep_ms(50);
+
+                       u16 peer_id;
+                       u8 recvdata[100];
+                       dstream<<"** running server.Receive()"<<std::endl;
+                       u32 size = server.Receive(peer_id, recvdata, 100);
+                       dstream<<"** Server received: peer_id="<<peer_id
+                                       <<", size="<<size
+                                       <<", data="<<*data
+                                       <<std::endl;
+                       assert(memcmp(*data, recvdata, data.getSize()) == 0);
+               }
+               
+               u16 peer_id_client = 2;
+
+               {
+                       /*
+                               Send consequent packets in different order
+                       */
+                       //u8 data1[] = "hello1";
+                       //u8 data2[] = "hello2";
+                       SharedBuffer<u8> data1 = SharedBufferFromString("hello1");
+                       SharedBuffer<u8> data2 = SharedBufferFromString("Hello2");
+
+                       Address client_address =
+                                       server.GetPeer(peer_id_client)->address;
+                       
+                       dstream<<"*** Sending packets in wrong order (2,1,2)"
+                                       <<std::endl;
+                       
+                       u8 chn = 0;
+                       con::Channel *ch = &server.GetPeer(peer_id_client)->channels[chn];
+                       u16 sn = ch->next_outgoing_seqnum;
+                       ch->next_outgoing_seqnum = sn+1;
+                       server.Send(peer_id_client, chn, data2, true);
+                       ch->next_outgoing_seqnum = sn;
+                       server.Send(peer_id_client, chn, data1, true);
+                       ch->next_outgoing_seqnum = sn+1;
+                       server.Send(peer_id_client, chn, data2, true);
+
+                       sleep_ms(50);
+
+                       dstream<<"*** Receiving the packets"<<std::endl;
+
+                       u16 peer_id;
+                       u8 recvdata[20];
+                       u32 size;
+
+                       dstream<<"** running client.Receive()"<<std::endl;
+                       peer_id = 132;
+                       size = client.Receive(peer_id, recvdata, 20);
+                       dstream<<"** Client received: peer_id="<<peer_id
+                                       <<", size="<<size
+                                       <<", data="<<recvdata
+                                       <<std::endl;
+                       assert(size == data1.getSize());
+                       assert(memcmp(*data1, recvdata, data1.getSize()) == 0);
+                       assert(peer_id == PEER_ID_SERVER);
+                       
+                       dstream<<"** running client.Receive()"<<std::endl;
+                       peer_id = 132;
+                       size = client.Receive(peer_id, recvdata, 20);
+                       dstream<<"** Client received: peer_id="<<peer_id
+                                       <<", size="<<size
+                                       <<", data="<<recvdata
+                                       <<std::endl;
+                       assert(size == data2.getSize());
+                       assert(memcmp(*data2, recvdata, data2.getSize()) == 0);
+                       assert(peer_id == PEER_ID_SERVER);
+                       
+                       bool got_exception = false;
+                       try
+                       {
+                               dstream<<"** running client.Receive()"<<std::endl;
+                               peer_id = 132;
+                               size = client.Receive(peer_id, recvdata, 20);
+                               dstream<<"** Client received: peer_id="<<peer_id
+                                               <<", size="<<size
+                                               <<", data="<<recvdata
+                                               <<std::endl;
+                       }
+                       catch(con::NoIncomingDataException &e)
+                       {
+                               dstream<<"** No incoming data for client"<<std::endl;
+                               got_exception = true;
+                       }
+                       assert(got_exception);
+               }
+               {
+                       //u8 data1[1100];
+                       SharedBuffer<u8> data1(1100);
+                       for(u16 i=0; i<1100; i++){
+                               data1[i] = i/4;
+                       }
+
+                       dstream<<"Sending data (size="<<1100<<"):";
+                       for(int i=0; i<1100 && i<20; i++){
+                               if(i%2==0) printf(" ");
+                               printf("%.2X", ((int)((const char*)*data1)[i])&0xff);
+                       }
+                       if(1100>20)
+                               dstream<<"...";
+                       dstream<<std::endl;
+                       
+                       server.Send(peer_id_client, 0, data1, true);
+
+                       sleep_ms(50);
+                       
+                       u8 recvdata[2000];
+                       dstream<<"** running client.Receive()"<<std::endl;
+                       u16 peer_id = 132;
+                       u16 size = client.Receive(peer_id, recvdata, 2000);
+                       dstream<<"** Client received: peer_id="<<peer_id
+                                       <<", size="<<size
+                                       <<std::endl;
+
+                       dstream<<"Received data (size="<<size<<"):";
+                       for(int i=0; i<size && i<20; i++){
+                               if(i%2==0) printf(" ");
+                               printf("%.2X", ((int)((const char*)recvdata)[i])&0xff);
+                       }
+                       if(size>20)
+                               dstream<<"...";
+                       dstream<<std::endl;
+
+                       assert(memcmp(*data1, recvdata, data1.getSize()) == 0);
+                       assert(peer_id == PEER_ID_SERVER);
+               }
+               
+               // Check peer handlers
+               assert(hand_client.count == 1);
+               assert(hand_client.last_id == 1);
+               assert(hand_server.count == 1);
+               assert(hand_server.last_id == 2);
+               
+               //assert(0);
+       }
+};
+
+#define TEST(X)\
+{\
+       X x;\
+       dstream<<"Running " #X <<std::endl;\
+       x.Run();\
+}
+
+void run_tests()
+{
+       DSTACK(__FUNCTION_NAME);
+       dstream<<"run_tests() started"<<std::endl;
+       TEST(TestUtilities);
+       TEST(TestCompress);
+       TEST(TestMapNode);
+       TEST(TestMapBlock);
+       TEST(TestMapSector);
+       TEST(TestHeightmap);
+       if(INTERNET_SIMULATOR == false){
+               TEST(TestSocket);
+               dout_con<<"=== BEGIN RUNNING UNIT TESTS FOR CONNECTION ==="<<std::endl;
+               TEST(TestConnection);
+               dout_con<<"=== END RUNNING UNIT TESTS FOR CONNECTION ==="<<std::endl;
+       }
+       dstream<<"run_tests() passed"<<std::endl;
+}
+
diff --git a/src/test.h b/src/test.h
new file mode 100644 (file)
index 0000000..79560ae
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef TEST_HEADER
+#define TEST_HEADER
+
+void run_tests();
+
+#endif
+
diff --git a/src/utility.cpp b/src/utility.cpp
new file mode 100644 (file)
index 0000000..f3b98f0
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "utility.h"
+
+const v3s16 g_26dirs[26] =
+{
+       // +right, +top, +back
+       v3s16( 0, 0, 1), // back
+       v3s16( 0, 1, 0), // top
+       v3s16( 1, 0, 0), // right
+       v3s16( 0, 0,-1), // front
+       v3s16( 0,-1, 0), // bottom
+       v3s16(-1, 0, 0), // left
+       // 6
+       v3s16(-1, 1, 0), // top left
+       v3s16( 1, 1, 0), // top right
+       v3s16( 0, 1, 1), // top back
+       v3s16( 0, 1,-1), // top front
+       v3s16(-1, 0, 1), // back left
+       v3s16( 1, 0, 1), // back right
+       v3s16(-1, 0,-1), // front left
+       v3s16( 1, 0,-1), // front right
+       v3s16(-1,-1, 0), // bottom left
+       v3s16( 1,-1, 0), // bottom right
+       v3s16( 0,-1, 1), // bottom back
+       v3s16( 0,-1,-1), // bottom front
+       // 18
+       v3s16(-1, 1, 1), // top back-left
+       v3s16( 1, 1, 1), // top back-right
+       v3s16(-1, 1,-1), // top front-left
+       v3s16( 1, 1,-1), // top front-right
+       v3s16(-1,-1, 1), // bottom back-left
+       v3s16( 1,-1, 1), // bottom back-right
+       v3s16(-1,-1,-1), // bottom front-left
+       v3s16( 1,-1,-1), // bottom front-right
+       // 26
+};
+
+
diff --git a/src/utility.h b/src/utility.h
new file mode 100644 (file)
index 0000000..4178e9d
--- /dev/null
@@ -0,0 +1,607 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef UTILITY_HEADER
+#define UTILITY_HEADER
+
+#include "common_irrlicht.h"
+#include "debug.h"
+#include "strfnd.h"
+#include <iostream>
+#include <string>
+
+extern const v3s16 g_26dirs[26];
+
+inline void writeU32(u8 *data, u32 i)
+{
+       data[0] = ((i>>24)&0xff);
+       data[1] = ((i>>16)&0xff);
+       data[2] = ((i>> 8)&0xff);
+       data[3] = ((i>> 0)&0xff);
+}
+
+inline void writeU16(u8 *data, u16 i)
+{
+       data[0] = ((i>> 8)&0xff);
+       data[1] = ((i>> 0)&0xff);
+}
+
+inline void writeU8(u8 *data, u8 i)
+{
+       data[0] = ((i>> 0)&0xff);
+}
+
+inline u32 readU32(u8 *data)
+{
+       return (data[0]<<24) | (data[1]<<16) | (data[2]<<8) | (data[3]<<0);
+}
+
+inline u16 readU16(u8 *data)
+{
+       return (data[0]<<8) | (data[1]<<0);
+}
+
+inline u8 readU8(u8 *data)
+{
+       return (data[0]<<0);
+}
+
+// Signed variants of the above
+
+inline void writeS32(u8 *data, s32 i){
+       writeU32(data, (u32)i);
+}
+inline s32 readS32(u8 *data){
+       return (s32)readU32(data);
+}
+
+inline void writeS16(u8 *data, s16 i){
+       writeU16(data, (u16)i);
+}
+inline s16 readS16(u8 *data){
+       return (s16)readU16(data);
+}
+
+inline void writeV3S32(u8 *data, v3s32 p)
+{
+       writeS32(&data[0], p.X);
+       writeS32(&data[4], p.Y);
+       writeS32(&data[8], p.Z);
+}
+
+inline v3s32 readV3S32(u8 *data)
+{
+       v3s32 p;
+       p.X = readS32(&data[0]);
+       p.Y = readS32(&data[4]);
+       p.Z = readS32(&data[8]);
+       return p;
+}
+
+inline void writeV2S16(u8 *data, v2s16 p)
+{
+       writeS16(&data[0], p.X);
+       writeS16(&data[2], p.Y);
+}
+
+inline v2s16 readV2S16(u8 *data)
+{
+       v2s16 p;
+       p.X = readS16(&data[0]);
+       p.Y = readS16(&data[2]);
+       return p;
+}
+
+inline void writeV2S32(u8 *data, v2s32 p)
+{
+       writeS32(&data[0], p.X);
+       writeS32(&data[2], p.Y);
+}
+
+inline v2s32 readV2S32(u8 *data)
+{
+       v2s32 p;
+       p.X = readS32(&data[0]);
+       p.Y = readS32(&data[2]);
+       return p;
+}
+
+inline void writeV3S16(u8 *data, v3s16 p)
+{
+       writeS16(&data[0], p.X);
+       writeS16(&data[2], p.Y);
+       writeS16(&data[4], p.Z);
+}
+
+inline v3s16 readV3S16(u8 *data)
+{
+       v3s16 p;
+       p.X = readS16(&data[0]);
+       p.Y = readS16(&data[2]);
+       p.Z = readS16(&data[4]);
+       return p;
+}
+
+/*
+       None of these are used at the moment
+*/
+
+template <typename T>
+class SharedPtr
+{
+public:
+       SharedPtr(T *t=NULL)
+       {
+               refcount = new int;
+               *refcount = 1;
+               ptr = t;
+       }
+       SharedPtr(SharedPtr<T> &t)
+       {
+               //*this = t;
+               drop();
+               refcount = t.refcount;
+               (*refcount)++;
+               ptr = t.ptr;
+       }
+       ~SharedPtr()
+       {
+               drop();
+       }
+       SharedPtr<T> & operator=(T *t)
+       {
+               drop();
+               refcount = new int;
+               *refcount = 1;
+               ptr = t;
+               return *this;
+       }
+       SharedPtr<T> & operator=(SharedPtr<T> &t)
+       {
+               drop();
+               refcount = t.refcount;
+               (*refcount)++;
+               ptr = t.ptr;
+               return *this;
+       }
+       T* operator->()
+       {
+               return ptr;
+       }
+       T & operator*()
+       {
+               return *ptr;
+       }
+       bool operator!=(T *t)
+       {
+               return ptr != t;
+       }
+       bool operator==(T *t)
+       {
+               return ptr == t;
+       }
+private:
+       void drop()
+       {
+               assert((*refcount) > 0);
+               (*refcount)--;
+               if(*refcount == 0)
+               {
+                       delete refcount;
+                       if(ptr != NULL)
+                               delete ptr;
+               }
+       }
+       T *ptr;
+       int *refcount;
+};
+
+template <typename T>
+class Buffer
+{
+public:
+       Buffer(unsigned int size)
+       {
+               m_size = size;
+               data = new T[size];
+       }
+       Buffer(const Buffer &buffer)
+       {
+               m_size = buffer.m_size;
+               data = new T[buffer.m_size];
+               memcpy(data, buffer.data, buffer.m_size);
+       }
+       Buffer(T *t, unsigned int size)
+       {
+               m_size = size;
+               data = new T[size];
+               memcpy(data, t, size);
+       }
+       ~Buffer()
+       {
+               delete[] data;
+       }
+       T & operator[](unsigned int i) const
+       {
+               return data[i];
+       }
+       T * operator*() const
+       {
+               return data;
+       }
+       unsigned int getSize() const
+       {
+               return m_size;
+       }
+private:
+       T *data;
+       unsigned int m_size;
+};
+
+template <typename T>
+class SharedBuffer
+{
+public:
+       SharedBuffer(unsigned int size)
+       {
+               m_size = size;
+               data = new T[size];
+               refcount = new unsigned int;
+               (*refcount) = 1;
+       }
+       SharedBuffer(const SharedBuffer &buffer)
+       {
+               //std::cout<<"SharedBuffer(const SharedBuffer &buffer)"<<std::endl;
+               m_size = buffer.m_size;
+               data = buffer.data;
+               refcount = buffer.refcount;
+               (*refcount)++;
+       }
+       SharedBuffer & operator=(const SharedBuffer & buffer)
+       {
+               //std::cout<<"SharedBuffer & operator=(const SharedBuffer & buffer)"<<std::endl;
+               if(this == &buffer)
+                       return *this;
+               drop();
+               m_size = buffer.m_size;
+               data = buffer.data;
+               refcount = buffer.refcount;
+               (*refcount)++;
+               return *this;
+       }
+       /*
+               Copies whole buffer
+       */
+       SharedBuffer(T *t, unsigned int size)
+       {
+               m_size = size;
+               data = new T[size];
+               memcpy(data, t, size);
+               refcount = new unsigned int;
+               (*refcount) = 1;
+       }
+       /*
+               Copies whole buffer
+       */
+       SharedBuffer(const Buffer<T> &buffer)
+       {
+               m_size = buffer.m_size;
+               data = new T[buffer.getSize()];
+               memcpy(data, *buffer, buffer.getSize());
+               refcount = new unsigned int;
+               (*refcount) = 1;
+       }
+       ~SharedBuffer()
+       {
+               drop();
+       }
+       T & operator[](unsigned int i) const
+       {
+               return data[i];
+       }
+       T * operator*() const
+       {
+               return data;
+       }
+       unsigned int getSize() const
+       {
+               return m_size;
+       }
+private:
+       void drop()
+       {
+               assert((*refcount) > 0);
+               (*refcount)--;
+               if(*refcount == 0)
+               {
+                       delete[] data;
+                       delete refcount;
+               }
+       }
+       T *data;
+       unsigned int m_size;
+       unsigned int *refcount;
+};
+
+inline SharedBuffer<u8> SharedBufferFromString(const char *string)
+{
+       SharedBuffer<u8> b((u8*)string, strlen(string)+1);
+       return b;
+}
+
+template<typename T>
+class MutexedVariable
+{
+public:
+       MutexedVariable(T value):
+               m_value(value)
+       {
+               m_mutex.Init();
+       }
+
+       T get()
+       {
+               JMutexAutoLock lock(m_mutex);
+               return m_value;
+       }
+
+       void set(T value)
+       {
+               JMutexAutoLock lock(m_mutex);
+               m_value = value;
+       }
+       
+       // You'll want to grab this in a SharedPtr
+       JMutexAutoLock * getLock()
+       {
+               return new JMutexAutoLock(m_mutex);
+       }
+       
+       // You pretty surely want to grab the lock when accessing this
+       T m_value;
+
+private:
+       JMutex m_mutex;
+};
+
+/*
+       TimeTaker
+*/
+
+class TimeTaker
+{
+public:
+       TimeTaker(const char *name, IrrlichtDevice *dev)
+       {
+               m_name = name;
+               m_dev = dev;
+               m_time1 = m_dev->getTimer()->getRealTime();
+               m_running = true;
+       }
+       ~TimeTaker()
+       {
+               stop();
+       }
+       u32 stop(bool quiet=false)
+       {
+               if(m_running)
+               {
+                       u32 time2 = m_dev->getTimer()->getRealTime();
+                       u32 dtime = time2 - m_time1;
+                       if(quiet == false)
+                               std::cout<<m_name<<" took "<<dtime<<"ms"<<std::endl;
+                       m_running = false;
+                       return dtime;
+               }
+               return 0;
+       }
+private:
+       const char *m_name;
+       IrrlichtDevice *m_dev;
+       u32 m_time1;
+       bool m_running;
+};
+
+// Calculates the borders of a "d-radius" cube
+inline void getFacePositions(core::list<v3s16> &list, u16 d)
+{
+       if(d == 0)
+       {
+               list.push_back(v3s16(0,0,0));
+               return;
+       }
+       if(d == 1)
+       {
+               /*
+                       This is an optimized sequence of coordinates.
+               */
+               list.push_back(v3s16( 0, 0, 1)); // back
+               list.push_back(v3s16(-1, 0, 0)); // left
+               list.push_back(v3s16( 1, 0, 0)); // right
+               list.push_back(v3s16( 0, 0,-1)); // front
+               list.push_back(v3s16( 0,-1, 0)); // bottom
+               list.push_back(v3s16( 0, 1, 0)); // top
+               // 6
+               list.push_back(v3s16(-1, 0, 1)); // back left
+               list.push_back(v3s16( 1, 0, 1)); // back right
+               list.push_back(v3s16(-1, 0,-1)); // front left
+               list.push_back(v3s16( 1, 0,-1)); // front right
+               list.push_back(v3s16(-1,-1, 0)); // bottom left
+               list.push_back(v3s16( 1,-1, 0)); // bottom right
+               list.push_back(v3s16( 0,-1, 1)); // bottom back
+               list.push_back(v3s16( 0,-1,-1)); // bottom front
+               list.push_back(v3s16(-1, 1, 0)); // top left
+               list.push_back(v3s16( 1, 1, 0)); // top right
+               list.push_back(v3s16( 0, 1, 1)); // top back
+               list.push_back(v3s16( 0, 1,-1)); // top front
+               // 18
+               list.push_back(v3s16(-1, 1, 1)); // top back-left
+               list.push_back(v3s16( 1, 1, 1)); // top back-right
+               list.push_back(v3s16(-1, 1,-1)); // top front-left
+               list.push_back(v3s16( 1, 1,-1)); // top front-right
+               list.push_back(v3s16(-1,-1, 1)); // bottom back-left
+               list.push_back(v3s16( 1,-1, 1)); // bottom back-right
+               list.push_back(v3s16(-1,-1,-1)); // bottom front-left
+               list.push_back(v3s16( 1,-1,-1)); // bottom front-right
+               // 26
+               return;
+       }
+
+       // Take blocks in all sides, starting from y=0 and going +-y
+       for(s16 y=0; y<=d-1; y++)
+       {
+               // Left and right side, including borders
+               for(s16 z=-d; z<=d; z++)
+               {
+                       list.push_back(v3s16(d,y,z));
+                       list.push_back(v3s16(-d,y,z));
+                       if(y != 0)
+                       {
+                               list.push_back(v3s16(d,-y,z));
+                               list.push_back(v3s16(-d,-y,z));
+                       }
+               }
+               // Back and front side, excluding borders
+               for(s16 x=-d+1; x<=d-1; x++)
+               {
+                       list.push_back(v3s16(x,y,d));
+                       list.push_back(v3s16(x,y,-d));
+                       if(y != 0)
+                       {
+                               list.push_back(v3s16(x,-y,d));
+                               list.push_back(v3s16(x,-y,-d));
+                       }
+               }
+       }
+
+       // Take the bottom and top face with borders
+       // -d<x<d, y=+-d, -d<z<d
+       for(s16 x=-d; x<=d; x++)
+       for(s16 z=-d; z<=d; z++)
+       {
+               list.push_back(v3s16(x,-d,z));
+               list.push_back(v3s16(x,d,z));
+       }
+}
+
+class IndentationRaiser
+{
+public:
+       IndentationRaiser(u16 *indentation)
+       {
+               m_indentation = indentation;
+               (*m_indentation)++;
+       }
+       ~IndentationRaiser()
+       {
+               (*m_indentation)--;
+       }
+private:
+       u16 *m_indentation;
+};
+
+inline s16 getContainerPos(s16 p, s16 d)
+{
+       return (p>=0 ? p : p-d+1) / d;
+}
+
+inline v2s16 getContainerPos(v2s16 p, s16 d)
+{
+       return v2s16(
+               getContainerPos(p.X, d),
+               getContainerPos(p.Y, d)
+       );
+}
+
+inline v3s16 getContainerPos(v3s16 p, s16 d)
+{
+       return v3s16(
+               getContainerPos(p.X, d),
+               getContainerPos(p.Y, d),
+               getContainerPos(p.Z, d)
+       );
+}
+
+inline bool isInArea(v3s16 p, s16 d)
+{
+       return (
+               p.X >= 0 && p.X < d &&
+               p.Y >= 0 && p.Y < d &&
+               p.Z >= 0 && p.Z < d
+       );
+}
+
+inline bool isInArea(v2s16 p, s16 d)
+{
+       return (
+               p.X >= 0 && p.X < d &&
+               p.Y >= 0 && p.Y < d
+       );
+}
+
+inline std::wstring narrow_to_wide(const std::string& mbs)
+{
+       size_t wcl = mbs.size();
+       SharedBuffer<wchar_t> wcs(wcl+1);
+       size_t l = mbstowcs(*wcs, mbs.c_str(), wcl);
+       wcs[l] = 0;
+       return *wcs;
+}
+
+inline std::string wide_to_narrow(const std::wstring& wcs)
+{
+       size_t mbl = wcs.size()*4;
+       SharedBuffer<char> mbs(mbl+1);
+       size_t l = wcstombs(*mbs, wcs.c_str(), mbl);
+       if((int)l == -1)
+               mbs[0] = 0;
+       else
+               mbs[l] = 0;
+       return *mbs;
+}
+
+/*
+       See test.cpp for example cases.
+       wraps degrees to the range of -360...360
+       NOTE: Wrapping to 0...360 is not used because pitch needs negative values.
+*/
+inline float wrapDegrees(float f)
+{
+       // Take examples of f=10, f=720.5, f=-0.5, f=-360.5
+       // This results in
+       // 10, 720, -1, -361
+       int i = floor(f);
+       // 0, 2, 0, -1
+       int l = i / 360;
+       // NOTE: This would be used for wrapping to 0...360
+       // 0, 2, -1, -2
+       /*if(i < 0)
+               l -= 1;*/
+       // 0, 720, 0, -360
+       int k = l * 360;
+       // 10, 0.5, -0.5, -0.5
+       f -= float(k);
+       return f;
+}
+
+inline std::string lowercase(std::string s)
+{
+       for(size_t i=0; i<s.size(); i++)
+       {
+               if(s[i] >= 'A' && s[i] <= 'Z')
+                       s[i] -= 'A' - 'a';
+       }
+       return s;
+}
+
+inline bool is_yes(std::string s)
+{
+       s = lowercase(trim(s));
+       if(s == "y" || s == "yes" || s == "true")
+               return true;
+       return false;
+}
+
+#endif
+