Initial files
|
@ -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
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 910 B |
After Width: | Height: | Size: 493 B |
After Width: | Height: | Size: 835 B |
After Width: | Height: | Size: 913 B |
After Width: | Height: | Size: 118 B |
After Width: | Height: | Size: 203 B |
After Width: | Height: | Size: 212 B |
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 920 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 847 B |
After Width: | Height: | Size: 856 B |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 873 B |
After Width: | Height: | Size: 103 B |
After Width: | Height: | Size: 103 B |
|
@ -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.
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
|
@ -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=""C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include";"..\jthread\jthread-1.2.1\src";"..\irrlicht\irrlicht-1.7.1\include""
|
||||
PreprocessorDefinitions="WIN32"
|
||||
EnableEnhancedInstructionSet="1"
|
||||
FloatingPointModel="2"
|
||||
DebugInformationFormat="1"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalLibraryDirectories=""C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib";"..\jthread\jthread-1.2.1\Release";"..\irrlicht\irrlicht-1.7.1\lib\Win32-visualstudio""
|
||||
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=""C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include";"..\jthread\jthread-1.2.1\src";"..\irrlicht\irrlicht-1.7.1\include""
|
||||
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=""C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib";"..\jthread\jthread-1.2.1\Release";"..\irrlicht\irrlicht-1.7.1\lib\Win32-visualstudio""
|
||||
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>
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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*)¤t_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*)¤t_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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef TEST_HEADER
|
||||
#define TEST_HEADER
|
||||
|
||||
void run_tests();
|
||||
|
||||
#endif
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|