Initial commit directly taken from the backup file minetest_10-10-24_16-33-41_wonderful.tar.gz

master
Perttu Ahola 2015-10-02 08:20:56 +03:00
commit 696c623e9e
43 changed files with 9971 additions and 0 deletions

41
.gitignore vendored Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
data/grass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

BIN
data/grass2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

BIN
data/stone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

BIN
data/tf.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
data/water.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

26
makepackage_windows.sh Executable file
View 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
View 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
View 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="&quot;C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include&quot;;&quot;..\jthread\jthread-1.2.1\src&quot;;&quot;..\irrlicht\irrlicht-1.7.1\include&quot;"
PreprocessorDefinitions="WIN32"
EnableEnhancedInstructionSet="1"
FloatingPointModel="2"
DebugInformationFormat="1"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalLibraryDirectories="&quot;C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib&quot;;&quot;..\jthread\jthread-1.2.1\Release&quot;;&quot;..\irrlicht\irrlicht-1.7.1\lib\Win32-visualstudio&quot;"
IgnoreAllDefaultLibraries="false"
GenerateDebugInformation="true"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCWebDeploymentTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="$(SolutionDir)\bin"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
UseOfMFC="0"
WholeProgramOptimization="3"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
InlineFunctionExpansion="2"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
WholeProgramOptimization="true"
AdditionalIncludeDirectories="&quot;C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include&quot;;&quot;..\jthread\jthread-1.2.1\src&quot;;&quot;..\irrlicht\irrlicht-1.7.1\include&quot;"
PreprocessorDefinitions="WIN32;_HAS_ITERATOR_DEBUGGING=0"
BufferSecurityCheck="false"
EnableEnhancedInstructionSet="1"
FloatingPointModel="2"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalLibraryDirectories="&quot;C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib&quot;;&quot;..\jthread\jthread-1.2.1\Release&quot;;&quot;..\irrlicht\irrlicht-1.7.1\lib\Win32-visualstudio&quot;"
IgnoreDefaultLibraryNames="libcmtd.lib"
LinkTimeCodeGeneration="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCWebDeploymentTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\src\client.cpp"
>
</File>
<File
RelativePath=".\src\connection.cpp"
>
</File>
<File
RelativePath=".\src\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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

440
src/connection.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

380
src/map.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

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

52
src/utility.cpp Normal file
View 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
View 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