Initial files

This commit is contained in:
Perttu Ahola 2010-11-27 01:02:21 +02:00
commit 4e249fb3fb
71 changed files with 22664 additions and 0 deletions

72
Makefile Normal file

@ -0,0 +1,72 @@
# Makefile for Irrlicht Examples
# It's usually sufficient to change just the target name and source file list
# and be sure that CXX is set to a valid compiler
TARGET = test
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
SOURCES = $(addprefix src/, $(SOURCE_FILES))
OBJECTS = $(SOURCES:.cpp=.o)
FASTTARGET = fasttest
IRRLICHTPATH = ../irrlicht/irrlicht-1.7.1
JTHREADPATH = ../jthread/jthread-1.2.1
CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src
#CXXFLAGS = -O2 -ffast-math -Wall -fomit-frame-pointer -pipe
CXXFLAGS = -O2 -ffast-math -Wall -g
#CXXFLAGS = -O1 -ffast-math -Wall -g
#CXXFLAGS = -Wall -g -O0
#CXXFLAGS = -O3 -ffast-math -Wall
#CXXFLAGS = -O3 -ffast-math -Wall -g
#CXXFLAGS = -O2 -ffast-math -Wall -g
#FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=pentium3
FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=i686
#Default target
all: all_linux
ifeq ($(HOSTTYPE), x86_64)
LIBSELECT=64
endif
# Target specific settings
all_linux fast_linux: LDFLAGS = -L/usr/X11R6/lib$(LIBSELECT) -L$(IRRLICHTPATH)/lib/Linux -L$(JTHREADPATH)/src/.libs -lIrrlicht -lGL -lXxf86vm -lXext -lX11 -ljthread
all_linux fast_linux clean_linux: SYSTEM=Linux
all_win32: LDFLAGS = -L$(IRRLICHTPATH)/lib/Win32-gcc -L$(JTHREADPATH)/Debug -lIrrlicht -lopengl32 -lm -ljthread
all_win32 clean_win32: SYSTEM=Win32-gcc
all_win32 clean_win32: SUF=.exe
# Name of the binary - only valid for targets which set SYSTEM
DESTPATH = bin/$(TARGET)$(SUF)
FASTDESTPATH = bin/$(FASTTARGET)$(SUF)
# Build commands
all_linux all_win32: $(DESTPATH)
fast_linux: $(FASTDESTPATH)
$(FASTDESTPATH): $(SOURCES)
$(CXX) -o $(FASTDESTPATH) $(SOURCES) $(CPPFLAGS) $(FASTCXXFLAGS) $(LDFLAGS) -DUNITTEST_DISABLE
$(DESTPATH): $(OBJECTS)
$(CXX) -o $@ $(OBJECTS) $(LDFLAGS)
.cpp.o:
$(CXX) -c -o $@ $< $(CPPFLAGS) $(CXXFLAGS)
clean: clean_linux clean_win32 clean_fast_linux
clean_linux clean_win32:
@$(RM) $(OBJECTS) $(DESTPATH)
clean_fast_linux:
@$(RM) $(FASTDESTPATH)
.PHONY: all all_win32 clean clean_linux clean_win32

BIN
bin/Irrlicht.dll Normal file

Binary file not shown.

BIN
data/fontlucida.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
data/grass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

BIN
data/grass2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

BIN
data/grass_footsteps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

BIN
data/leaves.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

BIN
data/light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

BIN
data/mese.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

BIN
data/player.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

BIN
data/player_back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

BIN
data/rat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

BIN
data/sign.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
data/sign_back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
data/stone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

BIN
data/stone_with_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

BIN
data/tf.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
data/tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

BIN
data/water.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

BIN
data/water_old.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

115
doc/README.txt Normal file

@ -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.

41
makepackage_binary.sh Executable file

@ -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

44
minetest.conf.example Normal file

@ -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

20
minetest.sln Normal file

