Initial commit directly taken from the backup file minetest_10-10-24_16-33-41_wonderful.tar.gz
This commit is contained in:
commit
696c623e9e
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
## Generic ignorable patterns and files
|
||||
.*
|
||||
!.gitignore
|
||||
*~
|
||||
*bak*
|
||||
tags
|
||||
*.vim
|
||||
*.orig
|
||||
*.rej
|
||||
|
||||
## Files related to minetest development cycle
|
||||
/*.patch
|
||||
|
||||
## Non-static Minetest directories
|
||||
/bin/
|
||||
|
||||
## Configuration/log files
|
||||
debug.txt
|
||||
|
||||
## Build files
|
||||
CMakeFiles
|
||||
Makefile
|
||||
!/Makefile
|
||||
cmake_install.cmake
|
||||
CMakeCache.txt
|
||||
CPackConfig.cmake
|
||||
CPackSourceConfig.cmake
|
||||
src/android_version.h
|
||||
src/android_version_githash.h
|
||||
src/cmake_config.h
|
||||
src/cmake_config_githash.h
|
||||
src/lua/build/
|
||||
locale/
|
||||
.directory
|
||||
.kdev4/
|
||||
*.cbp
|
||||
*.kdev4
|
||||
*.layout
|
||||
*.o
|
||||
*.a
|
||||
|
55
Makefile
Normal file
55
Makefile
Normal file
@ -0,0 +1,55 @@
|
||||
# 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 = 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)
|
||||
|
||||
IRRLICHTPATH = ../irrlicht/irrlicht-1.7.1
|
||||
JTHREADPATH = ../jthread/jthread-1.2.1
|
||||
|
||||
CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src
|
||||
|
||||
#CXXFLAGS = -O3 -ffast-math -Wall
|
||||
#CXXFLAGS = -O3 --fast-math -Wall -g
|
||||
#CXXFLAGS = -Wall -g -O0
|
||||
CXXFLAGS = -O2 --fast-math -Wall -g
|
||||
|
||||
#Default target
|
||||
|
||||
all: all_linux
|
||||
|
||||
ifeq ($(HOSTTYPE), x86_64)
|
||||
LIBSELECT=64
|
||||
endif
|
||||
|
||||
# Target specific settings
|
||||
|
||||
all_linux: LDFLAGS = -L/usr/X11R6/lib$(LIBSELECT) -L$(IRRLICHTPATH)/lib/Linux -L$(JTHREADPATH)/src/.libs -lIrrlicht -lGL -lXxf86vm -lXext -lX11 -ljthread
|
||||
all_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)
|
||||
|
||||
# Build commands
|
||||
|
||||
all_linux all_win32: $(DESTPATH)
|
||||
|
||||
$(DESTPATH): $(OBJECTS)
|
||||
$(CXX) -o $@ $(OBJECTS) $(LDFLAGS)
|
||||
|
||||
.cpp.o:
|
||||
$(CXX) -c -o $@ $< $(CPPFLAGS) $(CXXFLAGS)
|
||||
|
||||
clean: clean_linux clean_win32
|
||||
|
||||
clean_linux clean_win32:
|
||||
@$(RM) $(OBJECTS) $(DESTPATH)
|
||||
|
||||
.PHONY: all all_win32 clean clean_linux clean_win32
|
BIN
data/fontlucida.png
Normal file
BIN
data/fontlucida.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
data/grass.png
Normal file
BIN
data/grass.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 910 B |
BIN
data/grass2.png
Normal file
BIN
data/grass2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 493 B |
BIN
data/stone.png
Normal file
BIN
data/stone.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 856 B |
BIN
data/tf.jpg
Normal file
BIN
data/tf.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
BIN
data/water.png
Normal file
BIN
data/water.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 B |
26
makepackage_windows.sh
Executable file
26
makepackage_windows.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
|
||||
PACKAGEDIR=packages
|
||||
PACKAGENAME=minetest-c55-win32-`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 bin/minetest.exe $PACKAGEPATH/bin/
|
||||
cp bin/Irrlicht.dll $PACKAGEPATH/bin/
|
||||
|
||||
cp -r data/stone.png $PACKAGEPATH/data/
|
||||
cp -r data/grass.png $PACKAGEPATH/data/
|
||||
cp -r data/fontlucida.png $PACKAGEPATH/data/
|
||||
cp -r data/water.png $PACKAGEPATH/data/
|
||||
cp -r data/tf.jpg $PACKAGEPATH/data/
|
||||
|
||||
cp -r doc/README.txt $PACKAGEPATH/doc/README.txt
|
||||
|
||||
cd $PACKAGEDIR
|
||||
rm $PACKAGENAME.zip
|
||||
zip -r $PACKAGENAME.zip $PACKAGENAME
|
||||
|
20
minetest.sln
Normal file
20
minetest.sln
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 9.00
|
||||
# Visual C++ Express 2005
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minetest", "minetest.vcproj", "{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Release|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
318
minetest.vcproj
Normal file
318
minetest.vcproj
Normal file
@ -0,0 +1,318 @@
|
||||
<?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"
|
||||
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\environment.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\heightmap.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\main.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\map.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\mapblock.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\mapsector.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\player.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\environment.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\exceptions.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\heightmap.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\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>
|
513
src/client.cpp
Normal file
513
src/client.cpp
Normal file
@ -0,0 +1,513 @@
|
||||
#include "client.h"
|
||||
#include "utility.h"
|
||||
#include <iostream>
|
||||
#include "clientserver.h"
|
||||
#include "jmutexautolock.h"
|
||||
#include "main.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define sleep_ms(x) Sleep(x)
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#define sleep_ms(x) usleep(x*1000)
|
||||
#endif
|
||||
|
||||
/*
|
||||
FIXME: This thread can access the environment at any time
|
||||
*/
|
||||
|
||||
void * ClientUpdateThread::Thread()
|
||||
{
|
||||
ThreadStarted();
|
||||
|
||||
while(getRun())
|
||||
{
|
||||
m_client->AsyncProcessData();
|
||||
sleep_ms(200);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Client::Client(scene::ISceneManager* smgr, video::SMaterial *materials):
|
||||
m_thread(this),
|
||||
m_env(new ClientMap
|
||||
(this, materials, smgr->getRootSceneNode(), smgr, 666),
|
||||
dout_client),
|
||||
m_con(PROTOCOL_ID, 512),
|
||||
m_smgr(smgr)
|
||||
{
|
||||
m_fetchblock_mutex.Init();
|
||||
m_incoming_queue_mutex.Init();
|
||||
m_env_mutex.Init();
|
||||
m_con_mutex.Init();
|
||||
|
||||
m_thread.Start();
|
||||
|
||||
{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
m_env.getMap().StartUpdater();
|
||||
|
||||
Player *player = new Player(true, smgr->getRootSceneNode(), smgr, 0);
|
||||
//f32 y = BS*2 + m_env.getMap().getGroundHeight(v2s16(0,0));
|
||||
f32 y = BS*2 + BS*20;
|
||||
player->setPosition(v3f(0, y, 0));
|
||||
m_env.addPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
Client::~Client()
|
||||
{
|
||||
m_thread.setRun(false);
|
||||
while(m_thread.IsRunning())
|
||||
sleep_ms(100);
|
||||
}
|
||||
|
||||
void Client::connect(Address address)
|
||||
{
|
||||
{
|
||||
JMutexAutoLock lock(m_con_mutex);
|
||||
m_con.setTimeoutMs(0);
|
||||
m_con.Connect(address);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::step(float dtime)
|
||||
{
|
||||
ReceiveAll();
|
||||
|
||||
bool connected = false;
|
||||
{
|
||||
JMutexAutoLock lock(m_con_mutex);
|
||||
m_con.RunTimeouts(dtime);
|
||||
connected = m_con.Connected();
|
||||
}
|
||||
{
|
||||
JMutexAutoLock lock(m_env_mutex);
|
||||
m_env.step(dtime);
|
||||
}
|
||||
|
||||
/*
|
||||
Send stuff if connected
|
||||
*/
|
||||
if(connected)
|
||||
{
|
||||
sendPlayerPos(dtime);
|
||||
}
|
||||
|
||||
/*
|
||||
Clear old entries from fetchblock history
|
||||
*/
|
||||
{
|
||||
JMutexAutoLock lock(m_fetchblock_mutex);
|
||||
|
||||
core::list<v3s16> remove_queue;
|
||||
core::map<v3s16, float>::Iterator i;
|
||||
i = m_fetchblock_history.getIterator();
|
||||
for(; i.atEnd() == false; i++)
|
||||
{
|
||||
float value = i.getNode()->getValue();
|
||||
value += dtime;
|
||||
i.getNode()->setValue(value);
|
||||
if(value >= 60.0)
|
||||
remove_queue.push_back(i.getNode()->getKey());
|
||||
}
|
||||
core::list<v3s16>::Iterator j;
|
||||
j = remove_queue.begin();
|
||||
for(; j != remove_queue.end(); j++)
|
||||
{
|
||||
m_fetchblock_history.remove(*j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::ReceiveAll()
|
||||
{
|
||||
for(;;){
|
||||
try{
|
||||
Receive();
|
||||
}
|
||||
catch(con::NoIncomingDataException &e)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch(con::InvalidIncomingDataException &e)
|
||||
{
|
||||
dout_client<<"Client::ReceiveAll(): "
|
||||
"InvalidIncomingDataException: what()="
|
||||
<<e.what()<<std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::Receive()
|
||||
{
|
||||
u32 data_maxsize = 10000;
|
||||
Buffer<u8> data(data_maxsize);
|
||||
u16 peer_id;
|
||||
u32 datasize;
|
||||
{
|
||||
JMutexAutoLock lock(m_con_mutex);
|
||||
datasize = m_con.Receive(peer_id, *data, data_maxsize);
|
||||
}
|
||||
ProcessData(*data, datasize, peer_id);
|
||||
}
|
||||
|
||||
void Client::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
{
|
||||
// Ignore packets that don't even fit a command
|
||||
if(datasize < 2)
|
||||
return;
|
||||
|
||||
ToClientCommand command = (ToClientCommand)readU16(&data[0]);
|
||||
|
||||
// Execute fast commands straight away
|
||||
|
||||
if(command == TOCLIENT_REMOVENODE)
|
||||
{
|
||||
if(datasize < 8)
|
||||
return;
|
||||
v3s16 p;
|
||||
p.X = readS16(&data[2]);
|
||||
p.Y = readS16(&data[4]);
|
||||
p.Z = readS16(&data[6]);
|
||||
{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
m_env.getMap().removeNodeAndUpdate(p);
|
||||
}
|
||||
}
|
||||
else if(command == TOCLIENT_ADDNODE)
|
||||
{
|
||||
if(datasize < 8 + MapNode::serializedLength())
|
||||
return;
|
||||
|
||||
v3s16 p;
|
||||
p.X = readS16(&data[2]);
|
||||
p.Y = readS16(&data[4]);
|
||||
p.Z = readS16(&data[6]);
|
||||
|
||||
MapNode n;
|
||||
n.deSerialize(&data[8]);
|
||||
|
||||
{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
f32 light = m_env.getMap().getNode(p).light;
|
||||
m_env.getMap().setNode(p, n);
|
||||
m_env.getMap().nodeAddedUpdate(p, light);
|
||||
}
|
||||
}
|
||||
else if(command == TOCLIENT_PLAYERPOS)
|
||||
{
|
||||
u16 our_peer_id;
|
||||
{
|
||||
JMutexAutoLock lock(m_con_mutex);
|
||||
our_peer_id = m_con.GetPeerID();
|
||||
}
|
||||
// Cancel if we don't have a peer id
|
||||
if(our_peer_id == PEER_ID_NEW){
|
||||
dout_client<<"TOCLIENT_PLAYERPOS cancelled: "
|
||||
"we have no peer id"
|
||||
<<std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
{ //envlock
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
|
||||
u32 player_count = (datasize-2) / (2+12+12);
|
||||
u32 start = 2;
|
||||
for(u32 i=0; i<player_count; i++)
|
||||
{
|
||||
u16 peer_id = readU16(&data[start]);
|
||||
|
||||
|
||||
// Don't update the position of the local player
|
||||
if(peer_id == our_peer_id)
|
||||
{
|
||||
/*dout_client<<"Client: TOCLIENT_PLAYERPOS: "
|
||||
"player peer_id="<<peer_id;
|
||||
dout_client<<" is ours"<<std::endl;
|
||||
dout_client<<std::endl;*/
|
||||
|
||||
start += (2+12+12);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*dout_client<<"Client: TOCLIENT_PLAYERPOS: "
|
||||
"player peer_id="<<peer_id;
|
||||
dout_client<<std::endl;*/
|
||||
}
|
||||
|
||||
|
||||
Player *player = m_env.getPlayer(peer_id);
|
||||
|
||||
// Create a player if it doesn't exist
|
||||
if(player == NULL)
|
||||
{
|
||||
player = new Player
|
||||
(false, m_smgr->getRootSceneNode(), m_smgr, peer_id);
|
||||
player->peer_id = peer_id;
|
||||
m_env.addPlayer(player);
|
||||
dout_client<<"Client: Adding new player "
|
||||
<<peer_id<<std::endl;
|
||||
}
|
||||
|
||||
player->timeout_counter = 0.0;
|
||||
|
||||
v3s32 ps = readV3S32(&data[start+2]);
|
||||
v3s32 ss = readV3S32(&data[start+2+12]);
|
||||
v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
|
||||
v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
|
||||
player->setPosition(position);
|
||||
player->speed = speed;
|
||||
|
||||
start += (2+12+12);
|
||||
}
|
||||
} //envlock
|
||||
}
|
||||
// Default to queueing it (for slow commands)
|
||||
else
|
||||
{
|
||||
JMutexAutoLock lock(m_incoming_queue_mutex);
|
||||
|
||||
IncomingPacket packet(data, datasize);
|
||||
m_incoming_queue.push_back(packet);
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::AsyncProcessData()
|
||||
{
|
||||
getdata:
|
||||
IncomingPacket packet = getPacket();
|
||||
u8 *data = packet.m_data;
|
||||
u32 datasize = packet.m_datalen;
|
||||
|
||||
// An empty packet means queue is empty
|
||||
if(data == NULL){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(datasize < 2)
|
||||
goto getdata;
|
||||
|
||||
ToClientCommand command = (ToClientCommand)readU16(&data[0]);
|
||||
|
||||
if(command == TOCLIENT_BLOCKDATA)
|
||||
{
|
||||
// Ignore too small packet
|
||||
if(datasize < 8 + MapBlock::serializedLength())
|
||||
goto getdata;
|
||||
|
||||
v3s16 p;
|
||||
p.X = readS16(&data[2]);
|
||||
p.Y = readS16(&data[4]);
|
||||
p.Z = readS16(&data[6]);
|
||||
|
||||
dout_client<<"Client: Thread: BLOCKDATA for ("
|
||||
<<p.X<<","<<p.Y<<","<<p.Z<<")"
|
||||
<<std::endl;
|
||||
|
||||
{ //envlock
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
|
||||
v2s16 p2d(p.X, p.Z);
|
||||
MapSector *sector = m_env.getMap().getSector(p2d);
|
||||
|
||||
try{
|
||||
MapBlock *block = sector->getBlockNoCreate(p.Y);
|
||||
block->deSerialize(&data[8]);
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
{
|
||||
MapBlock *block = new MapBlock(&m_env.getMap(), p);
|
||||
block->deSerialize(&data[8]);
|
||||
sector->insertBlock(block);
|
||||
}
|
||||
} //envlock
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
dout_client<<"Client: Thread: Ingoring unknown command "
|
||||
<<command<<std::endl;
|
||||
}
|
||||
goto getdata;
|
||||
}
|
||||
|
||||
void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
|
||||
{
|
||||
JMutexAutoLock lock(m_con_mutex);
|
||||
m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
|
||||
}
|
||||
|
||||
void Client::fetchBlock(v3s16 p)
|
||||
{
|
||||
JMutexAutoLock conlock(m_con_mutex);
|
||||
if(m_con.Connected() == false)
|
||||
return;
|
||||
|
||||
JMutexAutoLock lock(m_fetchblock_mutex);
|
||||
|
||||
// If fetch request was recently sent, cancel
|
||||
if(m_fetchblock_history.find(p) != NULL)
|
||||
return;
|
||||
|
||||
|
||||
con::Peer *peer = m_con.GetPeer(PEER_ID_SERVER);
|
||||
con::Channel *channel = &(peer->channels[1]);
|
||||
|
||||
// Don't allow endless amounts of buffered reliable packets
|
||||
if(channel->incoming_reliables.size() >= 100)
|
||||
return;
|
||||
// Don't allow endless amounts of non-acked requests
|
||||
if(channel->outgoing_reliables.size() >= 10)
|
||||
return;
|
||||
|
||||
m_fetchblock_history.insert(p, 0.0);
|
||||
|
||||
SharedBuffer<u8> data(8);
|
||||
writeU16(&data[0], TOSERVER_GETBLOCK);
|
||||
writeS16(&data[2], p.X);
|
||||
writeS16(&data[4], p.Y);
|
||||
writeS16(&data[6], p.Z);
|
||||
m_con.Send(PEER_ID_SERVER, 1, data, true);
|
||||
}
|
||||
|
||||
IncomingPacket Client::getPacket()
|
||||
{
|
||||
JMutexAutoLock lock(m_incoming_queue_mutex);
|
||||
|
||||
core::list<IncomingPacket>::Iterator i;
|
||||
// Refer to first one
|
||||
i = m_incoming_queue.begin();
|
||||
|
||||
// If queue is empty, return empty packet
|
||||
if(i == m_incoming_queue.end()){
|
||||
IncomingPacket packet;
|
||||
return packet;
|
||||
}
|
||||
|
||||
// Pop out first packet and return it
|
||||
IncomingPacket packet = *i;
|
||||
m_incoming_queue.erase(i);
|
||||
return packet;
|
||||
}
|
||||
|
||||
void Client::removeNode(v3s16 nodepos)
|
||||
{
|
||||
// Test that the position exists
|
||||
try{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
m_env.getMap().getNode(nodepos);
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
{
|
||||
// Fail silently
|
||||
return;
|
||||
}
|
||||
|
||||
SharedBuffer<u8> data(8);
|
||||
writeU16(&data[0], TOSERVER_REMOVENODE);
|
||||
writeS16(&data[2], nodepos.X);
|
||||
writeS16(&data[4], nodepos.Y);
|
||||
writeS16(&data[6], nodepos.Z);
|
||||
Send(0, data, true);
|
||||
}
|
||||
|
||||
void Client::addNode(v3s16 nodepos, MapNode n)
|
||||
{
|
||||
// Test that the position exists
|
||||
try{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
m_env.getMap().getNode(nodepos);
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
{
|
||||
// Fail silently
|
||||
return;
|
||||
}
|
||||
|
||||
u8 datasize = 8 + MapNode::serializedLength();
|
||||
SharedBuffer<u8> data(datasize);
|
||||
writeU16(&data[0], TOSERVER_ADDNODE);
|
||||
writeS16(&data[2], nodepos.X);
|
||||
writeS16(&data[4], nodepos.Y);
|
||||
writeS16(&data[6], nodepos.Z);
|
||||
n.serialize(&data[8]);
|
||||
Send(0, data, true);
|
||||
}
|
||||
|
||||
void Client::sendPlayerPos(float dtime)
|
||||
{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
|
||||
Player *myplayer = m_env.getLocalPlayer();
|
||||
if(myplayer == NULL)
|
||||
return;
|
||||
|
||||
u16 our_peer_id;
|
||||
{
|
||||
JMutexAutoLock lock(m_con_mutex);
|
||||
our_peer_id = m_con.GetPeerID();
|
||||
}
|
||||
|
||||
// Set peer id if not set already
|
||||
if(myplayer->peer_id == PEER_ID_NEW)
|
||||
myplayer->peer_id = our_peer_id;
|
||||
// Check that an existing peer_id is the same as the connection's
|
||||
assert(myplayer->peer_id == our_peer_id);
|
||||
|
||||
// Update at reasonable intervals (0.2s)
|
||||
static float counter = 0.0;
|
||||
counter += dtime;
|
||||
if(counter < 0.2)
|
||||
return;
|
||||
counter = 0.0;
|
||||
|
||||
v3f pf = myplayer->getPosition();
|
||||
v3s32 position(pf.X*100, pf.Y*100, pf.Z*100);
|
||||
v3f sf = myplayer->speed;
|
||||
v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100);
|
||||
|
||||
/*static v3s32 oldpos(-104300300,-10004300,-13234344);
|
||||
static v3s32 oldspeed(-1234344,-2323424,-2343241);
|
||||
if(position == oldpos && speed == oldspeed)
|
||||
return;
|
||||
oldpos = position;
|
||||
oldspeed = speed;*/
|
||||
|
||||
SharedBuffer<u8> data(2+12+12);
|
||||
writeU16(&data[0], TOSERVER_PLAYERPOS);
|
||||
writeV3S32(&data[2], position);
|
||||
writeV3S32(&data[2+12], speed);
|
||||
// Send as unreliable
|
||||
Send(0, data, false);
|
||||
}
|
||||
|
||||
void Client::updateCamera(v3f pos, v3f dir)
|
||||
{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
m_env.getMap().updateCamera(pos, dir);
|
||||
}
|
||||
|
||||
MapNode Client::getNode(v3s16 p)
|
||||
{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
return m_env.getMap().getNode(p);
|
||||
}
|
||||
|
||||
Player * Client::getLocalPlayer()
|
||||
{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
return m_env.getLocalPlayer();
|
||||
}
|
||||
|
||||
core::list<Player*> Client::getPlayers()
|
||||
{
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
return m_env.getPlayers();
|
||||
}
|
||||
|
||||
|
154
src/client.h
Normal file
154
src/client.h
Normal file
@ -0,0 +1,154 @@
|
||||
#ifndef CLIENT_HEADER
|
||||
#define CLIENT_HEADER
|
||||
|
||||
#include "connection.h"
|
||||
#include "environment.h"
|
||||
#include "common_irrlicht.h"
|
||||
#include "jmutex.h"
|
||||
|
||||
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 Client
|
||||
{
|
||||
public:
|
||||
/*
|
||||
NOTE: Every public method should be thread-safe
|
||||
*/
|
||||
Client(scene::ISceneManager* smgr, video::SMaterial *materials);
|
||||
~Client();
|
||||
void connect(Address address);
|
||||
/*
|
||||
Stuff that references the environment is valid only as
|
||||
long as this is called. (eg. Players)
|
||||
*/
|
||||
void step(float dtime);
|
||||
|
||||
void ProcessData(u8 *data, u32 datasize, u16 peer_id);
|
||||
// Returns true if something was done
|
||||
bool AsyncProcessData();
|
||||
void Send(u16 channelnum, SharedBuffer<u8> data, bool reliable);
|
||||
|
||||
// Initiates block transfer
|
||||
void fetchBlock(v3s16 p);
|
||||
|
||||
// Pops out a packet from the packet queue
|
||||
IncomingPacket getPacket();
|
||||
|
||||
void removeNode(v3s16 nodepos);
|
||||
void addNode(v3s16 nodepos, MapNode n);
|
||||
|
||||
void updateCamera(v3f pos, v3f dir);
|
||||
|
||||
MapNode getNode(v3s16 p);
|
||||
|
||||
// Return value is valid until client is destroyed
|
||||
Player * getLocalPlayer();
|
||||
// Return value is valid until step()
|
||||
core::list<Player*> getPlayers();
|
||||
|
||||
private:
|
||||
|
||||
void ReceiveAll();
|
||||
void Receive();
|
||||
|
||||
// m_con_mutex must be locked when calling these
|
||||
void sendPlayerPos(float dtime);
|
||||
|
||||
ClientUpdateThread m_thread;
|
||||
|
||||
Environment m_env;
|
||||
JMutex m_env_mutex;
|
||||
|
||||
con::Connection m_con;
|
||||
JMutex m_con_mutex;
|
||||
|
||||
core::map<v3s16, float> m_fetchblock_history;
|
||||
//core::list<v3s16> m_fetchblock_queue;
|
||||
JMutex m_fetchblock_mutex;
|
||||
|
||||
core::list<IncomingPacket> m_incoming_queue;
|
||||
JMutex m_incoming_queue_mutex;
|
||||
|
||||
scene::ISceneManager* m_smgr;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
23
src/clientserver.h
Normal file
23
src/clientserver.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef CLIENTSERVER_HEADER
|
||||
#define CLIENTSERVER_HEADER
|
||||
|
||||
#define PROTOCOL_ID 0x4f457403
|
||||
|
||||
enum ToClientCommand
|
||||
{
|
||||
TOCLIENT_BLOCKDATA=0x20,
|
||||
TOCLIENT_ADDNODE,
|
||||
TOCLIENT_REMOVENODE,
|
||||
TOCLIENT_PLAYERPOS
|
||||
};
|
||||
|
||||
enum ToServerCommand
|
||||
{
|
||||
TOSERVER_GETBLOCK=0x20,
|
||||
TOSERVER_ADDNODE,
|
||||
TOSERVER_REMOVENODE,
|
||||
TOSERVER_PLAYERPOS
|
||||
};
|
||||
|
||||
#endif
|
||||
|
13
src/common_irrlicht.h
Normal file
13
src/common_irrlicht.h
Normal file
@ -0,0 +1,13 @@
|
||||
#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::vector2d<s16> v2s16;
|
||||
typedef core::vector2d<f32> v2f32;
|
||||
typedef core::vector3d<s32> v3s32;
|
||||
|
||||
#endif
|
||||
|
1181
src/connection.cpp
Normal file
1181
src/connection.cpp
Normal file
File diff suppressed because it is too large
Load Diff
440
src/connection.h
Normal file
440
src/connection.h
Normal file
@ -0,0 +1,440 @@
|
||||
#ifndef CONNECTION_HEADER
|
||||
#define CONNECTION_HEADER
|
||||
|
||||
#include <assert.h>
|
||||
#include "common_irrlicht.h"
|
||||
#include "socket.h"
|
||||
#include "utility.h"
|
||||
#include "exceptions.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
namespace con
|
||||
{
|
||||
|
||||
class NotFoundException : public BaseException
|
||||
{
|
||||
public:
|
||||
NotFoundException(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 RESEND_TIMEOUT_MIN 0.2
|
||||
#define RESEND_TIMEOUT_MAX 5.0
|
||||
|
||||
/*
|
||||
TODO: FIXME:
|
||||
- Move all and especially thread-sensitive methods to .cpp file
|
||||
- Think up a better way of handling unreliable split packets
|
||||
(currently all chunks are wasted if one doesn't arrive)
|
||||
- Fix reliable packets and use an own channel for these?
|
||||
*/
|
||||
|
||||
/*
|
||||
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);
|
||||
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;
|
||||
};
|
||||
|
||||
struct Peer
|
||||
{
|
||||
Peer();
|
||||
~Peer();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
class IndentationRaiser
|
||||
{
|
||||
public:
|
||||
IndentationRaiser(u16 *indentation)
|
||||
{
|
||||
m_indentation = indentation;
|
||||
(*m_indentation)++;
|
||||
}
|
||||
~IndentationRaiser()
|
||||
{
|
||||
(*m_indentation)--;
|
||||
}
|
||||
private:
|
||||
u16 *m_indentation;
|
||||
};
|
||||
|
||||
class Connection
|
||||
{
|
||||
public:
|
||||
Connection(u32 protocol_id, u32 max_packet_size);
|
||||
~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);
|
||||
Peer* GetPeer(u16 peer_id);
|
||||
|
||||
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();
|
||||
u16 m_indentation;
|
||||
|
||||
private:
|
||||
u32 m_protocol_id;
|
||||
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
|
||||
|
146
src/environment.cpp
Normal file
146
src/environment.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
#include "environment.h"
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Environment::step(float dtime)
|
||||
{
|
||||
// Increment timeout of players
|
||||
// TODO: Must reset the timeout somewhere, too.
|
||||
for(core::list<Player*>::Iterator i = m_players.begin();
|
||||
i != m_players.end(); i++)
|
||||
{
|
||||
Player *player = *i;
|
||||
player->timeout_counter += dtime;
|
||||
}
|
||||
// Remove timed-out players
|
||||
removed:
|
||||
for(core::list<Player*>::Iterator i = m_players.begin();
|
||||
i != m_players.end(); i++)
|
||||
{
|
||||
Player *player = *i;
|
||||
|
||||
// Don't remove local player
|
||||
if(player->isLocal())
|
||||
continue;
|
||||
|
||||
// 5 seconds is fine, the player will spawn again with no
|
||||
// problems anyway
|
||||
if(player->timeout_counter > 5.0)
|
||||
{
|
||||
m_dout<<"Environment: Removing timed-out player "
|
||||
<<player->peer_id<<std::endl;
|
||||
delete player;
|
||||
m_players.erase(i);
|
||||
goto removed;
|
||||
}
|
||||
}
|
||||
|
||||
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)->speed.getLength();
|
||||
if(speed > maximum_player_speed)
|
||||
maximum_player_speed = speed;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
/*
|
||||
Stuff that has a maximum time increment
|
||||
*/
|
||||
// Don't allow overly too much dtime
|
||||
if(dtime > 0.5)
|
||||
dtime = 0.5;
|
||||
do
|
||||
{
|
||||
f32 dtime_part;
|
||||
if(dtime > dtime_max_increment)
|
||||
dtime_part = dtime_max_increment;
|
||||
else
|
||||
dtime_part = dtime;
|
||||
dtime -= dtime_part;
|
||||
|
||||
/*
|
||||
Move players
|
||||
*/
|
||||
for(core::list<Player*>::Iterator i = m_players.begin();
|
||||
i != m_players.end(); i++)
|
||||
{
|
||||
Player *player = *i;
|
||||
player->speed.Y -= 9.81 * BS * dtime_part * 2;
|
||||
player->move(dtime_part, *m_map);
|
||||
}
|
||||
}
|
||||
while(dtime > 0.001);
|
||||
|
||||
}
|
||||
|
||||
Map & Environment::getMap()
|
||||
{
|
||||
return *m_map;
|
||||
}
|
||||
|
||||
void Environment::addPlayer(Player *player)
|
||||
{
|
||||
//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(Player *player)
|
||||
{
|
||||
//TODO
|
||||
throw;
|
||||
}
|
||||
|
||||
Player * Environment::getLocalPlayer()
|
||||
{
|
||||
for(core::list<Player*>::Iterator i = m_players.begin();
|
||||
i != m_players.end(); i++)
|
||||
{
|
||||
Player *player = *i;
|
||||
if(player->isLocal())
|
||||
return 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;
|
||||
}
|
||||
|
48
src/environment.h
Normal file
48
src/environment.h
Normal file
@ -0,0 +1,48 @@
|
||||
#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.
|
||||
*/
|
||||
void step(f32 dtime);
|
||||
|
||||
Map & getMap();
|
||||
/*
|
||||
Environment deallocates players after use.
|
||||
*/
|
||||
void addPlayer(Player *player);
|
||||
void removePlayer(Player *player);
|
||||
Player * getLocalPlayer();
|
||||
Player * getPlayer(u16 peer_id);
|
||||
core::list<Player*> getPlayers();
|
||||
private:
|
||||
Map *m_map;
|
||||
core::list<Player*> m_players;
|
||||
// Debug output goes here
|
||||
std::ostream &m_dout;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
73
src/exceptions.h
Normal file
73
src/exceptions.h
Normal file
@ -0,0 +1,73 @@
|
||||
#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)
|
||||
{}
|
||||
};
|
||||
|
||||
/*
|
||||
Some "old-style" interrupts:
|
||||
*/
|
||||
|
||||
class InvalidPositionException : public std::exception
|
||||
{
|
||||
virtual const char * what() const throw()
|
||||
{
|
||||
return "Somebody tried to get/set something in a nonexistent position.";
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
|
437
src/heightmap.cpp
Normal file
437
src/heightmap.cpp
Normal file
@ -0,0 +1,437 @@
|
||||
/*
|
||||
(c) 2010 Perttu Ahola <celeron55@gmail.com>
|
||||
*/
|
||||
|
||||
#include "heightmap.h"
|
||||
|
||||
bool g_heightmap_debugprint = false;
|
||||
|
||||
f32 FixedHeightmap::avgNeighbours(v2s16 p, s16 d)
|
||||
{
|
||||
//printf("avgNeighbours((%i,%i), %i): ", p.X, p.Y, 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;
|
||||
//printf("(%i,%i)=%f ", p2.X, p2.Y, n);
|
||||
}
|
||||
//printf("\n");
|
||||
assert(count > 0.001);
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
f32 FixedHeightmap::avgDiagNeighbours(v2s16 p, s16 d)
|
||||
{
|
||||
/*printf("avgDiagNeighbours((%i,%i), %i): ",
|
||||
p.X, p.Y, 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;
|
||||
//printf("(%i,%i)=%f ", p2.X, p2.Y, n);
|
||||
}
|
||||
//printf("\n");
|
||||
assert(count > 0.001);
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
/*
|
||||
Adds a point to transform into a diamond pattern
|
||||
a = 2, 4, 8, 16, ...
|
||||
*/
|
||||
void FixedHeightmap::makeDiamond(v2s16 center, s16 a, f32 randmax)
|
||||
{
|
||||
f32 n = avgDiagNeighbours(center, a/2);
|
||||
// Add (-1.0...1.0) * randmax
|
||||
n += ((float)rand() / (float)(RAND_MAX/2) - 1.0)*randmax;
|
||||
setGroundHeightParent(center, n);
|
||||
}
|
||||
|
||||
/*
|
||||
Adds points to transform into a diamond pattern
|
||||
a = 2, 4, 8, 16, ...
|
||||
*/
|
||||
void FixedHeightmap::makeDiamonds(s16 a, f32 randmax)
|
||||
{
|
||||
s16 count_y = (H-1) / a;
|
||||
s16 count_x = (W-1) / a;
|
||||
for(s32 yi=0; yi<count_y; yi++){
|
||||
for(s32 xi=0; xi<count_x; xi++){
|
||||
v2s16 center(xi*a + a/2, yi*a + a/2);
|
||||
makeDiamond(center, a, randmax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Adds a point to transform into a square pattern
|
||||
a = 2, 4, 8, 16, ...
|
||||
*/
|
||||
void FixedHeightmap::makeSquare(v2s16 center, s16 a, f32 randmax)
|
||||
{
|
||||
f32 n = avgNeighbours(center, a/2);
|
||||
// Add (-1.0...1.0) * randmax
|
||||
n += ((float)rand() / (float)(RAND_MAX/2) - 1.0)*randmax;
|
||||
setGroundHeightParent(center, n);
|
||||
}
|
||||
|
||||
/*
|
||||
Adds points to transform into a square pattern
|
||||
a = 2, 4, 8, 16, ...
|
||||
*/
|
||||
void FixedHeightmap::makeSquares(s16 a, f32 randmax)
|
||||
{
|
||||
/*
|
||||
This is a bit tricky; the points have to be kind of
|
||||
interlaced. (but not exactly)
|
||||
|
||||
On even rows (0, 2, 4, ...) the nodes are horizontally
|
||||
at a*n+a/2 positions, vertically at a*n.
|
||||
|
||||
On odd rows (1, 3, 5, ...) the nodes are horizontally
|
||||
at a*n positions, vertically at a*n+a/2.
|
||||
*/
|
||||
|
||||
s16 count_y = (H-1) / a + 1;
|
||||
for(s32 yi=0; yi<=count_y; yi++){
|
||||
s16 count_x;
|
||||
// Even rows
|
||||
count_x = (W-1) / a;
|
||||
for(s32 xi=0; xi<count_x; xi++){
|
||||
v2s16 center(xi*a + a/2, yi*a);
|
||||
makeSquare(center, a, randmax);
|
||||
}
|
||||
if(yi >= count_y - 1)
|
||||
break;
|
||||
// odd rows
|
||||
count_x = (W-1) / a + 1;
|
||||
for(s32 xi=0; xi<count_x; xi++){
|
||||
v2s16 center(xi*a, yi*a + a/2);
|
||||
makeSquare(center, a, randmax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
while(a >= 2)
|
||||
{
|
||||
makeDiamonds(a, randmax);
|
||||
if(HEIGHTMAP_DEBUGPRINT){
|
||||
printf("MakeDiamonds a=%i result:\n", a);
|
||||
print();
|
||||
}
|
||||
makeSquares(a, randmax);
|
||||
if(HEIGHTMAP_DEBUGPRINT){
|
||||
printf("MakeSquares a=%i result:\n", a);
|
||||
print();
|
||||
}
|
||||
a /= 2;
|
||||
randmax *= randfactor;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
for(s32 y=miny; y<=maxy; y++){
|
||||
for(s32 x=minx; x<=maxx; x++){
|
||||
f32 n = getGroundHeight(v2s16(x,y), false);
|
||||
if(n < GROUNDHEIGHT_VALID_MINVALUE)
|
||||
printf(" - ");
|
||||
else
|
||||
printf("% -5.1f ", getGroundHeight(v2s16(x,y), false));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
FixedHeightmap * UnlimitedHeightmap::getHeightmap(v2s16 p, bool generate)
|
||||
{
|
||||
core::map<v2s16, FixedHeightmap*>::Node *n = m_heightmaps.find(p);
|
||||
|
||||
if(n != NULL)
|
||||
{
|
||||
return n->getValue();
|
||||
}
|
||||
|
||||
/*std::cout<<"UnlimitedHeightmap::getHeightmap(("
|
||||
<<p.X<<","<<p.Y<<"), generate="
|
||||
<<generate<<")"<<std::endl;*/
|
||||
|
||||
if(generate == false)
|
||||
throw InvalidPositionException();
|
||||
|
||||
// If heightmap doesn't exist, generate one
|
||||
FixedHeightmap *heightmap = new FixedHeightmap(
|
||||
this, p, m_blocksize);
|
||||
|
||||
m_heightmaps.insert(p, heightmap);
|
||||
|
||||
f32 corners[] = {m_basevalue, m_basevalue, m_basevalue, m_basevalue};
|
||||
heightmap->generateContinued(m_randmax, m_randfactor, corners);
|
||||
|
||||
return heightmap;
|
||||
}
|
||||
|
||||
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){}
|
||||
|
||||
/*
|
||||
OK, wasn't there.
|
||||
|
||||
Mercilessly try to get it somewhere.
|
||||
*/
|
||||
#if 1
|
||||
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){}
|
||||
}
|
||||
#endif
|
||||
return GROUNDHEIGHT_NOTFOUND_SETVALUE;
|
||||
}
|
||||
|
||||
void UnlimitedHeightmap::setGroundHeight(v2s16 p, f32 y, bool generate)
|
||||
{
|
||||
v2s16 heightmappos = getNodeHeightmapPos(p);
|
||||
v2s16 relpos = p - heightmappos*m_blocksize;
|
||||
/*std::cout<<"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);
|
||||
}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);
|
||||
}catch(InvalidPositionException){}
|
||||
}
|
||||
if(relpos.Y == 0){
|
||||
try{
|
||||
FixedHeightmap * href = getHeightmap(
|
||||
heightmappos-v2s16(0,1), generate);
|
||||
href->setGroundHeight(v2s16(relpos.X, m_blocksize), y);
|
||||
}catch(InvalidPositionException){}
|
||||
}
|
||||
if(relpos.X == m_blocksize && relpos.Y == m_blocksize){
|
||||
try{
|
||||
FixedHeightmap * href = getHeightmap(
|
||||
heightmappos-v2s16(1,1), generate);
|
||||
href->setGroundHeight(v2s16(m_blocksize, m_blocksize), y);
|
||||
}catch(InvalidPositionException){}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FixedHeightmap::generateContinued(f32 randmax, f32 randfactor,
|
||||
f32 *corners)
|
||||
{
|
||||
if(HEIGHTMAP_DEBUGPRINT){
|
||||
std::cout<<"FixedHeightmap("<<m_pos_on_master.X
|
||||
<<","<<m_pos_on_master.Y
|
||||
<<")::generateContinued()"<<std::endl;
|
||||
}
|
||||
/*
|
||||
TODO: Implement changing neighboring heightmaps when needed
|
||||
*/
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
#if 1
|
||||
/*
|
||||
Seed borders from master heightmap
|
||||
TODO: Check that this works properly
|
||||
*/
|
||||
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);
|
||||
//std::cout<<"h="<<h<<std::endl;
|
||||
if(h < GROUNDHEIGHT_VALID_MINVALUE)
|
||||
continue;
|
||||
//break;
|
||||
setGroundHeight(hpos, h);
|
||||
hpos += seeds[i].dir;
|
||||
npos += seeds[i].dir;
|
||||
}
|
||||
}
|
||||
|
||||
if(HEIGHTMAP_DEBUGPRINT){
|
||||
std::cout<<"borders seeded:"<<std::endl;
|
||||
print();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
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]);
|
||||
}
|
||||
|
||||
if(HEIGHTMAP_DEBUGPRINT){
|
||||
std::cout<<"corners filled:"<<std::endl;
|
||||
print();
|
||||
}
|
||||
|
||||
/*std::cout<<"Seeded heightmap:"<<std::endl;
|
||||
print();*/
|
||||
|
||||
DiamondSquare(randmax, randfactor);
|
||||
}
|
||||
|
420
src/heightmap.h
Normal file
420
src/heightmap.h
Normal file
@ -0,0 +1,420 @@
|
||||
/*
|
||||
(c) 2010 Perttu Ahola <celeron55@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef HEIGHTMAP_HEADER
|
||||
#define HEIGHTMAP_HEADER
|
||||
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "common_irrlicht.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
#define GROUNDHEIGHT_NOTFOUND_SETVALUE (-10e6)
|
||||
#define GROUNDHEIGHT_VALID_MINVALUE ( -9e6)
|
||||
|
||||
extern bool g_heightmap_debugprint;
|
||||
#define HEIGHTMAP_DEBUGPRINT g_heightmap_debugprint
|
||||
|
||||
class Heightmappish
|
||||
{
|
||||
public:
|
||||
virtual f32 getGroundHeight(v2s16 p, bool generate=true)
|
||||
{
|
||||
printf("Heightmappish::getGroundHeight() stub called\n");
|
||||
assert(0);
|
||||
return 0.0;
|
||||
}
|
||||
virtual void setGroundHeight(v2s16 p, f32 y, bool generate=true)
|
||||
{
|
||||
printf("Heightmappish::setGroundHeight() stub called\n");
|
||||
assert(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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
class DummyHeightmap : public Heightmap
|
||||
{
|
||||
public:
|
||||
f32 m_value;
|
||||
|
||||
DummyHeightmap(f32 value=0.0)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
f32 getGroundHeight(v2s16 p, bool generate=true)
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
void setGroundHeight(v2s16 p, f32 y, bool generate=true)
|
||||
{
|
||||
std::cout<<"DummyHeightmap::getGroundHeight()"<<std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
/*std::cout<<"FixedHeightmap::setGroundHeight(("
|
||||
<<p.X<<","<<p.Y
|
||||
<<"), "<<y<<")"<<std::endl;*/
|
||||
if(overborder(p))
|
||||
throw InvalidPositionException();
|
||||
m_data[p.Y*W + p.X] = y;
|
||||
}
|
||||
|
||||
void 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;
|
||||
std::cout<<"FixedHeightmap::setGroundHeightParent(("
|
||||
<<p.X<<","<<p.Y
|
||||
<<"), "<<y<<"): nodepos_master=("
|
||||
<<nodepos_master.X<<","
|
||||
<<nodepos_master.Y<<")"<<std::endl;
|
||||
m_master->setGroundHeight(nodepos_master, y, 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);
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if(overborder(p))
|
||||
return;
|
||||
|
||||
setGroundHeight(p, y);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
Adds a point to transform into a diamond pattern
|
||||
a = 2, 4, 8, 16, ...
|
||||
*/
|
||||
void makeDiamond(v2s16 center, s16 a, f32 randmax);
|
||||
|
||||
/*
|
||||
Adds points to transform into a diamond pattern
|
||||
a = 2, 4, 8, 16, ...
|
||||
*/
|
||||
void makeDiamonds(s16 a, f32 randmax);
|
||||
|
||||
/*
|
||||
Adds a point to transform into a square pattern
|
||||
a = 2, 4, 8, 16, ...
|
||||
*/
|
||||
void makeSquare(v2s16 center, s16 a, f32 randmax);
|
||||
|
||||
/*
|
||||
Adds points to transform into a square pattern
|
||||
a = 2, 4, 8, 16, ...
|
||||
*/
|
||||
void makeSquares(s16 a, f32 randmax);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
//std::cout<<"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);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
TODO
|
||||
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)
|
||||
|
||||
TODO: Dynamic unloading and loading of stuff to/from disk
|
||||
|
||||
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;
|
||||
|
||||
f32 m_randmax;
|
||||
f32 m_randfactor;
|
||||
f32 m_basevalue;
|
||||
|
||||
public:
|
||||
|
||||
UnlimitedHeightmap(s16 blocksize, f32 randmax, f32 randfactor,
|
||||
f32 basevalue=0.0):
|
||||
m_blocksize(blocksize),
|
||||
m_randmax(randmax),
|
||||
m_randfactor(randfactor),
|
||||
m_basevalue(basevalue)
|
||||
{
|
||||
}
|
||||
|
||||
~UnlimitedHeightmap()
|
||||
{
|
||||
core::map<v2s16, FixedHeightmap*>::Iterator i;
|
||||
i = m_heightmaps.getIterator();
|
||||
for(; i.atEnd() == false; i++)
|
||||
{
|
||||
delete i.getNode()->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
FixedHeightmap * getHeightmap(v2s16 p, bool generate=true);
|
||||
|
||||
f32 getGroundHeight(v2s16 p, bool generate=true);
|
||||
void setGroundHeight(v2s16 p, f32 y, bool generate=true);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
38
src/light.h
Normal file
38
src/light.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef LIGHT_HEADER
|
||||
#define LIGHT_HEADER
|
||||
|
||||
/*
|
||||
RULES OF LIGHT:
|
||||
|
||||
light = 1.0 (0.999-1.001) is SUNLIGHT. It goes downwards infinitely from
|
||||
infinite highness and stops when it first hits a non-transparent node.
|
||||
The lighting of the node it hits is 0.
|
||||
|
||||
Other LIGHT SOURCES have a light value of UNDER 0.999.
|
||||
|
||||
Light diminishes at a constant factor between nodes.
|
||||
|
||||
TODO: Should there be a common container to be parent of map, sector and block?
|
||||
|
||||
*/
|
||||
|
||||
#define LIGHT_MAX 1.0
|
||||
#define LIGHT_MIN 0.0
|
||||
// If lighting value is under this, it can be assumed that
|
||||
// there is no light
|
||||
#define NO_LIGHT_MAX 0.03
|
||||
|
||||
//#define LIGHT_DIMINISH_FACTOR 0.75
|
||||
#define LIGHT_DIMINISH_FACTOR 0.8
|
||||
|
||||
/*
|
||||
When something changes lighting of a node, stuff around it is
|
||||
updated inside this radius.
|
||||
*/
|
||||
//#define LIGHTING_RADIUS 10
|
||||
#define LIGHTING_RADIUS 15
|
||||
|
||||
typedef f32 light_t;
|
||||
|
||||
#endif
|
||||
|
144
src/loadstatus.h
Normal file
144
src/loadstatus.h
Normal file
@ -0,0 +1,144 @@
|
||||
#ifndef LOADSTATUS_HEADER
|
||||
#define LOADSTATUS_HEADER
|
||||
|
||||
class LoadStatus
|
||||
{
|
||||
bool ready;
|
||||
JMutex ready_mutex;
|
||||
|
||||
u32 done;
|
||||
JMutex done_mutex;
|
||||
|
||||
u32 todo;
|
||||
JMutex todo_mutex;
|
||||
|
||||
wchar_t *text;
|
||||
JMutex text_mutex;
|
||||
|
||||
public:
|
||||
|
||||
LoadStatus(bool a_ready=false, u32 a_done=0, u32 a_todo=0)
|
||||
{
|
||||
ready = a_ready;
|
||||
done = a_done;
|
||||
todo = a_todo;
|
||||
text = NULL;
|
||||
ready_mutex.Init();
|
||||
done_mutex.Init();
|
||||
todo_mutex.Init();
|
||||
text_mutex.Init();
|
||||
}
|
||||
|
||||
void setReady(bool a_ready)
|
||||
{
|
||||
ready_mutex.Lock();
|
||||
ready = a_ready;
|
||||
ready_mutex.Unlock();
|
||||
}
|
||||
|
||||
bool getReady(void)
|
||||
{
|
||||
ready_mutex.Lock();
|
||||
bool a_ready = ready;
|
||||
ready_mutex.Unlock();
|
||||
return a_ready;
|
||||
}
|
||||
|
||||
void setDone(u32 a_done)
|
||||
{
|
||||
done_mutex.Lock();
|
||||
done = a_done;
|
||||
done_mutex.Unlock();
|
||||
}
|
||||
|
||||
u32 getDone(void)
|
||||
{
|
||||
done_mutex.Lock();
|
||||
u32 a_done = done;
|
||||
done_mutex.Unlock();
|
||||
return a_done;
|
||||
}
|
||||
|
||||
void setTodo(u32 a_todo)
|
||||
{
|
||||
todo_mutex.Lock();
|
||||
todo = a_todo;
|
||||
todo_mutex.Unlock();
|
||||
}
|
||||
|
||||
u32 getTodo(void)
|
||||
{
|
||||
todo_mutex.Lock();
|
||||
u32 a_todo = todo;
|
||||
todo_mutex.Unlock();
|
||||
return a_todo;
|
||||
}
|
||||
|
||||
/*
|
||||
Copies the text if not NULL,
|
||||
If NULL; sets text to NULL.
|
||||
*/
|
||||
void setText(const wchar_t *a_text)
|
||||
{
|
||||
text_mutex.Lock();
|
||||
if(text != NULL)
|
||||
free(text);
|
||||
if(a_text == NULL){
|
||||
text = NULL;
|
||||
text_mutex.Unlock();
|
||||
return;
|
||||
}
|
||||
u32 len = wcslen(a_text);
|
||||
text = (wchar_t*)malloc(sizeof(wchar_t) * (len+1));
|
||||
if(text == NULL) throw;
|
||||
swprintf(text, len+1, L"%ls", a_text);
|
||||
text_mutex.Unlock();
|
||||
}
|
||||
|
||||
/*
|
||||
Return value must be free'd
|
||||
Return value can be NULL
|
||||
*/
|
||||
wchar_t * getText()
|
||||
{
|
||||
text_mutex.Lock();
|
||||
if(text == NULL){
|
||||
text_mutex.Unlock();
|
||||
return NULL;
|
||||
}
|
||||
u32 len = wcslen(text);
|
||||
wchar_t *b_text = (wchar_t*)malloc(sizeof(wchar_t) * (len+1));
|
||||
if(b_text == NULL) throw;
|
||||
swprintf(b_text, len+1, L"%ls", text);
|
||||
text_mutex.Unlock();
|
||||
return b_text;
|
||||
}
|
||||
|
||||
/*
|
||||
Return value must be free'd
|
||||
*/
|
||||
wchar_t * getNiceText()
|
||||
{
|
||||
const wchar_t *defaulttext = L"Loading";
|
||||
wchar_t *t = getText();
|
||||
u32 maxlen = 20; // " (%i/%i)"
|
||||
if(t != NULL)
|
||||
maxlen += wcslen(t);
|
||||
else
|
||||
maxlen += wcslen(defaulttext);
|
||||
wchar_t *b_text = (wchar_t*)malloc(sizeof(wchar_t) * (maxlen+1));
|
||||
if(b_text == NULL) throw;
|
||||
if(t != NULL)
|
||||
swprintf(b_text, maxlen+1, L"%ls (%i/%i)",
|
||||
t, getDone(), getTodo());
|
||||
else
|
||||
swprintf(b_text, maxlen+1, L"%ls (%i/%i)",
|
||||
defaulttext, getDone(), getTodo());
|
||||
if(t != NULL)
|
||||
free(t);
|
||||
return b_text;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
976
src/main.cpp
Normal file
976
src/main.cpp
Normal file
@ -0,0 +1,976 @@
|
||||
/*
|
||||
(c) 2010 Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
Minetest
|
||||
|
||||
TODO: Check for obsolete todos
|
||||
TODO: Storing map on disk, preferably dynamically
|
||||
TODO: struct settings, with a mutex and get/set functions
|
||||
TODO: Implement torches and similar light sources (halfway done)
|
||||
TODO: A menu
|
||||
TODO: A cache class that can be used with lightNeighbors,
|
||||
unlightNeighbors and probably many others. Look for
|
||||
implementation in lightNeighbors
|
||||
TODO: Proper objects for random stuff in this file, like
|
||||
g_selected_material
|
||||
TODO: See if changing to 32-bit position variables doesn't raise
|
||||
memory consumption a lot.
|
||||
Now:
|
||||
TODO: Have to implement mutexes to MapSectors; otherwise initial
|
||||
lighting might fail
|
||||
TODO: Adding more heightmap points to MapSectors
|
||||
|
||||
Network protocol:
|
||||
- Client map data is only updated from the server's,
|
||||
EXCEPT FOR lighting.
|
||||
|
||||
Actions:
|
||||
|
||||
- User places block
|
||||
-> Client sends PLACED_BLOCK(pos, node)
|
||||
-> Server validates and sends MAP_SINGLE_CHANGE(pos, node)
|
||||
-> Client applies change and recalculates lighting and face cache
|
||||
|
||||
- User starts digging
|
||||
-> Client sends START_DIGGING(pos)
|
||||
-> Server starts timer
|
||||
- if user stops digging:
|
||||
-> Client sends STOP_DIGGING
|
||||
-> Server stops timer
|
||||
- if user continues:
|
||||
-> Server waits timer
|
||||
-> Server sends MAP_SINGLE_CHANGE(pos, node)
|
||||
-> Client applies change and recalculates lighting and face cache
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
Setting this to 1 enables a special camera mode that forces
|
||||
the renderers to think that the camera statically points from
|
||||
the starting place to a static direction.
|
||||
|
||||
This allows one to move around with the player and see what
|
||||
is actually drawn behind solid things etc.
|
||||
*/
|
||||
#define FIELD_OF_VIEW_TEST 0
|
||||
|
||||
// Enable unit tests
|
||||
#define ENABLE_TESTS 1
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "Irrlicht.lib")
|
||||
#pragma comment(lib, "jthread.lib")
|
||||
// This would get rid of the console window
|
||||
//#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
|
||||
#endif
|
||||
|
||||
#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
|
||||
|
||||
#include <iostream>
|
||||
#include <time.h>
|
||||
#include <jmutexautolock.h>
|
||||
#include "common_irrlicht.h"
|
||||
#include "map.h"
|
||||
#include "player.h"
|
||||
#include "main.h"
|
||||
#include "test.h"
|
||||
#include "environment.h"
|
||||
#include "server.h"
|
||||
#include "client.h"
|
||||
#include <string>
|
||||
|
||||
const char *g_material_filenames[MATERIALS_COUNT] =
|
||||
{
|
||||
"../data/stone.png",
|
||||
"../data/grass.png",
|
||||
"../data/water.png",
|
||||
};
|
||||
|
||||
#define FPS_MIN 15
|
||||
#define FPS_MAX 25
|
||||
|
||||
#define VIEWING_RANGE_NODES_MIN MAP_BLOCKSIZE
|
||||
#define VIEWING_RANGE_NODES_MAX 35
|
||||
|
||||
JMutex g_viewing_range_nodes_mutex;
|
||||
s16 g_viewing_range_nodes = MAP_BLOCKSIZE;
|
||||
|
||||
/*
|
||||
Random stuff
|
||||
*/
|
||||
u16 g_selected_material = 0;
|
||||
|
||||
/*
|
||||
Debug streams
|
||||
- use these to disable or enable outputs of parts of the program
|
||||
*/
|
||||
|
||||
std::ofstream dfile("debug.txt");
|
||||
//std::ofstream dfile2("debug2.txt");
|
||||
|
||||
// Connection
|
||||
//std::ostream dout_con(std::cout.rdbuf());
|
||||
std::ostream dout_con(dfile.rdbuf());
|
||||
|
||||
// Server
|
||||
//std::ostream dout_server(std::cout.rdbuf());
|
||||
std::ostream dout_server(dfile.rdbuf());
|
||||
|
||||
// Client
|
||||
//std::ostream dout_client(std::cout.rdbuf());
|
||||
std::ostream dout_client(dfile.rdbuf());
|
||||
|
||||
/*
|
||||
TimeTaker
|
||||
*/
|
||||
|
||||
class TimeTaker
|
||||
{
|
||||
public:
|
||||
TimeTaker(const char *name, IrrlichtDevice *dev)
|
||||
{
|
||||
m_name = name;
|
||||
m_dev = dev;
|
||||
m_time1 = m_dev->getTimer()->getRealTime();
|
||||
}
|
||||
~TimeTaker()
|
||||
{
|
||||
u32 time2 = m_dev->getTimer()->getRealTime();
|
||||
u32 dtime = time2 - m_time1;
|
||||
std::cout<<m_name<<" took "<<dtime<<"ms"<<std::endl;
|
||||
}
|
||||
private:
|
||||
const char *m_name;
|
||||
IrrlichtDevice *m_dev;
|
||||
u32 m_time1;
|
||||
};
|
||||
|
||||
class MyEventReceiver : public IEventReceiver
|
||||
{
|
||||
public:
|
||||
// This is the one method that we have to implement
|
||||
virtual bool OnEvent(const SEvent& event)
|
||||
{
|
||||
// Remember whether each key is down or up
|
||||
if(event.EventType == irr::EET_KEY_INPUT_EVENT){
|
||||
keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
|
||||
if(event.KeyInput.PressedDown){
|
||||
if(event.KeyInput.Key == irr::KEY_KEY_F){
|
||||
if(g_selected_material < USEFUL_MATERIAL_COUNT-1)
|
||||
g_selected_material++;
|
||||
else
|
||||
g_selected_material = 0;
|
||||
std::cout<<"Selected material: "
|
||||
<<g_selected_material<<std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
|
||||
{
|
||||
if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
|
||||
{
|
||||
leftclicked = true;
|
||||
}
|
||||
if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
|
||||
{
|
||||
rightclicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is used to check whether a key is being held down
|
||||
virtual bool IsKeyDown(EKEY_CODE keyCode) const
|
||||
{
|
||||
return keyIsDown[keyCode];
|
||||
}
|
||||
|
||||
MyEventReceiver()
|
||||
{
|
||||
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
|
||||
keyIsDown[i] = false;
|
||||
leftclicked = false;
|
||||
rightclicked = false;
|
||||
}
|
||||
|
||||
bool leftclicked;
|
||||
bool rightclicked;
|
||||
private:
|
||||
// We use this array to store the current state of each key
|
||||
bool keyIsDown[KEY_KEY_CODES_COUNT];
|
||||
//s32 mouseX;
|
||||
//s32 mouseY;
|
||||
};
|
||||
|
||||
void updateViewingRange(f32 frametime)
|
||||
{
|
||||
#if 1
|
||||
static f32 counter = 0;
|
||||
if(counter > 0){
|
||||
counter -= frametime;
|
||||
return;
|
||||
}
|
||||
counter = 5.0; //seconds
|
||||
|
||||
g_viewing_range_nodes_mutex.Lock();
|
||||
bool changed = false;
|
||||
if(frametime > 1.0/FPS_MIN
|
||||
|| g_viewing_range_nodes > VIEWING_RANGE_NODES_MAX){
|
||||
if(g_viewing_range_nodes > VIEWING_RANGE_NODES_MIN){
|
||||
g_viewing_range_nodes -= MAP_BLOCKSIZE/2;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else if(frametime < 1.0/FPS_MAX
|
||||
|| g_viewing_range_nodes < VIEWING_RANGE_NODES_MIN){
|
||||
if(g_viewing_range_nodes < VIEWING_RANGE_NODES_MAX){
|
||||
g_viewing_range_nodes += MAP_BLOCKSIZE/2;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if(changed){
|
||||
std::cout<<"g_viewing_range_nodes = "
|
||||
<<g_viewing_range_nodes<<std::endl;
|
||||
}
|
||||
g_viewing_range_nodes_mutex.Unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
s16 temp16;
|
||||
f32 tempf;
|
||||
v3f tempv3f1;
|
||||
v3f tempv3f2;
|
||||
|
||||
void SpeedTests(IrrlichtDevice *device)
|
||||
{
|
||||
/*
|
||||
Test stuff
|
||||
*/
|
||||
|
||||
//test();
|
||||
//return 0;
|
||||
/*TestThread thread;
|
||||
thread.Start();
|
||||
std::cout<<"thread started"<<std::endl;
|
||||
while(thread.IsRunning()) sleep(1);
|
||||
std::cout<<"thread ended"<<std::endl;
|
||||
return 0;*/
|
||||
|
||||
{
|
||||
std::cout<<"Testing floating-point conversion speed"<<std::endl;
|
||||
u32 time1 = device->getTimer()->getRealTime();
|
||||
tempf = 0.001;
|
||||
for(u32 i=0; i<10000000; i++){
|
||||
temp16 += tempf;
|
||||
tempf += 0.001;
|
||||
}
|
||||
u32 time2 = device->getTimer()->getRealTime();
|
||||
u32 fp_conversion_time = time2 - time1;
|
||||
std::cout<<"Done. "<<fp_conversion_time<<"ms"<<std::endl;
|
||||
//assert(fp_conversion_time < 1000);
|
||||
}
|
||||
|
||||
{
|
||||
std::cout<<"Testing floating-point vector speed"<<std::endl;
|
||||
u32 time1 = device->getTimer()->getRealTime();
|
||||
|
||||
tempv3f1 = v3f(1,2,3);
|
||||
tempv3f2 = v3f(4,5,6);
|
||||
for(u32 i=0; i<40000000; i++){
|
||||
tempf += tempv3f1.dotProduct(tempv3f2);
|
||||
tempv3f2 += v3f(7,8,9);
|
||||
}
|
||||
|
||||
u32 time2 = device->getTimer()->getRealTime();
|
||||
u32 dtime = time2 - time1;
|
||||
std::cout<<"Done. "<<dtime<<"ms"<<std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
std::cout<<"Testing core::map speed"<<std::endl;
|
||||
u32 time1 = device->getTimer()->getRealTime();
|
||||
|
||||
core::map<v2s16, f32> map1;
|
||||
tempf = -324;
|
||||
for(s16 y=0; y<500; y++){
|
||||
for(s16 x=0; x<500; x++){
|
||||
map1.insert(v2s16(x,y), tempf);
|
||||
tempf += 1;
|
||||
}
|
||||
}
|
||||
for(s16 y=500-1; y>=0; y--){
|
||||
for(s16 x=0; x<500; x++){
|
||||
tempf = map1[v2s16(x,y)];
|
||||
}
|
||||
}
|
||||
|
||||
u32 time2 = device->getTimer()->getRealTime();
|
||||
u32 dtime = time2 - time1;
|
||||
std::cout<<"Done. "<<dtime<<"ms"<<std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
std::cout<<"Testing mutex speed"<<std::endl;
|
||||
u32 time1 = device->getTimer()->getRealTime();
|
||||
u32 time2 = time1;
|
||||
|
||||
JMutex m;
|
||||
m.Init();
|
||||
u32 n = 0;
|
||||
u32 i = 0;
|
||||
do{
|
||||
n += 10000;
|
||||
for(; i<n; i++){
|
||||
m.Lock();
|
||||
m.Unlock();
|
||||
}
|
||||
time2 = device->getTimer()->getRealTime();
|
||||
}
|
||||
// Do at least 10ms
|
||||
while(time2 < time1 + 10);
|
||||
|
||||
u32 dtime = time2 - time1;
|
||||
u32 per_ms = n / dtime;
|
||||
std::cout<<"Done. "<<dtime<<"ms, "
|
||||
<<per_ms<<"/ms"<<std::endl;
|
||||
}
|
||||
|
||||
//assert(0);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
sockets_init();
|
||||
atexit(sockets_cleanup);
|
||||
|
||||
/*
|
||||
Run unit tests
|
||||
*/
|
||||
if(ENABLE_TESTS)
|
||||
run_tests();
|
||||
|
||||
//return 0; //DEBUG
|
||||
|
||||
/*
|
||||
Initialization
|
||||
*/
|
||||
|
||||
srand(time(0));
|
||||
|
||||
g_viewing_range_nodes_mutex.Init();
|
||||
assert(g_viewing_range_nodes_mutex.IsInitialized());
|
||||
|
||||
MyEventReceiver receiver;
|
||||
|
||||
// create device and exit if creation failed
|
||||
|
||||
/*
|
||||
Host selection
|
||||
*/
|
||||
char connect_name[100];
|
||||
std::cout<<std::endl<<std::endl;
|
||||
std::cout<<"Address to connect to [empty = host a game]: ";
|
||||
std::cin.getline(connect_name, 100);
|
||||
|
||||
bool hosting = false;
|
||||
if(connect_name[0] == 0){
|
||||
snprintf(connect_name, 100, "127.0.0.1");
|
||||
hosting = true;
|
||||
}
|
||||
std::cout<<"-> "<<connect_name<<std::endl;
|
||||
|
||||
std::cout<<"Port [empty=30000]: ";
|
||||
char templine[100];
|
||||
std::cin.getline(templine, 100);
|
||||
unsigned short port;
|
||||
if(templine[0] == 0)
|
||||
port = 30000;
|
||||
else
|
||||
port = atoi(templine);
|
||||
|
||||
/*
|
||||
Resolution selection
|
||||
*/
|
||||
|
||||
u16 screenW = 800;
|
||||
u16 screenH = 600;
|
||||
|
||||
/*
|
||||
u16 resolutions[][2] = {
|
||||
{640,480},
|
||||
{800,600},
|
||||
{1024,768},
|
||||
{1280,1024}
|
||||
};
|
||||
|
||||
u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
|
||||
|
||||
std::cout<<"Select window resolution "
|
||||
<<"(type a number and press enter):"<<std::endl;
|
||||
for(u16 i=0; i<res_count; i++)
|
||||
{
|
||||
std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
|
||||
<<resolutions[i][1]<<std::endl;
|
||||
}
|
||||
u16 r0;
|
||||
std::cin>>r0;
|
||||
if(r0 > res_count || r0 == 0)
|
||||
r0 = 0;
|
||||
u16 screenW = resolutions[r0-1][0];
|
||||
u16 screenH = resolutions[r0-1][1];
|
||||
*/
|
||||
|
||||
//
|
||||
|
||||
video::E_DRIVER_TYPE driverType;
|
||||
|
||||
#ifdef _WIN32
|
||||
//driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
|
||||
driverType = video::EDT_OPENGL;
|
||||
#else
|
||||
driverType = video::EDT_OPENGL;
|
||||
#endif
|
||||
|
||||
IrrlichtDevice *device;
|
||||
device = createDevice(driverType,
|
||||
core::dimension2d<u32>(screenW, screenH),
|
||||
16, false, false, false, &receiver);
|
||||
|
||||
if (device == 0)
|
||||
return 1; // could not create selected driver.
|
||||
|
||||
/*
|
||||
Run some speed tests
|
||||
*/
|
||||
//SpeedTests(device);
|
||||
|
||||
/*
|
||||
Continue initialization
|
||||
*/
|
||||
|
||||
video::IVideoDriver* driver = device->getVideoDriver();
|
||||
// These make the textures not to show at all
|
||||
//driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
|
||||
//driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
|
||||
|
||||
scene::ISceneManager* smgr = device->getSceneManager();
|
||||
|
||||
gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
|
||||
gui::IGUISkin* skin = guienv->getSkin();
|
||||
gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
|
||||
if(font)
|
||||
skin->setFont(font);
|
||||
//skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
|
||||
skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
|
||||
//skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
|
||||
//skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
|
||||
skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
|
||||
skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
|
||||
|
||||
const wchar_t *text = L"Loading...";
|
||||
core::vector2d<s32> center(screenW/2, screenH/2);
|
||||
core::dimension2d<u32> textd = font->getDimension(text);
|
||||
std::cout<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
|
||||
// Have to add a bit to disable the text from word wrapping
|
||||
//core::vector2d<s32> textsize(textd.Width+4, textd.Height);
|
||||
core::vector2d<s32> textsize(300, textd.Height);
|
||||
core::rect<s32> textrect(center - textsize/2, center + textsize/2);
|
||||
|
||||
gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
|
||||
text, textrect, false, false);
|
||||
gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
|
||||
|
||||
driver->beginScene(true, true, video::SColor(255,0,0,0));
|
||||
guienv->drawAll();
|
||||
driver->endScene();
|
||||
|
||||
video::SMaterial materials[MATERIALS_COUNT];
|
||||
for(u16 i=0; i<MATERIALS_COUNT; i++)
|
||||
{
|
||||
materials[i].Lighting = false;
|
||||
materials[i].BackfaceCulling = false;
|
||||
|
||||
const char *filename = g_material_filenames[i];
|
||||
if(filename != NULL){
|
||||
video::ITexture *t = driver->getTexture(filename);
|
||||
if(t == NULL){
|
||||
std::cout<<"Texture could not be loaded: \""
|
||||
<<filename<<"\""<<std::endl;
|
||||
return 1;
|
||||
}
|
||||
materials[i].setTexture(0, driver->getTexture(filename));
|
||||
}
|
||||
//materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT);
|
||||
materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
|
||||
//materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);
|
||||
}
|
||||
|
||||
// Make a scope here for the client so that it gets removed
|
||||
// before the irrlicht device
|
||||
{
|
||||
|
||||
std::cout<<"Creating server and client"<<std::endl;
|
||||
|
||||
Server *server = NULL;
|
||||
if(hosting){
|
||||
server = new Server();
|
||||
server->start(port);
|
||||
}
|
||||
|
||||
Client client(smgr, materials);
|
||||
Address connect_address(0,0,0,0, port);
|
||||
try{
|
||||
connect_address.Resolve(connect_name);
|
||||
}
|
||||
catch(ResolveError &e)
|
||||
{
|
||||
std::cout<<"Couldn't resolve address"<<std::endl;
|
||||
return 0;
|
||||
}
|
||||
client.connect(connect_address);
|
||||
|
||||
Player *player = client.getLocalPlayer();
|
||||
|
||||
/*
|
||||
Create the camera node
|
||||
*/
|
||||
|
||||
scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
|
||||
0, // Camera parent
|
||||
v3f(BS*100, BS*2, BS*100), // Look from
|
||||
v3f(BS*100+1, BS*2, BS*100), // Look to
|
||||
-1 // Camera ID
|
||||
);
|
||||
|
||||
if(camera == NULL)
|
||||
return 1;
|
||||
|
||||
camera->setFOV(FOV_ANGLE);
|
||||
// Just so big a value that everything rendered is visible
|
||||
camera->setFarValue(BS*1000);
|
||||
|
||||
f32 camera_yaw = 0; // "right/left"
|
||||
f32 camera_pitch = 0; // "up/down"
|
||||
|
||||
// Random constants
|
||||
#define WALK_ACCELERATION (4.0 * BS)
|
||||
#define WALKSPEED_MAX (4.0 * BS)
|
||||
//#define WALKSPEED_MAX (20.0 * BS)
|
||||
f32 walk_acceleration = WALK_ACCELERATION;
|
||||
f32 walkspeed_max = WALKSPEED_MAX;
|
||||
|
||||
/*
|
||||
The mouse cursor needs not be visible, so we hide it via the
|
||||
irr::IrrlichtDevice::ICursorControl.
|
||||
*/
|
||||
device->getCursorControl()->setVisible(false);
|
||||
|
||||
gui_loadingtext->remove();
|
||||
|
||||
gui::IGUIStaticText *guitext = guienv->addStaticText(
|
||||
L"Minetest-c55", core::rect<s32>(5, 5, 5+300, 5+textsize.Y),
|
||||
false, false);
|
||||
/*
|
||||
Main loop
|
||||
*/
|
||||
|
||||
bool first_loop_after_window_activation = true;
|
||||
|
||||
s32 lastFPS = -1;
|
||||
|
||||
// Time is in milliseconds
|
||||
u32 lasttime = device->getTimer()->getTime();
|
||||
|
||||
while(device->run())
|
||||
{
|
||||
/*
|
||||
Time difference calculation
|
||||
*/
|
||||
u32 time = device->getTimer()->getTime();
|
||||
f32 dtime; // in seconds
|
||||
if(time > lasttime)
|
||||
dtime = (time - lasttime) / 1000.0;
|
||||
else
|
||||
dtime = 0;
|
||||
lasttime = time;
|
||||
|
||||
updateViewingRange(dtime);
|
||||
|
||||
// Collected during the loop and displayed
|
||||
core::list< core::aabbox3d<f32> > hilightboxes;
|
||||
|
||||
/*
|
||||
Special keys
|
||||
*/
|
||||
if(receiver.IsKeyDown(irr::KEY_ESCAPE))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Player speed control
|
||||
*/
|
||||
|
||||
v3f move_direction = v3f(0,0,1);
|
||||
move_direction.rotateXZBy(camera_yaw);
|
||||
|
||||
v3f speed = v3f(0,0,0);
|
||||
if(receiver.IsKeyDown(irr::KEY_KEY_W))
|
||||
{
|
||||
speed += move_direction;
|
||||
}
|
||||
if(receiver.IsKeyDown(irr::KEY_KEY_S))
|
||||
{
|
||||
speed -= move_direction;
|
||||
}
|
||||
if(receiver.IsKeyDown(irr::KEY_KEY_A))
|
||||
{
|
||||
speed += move_direction.crossProduct(v3f(0,1,0));
|
||||
}
|
||||
if(receiver.IsKeyDown(irr::KEY_KEY_D))
|
||||
{
|
||||
speed += move_direction.crossProduct(v3f(0,-1,0));
|
||||
}
|
||||
if(receiver.IsKeyDown(irr::KEY_SPACE))
|
||||
{
|
||||
if(player->touching_ground){
|
||||
//player_speed.Y = 30*BS;
|
||||
//player.speed.Y = 5*BS;
|
||||
player->speed.Y = 6.5*BS;
|
||||
}
|
||||
}
|
||||
|
||||
// The speed of the player (Y is ignored)
|
||||
speed = speed.normalize() * walkspeed_max;
|
||||
|
||||
f32 inc = walk_acceleration * BS * dtime;
|
||||
|
||||
if(player->speed.X < speed.X - inc)
|
||||
player->speed.X += inc;
|
||||
else if(player->speed.X > speed.X + inc)
|
||||
player->speed.X -= inc;
|
||||
else if(player->speed.X < speed.X)
|
||||
player->speed.X = speed.X;
|
||||
else if(player->speed.X > speed.X)
|
||||
player->speed.X = speed.X;
|
||||
|
||||
if(player->speed.Z < speed.Z - inc)
|
||||
player->speed.Z += inc;
|
||||
else if(player->speed.Z > speed.Z + inc)
|
||||
player->speed.Z -= inc;
|
||||
else if(player->speed.Z < speed.Z)
|
||||
player->speed.Z = speed.Z;
|
||||
else if(player->speed.Z > speed.Z)
|
||||
player->speed.Z = speed.Z;
|
||||
|
||||
/*
|
||||
Process environment
|
||||
*/
|
||||
|
||||
{
|
||||
//TimeTaker("client.step(dtime)", device);
|
||||
client.step(dtime);
|
||||
}
|
||||
|
||||
if(server != NULL){
|
||||
//TimeTaker("server->step(dtime)", device);
|
||||
server->step(dtime);
|
||||
}
|
||||
|
||||
/*
|
||||
Mouse and camera control
|
||||
*/
|
||||
|
||||
if(device->isWindowActive())
|
||||
{
|
||||
if(first_loop_after_window_activation){
|
||||
first_loop_after_window_activation = false;
|
||||
}
|
||||
else{
|
||||
s32 dx = device->getCursorControl()->getPosition().X - 320;
|
||||
s32 dy = device->getCursorControl()->getPosition().Y - 240;
|
||||
camera_yaw -= dx*0.2;
|
||||
camera_pitch += dy*0.2;
|
||||
if(camera_pitch < -89.9) camera_pitch = -89.9;
|
||||
if(camera_pitch > 89.9) camera_pitch = 89.9;
|
||||
}
|
||||
device->getCursorControl()->setPosition(320, 240);
|
||||
}
|
||||
else{
|
||||
first_loop_after_window_activation = true;
|
||||
}
|
||||
|
||||
v3f camera_direction = v3f(0,0,1);
|
||||
camera_direction.rotateYZBy(camera_pitch);
|
||||
camera_direction.rotateXZBy(camera_yaw);
|
||||
|
||||
v3f camera_position =
|
||||
player->getPosition() + v3f(0, BS+BS/2, 0);
|
||||
|
||||
camera->setPosition(camera_position);
|
||||
camera->setTarget(camera_position + camera_direction);
|
||||
|
||||
if(FIELD_OF_VIEW_TEST){
|
||||
//client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
|
||||
client.updateCamera(v3f(0,0,0), v3f(0,0,1));
|
||||
}
|
||||
else{
|
||||
//client.m_env.getMap().updateCamera(camera_position, camera_direction);
|
||||
client.updateCamera(camera_position, camera_direction);
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate what block is the crosshair pointing to
|
||||
*/
|
||||
|
||||
//u32 t1 = device->getTimer()->getTime();
|
||||
|
||||
f32 d = 4; // max. distance
|
||||
core::line3d<f32> shootline(camera_position,
|
||||
camera_position + camera_direction * BS * (d+1));
|
||||
|
||||
bool nodefound = false;
|
||||
v3s16 nodepos;
|
||||
v3s16 neighbourpos;
|
||||
core::aabbox3d<f32> nodefacebox;
|
||||
f32 mindistance = BS * 1001;
|
||||
|
||||
v3s16 pos_i = Map::floatToInt(player->getPosition());
|
||||
|
||||
s16 a = d;
|
||||
s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
|
||||
s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
|
||||
s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
|
||||
s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
|
||||
s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
|
||||
s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
|
||||
|
||||
for(s16 y = ystart; y <= yend; y++){
|
||||
for(s16 z = zstart; z <= zend; z++){
|
||||
for(s16 x = xstart; x <= xend; x++)
|
||||
{
|
||||
try{
|
||||
//if(client.m_env.getMap().getNode(x,y,z).d == MATERIAL_AIR){
|
||||
if(client.getNode(v3s16(x,y,z)).d == MATERIAL_AIR){
|
||||
continue;
|
||||
}
|
||||
}catch(InvalidPositionException &e){
|
||||
continue;
|
||||
}
|
||||
|
||||
v3s16 np(x,y,z);
|
||||
v3f npf = Map::intToFloat(np);
|
||||
|
||||
f32 d = 0.01;
|
||||
|
||||
v3s16 directions[6] = {
|
||||
v3s16(0,0,1), // back
|
||||
v3s16(0,1,0), // top
|
||||
v3s16(1,0,0), // right
|
||||
v3s16(0,0,-1),
|
||||
v3s16(0,-1,0),
|
||||
v3s16(-1,0,0),
|
||||
};
|
||||
|
||||
for(u16 i=0; i<6; i++){
|
||||
//{u16 i=3;
|
||||
v3f dir_f = v3f(directions[i].X,
|
||||
directions[i].Y, directions[i].Z);
|
||||
v3f centerpoint = npf + dir_f * BS/2;
|
||||
f32 distance =
|
||||
(centerpoint - camera_position).getLength();
|
||||
|
||||
if(distance < mindistance){
|
||||
//std::cout<<"for centerpoint=("<<centerpoint.X<<","<<centerpoint.Y<<","<<centerpoint.Z<<"): distance < mindistance"<<std::endl;
|
||||
//std::cout<<"npf=("<<npf.X<<","<<npf.Y<<","<<npf.Z<<")"<<std::endl;
|
||||
core::CMatrix4<f32> m;
|
||||
m.buildRotateFromTo(v3f(0,0,1), dir_f);
|
||||
|
||||
// This is the back face
|
||||
v3f corners[2] = {
|
||||
v3f(BS/2, BS/2, BS/2),
|
||||
v3f(-BS/2, -BS/2, BS/2+d)
|
||||
};
|
||||
|
||||
for(u16 j=0; j<2; j++){
|
||||
m.rotateVect(corners[j]);
|
||||
corners[j] += npf;
|
||||
//std::cout<<"box corners["<<j<<"]: ("<<corners[j].X<<","<<corners[j].Y<<","<<corners[j].Z<<")"<<std::endl;
|
||||
}
|
||||
|
||||
//core::aabbox3d<f32> facebox(corners[0],corners[1]);
|
||||
core::aabbox3d<f32> facebox(corners[0]);
|
||||
facebox.addInternalPoint(corners[1]);
|
||||
|
||||
if(facebox.intersectsWithLine(shootline)){
|
||||
nodefound = true;
|
||||
nodepos = np;
|
||||
neighbourpos = np + directions[i];
|
||||
mindistance = distance;
|
||||
nodefacebox = facebox;
|
||||
}
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
if(nodefound){
|
||||
//std::cout<<"nodefound == true"<<std::endl;
|
||||
//std::cout<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
|
||||
//std::cout<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
|
||||
|
||||
static v3s16 nodepos_old(-1,-1,-1);
|
||||
if(nodepos != nodepos_old){
|
||||
std::cout<<"Pointing at ("<<nodepos.X<<","
|
||||
<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
|
||||
nodepos_old = nodepos;
|
||||
|
||||
/*wchar_t positiontext[20];
|
||||
swprintf(positiontext, 20, L"(%i,%i,%i)",
|
||||
nodepos.X, nodepos.Y, nodepos.Z);
|
||||
positiontextgui->setText(positiontext);*/
|
||||
}
|
||||
|
||||
hilightboxes.push_back(nodefacebox);
|
||||
|
||||
if(receiver.leftclicked){
|
||||
std::cout<<"Removing block (MapNode)"<<std::endl;
|
||||
u32 time1 = device->getTimer()->getRealTime();
|
||||
|
||||
//client.m_env.getMap().removeNodeAndUpdate(nodepos);
|
||||
client.removeNode(nodepos);
|
||||
|
||||
u32 time2 = device->getTimer()->getRealTime();
|
||||
u32 dtime = time2 - time1;
|
||||
std::cout<<"Took "<<dtime<<"ms"<<std::endl;
|
||||
}
|
||||
if(receiver.rightclicked){
|
||||
std::cout<<"Placing block (MapNode)"<<std::endl;
|
||||
u32 time1 = device->getTimer()->getRealTime();
|
||||
|
||||
/*f32 light = client.m_env.getMap().getNode(neighbourpos).light;
|
||||
MapNode n;
|
||||
n.d = g_selected_material;
|
||||
client.m_env.getMap().setNode(neighbourpos, n);
|
||||
client.m_env.getMap().nodeAddedUpdate(neighbourpos, light);*/
|
||||
MapNode n;
|
||||
n.d = g_selected_material;
|
||||
client.addNode(neighbourpos, n);
|
||||
|
||||
u32 time2 = device->getTimer()->getRealTime();
|
||||
u32 dtime = time2 - time1;
|
||||
std::cout<<"Took "<<dtime<<"ms"<<std::endl;
|
||||
}
|
||||
}
|
||||
else{
|
||||
//std::cout<<"nodefound == false"<<std::endl;
|
||||
//positiontextgui->setText(L"");
|
||||
}
|
||||
|
||||
receiver.leftclicked = false;
|
||||
receiver.rightclicked = false;
|
||||
|
||||
/*
|
||||
Update gui stuff
|
||||
*/
|
||||
|
||||
static u8 old_selected_material = MATERIAL_AIR;
|
||||
if(g_selected_material != old_selected_material)
|
||||
{
|
||||
old_selected_material = g_selected_material;
|
||||
wchar_t temptext[50];
|
||||
swprintf(temptext, 50, L"Minetest-c55 (F: material=%i)",
|
||||
g_selected_material);
|
||||
guitext->setText(temptext);
|
||||
}
|
||||
|
||||
/*
|
||||
Drawing begins
|
||||
*/
|
||||
|
||||
/*
|
||||
Background color is choosen based on whether the player is
|
||||
much beyond the initial ground level
|
||||
*/
|
||||
/*video::SColor bgcolor;
|
||||
v3s16 p0 = Map::floatToInt(player->position);
|
||||
s16 gy = client.m_env.getMap().getGroundHeight(v2s16(p0.X, p0.Z));
|
||||
if(p0.Y > gy - MAP_BLOCKSIZE)
|
||||
bgcolor = video::SColor(255,90,140,200);
|
||||
else
|
||||
bgcolor = video::SColor(255,0,0,0);*/
|
||||
video::SColor bgcolor = video::SColor(255,90,140,200);
|
||||
|
||||
driver->beginScene(true, true, bgcolor);
|
||||
|
||||
//std::cout<<"smgr->drawAll()"<<std::endl;
|
||||
|
||||
smgr->drawAll();
|
||||
|
||||
core::vector2d<s32> displaycenter(screenW/2,screenH/2);
|
||||
driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
|
||||
displaycenter + core::vector2d<s32>(10,0),
|
||||
video::SColor(255,255,255,255));
|
||||
driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
|
||||
displaycenter + core::vector2d<s32>(0,10),
|
||||
video::SColor(255,255,255,255));
|
||||
|
||||
video::SMaterial m;
|
||||
m.Thickness = 10;
|
||||
m.Lighting = false;
|
||||
driver->setMaterial(m);
|
||||
|
||||
for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
|
||||
i != hilightboxes.end(); i++){
|
||||
driver->draw3DBox(*i, video::SColor(255,0,0,0));
|
||||
}
|
||||
|
||||
guienv->drawAll();
|
||||
|
||||
driver->endScene();
|
||||
|
||||
/*
|
||||
Drawing ends
|
||||
*/
|
||||
|
||||
u16 fps = driver->getFPS();
|
||||
|
||||
if (lastFPS != fps)
|
||||
{
|
||||
core::stringw str = L"Minetest [";
|
||||
str += driver->getName();
|
||||
str += "] FPS:";
|
||||
str += fps;
|
||||
|
||||
device->setWindowCaption(str.c_str());
|
||||
lastFPS = fps;
|
||||
}
|
||||
|
||||
|
||||
/*}
|
||||
else
|
||||
device->yield();*/
|
||||
}
|
||||
|
||||
if(server != NULL)
|
||||
delete server;
|
||||
|
||||
} // client is deleted at this point
|
||||
|
||||
/*
|
||||
In the end, delete the Irrlicht device.
|
||||
*/
|
||||
device->drop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//END
|
26
src/main.h
Normal file
26
src/main.h
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
(c) 2010 Perttu Ahola <celeron55@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef MAIN_HEADER
|
||||
#define MAIN_HEADER
|
||||
|
||||
#include <jmutex.h>
|
||||
|
||||
#define PI 3.14159
|
||||
|
||||
#define FOV_ANGLE (PI/2.5)
|
||||
|
||||
// Change to struct settings or something
|
||||
extern s16 g_viewing_range_nodes;
|
||||
extern JMutex g_viewing_range_nodes_mutex;
|
||||
|
||||
#include <fstream>
|
||||
|
||||
// Debug streams
|
||||
extern std::ostream dout_con;
|
||||
extern std::ostream dout_client;
|
||||
extern std::ostream dout_server;
|
||||
|
||||
#endif
|
||||
|
1204
src/map.cpp
Normal file
1204
src/map.cpp
Normal file
File diff suppressed because it is too large
Load Diff
380
src/map.h
Normal file
380
src/map.h
Normal file
@ -0,0 +1,380 @@
|
||||
/*
|
||||
(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"
|
||||
|
||||
/*
|
||||
|
||||
TODO: Automatically unload blocks from memory and save on disk
|
||||
when they are far away
|
||||
*/
|
||||
|
||||
//void limitBox(core::aabbox3d<s16> & box, core::aabbox3d<s16> & limits);
|
||||
|
||||
class Map;
|
||||
|
||||
class MapUpdateThread : public JThread
|
||||
{
|
||||
bool run;
|
||||
JMutex run_mutex;
|
||||
|
||||
Map *map;
|
||||
|
||||
public:
|
||||
|
||||
MapUpdateThread(Map *the_map) : JThread(), run(true), map(the_map)
|
||||
{
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
class Map : public NodeContainer, public Heightmappish
|
||||
{
|
||||
public:
|
||||
/*
|
||||
TODO: Dynamic block loading
|
||||
Add a filename to the constructor, in which the map will be
|
||||
automatically stored - or something.
|
||||
*/
|
||||
|
||||
protected:
|
||||
|
||||
core::map<v2s16, MapSector*> m_sectors;
|
||||
JMutex m_getsector_mutex;
|
||||
JMutex m_gensector_mutex;
|
||||
|
||||
v3f camera_position;
|
||||
v3f camera_direction;
|
||||
JMutex camera_mutex;
|
||||
|
||||
MapUpdateThread updater;
|
||||
|
||||
UnlimitedHeightmap m_heightmap;
|
||||
|
||||
// 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;
|
||||
|
||||
//LoadStatus status;
|
||||
|
||||
Map();
|
||||
~Map();
|
||||
|
||||
void updateCamera(v3f pos, v3f dir)
|
||||
{
|
||||
camera_mutex.Lock();
|
||||
camera_position = pos;
|
||||
camera_direction = dir;
|
||||
camera_mutex.Unlock();
|
||||
}
|
||||
|
||||
void StartUpdater()
|
||||
{
|
||||
updater.Start();
|
||||
}
|
||||
|
||||
void StopUpdater()
|
||||
{
|
||||
updater.setRun(false);
|
||||
while(updater.IsRunning())
|
||||
sleep_s(1);
|
||||
}
|
||||
|
||||
bool UpdaterIsRunning()
|
||||
{
|
||||
return updater.IsRunning();
|
||||
}
|
||||
|
||||
/*
|
||||
Returns integer position of the node in given
|
||||
floating point position.
|
||||
*/
|
||||
static 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;
|
||||
}
|
||||
|
||||
static v3f intToFloat(v3s16 p)
|
||||
{
|
||||
v3f p2(
|
||||
p.X * BS,
|
||||
p.Y * BS,
|
||||
p.Z * BS
|
||||
);
|
||||
return p2;
|
||||
}
|
||||
|
||||
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);
|
||||
virtual MapSector * getSector(v2s16 p);
|
||||
|
||||
MapBlock * getBlockNoCreate(v3s16 p);
|
||||
virtual MapBlock * getBlock(v3s16 p);
|
||||
|
||||
f32 getGroundHeight(v2s16 p, bool generate=false);
|
||||
void setGroundHeight(v2s16 p, f32 y, bool generate=false);
|
||||
|
||||
bool isValidPosition(v3s16 p)
|
||||
{
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
MapBlock * blockref = getBlockNoCreate(blockpos);
|
||||
if(blockref == NULL){
|
||||
/*std::cout<<"Map::isValidPosition("<<p.X<<","<<p.Y<<","<<p.Z
|
||||
<<"): is_valid=0 (block inexistent)";*/
|
||||
return false;
|
||||
}
|
||||
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
|
||||
bool is_valid = blockref->isValidPosition(relpos);
|
||||
/*std::cout<<"Map::isValidPosition("<<p.X<<","<<p.Y<<","<<p.Z
|
||||
<<"): is_valid="<<is_valid<<" (block reported)";*/
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the position of the block where the node is located
|
||||
*/
|
||||
v3s16 getNodeBlockPos(v3s16 p)
|
||||
{
|
||||
return v3s16(
|
||||
(p.X>=0 ? p.X : p.X-MAP_BLOCKSIZE+1) / MAP_BLOCKSIZE,
|
||||
(p.Y>=0 ? p.Y : p.Y-MAP_BLOCKSIZE+1) / MAP_BLOCKSIZE,
|
||||
(p.Z>=0 ? p.Z : p.Z-MAP_BLOCKSIZE+1) / MAP_BLOCKSIZE);
|
||||
}
|
||||
|
||||
v2s16 getNodeSectorPos(v2s16 p)
|
||||
{
|
||||
return v2s16(
|
||||
(p.X>=0 ? p.X : p.X-MAP_BLOCKSIZE+1) / MAP_BLOCKSIZE,
|
||||
(p.Y>=0 ? p.Y : p.Y-MAP_BLOCKSIZE+1) / MAP_BLOCKSIZE);
|
||||
}
|
||||
|
||||
s16 getNodeBlockY(s16 y)
|
||||
{
|
||||
return (y>=0 ? y : y-MAP_BLOCKSIZE+1) / MAP_BLOCKSIZE;
|
||||
}
|
||||
|
||||
MapBlock * getNodeBlock(v3s16 p)
|
||||
{
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
return getBlock(blockpos);
|
||||
}
|
||||
|
||||
MapNode getNode(v3s16 p)
|
||||
{
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
MapBlock * blockref = getBlockNoCreate(blockpos);
|
||||
if(blockref == NULL)
|
||||
throw InvalidPositionException();
|
||||
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
|
||||
|
||||
return blockref->getNode(relpos);
|
||||
}
|
||||
|
||||
MapNode getNode(s16 x, s16 y, s16 z)
|
||||
{
|
||||
return getNode(v3s16(x,y,z));
|
||||
}
|
||||
|
||||
void setNode(v3s16 p, MapNode & n)
|
||||
{
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
MapBlock * blockref = getBlockNoCreate(blockpos);
|
||||
if(blockref == NULL)
|
||||
throw InvalidPositionException();
|
||||
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
|
||||
blockref->setNode(relpos, n);
|
||||
}
|
||||
|
||||
void setNode(s16 x, s16 y, s16 z, MapNode & n)
|
||||
{
|
||||
setNode(v3s16(x,y,z), n);
|
||||
}
|
||||
|
||||
|
||||
MapNode getNode(v3f p)
|
||||
{
|
||||
return getNode(floatToInt(p));
|
||||
}
|
||||
|
||||
void drawbox(s16 x0, s16 y0, s16 z0, s16 w, s16 h, s16 d, MapNode node)
|
||||
{
|
||||
x0 += drawoffset.X;
|
||||
y0 += drawoffset.Y;
|
||||
z0 += drawoffset.Z;
|
||||
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);
|
||||
}
|
||||
|
||||
void drawslope(s16 x0, s16 y0, s16 z0, s16 w, s16 h, s16 d, s16 dy_x, s16 dy_z, MapNode node)
|
||||
{
|
||||
x0 += drawoffset.X;
|
||||
y0 += drawoffset.Y;
|
||||
z0 += drawoffset.Z;
|
||||
for(u16 z=0; z<d; z++){
|
||||
for(u16 x=0; x<w; x++){
|
||||
for(u16 y=0; y < h + z*dy_z + x*dy_x; y++){
|
||||
setNode(x0+x, y0+y, z0+z, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unLightNeighbors(v3s16 pos, f32 oldlight,
|
||||
core::list<v3s16> & light_sources,
|
||||
core::map<v3s16, MapBlock*> & modified_blocks);
|
||||
|
||||
/*void lightNeighbors(v3s16 pos, f32 oldlight,
|
||||
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::list< MapBlock*> & a_blocks,
|
||||
core::map<v3s16, MapBlock*> & modified_blocks);
|
||||
|
||||
void nodeAddedUpdate(v3s16 p, f32 light);
|
||||
void removeNodeAndUpdate(v3s16 p);
|
||||
|
||||
core::aabbox3d<s16> getDisplayedBlockArea();
|
||||
|
||||
/*void generateBlock(MapBlock *block);
|
||||
void generateMaster();*/
|
||||
|
||||
bool updateChangedVisibleArea();
|
||||
|
||||
void renderMap(video::IVideoDriver* driver,
|
||||
video::SMaterial *materials);
|
||||
|
||||
};
|
||||
|
||||
class MasterMap : public Map
|
||||
{
|
||||
public:
|
||||
MapSector * getSector(v2s16 p);
|
||||
};
|
||||
|
||||
class Client;
|
||||
|
||||
class ClientMap : public Map, public scene::ISceneNode
|
||||
{
|
||||
public:
|
||||
ClientMap(
|
||||
Client *client,
|
||||
video::SMaterial *materials,
|
||||
scene::ISceneNode* parent,
|
||||
scene::ISceneManager* mgr,
|
||||
s32 id
|
||||
);
|
||||
|
||||
MapSector * getSector(v2s16 p);
|
||||
|
||||
/*
|
||||
ISceneNode methods
|
||||
*/
|
||||
|
||||
virtual void OnRegisterSceneNode()
|
||||
{
|
||||
if (IsVisible)
|
||||
SceneManager->registerNodeForRendering(this);
|
||||
|
||||
ISceneNode::OnRegisterSceneNode();
|
||||
}
|
||||
|
||||
virtual void render()
|
||||
{
|
||||
video::IVideoDriver* driver = SceneManager->getVideoDriver();
|
||||
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
|
||||
renderMap(driver, m_materials);
|
||||
}
|
||||
|
||||
virtual const core::aabbox3d<f32>& getBoundingBox() const
|
||||
{
|
||||
return m_box;
|
||||
}
|
||||
|
||||
/*virtual u32 getMaterialCount() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual video::SMaterial& getMaterial(u32 i)
|
||||
{
|
||||
return materials[0];
|
||||
}*/
|
||||
|
||||
private:
|
||||
Client *m_client;
|
||||
|
||||
video::SMaterial *m_materials;
|
||||
|
||||
core::aabbox3d<f32> m_box;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
448
src/mapblock.cpp
Normal file
448
src/mapblock.cpp
Normal file
@ -0,0 +1,448 @@
|
||||
#include "mapblock.h"
|
||||
#include "map.h"
|
||||
|
||||
bool MapBlock::isValidPositionParent(v3s16 p)
|
||||
{
|
||||
if(isValidPosition(p))
|
||||
{
|
||||
//std::cout<<"[("<<p.X<<","<<p.Y<<","<<p.Z<<") valid pos. in block]"<<std::endl;
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
//std::cout<<"[("<<p.X<<","<<p.Y<<","<<p.Z<<") not valid pos. in block]"<<std::endl;
|
||||
return m_parent->isValidPosition(getPosRelative() + p);
|
||||
}
|
||||
}
|
||||
|
||||
MapNode MapBlock::getNodeParent(v3s16 p)
|
||||
{
|
||||
if(isValidPosition(p) == false)
|
||||
{
|
||||
/*std::cout<<"MapBlock::getNodeParent(("
|
||||
<<p.X<<","<<p.Y<<","<<p.Z<<"))"
|
||||
<<": getting from parent"<<std::endl;*/
|
||||
return m_parent->getNode(getPosRelative() + p);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*std::cout<<"MapBlock::getNodeParent(("
|
||||
<<p.X<<","<<p.Y<<","<<p.Z<<"))"
|
||||
<<": getting from self"<<std::endl;*/
|
||||
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
|
||||
{
|
||||
data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
|
||||
}
|
||||
}
|
||||
|
||||
FastFace * MapBlock::makeFastFace(u8 material, light_t 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 right
|
||||
*/
|
||||
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 = (1.0+light)/2 * 255.0; //DEBUG
|
||||
u8 li = light * 255.0;
|
||||
//u8 li = light / 128;
|
||||
video::SColor c = video::SColor(255,li,li,li);
|
||||
|
||||
//video::SColor c = video::SColor(255,255,255,255);
|
||||
//video::SColor c = video::SColor(255,64,64,64);
|
||||
|
||||
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;
|
||||
//f->direction = direction;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/*
|
||||
Parameters must consist of air and !air.
|
||||
Order doesn't matter.
|
||||
|
||||
If either of the nodes doesn't exist, light is 0.
|
||||
*/
|
||||
light_t MapBlock::getFaceLight(v3s16 p, v3s16 face_dir)
|
||||
{
|
||||
//return 1.0;
|
||||
|
||||
// Make some nice difference to different sides
|
||||
//float factor = ((face_dir.X != 0) ? 0.75 : 1.0);
|
||||
float factor;
|
||||
/*if(face_dir.X != 0)
|
||||
factor = 0.70;
|
||||
else if(face_dir.Z != 0)
|
||||
factor = 0.90;
|
||||
else
|
||||
factor = 1.00;*/
|
||||
if(face_dir.X == 1 || face_dir.Z == 1)
|
||||
factor = 0.70;
|
||||
else if(face_dir.X == -1 || face_dir.Z == -1)
|
||||
factor = 0.90;
|
||||
else
|
||||
factor = 1.00;
|
||||
|
||||
try{
|
||||
MapNode n = getNodeParent(p);
|
||||
MapNode n2 = getNodeParent(p + face_dir);
|
||||
if(n.transparent())
|
||||
return n.light * factor;
|
||||
else{
|
||||
return n2.light * factor;
|
||||
}
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
{
|
||||
return 0.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)
|
||||
{
|
||||
// The maximum difference in light to tolerate in the same face
|
||||
// TODO: make larger?
|
||||
//light_t max_light_diff = 0.15;
|
||||
light_t max_light_diff = 0.2;
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
light_t 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;
|
||||
light_t 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
|
||||
&& fabs(light_next - light) / (light+0.01) < max_light_diff)
|
||||
{
|
||||
next_is_different = false;
|
||||
}
|
||||
}
|
||||
|
||||
continuous_materials_count++;
|
||||
|
||||
if(next_is_different)
|
||||
{
|
||||
/*
|
||||
If there is a face (= materials differ and the other one is air)
|
||||
*/
|
||||
if(material0 != material1
|
||||
&& material0 != MATERIAL_IGNORE
|
||||
&& material1 != MATERIAL_IGNORE
|
||||
&& (material0 == MATERIAL_AIR
|
||||
|| material1 == MATERIAL_AIR)){
|
||||
// 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 is not air
|
||||
if(material0 != MATERIAL_AIR){
|
||||
f = makeFastFace(material0, light,
|
||||
sp, face_dir_f, scale,
|
||||
posRelative_f);
|
||||
}
|
||||
// If node at sp is air
|
||||
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::updateFastFaces()
|
||||
{
|
||||
/*v3s16 p = getPosRelative();
|
||||
std::cout<<"MapBlock("<<p.X<<","<<p.Y<<","<<p.Z<<")"
|
||||
<<"::updateFastFaces(): ";*/
|
||||
//<<"::updateFastFaces()"<<std::endl;
|
||||
|
||||
core::list<FastFace*> *fastfaces_new = new core::list<FastFace*>;
|
||||
|
||||
/*
|
||||
These are started and stopped one node overborder to
|
||||
take changes in those blocks in account.
|
||||
|
||||
TODO: This could be optimized by combining it with updateLighting, as
|
||||
it can record the surfaces that can be seen.
|
||||
*/
|
||||
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Replace the list
|
||||
*/
|
||||
|
||||
core::list<FastFace*> *fastfaces_old = fastfaces;
|
||||
|
||||
fastfaces_mutex.Lock();
|
||||
fastfaces = fastfaces_new;
|
||||
fastfaces_mutex.Unlock();
|
||||
|
||||
/*std::cout<<" Number of faces: old="<<fastfaces_old->getSize()
|
||||
<<" new="<<fastfaces_new->getSize()<<std::endl;*/
|
||||
|
||||
/*
|
||||
Clear and dump the old list
|
||||
*/
|
||||
|
||||
core::list<FastFace*>::Iterator i = fastfaces_old->begin();
|
||||
for(; i != fastfaces_old->end(); i++)
|
||||
{
|
||||
delete *i;
|
||||
}
|
||||
fastfaces_old->clear();
|
||||
delete fastfaces_old;
|
||||
|
||||
//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 true if some sunlight touches the bottom of the block.
|
||||
|
||||
If there is a block above, continues from it.
|
||||
If there is no block above, assumes there is sunlight, unless
|
||||
probably_dark is set.
|
||||
*/
|
||||
|
||||
bool MapBlock::propagateSunlight()
|
||||
{
|
||||
bool sunlight_touches_bottom = false;
|
||||
bool has_some_sunlight = false;
|
||||
|
||||
for(s16 x=0; x<MAP_BLOCKSIZE; x++)
|
||||
{
|
||||
for(s16 z=0; z<MAP_BLOCKSIZE; z++)
|
||||
{
|
||||
// Check if node above block has sunlight
|
||||
try{
|
||||
MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
|
||||
if(n.light < 0.999){
|
||||
// No sunlight here
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
{
|
||||
// Assume sunlight, unless probably_dark==true
|
||||
if(probably_dark)
|
||||
continue;
|
||||
}
|
||||
|
||||
has_some_sunlight = true;
|
||||
|
||||
// Continue spreading sunlight downwards
|
||||
s16 y = MAP_BLOCKSIZE-1;
|
||||
for(; y >= 0; y--){
|
||||
v3s16 pos(x, y, z);
|
||||
|
||||
MapNode *n = getNodePtr(pos);
|
||||
if(n == NULL)
|
||||
break;
|
||||
|
||||
if(n->transparent())
|
||||
{
|
||||
n->light = 1.0;
|
||||
}
|
||||
else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(y == -1)
|
||||
sunlight_touches_bottom = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*std::cout<<"MapBlock("<<m_pos.X<<","<<m_pos.Y<<","
|
||||
<<m_pos.Z<<") has_some_sunlight="
|
||||
<<has_some_sunlight<<std::endl;*/
|
||||
return sunlight_touches_bottom;
|
||||
}
|
||||
|
||||
u32 MapBlock::serializedLength()
|
||||
{
|
||||
//return 1 + MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE*1;
|
||||
return 1 + MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE*1;
|
||||
}
|
||||
|
||||
void MapBlock::serialize(u8 *dest)
|
||||
{
|
||||
dest[0] = probably_dark;
|
||||
for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
|
||||
{
|
||||
dest[i+1] = data[i].d;
|
||||
}
|
||||
}
|
||||
|
||||
void MapBlock::deSerialize(u8 *source)
|
||||
{
|
||||
probably_dark = source[0];
|
||||
for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
|
||||
{
|
||||
data[i].d = source[i+1];
|
||||
}
|
||||
setChangedFlag();
|
||||
}
|
||||
|
||||
|
275
src/mapblock.h
Normal file
275
src/mapblock.h
Normal file
@ -0,0 +1,275 @@
|
||||
/*
|
||||
(c) 2010 Perttu Ahola <celeron55@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef MAPBLOCK_HEADER
|
||||
#define MAPBLOCK_HEADER
|
||||
|
||||
#include <jmutex.h>
|
||||
#include <assert.h>
|
||||
#include <exception>
|
||||
#include "common_irrlicht.h"
|
||||
#include "mapnode.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
//#define MAP_BLOCKSIZE 20
|
||||
//#define MAP_BLOCKSIZE 32
|
||||
#define MAP_BLOCKSIZE 16
|
||||
//#define MAP_BLOCKSIZE 8
|
||||
|
||||
// Named by looking towards z+
|
||||
enum{
|
||||
FACE_BACK=0,
|
||||
FACE_TOP,
|
||||
FACE_RIGHT,
|
||||
FACE_FRONT,
|
||||
FACE_BOTTOM,
|
||||
FACE_LEFT
|
||||
};
|
||||
|
||||
struct FastFace
|
||||
{
|
||||
u8 material;
|
||||
/*u16 x;
|
||||
u16 y;
|
||||
u16 z;*/
|
||||
video::S3DVertex vertices[4]; // Precalculated vertices
|
||||
};
|
||||
|
||||
class NodeContainer
|
||||
{
|
||||
public:
|
||||
//TODO: Change these exceptions to something more descriptive
|
||||
virtual bool isValidPosition(v3s16 p)
|
||||
{
|
||||
throw InvalidPositionException();
|
||||
}
|
||||
virtual MapNode getNode(v3s16 p) {
|
||||
throw InvalidPositionException();
|
||||
}
|
||||
virtual void setNode(v3s16 p, MapNode & n) {
|
||||
throw InvalidPositionException();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class MapBlock : public NodeContainer
|
||||
{
|
||||
private:
|
||||
|
||||
NodeContainer *m_parent;
|
||||
v3s16 m_pos; // Position in blocks on parent
|
||||
MapNode * data;
|
||||
bool changed;
|
||||
bool probably_dark;
|
||||
|
||||
public:
|
||||
|
||||
core::list<FastFace*> *fastfaces;
|
||||
JMutex fastfaces_mutex;
|
||||
|
||||
MapBlock(NodeContainer *parent, v3s16 pos)
|
||||
: m_parent(parent), m_pos(pos), changed(true)
|
||||
{
|
||||
data = NULL;
|
||||
reallocate();
|
||||
fastfaces = new core::list<FastFace*>;
|
||||
fastfaces_mutex.Init();
|
||||
}
|
||||
|
||||
~MapBlock()
|
||||
{
|
||||
fastfaces_mutex.Lock();
|
||||
core::list<FastFace*>::Iterator i = fastfaces->begin();
|
||||
for(; i != fastfaces->end(); i++)
|
||||
{
|
||||
delete *i;
|
||||
}
|
||||
delete fastfaces;
|
||||
fastfaces_mutex.Unlock();
|
||||
|
||||
if(data)
|
||||
free(data);
|
||||
}
|
||||
|
||||
bool getChangedFlag()
|
||||
{
|
||||
return changed;
|
||||
}
|
||||
|
||||
void resetChangedFlag()
|
||||
{
|
||||
/*v3s16 p = getPosRelative();
|
||||
std::cout<<"MapBlock("<<p.X<<","<<p.Y<<","<<p.Z<<")"
|
||||
<<"::resetChangedFlag()"
|
||||
<<std::endl;*/
|
||||
|
||||
changed = false;
|
||||
}
|
||||
|
||||
void setChangedFlag()
|
||||
{
|
||||
/*v3s16 p = getPosRelative();
|
||||
std::cout<<"MapBlock("<<p.X<<","<<p.Y<<","<<p.Z<<")"
|
||||
<<"::setChangedFlag()"
|
||||
<<std::endl;*/
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
v3s16 getPos()
|
||||
{
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
v3s16 getPosRelative()
|
||||
{
|
||||
//return posRelative;
|
||||
return m_pos * MAP_BLOCKSIZE;
|
||||
}
|
||||
|
||||
//TODO: remove
|
||||
v3s16 getSizeNodes()
|
||||
{
|
||||
//return size;
|
||||
return v3s16(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
|
||||
}
|
||||
|
||||
bool getProbablyDark()
|
||||
{
|
||||
return probably_dark;
|
||||
}
|
||||
void setProbablyDark(bool a_probably_dark)
|
||||
{
|
||||
probably_dark = a_probably_dark;
|
||||
}
|
||||
|
||||
core::aabbox3d<s16> getBox()
|
||||
{
|
||||
return core::aabbox3d<s16>(getPosRelative(),
|
||||
getPosRelative() + getSizeNodes() - v3s16(1,1,1));
|
||||
}
|
||||
|
||||
void reallocate()
|
||||
{
|
||||
//data.clear();
|
||||
if(data != NULL)
|
||||
free(data);
|
||||
u32 l = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE;
|
||||
//data.reallocate(l);
|
||||
data = (MapNode*)malloc(sizeof(MapNode) * l);
|
||||
for(u32 i=0; i<l; i++){
|
||||
//data.push_back(MapNode());
|
||||
data[i] = MapNode();
|
||||
}
|
||||
}
|
||||
|
||||
bool isValidPosition(v3s16 p)
|
||||
{
|
||||
return (p.X >= 0 && p.X < MAP_BLOCKSIZE
|
||||
&& p.Y >= 0 && p.Y < MAP_BLOCKSIZE
|
||||
&& p.Z >= 0 && p.Z < MAP_BLOCKSIZE);
|
||||
}
|
||||
/*
|
||||
As long as a reference of this MapBlock is kept,
|
||||
the pointers returned by these can be used.
|
||||
*/
|
||||
MapNode * getNodePtr(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 * getNodePtr(v3s16 p)
|
||||
{
|
||||
return getNodePtr(p.X, p.Y, p.Z);
|
||||
}
|
||||
|
||||
/*
|
||||
Regular MapNode get-setters
|
||||
*/
|
||||
|
||||
MapNode getNode(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 getNode(v3s16 p)
|
||||
{
|
||||
return getNode(p.X, p.Y, p.Z);
|
||||
}
|
||||
|
||||
void setNode(s16 x, s16 y, s16 z, MapNode & n)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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, light_t light, v3f p,
|
||||
v3f dir, v3f scale, v3f posRelative_f);
|
||||
|
||||
light_t 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);
|
||||
|
||||
/*
|
||||
Find all faces being between material and air
|
||||
*/
|
||||
void updateFastFaces();
|
||||
|
||||
/*
|
||||
Propagates sunlight down through the block.
|
||||
Returns true if some sunlight touches the bottom of the block.
|
||||
*/
|
||||
|
||||
bool propagateSunlight();
|
||||
|
||||
static u32 serializedLength();
|
||||
void serialize(u8 *dest);
|
||||
void deSerialize(u8 *source);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
102
src/mapnode.h
Normal file
102
src/mapnode.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
(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"
|
||||
|
||||
// Size of node in rendering units
|
||||
#define BS 10
|
||||
|
||||
#define MATERIALS_COUNT 254
|
||||
|
||||
// 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
|
||||
|
||||
#define USEFUL_MATERIAL_COUNT 4
|
||||
|
||||
enum Material
|
||||
{
|
||||
MATERIAL_STONE=0,
|
||||
MATERIAL_GRASS,
|
||||
/*
|
||||
For water, the param is water pressure. 0...127.
|
||||
|
||||
- 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.
|
||||
|
||||
TODO: Before implementing water, do a server-client framework.
|
||||
*/
|
||||
MATERIAL_WATER,
|
||||
MATERIAL_TORCH,
|
||||
};
|
||||
|
||||
struct MapNode
|
||||
{
|
||||
//TODO: block type to differ from material (e.g. grass edges)
|
||||
u8 d; // block type
|
||||
light_t light;
|
||||
s8 param; // Initialized to 0
|
||||
|
||||
MapNode(const MapNode & n)
|
||||
{
|
||||
*this = n;
|
||||
}
|
||||
|
||||
MapNode(u8 data=MATERIAL_AIR, light_t a_light=LIGHT_MIN, u8 a_param=0)
|
||||
//MapNode(u8 data=MATERIAL_AIR, light_t a_light=LIGHT_MAX, u8 a_param=0)
|
||||
{
|
||||
d = data;
|
||||
light = a_light;
|
||||
param = a_param;
|
||||
}
|
||||
|
||||
/*
|
||||
If true, the node allows light propagation
|
||||
*/
|
||||
bool transparent()
|
||||
{
|
||||
return (d == MATERIAL_AIR || d == MATERIAL_TORCH);
|
||||
}
|
||||
|
||||
light_t light_source()
|
||||
{
|
||||
if(d == MATERIAL_TORCH)
|
||||
return 0.9;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static u32 serializedLength()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
void serialize(u8 *dest)
|
||||
{
|
||||
dest[0] = d;
|
||||
dest[1] = param;
|
||||
}
|
||||
void deSerialize(u8 *source)
|
||||
{
|
||||
d = source[0];
|
||||
param = source[1];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
252
src/mapsector.cpp
Normal file
252
src/mapsector.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
#include "mapsector.h"
|
||||
#include "jmutexautolock.h"
|
||||
#include "client.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
HeightmapBlockGenerator::HeightmapBlockGenerator
|
||||
(v2s16 p2d, Heightmap * masterheightmap):
|
||||
m_heightmap(NULL)
|
||||
{
|
||||
m_heightmap = new FixedHeightmap(masterheightmap,
|
||||
p2d, MAP_BLOCKSIZE);
|
||||
}
|
||||
|
||||
/*HeightmapBlockGenerator::HeightmapBlockGenerator
|
||||
(MapSector *sector, Heightmap * masterheightmap):
|
||||
m_heightmap(NULL)
|
||||
{
|
||||
m_heightmap = new FixedHeightmap(masterheightmap,
|
||||
sector->getPos(), MAP_BLOCKSIZE);
|
||||
sector->setHeightmap(m_heightmap);
|
||||
}*/
|
||||
|
||||
MapBlock * HeightmapBlockGenerator::makeBlock(MapSector *sector, s16 block_y)
|
||||
{
|
||||
MapBlock *block = sector->createBlankBlockNoInsert(block_y);
|
||||
|
||||
// Randomize a bit. This makes dungeons.
|
||||
bool low_block_is_empty = false;
|
||||
if(rand() % 4 == 0)
|
||||
low_block_is_empty = true;
|
||||
|
||||
u8 material = MATERIAL_GRASS;
|
||||
s32 avg_ground_y = 0.0;
|
||||
|
||||
for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++){
|
||||
for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++){
|
||||
s16 target_y = m_heightmap->getGroundHeight(v2s16(x0,z0));
|
||||
avg_ground_y += target_y;
|
||||
|
||||
if(m_heightmap->getSlope(v2s16(x0,z0)).getLength() > 1.05)
|
||||
material = MATERIAL_STONE;
|
||||
else
|
||||
material = MATERIAL_GRASS;
|
||||
|
||||
for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++){
|
||||
s16 real_y = block_y * MAP_BLOCKSIZE + y0;
|
||||
MapNode n;
|
||||
// If node is very low
|
||||
if(real_y <= target_y - 10){
|
||||
if(low_block_is_empty){
|
||||
n.d = MATERIAL_AIR;
|
||||
}
|
||||
else{
|
||||
n.d = MATERIAL_STONE;
|
||||
}
|
||||
}
|
||||
// If node is low
|
||||
else if(real_y <= target_y - 6)
|
||||
n.d = MATERIAL_STONE;
|
||||
// If node is at or under heightmap y
|
||||
else if(real_y <= target_y)
|
||||
n.d = material;
|
||||
// If node is over heightmap y
|
||||
else
|
||||
n.d = MATERIAL_AIR;
|
||||
block->setNode(v3s16(x0,y0,z0), n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
avg_ground_y /= MAP_BLOCKSIZE * MAP_BLOCKSIZE;
|
||||
bool probably_dark = (block_y+1) * MAP_BLOCKSIZE < avg_ground_y;
|
||||
block->setProbablyDark(probably_dark);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
MapBlock * ClientBlockGenerator::makeBlock(MapSector *sector, s16 block_y)
|
||||
{
|
||||
//std::cout<<"ClientBlockGenerator::makeBlock()"<<std::endl;
|
||||
/*
|
||||
TODO:
|
||||
Add the block to the client's fetch queue.
|
||||
As of now, the only other thing we can do is to throw an exception.
|
||||
*/
|
||||
v3s16 blockpos_map(sector->getPos().X, block_y, sector->getPos().Y);
|
||||
|
||||
//m_client->addToBlockFetchQueue(blockpos_map);
|
||||
m_client->fetchBlock(blockpos_map);
|
||||
|
||||
throw AsyncQueuedException("Client will fetch later");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
/*
|
||||
TODO: Check if block is stored on disk
|
||||
and load dynamically
|
||||
*/
|
||||
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)
|
||||
{
|
||||
/*std::cout<<"MapSector("<<m_pos.X<<","<<m_pos.Y<<")::getBlock("<<y
|
||||
<<")"<<std::endl;*/
|
||||
|
||||
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);
|
||||
|
||||
/*std::cout<<"MapSector("<<m_pos.X<<","<<m_pos.Y<<")::createBlankBlock("<<y
|
||||
<<")"<<std::endl;*/
|
||||
|
||||
MapBlock *block = createBlankBlockNoInsert(y);
|
||||
|
||||
m_blocks.insert(y, block);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
/*
|
||||
This will generate a new block as needed.
|
||||
|
||||
A valid heightmap is assumed to exist already.
|
||||
*/
|
||||
MapBlock * MapSector::getBlock(s16 block_y)
|
||||
{
|
||||
{
|
||||
JMutexAutoLock lock(m_mutex);
|
||||
MapBlock *block = getBlockBuffered(block_y);
|
||||
if(block != NULL)
|
||||
return block;
|
||||
}
|
||||
|
||||
/*std::cout<<"MapSector("<<m_pos.X<<","<<m_pos.Y<<
|
||||
")::getBlock("<<block_y<<")"<<std::endl;*/
|
||||
|
||||
MapBlock *block = m_block_gen->makeBlock(this, block_y);
|
||||
|
||||
//block->propagateSunlight();
|
||||
|
||||
// Insert to container
|
||||
{
|
||||
JMutexAutoLock lock(m_mutex);
|
||||
m_blocks.insert(block_y, block);
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
void MapSector::insertBlock(MapBlock *block)
|
||||
{
|
||||
s16 block_y = block->getPos().Y;
|
||||
|
||||
{
|
||||
JMutexAutoLock lock(m_mutex);
|
||||
MapBlock *block = getBlockBuffered(block_y);
|
||||
if(block != NULL){
|
||||
throw AlreadyExistsException("Block already exists");
|
||||
}
|
||||
}
|
||||
|
||||
v2s16 p2d(block->getPos().X, block->getPos().Z);
|
||||
assert(p2d == m_pos);
|
||||
|
||||
block->propagateSunlight();
|
||||
|
||||
// Insert to container
|
||||
{
|
||||
JMutexAutoLock lock(m_mutex);
|
||||
m_blocks.insert(block_y, block);
|
||||
}
|
||||
}
|
||||
|
||||
core::list<MapBlock*> MapSector::getBlocks()
|
||||
{
|
||||
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();
|
||||
ref_list.push_back(b);
|
||||
}
|
||||
|
||||
return ref_list;
|
||||
}
|
||||
|
||||
f32 MapSector::getGroundHeight(v2s16 p, bool generate)
|
||||
{
|
||||
if(m_heightmap == NULL)
|
||||
return GROUNDHEIGHT_NOTFOUND_SETVALUE;
|
||||
return m_heightmap->getGroundHeight(p);
|
||||
}
|
||||
|
||||
void MapSector::setGroundHeight(v2s16 p, f32 y, bool generate)
|
||||
{
|
||||
if(m_heightmap == NULL)
|
||||
return;
|
||||
m_heightmap->setGroundHeight(p, y);
|
||||
}
|
||||
|
||||
//END
|
156
src/mapsector.h
Normal file
156
src/mapsector.h
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
(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.
|
||||
|
||||
TODO:
|
||||
- Add heightmap functionality
|
||||
- Implement node container functionality and modify Map to use it
|
||||
*/
|
||||
|
||||
class MapSector;
|
||||
|
||||
class BlockGenerator
|
||||
{
|
||||
public:
|
||||
virtual MapBlock * makeBlock(MapSector *sector, s16 block_y)
|
||||
{
|
||||
assert(0);
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
class HeightmapBlockGenerator : public BlockGenerator
|
||||
{
|
||||
public:
|
||||
HeightmapBlockGenerator(v2s16 p2d, Heightmap * masterheightmap);
|
||||
//HeightmapBlockGenerator(MapSector *sector, Heightmap * masterheightmap);
|
||||
|
||||
~HeightmapBlockGenerator()
|
||||
{
|
||||
delete m_heightmap;
|
||||
}
|
||||
|
||||
MapBlock * makeBlock(MapSector *sector, s16 block_y);
|
||||
|
||||
// This should be generated before usage of the class
|
||||
FixedHeightmap *m_heightmap;
|
||||
};
|
||||
|
||||
class Client;
|
||||
|
||||
/*
|
||||
A block "generator" class that fetches blocks
|
||||
using the client from the server
|
||||
*/
|
||||
class ClientBlockGenerator : public BlockGenerator
|
||||
{
|
||||
public:
|
||||
ClientBlockGenerator(Client * client)
|
||||
{
|
||||
m_client = client;
|
||||
}
|
||||
~ClientBlockGenerator()
|
||||
{
|
||||
}
|
||||
MapBlock * makeBlock(MapSector *sector, s16 block_y);
|
||||
private:
|
||||
Client *m_client;
|
||||
};
|
||||
|
||||
class MapSector :
|
||||
public NodeContainer,
|
||||
public Heightmappish
|
||||
{
|
||||
private:
|
||||
|
||||
// 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;
|
||||
|
||||
MapBlock *getBlockBuffered(s16 y);
|
||||
|
||||
// Be sure to set this to NULL when the cached block is deleted
|
||||
MapBlock *m_block_cache;
|
||||
s16 m_block_cache_y;
|
||||
|
||||
JMutex m_mutex;
|
||||
|
||||
// This is a pointer to the generator's heightmap if applicable
|
||||
FixedHeightmap *m_heightmap;
|
||||
|
||||
BlockGenerator *m_block_gen;
|
||||
|
||||
public:
|
||||
|
||||
MapSector(NodeContainer *parent, v2s16 pos, BlockGenerator *gen):
|
||||
m_parent(parent),
|
||||
m_pos(pos),
|
||||
m_block_cache(NULL),
|
||||
m_heightmap(NULL),
|
||||
m_block_gen(gen)
|
||||
{
|
||||
m_mutex.Init();
|
||||
assert(m_mutex.IsInitialized());
|
||||
}
|
||||
|
||||
~MapSector()
|
||||
{
|
||||
//TODO: clear m_blocks
|
||||
core::map<s16, MapBlock*>::Iterator i = m_blocks.getIterator();
|
||||
for(; i.atEnd() == false; i++)
|
||||
{
|
||||
delete i.getNode()->getValue();
|
||||
}
|
||||
|
||||
delete m_block_gen;
|
||||
}
|
||||
|
||||
/*FixedHeightmap * getHeightmap()
|
||||
{
|
||||
if(m_heightmap == NULL)
|
||||
throw TargetInexistentException();
|
||||
return m_heightmap;
|
||||
}*/
|
||||
|
||||
void setHeightmap(FixedHeightmap *fh)
|
||||
{
|
||||
m_heightmap = fh;
|
||||
}
|
||||
|
||||
v2s16 getPos()
|
||||
{
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
MapBlock * getBlockNoCreate(s16 y);
|
||||
MapBlock * createBlankBlockNoInsert(s16 y);
|
||||
MapBlock * createBlankBlock(s16 y);
|
||||
MapBlock * getBlock(s16 y);
|
||||
|
||||
void insertBlock(MapBlock *block);
|
||||
|
||||
core::list<MapBlock*> getBlocks();
|
||||
|
||||
// These have to exist
|
||||
f32 getGroundHeight(v2s16 p, bool generate=false);
|
||||
void setGroundHeight(v2s16 p, f32 y, bool generate=false);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
196
src/player.cpp
Normal file
196
src/player.cpp
Normal file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
(c) 2010 Perttu Ahola <celeron55@gmail.com>
|
||||
*/
|
||||
|
||||
#include "player.h"
|
||||
#include "map.h"
|
||||
#include "connection.h"
|
||||
|
||||
Player::Player(bool is_local):
|
||||
scene::ISceneNode(NULL, NULL, 0),
|
||||
speed(0,0,0),
|
||||
touching_ground(false),
|
||||
peer_id(PEER_ID_NEW),
|
||||
timeout_counter(0.0),
|
||||
m_is_local(is_local),
|
||||
m_position(0,0,0)
|
||||
{
|
||||
}
|
||||
|
||||
Player::Player(
|
||||
bool is_local,
|
||||
scene::ISceneNode* parent,
|
||||
scene::ISceneManager* mgr,
|
||||
s32 id):
|
||||
scene::ISceneNode(parent, mgr, id),
|
||||
speed(0,0,0),
|
||||
touching_ground(false),
|
||||
peer_id(PEER_ID_NEW),
|
||||
timeout_counter(0.0),
|
||||
m_is_local(is_local),
|
||||
m_position(0,0,0)
|
||||
{
|
||||
m_box = core::aabbox3d<f32>(-BS,-BS,-BS,BS,BS,BS);
|
||||
|
||||
m_bill = NULL;
|
||||
|
||||
if(is_local == false)
|
||||
{
|
||||
// attach billboard to the light
|
||||
m_bill = mgr->addBillboardSceneNode(this, core::dimension2d<f32>(60, 60));
|
||||
|
||||
// ISceneNode stores a member called SceneManager
|
||||
video::IVideoDriver* driver = SceneManager->getVideoDriver();
|
||||
|
||||
m_bill->setMaterialFlag(video::EMF_LIGHTING, false);
|
||||
//m_bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
|
||||
//m_bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
|
||||
m_bill->setMaterialTexture(0, driver->getTexture("../data/tf.jpg"));
|
||||
m_bill->setSize(core::dimension2d<f32>(BS,BS));
|
||||
|
||||
m_bill->setPosition(v3f(0, BS+BS/3, 0));
|
||||
|
||||
updateSceneNodePosition();
|
||||
}
|
||||
}
|
||||
|
||||
Player::~Player()
|
||||
{
|
||||
if(SceneManager != NULL)
|
||||
ISceneNode::remove();
|
||||
}
|
||||
|
||||
void Player::move(f32 dtime, Map &map)
|
||||
{
|
||||
v3f position = getPosition();
|
||||
v3f oldpos = position;
|
||||
v3s16 oldpos_i = Map::floatToInt(oldpos);
|
||||
|
||||
/*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
|
||||
<<oldpos_i.Z<<")"<<std::endl;*/
|
||||
|
||||
position += speed * dtime;
|
||||
|
||||
v3s16 pos_i = Map::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(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(
|
||||
/*(nodebox.MaxEdge.Y+d > playerbox.MinEdge.Y &&
|
||||
nodebox.MaxEdge.Y-d < playerbox.MinEdge.Y)*/
|
||||
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
|
||||
&& speed.dotProduct(dirs[i]) < 0)
|
||||
||
|
||||
(nodemin < playermax && nodemin >= playermax_old - d
|
||||
&& 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)
|
||||
{
|
||||
speed -= 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);
|
||||
}
|
||||
|
93
src/player.h
Normal file
93
src/player.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
(c) 2010 Perttu Ahola <celeron55@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef PLAYER_HEADER
|
||||
#define PLAYER_HEADER
|
||||
|
||||
#include <irrlicht.h>
|
||||
|
||||
using namespace irr;
|
||||
typedef core::vector3df v3f;
|
||||
typedef core::vector3d<s16> v3s16;
|
||||
|
||||
class Map;
|
||||
|
||||
/*
|
||||
TODO: Make this a scene::ISceneNode
|
||||
*/
|
||||
|
||||
class Player : public scene::ISceneNode
|
||||
{
|
||||
public:
|
||||
Player(bool is_local);
|
||||
|
||||
Player(
|
||||
bool is_local,
|
||||
scene::ISceneNode* parent,
|
||||
scene::ISceneManager* mgr,
|
||||
s32 id);
|
||||
|
||||
~Player();
|
||||
|
||||
void move(f32 dtime, Map &map);
|
||||
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
v3f getPosition()
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
void setPosition(v3f position)
|
||||
{
|
||||
m_position = position;
|
||||
updateSceneNodePosition();
|
||||
}
|
||||
|
||||
void updateSceneNodePosition()
|
||||
{
|
||||
ISceneNode::setPosition(m_position);
|
||||
}
|
||||
|
||||
bool isLocal()
|
||||
{
|
||||
return m_is_local;
|
||||
}
|
||||
|
||||
v3f speed;
|
||||
bool touching_ground;
|
||||
u16 peer_id;
|
||||
float timeout_counter;
|
||||
|
||||
private:
|
||||
bool m_is_local;
|
||||
v3f m_position;
|
||||
//scene::ISceneNode* m_bill;
|
||||
scene::IBillboardSceneNode* m_bill;
|
||||
|
||||
core::aabbox3d<f32> m_box;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
313
src/server.cpp
Normal file
313
src/server.cpp
Normal file
@ -0,0 +1,313 @@
|
||||
#include "server.h"
|
||||
#include "utility.h"
|
||||
#include <iostream>
|
||||
#include "clientserver.h"
|
||||
#include "map.h"
|
||||
#include "jmutexautolock.h"
|
||||
#include "main.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define sleep_ms(x) Sleep(x)
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#define sleep_ms(x) usleep(x*1000)
|
||||
#endif
|
||||
|
||||
void * ServerNetworkThread::Thread()
|
||||
{
|
||||
ThreadStarted();
|
||||
|
||||
while(getRun())
|
||||
{
|
||||
m_server->AsyncRunStep();
|
||||
|
||||
try{
|
||||
//dout_server<<"Running m_server->Receive()"<<std::endl;
|
||||
m_server->Receive();
|
||||
}
|
||||
catch(con::NoIncomingDataException &e)
|
||||
{
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
dout_server<<"ServerNetworkThread: Some exception: "
|
||||
<<e.what()<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
Server::Server():
|
||||
m_env(new MasterMap(), dout_server),
|
||||
m_con(PROTOCOL_ID, 512),
|
||||
m_thread(this)
|
||||
{
|
||||
m_env_mutex.Init();
|
||||
m_con_mutex.Init();
|
||||
m_step_dtime_mutex.Init();
|
||||
m_step_dtime = 0.0;
|
||||
}
|
||||
|
||||
Server::~Server()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void Server::start(unsigned short port)
|
||||
{
|
||||
stop();
|
||||
m_con.setTimeoutMs(200);
|
||||
m_con.Serve(port);
|
||||
m_thread.setRun(true);
|
||||
m_thread.Start();
|
||||
}
|
||||
|
||||
void Server::stop()
|
||||
{
|
||||
m_thread.setRun(false);
|
||||
while(m_thread.IsRunning())
|
||||
sleep_ms(100);
|
||||
}
|
||||
|
||||
void Server::step(float dtime)
|
||||
{
|
||||
{
|
||||
JMutexAutoLock lock(m_env_mutex);
|
||||
m_env.step(dtime);
|
||||
}
|
||||
{
|
||||
JMutexAutoLock lock(m_step_dtime_mutex);
|
||||
m_step_dtime += dtime;
|
||||
}
|
||||
}
|
||||
|
||||
void Server::AsyncRunStep()
|
||||
{
|
||||
float dtime;
|
||||
{
|
||||
JMutexAutoLock lock1(m_step_dtime_mutex);
|
||||
dtime = m_step_dtime;
|
||||
m_step_dtime = 0.0;
|
||||
}
|
||||
{
|
||||
// Process connection's timeouts
|
||||
JMutexAutoLock lock2(m_con_mutex);
|
||||
m_con.RunTimeouts(dtime);
|
||||
}
|
||||
{
|
||||
/*
|
||||
Do background stuff that needs the connection
|
||||
*/
|
||||
|
||||
// Send player positions
|
||||
SendPlayerPositions(dtime);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::Receive()
|
||||
{
|
||||
u32 data_maxsize = 10000;
|
||||
Buffer<u8> data(data_maxsize);
|
||||
u16 peer_id;
|
||||
u32 datasize;
|
||||
try{
|
||||
{
|
||||
JMutexAutoLock lock(m_con_mutex);
|
||||
datasize = m_con.Receive(peer_id, *data, data_maxsize);
|
||||
}
|
||||
ProcessData(*data, datasize, peer_id);
|
||||
}
|
||||
catch(con::InvalidIncomingDataException &e)
|
||||
{
|
||||
dout_server<<"Server::Receive(): "
|
||||
"InvalidIncomingDataException: what()="
|
||||
<<e.what()<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
{
|
||||
// Let free access to the environment and the connection
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
JMutexAutoLock conlock(m_con_mutex);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if(datasize < 2)
|
||||
return;
|
||||
|
||||
ToServerCommand command = (ToServerCommand)readU16(&data[0]);
|
||||
|
||||
if(command == TOSERVER_GETBLOCK)
|
||||
{
|
||||
//throw NotImplementedException("");
|
||||
|
||||
// Check for too short data
|
||||
if(datasize < 8)
|
||||
return;
|
||||
dout_server<<"Server::ProcessData(): GETBLOCK"
|
||||
<<std::endl;
|
||||
/*
|
||||
Get block data and send it
|
||||
*/
|
||||
v3s16 p;
|
||||
p.X = readS16(&data[2]);
|
||||
p.Y = readS16(&data[4]);
|
||||
p.Z = readS16(&data[6]);
|
||||
MapBlock *block = m_env.getMap().getBlock(p);
|
||||
|
||||
u32 replysize = 8 + MapBlock::serializedLength();
|
||||
SharedBuffer<u8> reply(replysize);
|
||||
writeU16(&reply[0], TOCLIENT_BLOCKDATA);
|
||||
writeS16(&reply[2], p.X);
|
||||
writeS16(&reply[4], p.Y);
|
||||
writeS16(&reply[6], p.Z);
|
||||
block->serialize(&reply[8]);
|
||||
// Send as unreliable
|
||||
//m_con.Send(peer_id, 1, reply, false);
|
||||
m_con.Send(peer_id, 1, reply, true);
|
||||
}
|
||||
else if(command == TOSERVER_REMOVENODE)
|
||||
{
|
||||
if(datasize < 8)
|
||||
return;
|
||||
|
||||
v3s16 p;
|
||||
p.X = readS16(&data[2]);
|
||||
p.Y = readS16(&data[4]);
|
||||
p.Z = readS16(&data[6]);
|
||||
|
||||
MapNode n;
|
||||
n.d = MATERIAL_AIR;
|
||||
m_env.getMap().setNode(p, n);
|
||||
|
||||
u32 replysize = 8;
|
||||
//u8 reply[replysize];
|
||||
SharedBuffer<u8> reply(replysize);
|
||||
writeU16(&reply[0], TOCLIENT_REMOVENODE);
|
||||
writeS16(&reply[2], p.X);
|
||||
writeS16(&reply[4], p.Y);
|
||||
writeS16(&reply[6], p.Z);
|
||||
// Send as reliable
|
||||
m_con.SendToAll(0, reply, true);
|
||||
}
|
||||
else if(command == TOSERVER_ADDNODE)
|
||||
{
|
||||
if(datasize < 8 + MapNode::serializedLength())
|
||||
return;
|
||||
|
||||
v3s16 p;
|
||||
p.X = readS16(&data[2]);
|
||||
p.Y = readS16(&data[4]);
|
||||
p.Z = readS16(&data[6]);
|
||||
|
||||
MapNode n;
|
||||
n.deSerialize(&data[8]);
|
||||
m_env.getMap().setNode(p, n);
|
||||
|
||||
u32 replysize = 8 + MapNode::serializedLength();
|
||||
//u8 reply[replysize];
|
||||
SharedBuffer<u8> reply(replysize);
|
||||
writeU16(&reply[0], TOCLIENT_ADDNODE);
|
||||
writeS16(&reply[2], p.X);
|
||||
writeS16(&reply[4], p.Y);
|
||||
writeS16(&reply[6], p.Z);
|
||||
n.serialize(&reply[8]);
|
||||
// Send as reliable
|
||||
m_con.SendToAll(0, reply, true);
|
||||
}
|
||||
else if(command == TOSERVER_PLAYERPOS)
|
||||
{
|
||||
if(datasize < 2+12+12)
|
||||
return;
|
||||
|
||||
Player *player = m_env.getPlayer(peer_id);
|
||||
|
||||
// Create a player if it doesn't exist
|
||||
if(player == NULL)
|
||||
{
|
||||
dout_server<<"Server::ProcessData(): Adding player "
|
||||
<<peer_id<<std::endl;
|
||||
player = new Player(false);
|
||||
player->peer_id = peer_id;
|
||||
m_env.addPlayer(player);
|
||||
}
|
||||
|
||||
player->timeout_counter = 0.0;
|
||||
|
||||
v3s32 ps = readV3S32(&data[2]);
|
||||
v3s32 ss = readV3S32(&data[2+12]);
|
||||
v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
|
||||
v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
|
||||
player->setPosition(position);
|
||||
player->speed = speed;
|
||||
|
||||
/*dout_server<<"Server::ProcessData(): Moved player "<<peer_id
|
||||
<<" to ("<<position.X
|
||||
<<","<<position.Y
|
||||
<<","<<position.Z
|
||||
<<")"<<std::endl;*/
|
||||
}
|
||||
else
|
||||
{
|
||||
dout_server<<"WARNING: Server::ProcessData(): Ingoring "
|
||||
"unknown command "<<command<<std::endl;
|
||||
}
|
||||
|
||||
} //try
|
||||
catch(SendFailedException &e)
|
||||
{
|
||||
dout_server<<"Server::ProcessData(): SendFailedException: "
|
||||
<<"what="<<e.what()
|
||||
<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Server::SendPlayerPositions(float dtime)
|
||||
{
|
||||
// Update at reasonable intervals (0.2s)
|
||||
static float counter = 0.0;
|
||||
counter += dtime;
|
||||
if(counter < 0.2)
|
||||
return;
|
||||
counter = 0.0;
|
||||
|
||||
JMutexAutoLock envlock(m_env_mutex);
|
||||
|
||||
core::list<Player*> players = m_env.getPlayers();
|
||||
|
||||
u32 player_count = players.getSize();
|
||||
u32 datasize = 2+(2+12+12)*player_count;
|
||||
|
||||
SharedBuffer<u8> data(datasize);
|
||||
writeU16(&data[0], TOCLIENT_PLAYERPOS);
|
||||
|
||||
u32 start = 2;
|
||||
core::list<Player*>::Iterator i;
|
||||
for(i = players.begin();
|
||||
i != players.end(); i++)
|
||||
{
|
||||
Player *player = *i;
|
||||
|
||||
v3f pf = player->getPosition();
|
||||
v3s32 position(pf.X*100, pf.Y*100, pf.Z*100);
|
||||
v3f sf = player->speed;
|
||||
v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100);
|
||||
|
||||
writeU16(&data[start], player->peer_id);
|
||||
writeV3S32(&data[start+2], position);
|
||||
writeV3S32(&data[start+2+12], speed);
|
||||
start += 2+12+12;
|
||||
}
|
||||
|
||||
JMutexAutoLock conlock(m_con_mutex);
|
||||
|
||||
// Send as unreliable
|
||||
m_con.SendToAll(0, data, false);
|
||||
}
|
||||
|
71
src/server.h
Normal file
71
src/server.h
Normal file
@ -0,0 +1,71 @@
|
||||
#ifndef SERVER_HEADER
|
||||
#define SERVER_HEADER
|
||||
|
||||
#include "connection.h"
|
||||
#include "environment.h"
|
||||
|
||||
class Server;
|
||||
|
||||
class ServerNetworkThread : public JThread
|
||||
{
|
||||
bool run;
|
||||
JMutex run_mutex;
|
||||
|
||||
Server *m_server;
|
||||
|
||||
public:
|
||||
|
||||
ServerNetworkThread(Server *server) : JThread(), run(true), m_server(server)
|
||||
{
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
/*
|
||||
NOTE: Every public method should be thread-safe
|
||||
*/
|
||||
Server();
|
||||
~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);
|
||||
private:
|
||||
|
||||
void SendPlayerPositions(float dtime);
|
||||
|
||||
Environment m_env;
|
||||
JMutex m_env_mutex;
|
||||
|
||||
con::Connection m_con;
|
||||
JMutex m_con_mutex;
|
||||
|
||||
float m_step_dtime;
|
||||
JMutex m_step_dtime_mutex;
|
||||
|
||||
ServerNetworkThread m_thread;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
295
src/socket.cpp
Normal file
295
src/socket.cpp
Normal file
@ -0,0 +1,295 @@
|
||||
#include "socket.h"
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Debug print options
|
||||
#define DP 0
|
||||
//#define DPS " "
|
||||
#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);
|
||||
}
|
||||
|
||||
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() const
|
||||
{
|
||||
std::cout<<((m_address>>24)&0xff)<<"."
|
||||
<<((m_address>>16)&0xff)<<"."
|
||||
<<((m_address>>8)&0xff)<<"."
|
||||
<<((m_address>>0)&0xff)<<":"
|
||||
<<m_port;
|
||||
}
|
||||
|
||||
UDPSocket::UDPSocket()
|
||||
{
|
||||
if(g_sockets_initialized == false)
|
||||
throw SocketException("Sockets not initialized");
|
||||
|
||||
m_handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
if(DP)
|
||||
std::cout<<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)
|
||||
std::cout<<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)
|
||||
std::cout<<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)
|
||||
{
|
||||
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){
|
||||
/*std::cout<<DPS<<"UDPSocket("<<(int)m_handle
|
||||
<<")::Send(): destination=";*/
|
||||
std::cout<<DPS;
|
||||
std::cout<<(int)m_handle<<" -> ";
|
||||
destination.print();
|
||||
std::cout<<", size="<<size<<", data=";
|
||||
for(int i=0; i<size && i<20; i++){
|
||||
if(i%2==0) printf(" ");
|
||||
printf("%.2X", ((int)((const char*)data)[i])&0xff);
|
||||
}
|
||||
if(size>20)
|
||||
std::cout<<"...";
|
||||
if(dumping_packet)
|
||||
std::cout<<" (DUMPED BY INTERNET_SIMULATOR)";
|
||||
std::cout<<std::endl;
|
||||
}
|
||||
else if(dumping_packet)
|
||||
{
|
||||
// Lol let's forget it
|
||||
std::cout<<"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){
|
||||
//std::cout<<DPS<<"UDPSocket("<<(int)m_handle<<")::Receive(): sender=";
|
||||
std::cout<<DPS<<(int)m_handle<<" <- ";
|
||||
sender.print();
|
||||
//std::cout<<", received="<<received<<std::endl;
|
||||
std::cout<<", size="<<received<<", data=";
|
||||
for(int i=0; i<received && i<20; i++){
|
||||
if(i%2==0) printf(" ");
|
||||
printf("%.2X", ((int)((const char*)data)[i])&0xff);
|
||||
}
|
||||
if(received>20)
|
||||
std::cout<<"...";
|
||||
std::cout<<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
|
||||
/*std::cout<<"Select timed out (timeout_ms="
|
||||
<<timeout_ms<<")"<<std::endl;*/
|
||||
return false;
|
||||
}
|
||||
else if(result < 0){
|
||||
// Error
|
||||
throw SocketException("Select failed");
|
||||
}
|
||||
else if(FD_ISSET(m_handle, &readset) == false){
|
||||
// No data
|
||||
//std::cout<<"Select reported no data in m_handle"<<std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is data
|
||||
//std::cout<<"Select reported data in m_handle"<<std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
98
src/socket.h
Normal file
98
src/socket.h
Normal file
@ -0,0 +1,98 @@
|
||||
#ifndef SOCKET_HEADER
|
||||
#define SOCKET_HEADER
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "wsock32.lib")
|
||||
typedef SOCKET socket_t;
|
||||
typedef int socklen_t;
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
typedef int socket_t;
|
||||
#endif
|
||||
|
||||
#include "exceptions.h"
|
||||
|
||||
// Define for simulating the quirks of sending through internet
|
||||
// WARNING: This disables unit testing of socket and connection
|
||||
#define INTERNET_SIMULATOR 0
|
||||
|
||||
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);
|
||||
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() 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
|
||||
|
744
src/test.cpp
Normal file
744
src/test.cpp
Normal file
@ -0,0 +1,744 @@
|
||||
#include "test.h"
|
||||
#include "common_irrlicht.h"
|
||||
|
||||
#include "map.h"
|
||||
#include "player.h"
|
||||
#include "main.h"
|
||||
//#include "referencecounted.h"
|
||||
#include "heightmap.h"
|
||||
#include "socket.h"
|
||||
#include "connection.h"
|
||||
#include "utility.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define sleep_ms(x) Sleep(x)
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#define sleep_ms(x) usleep(x*1000)
|
||||
#endif
|
||||
|
||||
/*
|
||||
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 TestRefcount
|
||||
{
|
||||
class TC : public ReferenceCounted
|
||||
{
|
||||
public:
|
||||
s16 value;
|
||||
TC() : ReferenceCounted("TC")
|
||||
{
|
||||
value = 0;
|
||||
}
|
||||
};
|
||||
|
||||
Ref<TC> TestReturn()
|
||||
{
|
||||
static TC tc;
|
||||
tc.value = 5;
|
||||
return &tc;
|
||||
}
|
||||
|
||||
void Run()
|
||||
{
|
||||
TC tc;
|
||||
|
||||
tc.grab();
|
||||
assert(tc.getRefcount() == 1);
|
||||
|
||||
tc.drop();
|
||||
assert(tc.getRefcount() == 0);
|
||||
|
||||
bool exception = false;
|
||||
try
|
||||
{
|
||||
tc.drop();
|
||||
}
|
||||
catch(ReferenceCounted::Exception & e)
|
||||
{
|
||||
exception = true;
|
||||
}
|
||||
assert(exception == true);
|
||||
|
||||
TC tc2;
|
||||
{
|
||||
Ref<TC> ref(&tc2);
|
||||
assert(ref.get() == &tc2);
|
||||
assert(tc2.getRefcount() == 1);
|
||||
}
|
||||
assert(tc2.getRefcount() == 0);
|
||||
|
||||
Ref<TC> ref2 = TestReturn();
|
||||
assert(ref2.get()->value == 5);
|
||||
assert(ref2.get()->getRefcount() == 1);
|
||||
}
|
||||
};*/
|
||||
|
||||
struct TestMapNode
|
||||
{
|
||||
void Run()
|
||||
{
|
||||
MapNode n;
|
||||
|
||||
// Default values
|
||||
assert(n.d == MATERIAL_AIR);
|
||||
assert(n.light == 0.0);
|
||||
|
||||
// Transparency
|
||||
n.d = MATERIAL_AIR;
|
||||
assert(n.transparent() == true);
|
||||
n.d = 0;
|
||||
assert(n.transparent() == false);
|
||||
}
|
||||
};
|
||||
|
||||
struct TestMapBlock
|
||||
{
|
||||
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();
|
||||
};
|
||||
};
|
||||
|
||||
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 .light < 0.001
|
||||
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)).light < 0.001);
|
||||
}
|
||||
|
||||
/*
|
||||
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.light = 0.0;
|
||||
b.setNode(v3s16(x,y,z), n);
|
||||
}
|
||||
}
|
||||
}
|
||||
parent.position_valid = false;
|
||||
b.setProbablyDark(false);
|
||||
assert(b.propagateSunlight() == true);
|
||||
assert(b.getNode(v3s16(1,4,0)).light > 0.999);
|
||||
assert(b.getNode(v3s16(1,3,0)).light > 0.999);
|
||||
assert(b.getNode(v3s16(1,2,0)).light < 0.001);
|
||||
assert(b.getNode(v3s16(1,1,0)).light < 0.001);
|
||||
assert(b.getNode(v3s16(1,0,0)).light < 0.001);
|
||||
assert(b.getNode(v3s16(1,2,3)).light > 0.999);
|
||||
assert(b.getFaceLight(p, v3s16(0,1,0)) > 0.999);
|
||||
assert(b.getFaceLight(p, v3s16(0,-1,0)) < 0.001);
|
||||
assert(fabs(b.getFaceLight(p, v3s16(0,0,1))-0.7) < 0.001);
|
||||
parent.position_valid = true;
|
||||
parent.node.light = 0.500;
|
||||
assert(b.propagateSunlight() == false);
|
||||
// Should not touch blocks that are not affected (that is, all of them)
|
||||
assert(b.getNode(v3s16(1,2,3)).light > 0.999);
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
};
|
||||
|
||||
void Run()
|
||||
{
|
||||
TC parent;
|
||||
parent.position_valid = false;
|
||||
|
||||
DummyHeightmap dummyheightmap;
|
||||
|
||||
HeightmapBlockGenerator *gen =
|
||||
new HeightmapBlockGenerator(v2s16(1,1), &dummyheightmap);
|
||||
MapSector sector(&parent, v2s16(1,1), gen);
|
||||
|
||||
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, 0.0, 0.0, 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
|
||||
std::cout<<"UnlimitedHeightmap hm1:"<<std::endl;
|
||||
hm1.print();
|
||||
/*for(s16 y=0; y<=BS1; y++){
|
||||
for(s16 x=0; x<=BS1; x++){
|
||||
v2s16 p(x,y);
|
||||
f32 h = hm1.getGroundHeight(p);
|
||||
printf("% 2.1f ", h);
|
||||
}
|
||||
std::cout<<std::endl;
|
||||
}*/
|
||||
|
||||
std::cout<<"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()
|
||||
{
|
||||
std::cout<<"Running random code"<<std::endl;
|
||||
std::cout<<"rand() values: ";
|
||||
for(u16 i=0; i<5; i++)
|
||||
std::cout<<(u16)rand()<<" ";
|
||||
std::cout<<std::endl;
|
||||
|
||||
const s16 BS1 = 4;
|
||||
//UnlimitedHeightmap hm1(BS1, 0.0, 0.0, 5.0);
|
||||
UnlimitedHeightmap hm1(BS1, 2.5, 0.5, 0.0);
|
||||
|
||||
// Force hm1 to generate a some heightmap
|
||||
hm1.getGroundHeight(v2s16(0,0));
|
||||
hm1.getGroundHeight(v2s16(BS1,0));
|
||||
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();*/
|
||||
}
|
||||
|
||||
void Run()
|
||||
{
|
||||
//srand(7); // Get constant random
|
||||
srand(time(0)); // Get better random
|
||||
|
||||
TestSingleFixed();
|
||||
TestUnlimited();
|
||||
Random();
|
||||
|
||||
g_heightmap_debugprint = false;
|
||||
//g_heightmap_debugprint = true;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
assert(strncmp(sendbuffer, rcvbuffer, sizeof(rcvbuffer))==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]);
|
||||
|
||||
SharedBuffer<u8> p2 = con::makeReliablePacket(data1, seqnum);
|
||||
assert(readU8(&p2[0]) == TYPE_RELIABLE);
|
||||
assert(readU16(&p2[1]) == seqnum);
|
||||
assert(readU8(&p2[3]) == data1[0]);
|
||||
}
|
||||
void Run()
|
||||
{
|
||||
TestHelpers();
|
||||
|
||||
/*
|
||||
Test some real connections
|
||||
*/
|
||||
u32 proto_id = 0xad26846a;
|
||||
|
||||
std::cout<<"** Creating server Connection"<<std::endl;
|
||||
con::Connection server(proto_id, 512);
|
||||
server.Serve(30001);
|
||||
|
||||
std::cout<<"** Creating client Connection"<<std::endl;
|
||||
con::Connection client(proto_id, 512);
|
||||
|
||||
sleep_ms(50);
|
||||
|
||||
Address server_address(127,0,0,1, 30001);
|
||||
std::cout<<"** running client.Connect()"<<std::endl;
|
||||
client.Connect(server_address);
|
||||
|
||||
sleep_ms(50);
|
||||
|
||||
try
|
||||
{
|
||||
u16 peer_id;
|
||||
u8 data[100];
|
||||
std::cout<<"** running server.Receive()"<<std::endl;
|
||||
u32 size = server.Receive(peer_id, data, 100);
|
||||
std::cout<<"** 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
|
||||
}
|
||||
|
||||
//sleep_ms(50);
|
||||
|
||||
while(client.Connected() == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
u16 peer_id;
|
||||
u8 data[100];
|
||||
std::cout<<"** running client.Receive()"<<std::endl;
|
||||
u32 size = client.Receive(peer_id, data, 100);
|
||||
std::cout<<"** 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];
|
||||
std::cout<<"** running server.Receive()"<<std::endl;
|
||||
u32 size = server.Receive(peer_id, data, 100);
|
||||
std::cout<<"** 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!");
|
||||
|
||||
std::cout<<"** running client.Send()"<<std::endl;
|
||||
client.Send(PEER_ID_SERVER, 0, data, true);
|
||||
|
||||
sleep_ms(50);
|
||||
|
||||
u16 peer_id;
|
||||
u8 recvdata[100];
|
||||
std::cout<<"** running server.Receive()"<<std::endl;
|
||||
u32 size = server.Receive(peer_id, recvdata, 100);
|
||||
std::cout<<"** 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;
|
||||
|
||||
std::cout<<"*** 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);
|
||||
|
||||
std::cout<<"*** Receiving the packets"<<std::endl;
|
||||
|
||||
u16 peer_id;
|
||||
u8 recvdata[20];
|
||||
u32 size;
|
||||
|
||||
std::cout<<"** running client.Receive()"<<std::endl;
|
||||
peer_id = 132;
|
||||
size = client.Receive(peer_id, recvdata, 20);
|
||||
std::cout<<"** 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);
|
||||
|
||||
std::cout<<"** running client.Receive()"<<std::endl;
|
||||
peer_id = 132;
|
||||
size = client.Receive(peer_id, recvdata, 20);
|
||||
std::cout<<"** 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
|
||||
{
|
||||
std::cout<<"** running client.Receive()"<<std::endl;
|
||||
peer_id = 132;
|
||||
size = client.Receive(peer_id, recvdata, 20);
|
||||
std::cout<<"** Client received: peer_id="<<peer_id
|
||||
<<", size="<<size
|
||||
<<", data="<<recvdata
|
||||
<<std::endl;
|
||||
}
|
||||
catch(con::NoIncomingDataException &e)
|
||||
{
|
||||
std::cout<<"** 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;
|
||||
}
|
||||
|
||||
std::cout<<"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)
|
||||
std::cout<<"...";
|
||||
std::cout<<std::endl;
|
||||
|
||||
server.Send(peer_id_client, 0, data1, true);
|
||||
|
||||
sleep_ms(50);
|
||||
|
||||
u8 recvdata[2000];
|
||||
std::cout<<"** running client.Receive()"<<std::endl;
|
||||
u16 peer_id = 132;
|
||||
u16 size = client.Receive(peer_id, recvdata, 2000);
|
||||
std::cout<<"** Client received: peer_id="<<peer_id
|
||||
<<", size="<<size
|
||||
<<std::endl;
|
||||
|
||||
std::cout<<"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)
|
||||
std::cout<<"...";
|
||||
std::cout<<std::endl;
|
||||
|
||||
assert(memcmp(*data1, recvdata, data1.getSize()) == 0);
|
||||
assert(peer_id == PEER_ID_SERVER);
|
||||
}
|
||||
|
||||
//assert(0);
|
||||
}
|
||||
};
|
||||
|
||||
#define TEST(X)\
|
||||
{\
|
||||
X x;\
|
||||
std::cout<<"Running " #X <<std::endl;\
|
||||
x.Run();\
|
||||
}
|
||||
|
||||
void run_tests()
|
||||
{
|
||||
std::cout<<"run_tests() started"<<std::endl;
|
||||
//TEST(TestRefcount);
|
||||
TEST(TestMapNode);
|
||||
TEST(TestMapBlock);
|
||||
TEST(TestMapSector);
|
||||
TEST(TestHeightmap);
|
||||
if(INTERNET_SIMULATOR == false){
|
||||
TEST(TestSocket);
|
||||
TEST(TestConnection);
|
||||
}
|
||||
std::cout<<"run_tests() passed"<<std::endl;
|
||||
}
|
||||
|
7
src/test.h
Normal file
7
src/test.h
Normal file
@ -0,0 +1,7 @@
|
||||
#ifndef TEST_HEADER
|
||||
#define TEST_HEADER
|
||||
|
||||
void run_tests();
|
||||
|
||||
#endif
|
||||
|
52
src/utility.cpp
Normal file
52
src/utility.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "utility.h"
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void writeU16(u8 *data, u16 i)
|
||||
{
|
||||
data[0] = ((i>> 8)&0xff);
|
||||
data[1] = ((i>> 0)&0xff);
|
||||
}
|
||||
|
||||
void writeU8(u8 *data, u8 i)
|
||||
{
|
||||
data[0] = ((i>> 0)&0xff);
|
||||
}
|
||||
|
||||
u32 readU32(u8 *data)
|
||||
{
|
||||
return (data[0]<<24) | (data[1]<<16) | (data[2]<<8) | (data[3]<<0);
|
||||
}
|
||||
|
||||
u16 readU16(u8 *data)
|
||||
{
|
||||
return (data[0]<<8) | (data[1]<<0);
|
||||
}
|
||||
|
||||
u8 readU8(u8 *data)
|
||||
{
|
||||
return (data[0]<<0);
|
||||
}
|
||||
|
||||
void writeV3S32(u8 *data, v3s32 p)
|
||||
{
|
||||
writeS32(&data[0], p.X);
|
||||
writeS32(&data[4], p.Y);
|
||||
writeS32(&data[8], p.Z);
|
||||
}
|
||||
|
||||
v3s32 readV3S32(u8 *data)
|
||||
{
|
||||
v3s32 p;
|
||||
p.X = readS32(&data[0]);
|
||||
p.Y = readS32(&data[4]);
|
||||
p.Z = readS32(&data[8]);
|
||||
return p;
|
||||
}
|
||||
|
193
src/utility.h
Normal file
193
src/utility.h
Normal file
@ -0,0 +1,193 @@
|
||||
#ifndef UTILITY_HEADER
|
||||
#define UTILITY_HEADER
|
||||
|
||||
// This is actually only included for u8, u16, etc.
|
||||
#include "common_irrlicht.h"
|
||||
|
||||
void writeU32(u8 *data, u32 i);
|
||||
void writeU16(u8 *data, u16 i);
|
||||
void writeU8(u8 *data, u8 i);
|
||||
u32 readU32(u8 *data);
|
||||
u16 readU16(u8 *data);
|
||||
u8 readU8(u8 *data);
|
||||
|
||||
void writeV3S32(u8 *data, v3s32 p);
|
||||
v3s32 readV3S32(u8 *data);
|
||||
|
||||
// Inlines for 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);
|
||||
}
|
||||
|
||||
/*
|
||||
None of these are used at the moment
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
class SharedPtr
|
||||
{
|
||||
public:
|
||||
SharedPtr(T *t)
|
||||
{
|
||||
assert(t != NULL);
|
||||
refcount = new int;
|
||||
*refcount = 1;
|
||||
ptr = t;
|
||||
}
|
||||
SharedPtr(SharedPtr<T> &t)
|
||||
{
|
||||
*this = t;
|
||||
}
|
||||
~SharedPtr()
|
||||
{
|
||||
(*refcount)--;
|
||||
if(*refcount == 0)
|
||||
{
|
||||
delete refcount;
|
||||
delete ptr;
|
||||
}
|
||||
}
|
||||
SharedPtr<T> & operator=(SharedPtr<T> &t)
|
||||
{
|
||||
refcount = t.refcount;
|
||||
(*refcount)++;
|
||||
ptr = t.ptr;
|
||||
return *this;
|
||||
}
|
||||
T* operator->()
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
T & operator*()
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
private:
|
||||
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 (1);
|
||||
}
|
||||
SharedBuffer(const SharedBuffer &buffer)
|
||||
{
|
||||
m_size = buffer.m_size;
|
||||
//data = new T[buffer.m_size];
|
||||
//memcpy(data, buffer.data, buffer.m_size);
|
||||
data = buffer.data;
|
||||
refcount = buffer.refcount;
|
||||
(*refcount)++;
|
||||
}
|
||||
/*
|
||||
Copies whole buffer
|
||||
*/
|
||||
SharedBuffer(T *t, unsigned int size)
|
||||
{
|
||||
m_size = size;
|
||||
data = new T[size];
|
||||
memcpy(data, t, size);
|
||||
refcount = new unsigned int (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 (1);
|
||||
}
|
||||
~SharedBuffer()
|
||||
{
|
||||
(*refcount)--;
|
||||
if(*refcount == 0)
|
||||
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;
|
||||
unsigned int *refcount;
|
||||
};
|
||||
|
||||
inline SharedBuffer<u8> SharedBufferFromString(const char *string)
|
||||
{
|
||||
SharedBuffer<u8> b((u8*)string, strlen(string)+1);
|
||||
return b;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user