@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 9.00
# Visual C++ Express 2005
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minetest", "minetest.vcproj", "{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Release|Win32 = Release|Win32
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Debug|Win32.ActiveCfg = Debug|Win32
{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Debug|Win32.Build.0 = Debug|Win32
{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Release|Win32.ActiveCfg = Release|Win32
{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

358
minetest.vcproj Normal file

@ -0,0 +1,358 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="8,00"
Name="minetest"
ProjectGUID="{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}"
RootNamespace="minetest"
>
<Platforms>
<Platform
Name="Win32"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="$(SolutionDir)\bin"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
UseOfATL="1"
ATLMinimizesCRunTimeLibraryUsage="true"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
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;"
PreprocessorDefinitions="WIN32"
EnableEnhancedInstructionSet="1"
FloatingPointModel="2"
DebugInformationFormat="1"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
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;"
IgnoreAllDefaultLibraries="false"
GenerateDebugInformation="true"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCWebDeploymentTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="$(SolutionDir)\bin"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
UseOfMFC="0"
WholeProgramOptimization="3"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
InlineFunctionExpansion="2"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
WholeProgramOptimization="true"
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;"
PreprocessorDefinitions="WIN32;_HAS_ITERATOR_DEBUGGING=0,UNITTEST_DISABLE"
BufferSecurityCheck="false"
EnableEnhancedInstructionSet="1"
FloatingPointModel="2"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
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;"
IgnoreDefaultLibraryNames="libcmtd.lib"
LinkTimeCodeGeneration="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCWebDeploymentTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\src\client.cpp"
>
</File>
<File
RelativePath=".\src\connection.cpp"
>
</File>
<File
RelativePath=".\src\debug.cpp"
>
</File>
<File
RelativePath=".\src\environment.cpp"
>
</File>
<File
RelativePath=".\src\filesys.cpp"
>
</File>
<File
RelativePath=".\src\heightmap.cpp"
>
</File>
<File
RelativePath=".\src\inventory.cpp"
>
</File>
<File
RelativePath=".\src\light.cpp"
>
</File>
<File
RelativePath=".\src\main.cpp"
>
</File>
<File
RelativePath=".\src\map.cpp"
>
</File>
<File
RelativePath=".\src\mapblock.cpp"
>
</File>
<File
RelativePath=".\src\mapblockobject.cpp"
>
</File>
<File
RelativePath=".\src\mapsector.cpp"
>
</File>
<File
RelativePath=".\src\player.cpp"
>
</File>
<File
RelativePath=".\src\serialization.cpp"
>
</File>
<File
RelativePath=".\src\server.cpp"
>
</File>
<File
RelativePath=".\src\socket.cpp"
>
</File>
<File
RelativePath=".\src\test.cpp"
>
</File>
<File
RelativePath=".\src\utility.cpp"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
<File
RelativePath=".\src\client.h"
>
</File>
<File
RelativePath=".\src\clientserver.h"
>
</File>
<File
RelativePath=".\src\common_irrlicht.h"
>
</File>
<File
RelativePath=".\src\connection.h"
>
</File>
<File
RelativePath=".\src\constants.h"
>
</File>
<File
RelativePath=".\src\debug.h"
>
</File>
<File
RelativePath=".\src\environment.h"
>
</File>
<File
RelativePath=".\src\exceptions.h"
>
</File>
<File
RelativePath=".\src\heightmap.h"
>
</File>
<File
RelativePath=".\src\inventory.h"
>
</File>
<File
RelativePath=".\src\light.h"
>
</File>
<File
RelativePath=".\src\loadstatus.h"
>
</File>
<File
RelativePath=".\src\main.h"
>
</File>
<File
RelativePath=".\src\map.h"
>
</File>
<File
RelativePath=".\src\mapblock.h"
>
</File>
<File
RelativePath=".\src\mapnode.h"
>
</File>
<File
RelativePath=".\src\mapsector.h"
>
</File>
<File
RelativePath=".\src\player.h"
>
</File>
<File
RelativePath=".\src\serialization.h"
>
</File>
<File
RelativePath=".\src\server.h"
>
</File>
<File
RelativePath=".\src\socket.h"
>
</File>
<File
RelativePath=".\src\test.h"
>
</File>
<File
RelativePath=".\src\utility.h"
>
</File>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

1639
src/client.cpp Normal file

File diff suppressed because it is too large Load Diff

272
src/client.h Normal file

@ -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

174
src/clientserver.h Normal file

@ -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

17
src/common_irrlicht.h Normal file

@ -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

1321
src/connection.cpp Normal file

File diff suppressed because it is too large Load Diff

474
src/connection.h Normal file

@ -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

72
src/constants.h Normal file

@ -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

189
src/debug.cpp Normal file

@ -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);
}
}

176
src/debug.h Normal file

@ -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

206
src/environment.cpp Normal file

@ -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;
}
}

50
src/environment.h Normal file

@ -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

116
src/exceptions.h Normal file

@ -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

205
src/filesys.cpp Normal file

@ -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
}

27
src/filesys.h Normal file

@ -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

872
src/heightmap.cpp Normal file

@ -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;
}
}

556
src/heightmap.h Normal file

@ -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

320
src/inventory.cpp Normal file

@ -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

195
src/inventory.h Normal file

@ -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

85
src/light.cpp Normal file

@ -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,
};

54
src/light.h Normal file

@ -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

144
src/loadstatus.h Normal file

@ -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

2339
src/main.cpp Normal file

File diff suppressed because it is too large Load Diff

50
src/main.h Normal file

@ -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

2854
src/map.cpp Normal file

File diff suppressed because it is too large Load Diff

430
src/map.h Normal file

@ -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

698
src/mapblock.cpp Normal file

@ -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

404
src/mapblock.h Normal file

@ -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

641
src/mapblockobject.cpp Normal file

@ -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

892
src/mapblockobject.h Normal file

@ -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

280
src/mapnode.h Normal file

@ -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

652
src/mapsector.cpp Normal file

@ -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

318
src/mapsector.h Normal file

@ -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

358
src/player.cpp Normal file

@ -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);
}

210
src/player.h Normal file

@ -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

15
src/porting.h Normal file

@ -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

77
src/serialization.cpp Normal file

@ -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;
}
}

49
src/serialization.h Normal file

@ -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

2115
src/server.cpp Normal file

File diff suppressed because it is too large Load Diff

388
src/server.h Normal file

@ -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

311
src/socket.cpp Normal file

@ -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;
}

98
src/socket.h Normal file

@ -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

96
src/strfnd.h Normal file

@ -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

920
src/test.cpp Normal file

@ -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;
}

7
src/test.h Normal file

@ -0,0 +1,7 @@
#ifndef TEST_HEADER
#define TEST_HEADER
void run_tests();
#endif

41
src/utility.cpp Normal file

@ -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
};

607
src/utility.h Normal file

@ -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