commit 696c623e9e841dde631573cf69ccaca3fa9de678 Author: Perttu Ahola Date: Fri Oct 2 08:20:56 2015 +0300 Initial commit directly taken from the backup file minetest_10-10-24_16-33-41_wonderful.tar.gz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a724ddc --- /dev/null +++ b/.gitignore @@ -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 + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8cc0eae --- /dev/null +++ b/Makefile @@ -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 diff --git a/data/fontlucida.png b/data/fontlucida.png new file mode 100644 index 0000000..c63fa02 Binary files /dev/null and b/data/fontlucida.png differ diff --git a/data/grass.png b/data/grass.png new file mode 100644 index 0000000..5636205 Binary files /dev/null and b/data/grass.png differ diff --git a/data/grass2.png b/data/grass2.png new file mode 100644 index 0000000..66b3e58 Binary files /dev/null and b/data/grass2.png differ diff --git a/data/stone.png b/data/stone.png new file mode 100644 index 0000000..d644706 Binary files /dev/null and b/data/stone.png differ diff --git a/data/tf.jpg b/data/tf.jpg new file mode 100644 index 0000000..d08b6c9 Binary files /dev/null and b/data/tf.jpg differ diff --git a/data/water.png b/data/water.png new file mode 100644 index 0000000..37b2898 Binary files /dev/null and b/data/water.png differ diff --git a/makepackage_windows.sh b/makepackage_windows.sh new file mode 100755 index 0000000..4ca3695 --- /dev/null +++ b/makepackage_windows.sh @@ -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 + diff --git a/minetest.sln b/minetest.sln new file mode 100644 index 0000000..d1f144c --- /dev/null +++ b/minetest.sln @@ -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 diff --git a/minetest.vcproj b/minetest.vcproj new file mode 100644 index 0000000..f9ec13a --- /dev/null +++ b/minetest.vcproj @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 0000000..a37aa2d --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,513 @@ +#include "client.h" +#include "utility.h" +#include +#include "clientserver.h" +#include "jmutexautolock.h" +#include "main.h" + +#ifdef _WIN32 + #include + #define sleep_ms(x) Sleep(x) +#else + #include + #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 remove_queue; + core::map::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::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()=" + < 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" + <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 (" + <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 " + < 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 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::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 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 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 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 Client::getPlayers() +{ + JMutexAutoLock envlock(m_env_mutex); + return m_env.getPlayers(); +} + + diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..c76fc59 --- /dev/null +++ b/src/client.h @@ -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 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 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 m_fetchblock_history; + //core::list m_fetchblock_queue; + JMutex m_fetchblock_mutex; + + core::list m_incoming_queue; + JMutex m_incoming_queue_mutex; + + scene::ISceneManager* m_smgr; +}; + +#endif + diff --git a/src/clientserver.h b/src/clientserver.h new file mode 100644 index 0000000..7622375 --- /dev/null +++ b/src/clientserver.h @@ -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 + diff --git a/src/common_irrlicht.h b/src/common_irrlicht.h new file mode 100644 index 0000000..ecf7cc7 --- /dev/null +++ b/src/common_irrlicht.h @@ -0,0 +1,13 @@ +#ifndef COMMON_IRRLICHT_HEADER +#define COMMON_IRRLICHT_HEADER + +#include +using namespace irr; +typedef core::vector3df v3f; +typedef core::vector3d v3s16; +typedef core::vector2d v2s16; +typedef core::vector2d v2f32; +typedef core::vector3d v3s32; + +#endif + diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..720b8d7 --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,1181 @@ +#include "connection.h" +#include "main.h" + +namespace con +{ + +BufferedPacket makePacket(Address &address, u8 *data, u32 datasize, + u32 protocol_id, u16 sender_peer_id, u8 channel) +{ + u32 packet_size = datasize + BASE_HEADER_SIZE; + BufferedPacket p(packet_size); + p.address = address; + + writeU32(&p.data[0], protocol_id); + writeU16(&p.data[4], sender_peer_id); + writeU8(&p.data[6], channel); + + memcpy(&p.data[BASE_HEADER_SIZE], data, datasize); + + return p; +} + +BufferedPacket makePacket(Address &address, SharedBuffer &data, + u32 protocol_id, u16 sender_peer_id, u8 channel) +{ + return makePacket(address, *data, data.getSize(), + protocol_id, sender_peer_id, channel); +} + +SharedBuffer makeOriginalPacket( + SharedBuffer data) +{ + u32 header_size = 1; + u32 packet_size = data.getSize() + header_size; + SharedBuffer b(packet_size); + + writeU8(&b[0], TYPE_ORIGINAL); + + memcpy(&b[header_size], *data, data.getSize()); + + return b; +} + +core::list > makeSplitPacket( + SharedBuffer data, + u32 chunksize_max, + u16 seqnum) +{ + // Chunk packets, containing the TYPE_SPLIT header + core::list > chunks; + + u32 chunk_header_size = 7; + u32 maximum_data_size = chunksize_max - chunk_header_size; + u32 start = 0; + u32 end = 0; + u32 chunk_num = 0; + do{ + end = start + maximum_data_size - 1; + if(end > data.getSize() - 1) + end = data.getSize() - 1; + + u32 payload_size = end - start + 1; + u32 packet_size = chunk_header_size + payload_size; + + SharedBuffer chunk(packet_size); + + writeU8(&chunk[0], TYPE_SPLIT); + writeU16(&chunk[1], seqnum); + // [3] u16 chunk_count is written at next stage + writeU16(&chunk[5], chunk_num); + memcpy(&chunk[chunk_header_size], &data[start], payload_size); + + chunks.push_back(chunk); + + start = end + 1; + chunk_num++; + } + while(end != data.getSize() - 1); + + u16 chunk_count = chunks.getSize(); + + core::list >::Iterator i = chunks.begin(); + for(; i != chunks.end(); i++) + { + // Write chunk_count + writeU16(&((*i)[3]), chunk_count); + } + + return chunks; +} + +core::list > makeAutoSplitPacket( + SharedBuffer data, + u32 chunksize_max, + u16 &split_seqnum) +{ + u32 original_header_size = 1; + core::list > list; + if(data.getSize() + original_header_size > chunksize_max) + { + list = makeSplitPacket(data, chunksize_max, split_seqnum); + split_seqnum++; + return list; + } + else + { + list.push_back(makeOriginalPacket(data)); + } + return list; +} + +SharedBuffer makeReliablePacket( + SharedBuffer data, + u16 seqnum) +{ + u32 header_size = 3; + u32 packet_size = data.getSize() + header_size; + SharedBuffer b(packet_size); + + writeU8(&b[0], TYPE_RELIABLE); + writeU16(&b[1], seqnum); + + memcpy(&b[header_size], *data, data.getSize()); + + return b; +} + +/* + ReliablePacketBuffer +*/ + +void ReliablePacketBuffer::print() +{ + core::list::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++) + { + u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + dout_con<::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++) + { + u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + /*dout_con<<"findPacket(): finding seqnum="<::Iterator i = m_list.begin(); + m_list.erase(i); + return p; +} +BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) +{ + RPBSearchResult r = findPacket(seqnum); + if(r == notFound()){ + dout_con<<"Not found"<= BASE_HEADER_SIZE+3); + u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); + assert(type == TYPE_RELIABLE); + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); + + // Find the right place for the packet and insert it there + + // If list is empty, just add it + if(m_list.empty()) + { + m_list.push_back(p); + // Done. + return; + } + // Otherwise find the right place + core::list::Iterator i; + i = m_list.begin(); + // Find the first packet in the list which has a higher seqnum + for(; i != m_list.end(); i++){ + u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + if(s == seqnum){ + throw AlreadyExistsException("Same seqnum in list"); + } + if(seqnum_higher(s, seqnum)){ + break; + } + } + // If we're at the end of the list, add the packet to the + // end of the list + if(i == m_list.end()) + { + m_list.push_back(p); + // Done. + return; + } + // Insert before i + m_list.insert_before(i, p); +} + +void ReliablePacketBuffer::incrementTimeouts(float dtime) +{ + core::list::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++){ + i->time += dtime; + i->totaltime += dtime; + } +} + +void ReliablePacketBuffer::resetTimedOuts(float timeout) +{ + core::list::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++){ + if(i->time >= timeout) + i->time = 0.0; + } +} + +core::list ReliablePacketBuffer::getTimedOuts(float timeout) +{ + core::list timed_outs; + core::list::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++) + { + if(i->time >= timeout) + timed_outs.push_back(*i); + } + return timed_outs; +} + +/* + IncomingSplitBuffer +*/ + +IncomingSplitBuffer::~IncomingSplitBuffer() +{ + core::map::Iterator i; + i = m_buf.getIterator(); + for(; i.atEnd() == false; i++) + { + delete i.getNode()->getValue(); + } +} +/* + This will throw a GotSplitPacketException when a full + split packet is constructed. +*/ +void IncomingSplitBuffer::insert(BufferedPacket &p, bool reliable) +{ + u32 headersize = BASE_HEADER_SIZE + 7; + assert(p.data.getSize() >= headersize); + u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); + assert(type == TYPE_SPLIT); + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); + u16 chunk_count = readU16(&p.data[BASE_HEADER_SIZE+3]); + u16 chunk_num = readU16(&p.data[BASE_HEADER_SIZE+5]); + + // Add if doesn't exist + if(m_buf.find(seqnum) == NULL) + { + IncomingSplitPacket *sp = new IncomingSplitPacket(); + sp->chunk_count = chunk_count; + sp->reliable = reliable; + m_buf[seqnum] = sp; + } + + IncomingSplitPacket *sp = m_buf[seqnum]; + + if(chunk_count != sp->chunk_count) + dout_con<<"WARNING: chunk_count="<chunk_count="<chunk_count + <reliable) + dout_con<<"WARNING: reliable="<reliable="<reliable + <chunks.find(chunk_num) != NULL) + throw AlreadyExistsException("Chunk already in buffer"); + + // Cut chunk data out of packet + u32 chunkdatasize = p.data.getSize() - headersize; + SharedBuffer chunkdata(chunkdatasize); + memcpy(*chunkdata, &(p.data[headersize]), chunkdatasize); + + // Set chunk data in buffer + sp->chunks[chunk_num] = chunkdata; + + // If not all chunks are received, return + if(sp->allReceived() == false) + return; + + // Calculate total size + u32 totalsize = 0; + core::map >::Iterator i; + i = sp->chunks.getIterator(); + for(; i.atEnd() == false; i++) + { + totalsize += i.getNode()->getValue().getSize(); + } + + SharedBuffer fulldata(totalsize); + + // Copy chunks to data buffer + u32 start = 0; + for(u32 chunk_i=0; chunk_ichunk_count; + chunk_i++) + { + SharedBuffer buf = sp->chunks[chunk_i]; + u16 chunkdatasize = buf.getSize(); + memcpy(&fulldata[start], *buf, chunkdatasize); + start += chunkdatasize;; + } + + // Remove sp from buffer + m_buf.remove(seqnum); + delete sp; + + throw GotSplitPacketException(fulldata); +} +void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout) +{ + core::list remove_queue; + core::map::Iterator i; + i = m_buf.getIterator(); + for(; i.atEnd() == false; i++) + { + IncomingSplitPacket *p = i.getNode()->getValue(); + // Reliable ones are not removed by timeout + if(p->reliable == true) + continue; + p->time += dtime; + if(p->time >= timeout) + remove_queue.push_back(i.getNode()->getKey()); + } + core::list::Iterator j; + j = remove_queue.begin(); + for(; j != remove_queue.end(); j++) + { + dout_con<<"NOTE: Removing timed out unreliable split packet" + <::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + delete peer; + } +} + +void Connection::Serve(unsigned short port) +{ + m_socket.Bind(port); + m_peer_id = PEER_ID_SERVER; +} + +void Connection::Connect(Address address) +{ + core::map::Node *node = m_peers.find(PEER_ID_SERVER); + if(node != NULL){ + throw ConnectionException("Already connected to a server"); + } + + Peer *peer = new Peer(); + peer->address = address; + peer->id = PEER_ID_SERVER; + m_peers.insert(peer->id, peer); + + m_socket.Bind(0); + + // Send a dummy packet to server with peer_id = PEER_ID_NEW + m_peer_id = PEER_ID_NEW; + SharedBuffer data(0); + Send(PEER_ID_SERVER, 0, data, true); + + m_waiting_new_peer_id = true; +} + +bool Connection::Connected() +{ + if(m_peers.size() != 1) + return false; + + core::map::Node *node = m_peers.find(PEER_ID_SERVER); + if(node == NULL) + return false; + + if(m_peer_id == PEER_ID_NEW) + return false; + + return true; +} + +SharedBuffer Channel::ProcessPacket( + SharedBuffer packetdata, + Connection *con, + u16 peer_id, + u8 channelnum, + bool reliable) +{ + IndentationRaiser iraiser(&(con->m_indentation)); + + if(packetdata.getSize() < 1) + throw InvalidIncomingDataException("packetdata.getSize() < 1"); + + u8 type = readU8(&packetdata[0]); + + if(type == TYPE_CONTROL) + { + if(packetdata.getSize() < 2) + throw InvalidIncomingDataException("packetdata.getSize() < 2"); + + u8 controltype = readU8(&packetdata[1]); + + if(controltype == CONTROLTYPE_ACK) + { + if(packetdata.getSize() < 4) + throw InvalidIncomingDataException + ("packetdata.getSize() < 4 (ACK header size)"); + + u16 seqnum = readU16(&packetdata[2]); + con->PrintInfo(); + dout_con<<"Got CONTROLTYPE_ACK: channelnum=" + <<((int)channelnum&0xff)<<", peer_id="<PrintInfo(); + outgoing_reliables.print(); + dout_con<PrintInfo(); + dout_con<<"WARNING: ACKed packet not in outgoing queue" + <PrintInfo(); + dout_con<<"Got new peer id: "<GetPeerID() != PEER_ID_NEW) + { + dout_con<<"WARNING: not changing."<SetPeerID(peer_id_new); + } + throw ProcessedSilentlyException("Got a SET_PEER_ID"); + } + else if(controltype == CONTROLTYPE_PING) + { + // Just ignore it, the incoming data already reset + // the timeout counter + con->PrintInfo(); + dout_con<<"PING"<PrintInfo(); + dout_con<<"INVALID TYPE_CONTROL: invalid controltype=" + <<((int)controltype&0xff)<PrintInfo(); + dout_con<<"RETURNING TYPE_ORIGINAL to user" + < payload(packetdata.getSize() - ORIGINAL_HEADER_SIZE); + memcpy(*payload, &packetdata[ORIGINAL_HEADER_SIZE], payload.getSize()); + return payload; + } + else if(type == TYPE_SPLIT) + { + // We have to create a packet again for buffering + // This isn't actually too bad an idea. + BufferedPacket packet = makePacket( + con->GetPeer(peer_id)->address, + packetdata, + con->GetProtocolID(), + peer_id, + channelnum); + try{ + // Buffer the packet + incoming_splits.insert(packet, reliable); + } + // This exception happens when all the pieces of a packet + // are collected. + catch(GotSplitPacketException &e) + { + con->PrintInfo(); + dout_con<<"RETURNING TYPE_SPLIT: Constructed full data, " + <<"size="<PrintInfo(); + dout_con<<"BUFFERING TYPE_SPLIT"<PrintInfo(); + if(is_future_packet) + dout_con<<"BUFFERING"; + else if(is_old_packet) + dout_con<<"OLD"; + else + dout_con<<"RECUR"; + dout_con<<" TYPE_RELIABLE seqnum="< reply(4); + writeU8(&reply[0], TYPE_CONTROL); + writeU8(&reply[1], CONTROLTYPE_ACK); + writeU16(&reply[2], seqnum); + con->SendAsPacket(peer_id, channelnum, reply, false); + + //if(seqnum_higher(seqnum, next_incoming_seqnum)) + if(is_future_packet) + { + /*con->PrintInfo(); + dout_con<<"Buffering reliable packet (seqnum=" + <GetPeer(peer_id)->address, + packetdata, + con->GetProtocolID(), + peer_id, + channelnum); + try{ + incoming_reliables.insert(packet); + + /*con->PrintInfo(); + dout_con<<"INCOMING: "; + incoming_reliables.print(); + dout_con< payload(packetdata.getSize() - RELIABLE_HEADER_SIZE); + memcpy(*payload, &packetdata[RELIABLE_HEADER_SIZE], payload.getSize()); + + return ProcessPacket(payload, con, peer_id, channelnum, true); + } + else + { + con->PrintInfo(); + dout_con<<"Got invalid type="<<((int)type&0xff)< Channel::CheckIncomingBuffers(Connection *con, + u16 &peer_id) +{ + u16 firstseqnum = 0; + // Clear old packets from start of buffer + try{ + for(;;){ + firstseqnum = incoming_reliables.getFirstSeqnum(); + if(seqnum_higher(next_incoming_seqnum, firstseqnum)) + incoming_reliables.popFirst(); + else + break; + } + // This happens if all packets are old + }catch(con::NotFoundException) + {} + + if(incoming_reliables.empty() == false) + { + if(firstseqnum == next_incoming_seqnum) + { + BufferedPacket p = incoming_reliables.popFirst(); + + peer_id = readPeerId(*p.data); + u8 channelnum = readChannel(*p.data); + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); + + con->PrintInfo(); + dout_con<<"UNBUFFERING TYPE_RELIABLE" + <<" seqnum="< Connection::GetFromBuffers(u16 &peer_id) +{ + core::map::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + for(u16 i=0; ichannels[i]; + try{ + SharedBuffer resultdata = channel->CheckIncomingBuffers + (this, peer_id); + + return resultdata; + } + catch(NoIncomingDataException &e) + { + } + catch(InvalidIncomingDataException &e) + { + } + catch(ProcessedSilentlyException &e) + { + } + } + } + throw NoIncomingDataException("No relevant data in buffers"); +} + +u32 Connection::Receive(u16 &peer_id, u8 *data, u32 datasize) +{ + /* + Receive a packet from the network + */ + + // TODO: We can not know how many layers of header there are. + // For now, just assume there are no other than the base headers. + u32 packet_maxsize = datasize + BASE_HEADER_SIZE; + Buffer packetdata(packet_maxsize); + + for(;;) + { + try + { + /* + Check if some buffer has relevant data + */ + try{ + SharedBuffer resultdata = GetFromBuffers(peer_id); + + if(datasize < resultdata.getSize()) + throw InvalidIncomingDataException + ("Buffer too small for received data"); + + memcpy(data, *resultdata, resultdata.getSize()); + return resultdata.getSize(); + } + catch(NoIncomingDataException &e) + { + } + + Address sender; + + s32 received_size = m_socket.Receive(sender, *packetdata, packet_maxsize); + + if(received_size < 0) + throw NoIncomingDataException("No incoming data"); + if(received_size < BASE_HEADER_SIZE) + throw InvalidIncomingDataException("No full header received"); + if(readU32(&packetdata[0]) != m_protocol_id) + throw InvalidIncomingDataException("Invalid protocol id"); + + peer_id = readPeerId(*packetdata); + u8 channelnum = readChannel(*packetdata); + if(channelnum > CHANNEL_COUNT-1){ + PrintInfo(); + dout_con<<"Receive(): Invalid channel "<::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + if(peer->has_sent_with_id) + continue; + if(peer->address == sender) + break; + } + + /* + If no peer was found with the same address and port, + we shall assume it is a new peer and create an entry. + */ + if(j.atEnd()) + { + // Pass on to adding the peer + } + // Else: A peer was found. + else + { + Peer *peer = j.getNode()->getValue(); + peer_id = peer->id; + PrintInfo(); + dout_con<<"WARNING: Assuming unknown peer to be " + <<"peer_id="<::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + if(peer_id_new <= peer->id) + peer_id_new = peer->id + 1; + } + + PrintInfo(); + dout_con<<"Receive(): Got a packet with peer_id=PEER_ID_NEW," + " giving peer_id="<address = sender; + peer->id = peer_id_new; + m_peers.insert(peer->id, peer); + + // Create CONTROL packet to tell the peer id to the new peer. + SharedBuffer reply(4); + writeU8(&reply[0], TYPE_CONTROL); + writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); + writeU16(&reply[2], peer_id_new); + SendAsPacket(peer_id_new, 0, reply, true); + + // We're now talking to a valid peer_id + peer_id = peer_id_new; + + // Go on and process whatever it sent + } + + core::map::Node *node = m_peers.find(peer_id); + + if(node == NULL){ + // Peer not found + PrintInfo(); + dout_con<<"Receive(): Peer not found"<getValue(); + + //TODO: Validate peer address + + peer->timeout_counter = 0.0; + + Channel *channel = &(peer->channels[channelnum]); + + // Throw the received packet to channel->processPacket() + + // Make a new SharedBuffer from the data without the base headers + SharedBuffer strippeddata(received_size - BASE_HEADER_SIZE); + memcpy(*strippeddata, &packetdata[BASE_HEADER_SIZE], + strippeddata.getSize()); + + try{ + // Process it (the result is some data with no headers made by us) + SharedBuffer resultdata = channel->ProcessPacket + (strippeddata, this, peer_id, channelnum); + + PrintInfo(); + dout_con<<"ProcessPacket returned data of size " + < data, bool reliable) +{ + core::map::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + Send(peer->id, channelnum, data, reliable); + } +} + +void Connection::Send(u16 peer_id, u8 channelnum, + SharedBuffer data, bool reliable) +{ + assert(channelnum < CHANNEL_COUNT); + + Peer *peer = GetPeer(peer_id); + Channel *channel = &(peer->channels[channelnum]); + + u32 chunksize_max = m_max_packet_size - BASE_HEADER_SIZE; + if(reliable) + chunksize_max -= RELIABLE_HEADER_SIZE; + + core::list > originals; + originals = makeAutoSplitPacket(data, chunksize_max, + channel->next_outgoing_split_seqnum); + + core::list >::Iterator i; + i = originals.begin(); + for(; i != originals.end(); i++) + { + SharedBuffer original = *i; + + SendAsPacket(peer_id, channelnum, original, reliable); + } +} + +void Connection::SendAsPacket(u16 peer_id, u8 channelnum, + SharedBuffer data, bool reliable) +{ + Peer *peer = GetPeer(peer_id); + Channel *channel = &(peer->channels[channelnum]); + + if(reliable) + { + u16 seqnum = channel->next_outgoing_seqnum; + channel->next_outgoing_seqnum++; + + SharedBuffer reliable = makeReliablePacket(data, seqnum); + + // Add base headers and make a packet + BufferedPacket p = makePacket(peer->address, reliable, + m_protocol_id, m_peer_id, channelnum); + + try{ + // Buffer the packet + channel->outgoing_reliables.insert(p); + } + catch(AlreadyExistsException &e) + { + PrintInfo(); + dout_con<<"WARNING: Going to send a reliable packet " + "seqnum="<address, data, + m_protocol_id, m_peer_id, channelnum); + + // Send the packet + RawSend(p); + } +} + +void Connection::RawSend(const BufferedPacket &packet) +{ + m_socket.Send(packet.address, *packet.data, packet.data.getSize()); +} + +void Connection::RunTimeouts(float dtime) +{ + core::list timeouted_peers; + core::map::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + + // Check peer timeout + peer->timeout_counter += dtime; + // TODO: Get the constant timeout from somewhere else + if(peer->timeout_counter > 30.0) + { + PrintInfo(); + dout_con<<"RunTimeouts(): Peer "<id + <<" has timeouted."<id); + // Don't bother going through the buffers of this one + continue; + } + + peer->ping_timer += dtime; + if(peer->ping_timer >= 5.0) + { + // Create and send PING packet + SharedBuffer data(2); + writeU8(&data[0], TYPE_CONTROL); + writeU8(&data[1], CONTROLTYPE_PING); + SendAsPacket(peer->id, 0, data, true); + + peer->ping_timer = 0.0; + } + + float resend_timeout = peer->resend_timeout; + for(u16 i=0; ichannels[i]; + + // Re-send timed out outgoing reliables + + channel->outgoing_reliables.incrementTimeouts(dtime); + + core::list timed_outs + = channel->outgoing_reliables.getTimedOuts(resend_timeout); + + channel->outgoing_reliables.resetTimedOuts(resend_timeout); + + core::list::Iterator j = timed_outs.begin(); + for(; j != timed_outs.end(); j++) + { + u16 peer_id = readPeerId(*(j->data)); + u8 channel = readChannel(*(j->data)); + u16 seqnum = readU16(&(j->data[BASE_HEADER_SIZE+1])); + PrintInfo(); + dout_con<<"RE-SENDING timed-out RELIABLE: " + <<"peer_id="<::Node *node = m_peers.find(peer_id); + + if(node == NULL){ + // Peer not found + throw NotFoundException("Peer not found (possible timeout)"); + } + + // Error checking + assert(node->getValue()->id == peer_id); + + return node->getValue(); +} + +void Connection::PrintInfo() +{ + /*for(u16 i=0; i +#include "common_irrlicht.h" +#include "socket.h" +#include "utility.h" +#include "exceptions.h" +#include +#include + +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 m_data; +public: + GotSplitPacketException(SharedBuffer data): + m_data(data) + {} + SharedBuffer 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 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 &data, + u32 protocol_id, u16 sender_peer_id, u8 channel); + +// Add the TYPE_ORIGINAL header to the data +SharedBuffer makeOriginalPacket( + SharedBuffer data); + +// Split data in chunks and add TYPE_SPLIT headers to them +core::list > makeSplitPacket( + SharedBuffer 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 > makeAutoSplitPacket( + SharedBuffer data, + u32 chunksize_max, + u16 &split_seqnum); + +// Add the TYPE_RELIABLE header to the data +SharedBuffer makeReliablePacket( + SharedBuffer data, + u16 seqnum); + +struct IncomingSplitPacket +{ + IncomingSplitPacket() + { + time = 0.0; + reliable = false; + } + // Key is chunk number, value is data without headers + core::map > 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::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 getTimedOuts(float timeout); + +private: + core::list 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 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 ProcessPacket( + SharedBuffer 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 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 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 data, bool reliable); + void Send(u16 peer_id, u8 channelnum, SharedBuffer 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 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 m_peers; + u16 m_peer_id; + bool m_waiting_new_peer_id; + u32 m_max_packet_size; + UDPSocket m_socket; +}; + +} // namespace + +#endif + diff --git a/src/environment.cpp b/src/environment.cpp new file mode 100644 index 0000000..bc28195 --- /dev/null +++ b/src/environment.cpp @@ -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::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::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::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 " + <peer_id<::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::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::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::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 Environment::getPlayers() +{ + return m_players; +} + diff --git a/src/environment.h b/src/environment.h new file mode 100644 index 0000000..1f8b572 --- /dev/null +++ b/src/environment.h @@ -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 +#include "common_irrlicht.h" +#include "player.h" +#include "map.h" +#include + +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 getPlayers(); +private: + Map *m_map; + core::list m_players; + // Debug output goes here + std::ostream &m_dout; +}; + +#endif + diff --git a/src/exceptions.h b/src/exceptions.h new file mode 100644 index 0000000..8c2c0c0 --- /dev/null +++ b/src/exceptions.h @@ -0,0 +1,73 @@ +#ifndef EXCEPTIONS_HEADER +#define EXCEPTIONS_HEADER + +#include + +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 + diff --git a/src/heightmap.cpp b/src/heightmap.cpp new file mode 100644 index 0000000..3fca5f1 --- /dev/null +++ b/src/heightmap.cpp @@ -0,0 +1,437 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#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 - 1) + break; + // odd rows + count_x = (W-1) / a + 1; + for(s32 xi=0; xi= 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::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::Node *n = m_heightmaps.find(p); + + if(n != NULL) + { + return n->getValue(); + } + + /*std::cout<<"UnlimitedHeightmap::getHeightmap((" + <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((" + <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("<getGroundHeight(npos, false); + //std::cout<<"h="< GROUNDHEIGHT_VALID_MINVALUE) + continue; + setGroundHeight(dirs[i] * a, corners[i]); + } + + if(HEIGHTMAP_DEBUGPRINT){ + std::cout<<"corners filled:"< +*/ + +#ifndef HEIGHTMAP_HEADER +#define HEIGHTMAP_HEADER + +#include +#include +#include + +#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()"<= 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((" + <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()"< 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 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::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 + diff --git a/src/light.h b/src/light.h new file mode 100644 index 0000000..fe641db --- /dev/null +++ b/src/light.h @@ -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 + diff --git a/src/loadstatus.h b/src/loadstatus.h new file mode 100644 index 0000000..a5fb6b3 --- /dev/null +++ b/src/loadstatus.h @@ -0,0 +1,144 @@ +#ifndef LOADSTATUS_HEADER +#define LOADSTATUS_HEADER + +class LoadStatus +{ + bool ready; + JMutex ready_mutex; + + u32 done; + JMutex done_mutex; + + u32 todo; + JMutex todo_mutex; + + wchar_t *text; + JMutex text_mutex; + +public: + + LoadStatus(bool a_ready=false, u32 a_done=0, u32 a_todo=0) + { + ready = a_ready; + done = a_done; + todo = a_todo; + text = NULL; + ready_mutex.Init(); + done_mutex.Init(); + todo_mutex.Init(); + text_mutex.Init(); + } + + void setReady(bool a_ready) + { + ready_mutex.Lock(); + ready = a_ready; + ready_mutex.Unlock(); + } + + bool getReady(void) + { + ready_mutex.Lock(); + bool a_ready = ready; + ready_mutex.Unlock(); + return a_ready; + } + + void setDone(u32 a_done) + { + done_mutex.Lock(); + done = a_done; + done_mutex.Unlock(); + } + + u32 getDone(void) + { + done_mutex.Lock(); + u32 a_done = done; + done_mutex.Unlock(); + return a_done; + } + + void setTodo(u32 a_todo) + { + todo_mutex.Lock(); + todo = a_todo; + todo_mutex.Unlock(); + } + + u32 getTodo(void) + { + todo_mutex.Lock(); + u32 a_todo = todo; + todo_mutex.Unlock(); + return a_todo; + } + + /* + Copies the text if not NULL, + If NULL; sets text to NULL. + */ + void setText(const wchar_t *a_text) + { + text_mutex.Lock(); + if(text != NULL) + free(text); + if(a_text == NULL){ + text = NULL; + text_mutex.Unlock(); + return; + } + u32 len = wcslen(a_text); + text = (wchar_t*)malloc(sizeof(wchar_t) * (len+1)); + if(text == NULL) throw; + swprintf(text, len+1, L"%ls", a_text); + text_mutex.Unlock(); + } + + /* + Return value must be free'd + Return value can be NULL + */ + wchar_t * getText() + { + text_mutex.Lock(); + if(text == NULL){ + text_mutex.Unlock(); + return NULL; + } + u32 len = wcslen(text); + wchar_t *b_text = (wchar_t*)malloc(sizeof(wchar_t) * (len+1)); + if(b_text == NULL) throw; + swprintf(b_text, len+1, L"%ls", text); + text_mutex.Unlock(); + return b_text; + } + + /* + Return value must be free'd + */ + wchar_t * getNiceText() + { + const wchar_t *defaulttext = L"Loading"; + wchar_t *t = getText(); + u32 maxlen = 20; // " (%i/%i)" + if(t != NULL) + maxlen += wcslen(t); + else + maxlen += wcslen(defaulttext); + wchar_t *b_text = (wchar_t*)malloc(sizeof(wchar_t) * (maxlen+1)); + if(b_text == NULL) throw; + if(t != NULL) + swprintf(b_text, maxlen+1, L"%ls (%i/%i)", + t, getDone(), getTodo()); + else + swprintf(b_text, maxlen+1, L"%ls (%i/%i)", + defaulttext, getDone(), getTodo()); + if(t != NULL) + free(t); + return b_text; + } +}; + +#endif + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6ca6a73 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,976 @@ +/* +(c) 2010 Perttu Ahola + +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 + #define sleep_ms(x) Sleep(x) +#else + #include + #define sleep_ms(x) usleep(x*1000) +#endif + +#include +#include +#include +#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 + +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< 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 = " + <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. "<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. "<getTimer()->getRealTime(); + + core::map 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. "<getTimer()->getRealTime(); + u32 time2 = time1; + + JMutex m; + m.Init(); + u32 n = 0; + u32 i = 0; + do{ + n += 10000; + for(; igetTimer()->getRealTime(); + } + // Do at least 10ms + while(time2 < time1 + 10); + + u32 dtime = time2 - time1; + u32 per_ms = n / dtime; + std::cout<<"Done. "< "<>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(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 center(screenW/2, screenH/2); + core::dimension2d textd = font->getDimension(text); + std::cout<<"Text w="<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"<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"<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(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 > 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 shootline(camera_position, + camera_position + camera_direction * BS * (d+1)); + + bool nodefound = false; + v3s16 nodepos; + v3s16 neighbourpos; + core::aabbox3d 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=("< 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["< facebox(corners[0],corners[1]); + core::aabbox3d 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"<setText(positiontext);*/ + } + + hilightboxes.push_back(nodefacebox); + + if(receiver.leftclicked){ + std::cout<<"Removing block (MapNode)"<getTimer()->getRealTime(); + + //client.m_env.getMap().removeNodeAndUpdate(nodepos); + client.removeNode(nodepos); + + u32 time2 = device->getTimer()->getRealTime(); + u32 dtime = time2 - time1; + std::cout<<"Took "<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 "<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()"<drawAll(); + + core::vector2d displaycenter(screenW/2,screenH/2); + driver->draw2DLine(displaycenter - core::vector2d(10,0), + displaycenter + core::vector2d(10,0), + video::SColor(255,255,255,255)); + driver->draw2DLine(displaycenter - core::vector2d(0,10), + displaycenter + core::vector2d(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 >::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 diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..04102a3 --- /dev/null +++ b/src/main.h @@ -0,0 +1,26 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef MAIN_HEADER +#define MAIN_HEADER + +#include + +#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 + +// Debug streams +extern std::ostream dout_con; +extern std::ostream dout_client; +extern std::ostream dout_server; + +#endif + diff --git a/src/map.cpp b/src/map.cpp new file mode 100644 index 0000000..a4e0a2e --- /dev/null +++ b/src/map.cpp @@ -0,0 +1,1204 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#include "map.h" +//#include "player.h" +#include "main.h" +#include "jmutexautolock.h" +#include "client.h" + +#ifdef _WIN32 + #include + #define sleep_ms(x) Sleep(x) +#else + #include + #define sleep_ms(x) usleep(x*1000) +#endif + +/* +void limitBox(core::aabbox3d & box, core::aabbox3d & limits) +{ + if(box.MinEdge.X < limits.MinEdge.X) + box.MinEdge.X = limits.MinEdge.X; + if(box.MaxEdge.X > limits.MaxEdge.X) + box.MaxEdge.X = limits.MaxEdge.X; + if(box.MinEdge.Y < limits.MinEdge.Y) + box.MinEdge.Y = limits.MinEdge.Y; + if(box.MaxEdge.Y > limits.MaxEdge.Y) + box.MaxEdge.Y = limits.MaxEdge.Y; + if(box.MinEdge.Z < limits.MinEdge.Z) + box.MinEdge.Z = limits.MinEdge.Z; + if(box.MaxEdge.Z > limits.MaxEdge.Z) + box.MaxEdge.Z = limits.MaxEdge.Z; +} +*/ + +void * MapUpdateThread::Thread() +{ + if(map == NULL) + return NULL; + + ThreadStarted(); + + while(getRun()) + { + //std::cout<<"UpdateThread running"<updateChangedVisibleArea(); + + if(did_something == false) + sleep_ms(500); + } + + std::cout<<"UpdateThread stopped"<::Iterator i = m_sectors.getIterator(); + for(; i.atEnd() == false; i++) + { + MapSector *sector = i.getNode()->getValue(); + delete sector; + } +} + +/* + TODO: These mutexes need rethinking. They don't work properly + at the moment. Maybe. Or do they? +*/ + +/*bool Map::sectorExists(v2s16 p) +{ + JMutexAutoLock lock(m_getsector_mutex); + core::map::Node *n = m_sectors.find(p); + return (n != NULL); +}*/ + +MapSector * Map::getSectorNoGenerate(v2s16 p) +{ + //JMutexAutoLock lock(m_getsector_mutex); + + if(m_sector_cache != NULL && p == m_sector_cache_p){ + MapSector * ref(m_sector_cache); + return ref; + } + + core::map::Node *n = m_sectors.find(p); + // If sector doesn't exist, throw an exception + if(n == NULL) + { + /* + TODO: Check if sector is stored on disk + and load dynamically + */ + //sector = NULL; + throw InvalidPositionException(); + } + + MapSector *sector = n->getValue(); + + // Cache the last result + m_sector_cache_p = p; + m_sector_cache = sector; + + //MapSector * ref(sector); + + return sector; +} + +MapSector * Map::getSector(v2s16 p2d) +{ + // Stub virtual method + assert(0); + return getSectorNoGenerate(p2d); +} + +/* + If sector doesn't exist, returns NULL + Otherwise returns what the sector returns + + TODO: Change this to use exceptions? +*/ +MapBlock * Map::getBlockNoCreate(v3s16 p3d) +{ + /*std::cout<<"Map::getBlockNoCreate((" + <mutex); + + MapBlock * blockref = sector->getBlockNoCreate(p3d.Y); + + return blockref; +} + +/* + If sector doesn't exist, generates a sector. + Returns what the sector returns. +*/ +MapBlock * Map::getBlock(v3s16 p3d) +{ + v2s16 p2d(p3d.X, p3d.Z); + //v2s16 sectorpos = getNodeSectorPos(p2d); + MapSector * sref = getSector(p2d); + + /*std::cout<<"Map::GetBlock(): sref->getRefcount()=" + <getRefcount() + <mutex); + + MapBlock * blockref = sector->getBlock(p3d.Y); + + /*std::cout<<"Map::GetBlock(): blockref->getRefcount()=" + <getRefcount() + <mutex.Lock(); + f32 y = sref->getGroundHeight(relpos); + //sref->mutex.Unlock(); + //std::cout<<"[found]"<mutex.Lock(); + sref->setGroundHeight(relpos, y); + //sref->mutex.Unlock(); +} + +/* + TODO: Check the order of changing lighting and recursing in + these functions (choose the faster one) + + TODO: It has to done like this: cache the neighbours that were + changed; then recurse to them. +*/ + +/* + Goes recursively through the neighbours of the node. + + Alters only transparent nodes. + + If the lighting of the neighbour is lower than the lighting of + the node was (before changing it to 0 at the step before), the + lighting of the neighbour is set to 0 and then the same stuff + repeats for the neighbour. + + Some things are made strangely to make it as fast as possible. + + Usage: + core::list light_sources; + core::map modified_blocks; + f32 oldlight = node_at_pos.light; + node_at_pos.light = 0; + unLightNeighbors(pos, oldlight, light_sources, modified_blocks); +*/ +void Map::unLightNeighbors(v3s16 pos, f32 oldlight, + core::list & light_sources, + core::map & modified_blocks) +{ + v3s16 dirs[6] = { + v3s16(0,0,1), // back + v3s16(0,1,0), // top + v3s16(1,0,0), // right + v3s16(0,0,-1), // front + v3s16(0,-1,0), // bottom + v3s16(-1,0,0), // left + }; + + // Loop through 6 neighbors + for(u16 i=0; i<6; i++){ + // Get the position of the neighbor node + v3s16 n2pos = pos + dirs[i]; + + // Get the block where the node is located + v3s16 blockpos = getNodeBlockPos(n2pos); + MapBlock *block; + try{ + block = getBlockNoCreate(blockpos); + } + catch(InvalidPositionException &e) + { + // If block is inexistent, move to next one. + continue; + } + + // Find if block is in cache container + core::map::Node *cacheblocknode; + cacheblocknode = modified_blocks.find(blockpos); + + // If the block is not found in the cache + if(cacheblocknode == NULL) + { + /* + Add block to cache container. It could be a 'set' but + there is no such thing in Irrlicht. + + We can use the pointer as the value of a map just fine, + it gets nicely cached that way, too. + */ + modified_blocks.insert(blockpos, block); + } + + // Calculate relative position in block + v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE; + // Get node straight from the block (fast!) + MapNode *n2 = block->getNodePtr(relpos); + + /* + If the neighbor is dimmer than what was specified + as oldlight (the light of the previous node)... + */ + if(n2->light < oldlight - 0.001){ + if(n2->transparent()){ + light_t current_light = n2->light; + n2->light = 0.0; + unLightNeighbors(n2pos, current_light, + light_sources, modified_blocks); + } + } + else{ + light_sources.push_back(n2pos); + } + } +} + +/* + Goes recursively through the neighbours of the node. + + Alters only transparent nodes. + + If the lighting of the neighbour is lower than the calculated + lighting coming from the current node to it, the lighting of + the neighbour is set and the same thing repeats. + + Some things are made strangely to make it as fast as possible. + + Usage: + core::map modified_blocks; + lightNeighbors(pos, node_at_pos.light, modified_blocks); +*/ +/*void Map::lightNeighbors(v3s16 pos, f32 oldlight, + core::map & modified_blocks)*/ +void Map::lightNeighbors(v3s16 pos, + core::map & modified_blocks) +{ + v3s16 dirs[6] = { + v3s16(0,0,1), // back + v3s16(0,1,0), // top + v3s16(1,0,0), // right + v3s16(0,0,-1), // front + v3s16(0,-1,0), // bottom + v3s16(-1,0,0), // left + }; + + core::list neighbour_cache; + + /* + Initialize block cache + (using center node as a starting position) + */ + v3s16 blockpos_last = getNodeBlockPos(pos); + MapBlock *block = NULL; + try{ + block = getBlockNoCreate(blockpos_last); + } + catch(InvalidPositionException &e) + { + return; + } + + // Calculate relative position in block + v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE; + // Get node straight from the block (fast!) + MapNode *n = block->getNodePtr(relpos); + + f32 oldlight = n->light; + f32 newlight = oldlight * LIGHT_DIMINISH_FACTOR; + + // Loop through 6 neighbors + for(u16 i=0; i<6; i++){ + // Get the position of the neighbor node + v3s16 n2pos = pos + dirs[i]; + + // Get the block where the node is located + v3s16 blockpos = getNodeBlockPos(n2pos); + + try + { + /* + Only fetch a new block if the block position has changed + */ + if(blockpos != blockpos_last){ + block = getBlockNoCreate(blockpos); + } + blockpos_last = blockpos; + + // Find if block is in cache container + core::map::Node *cacheblocknode; + cacheblocknode = modified_blocks.find(blockpos); + + // If the block is not found in the cache + if(cacheblocknode == NULL) + { + /* + Add block to cache container. It could be a 'set' but + there is no such thing in Irrlicht. + + We can use the pointer as the value of a map just fine, + it gets nicely cached that way, too. + */ + modified_blocks.insert(blockpos, block); + } + + // Calculate relative position in block + v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE; + // Get node straight from the block (fast!) + MapNode *n2 = block->getNodePtr(relpos); + + /* + If the neighbor is dimmer than what was specified + as oldlight (the light of the previous node)... + */ + if(n2->light_source() > oldlight / LIGHT_DIMINISH_FACTOR + 0.01) + { + n2->light = n2->light_source(); + neighbour_cache.push_back(n2pos); + } + if(n2->light < newlight - 0.001) + { + if(n2->transparent()) + { + n2->light = newlight; + // Cache and recurse at last step for maximum speed + neighbour_cache.push_back(n2pos); + } + } + } + catch(InvalidPositionException &e) + { + continue; + } + } + + core::list::Iterator j = neighbour_cache.begin(); + for(; j != neighbour_cache.end(); j++){ + lightNeighbors(*j, modified_blocks); + } +} + +v3s16 Map::getBrightestNeighbour(v3s16 p) +{ + v3s16 dirs[6] = { + v3s16(0,0,1), // back + v3s16(0,1,0), // top + v3s16(1,0,0), // right + v3s16(0,0,-1), // front + v3s16(0,-1,0), // bottom + v3s16(-1,0,0), // left + }; + + f32 brightest_light = -1.0; + v3s16 brightest_pos(0,0,0); + + // Loop through 6 neighbors + for(u16 i=0; i<6; i++){ + // Get the position of the neighbor node + v3s16 n2pos = p + dirs[i]; + MapNode n2; + try{ + n2 = getNode(n2pos); + } + catch(InvalidPositionException &e) + { + continue; + } + if(n2.light > brightest_light){ + brightest_light = n2.light; + brightest_pos = n2pos; + } + } + if(brightest_light < -0.001) + throw; + return brightest_pos; +} + +/* + Propagates sunlight down from a node. + Starting point gets sunlight. + + Returns the lowest y value of where the sunlight went. +*/ +s16 Map::propagateSunlight(v3s16 start, + core::map & modified_blocks) +{ + s16 y = start.Y; + for(; ; y--) + { + v3s16 pos(start.X, y, start.Z); + + v3s16 blockpos = getNodeBlockPos(pos); + MapBlock *block; + try{ + block = getBlockNoCreate(blockpos); + } + catch(InvalidPositionException &e) + { + break; + } + + v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE; + MapNode *n = block->getNodePtr(relpos); + + if(n->transparent()) + { + n->light = 1.0; + + modified_blocks.insert(blockpos, block); + } + else{ + break; + } + } + return y + 1; +} + +void Map::updateLighting(core::list< MapBlock*> & a_blocks, + core::map & modified_blocks) +{ + std::cout<<"Map::updateLighting(): " + < temp_blocks = a_blocks; + + /* + Go from the highest blocks to the lowest, propagating sunlight + through them. + */ + + while(temp_blocks.empty() == false){ + // Get block with highest position in Y + + core::list< MapBlock * >::Iterator highest_i = temp_blocks.end(); + v3s16 highest_pos(0,-32768,0); + + core::list< MapBlock * >::Iterator i; + for(i = temp_blocks.begin(); i != temp_blocks.end(); i++) + { + MapBlock *block = *i; + v3s16 pos = block->getPosRelative(); + if(highest_i == temp_blocks.end() || pos.Y > highest_pos.Y){ + highest_i = i; + highest_pos = pos; + } + } + + // Update sunlight in block + MapBlock *block = *highest_i; + block->propagateSunlight(); + + /*std::cout<<"block ("<getPosRelative().X<<"," + <getPosRelative().Y<<"," + <getPosRelative().Z<<") "; + std::cout<<"touches_bottom="<::Iterator i = a_blocks.begin(); + for(; i != a_blocks.end(); i++) + { + MapBlock *block = *i; + v3s16 pos = block->getPosRelative(); + s16 xmin = pos.X; + s16 zmin = pos.Z; + s16 ymin = pos.Y; + s16 xmax = pos.X + MAP_BLOCKSIZE - 1; + s16 zmax = pos.Z + MAP_BLOCKSIZE - 1; + s16 ymax = pos.Y + MAP_BLOCKSIZE - 1; + for(s16 z=zmin; z<=zmax; z++){ + for(s16 x=xmin; x<=xmax; x++){ + for(s16 y=ymax; y>=ymin; y--){ + v3s16 pos(x, y, z); + MapNode n; + try{ + n = getNode(pos); + } + catch(InvalidPositionException &e) + { + break; + } + if(n.light > NO_LIGHT_MAX){ + v3s16 pos(x,y,z); + lightNeighbors(pos, modified_blocks); + } + } + } + } + std::cout<<"X"; + std::cout.flush(); + + //status.setDone(status.getDone()+1); + } + std::cout< light_sources; + core::map modified_blocks; + //MapNode n = getNode(p); + + /* + From this node to nodes underneath: + If lighting is sunlight (1.0), unlight neighbours and + set lighting to 0. + Else discontinue. + */ + + bool node_under_sunlight = true; + + v3s16 toppos = p + v3s16(0,1,0); + + /* + If there is a node at top and it doesn't have sunlight, + there has not been any sunlight going down. + + Otherwise there probably is. + */ + try{ + MapNode topnode = getNode(toppos); + + if(topnode.light < 0.999) + node_under_sunlight = false; + } + catch(InvalidPositionException &e) + { + } + + // Add the block of the added node to modified_blocks + v3s16 blockpos = getNodeBlockPos(p); + MapBlock * blockref = getBlockNoCreate(blockpos); + MapBlock *block = blockref; + assert(block != NULL); + modified_blocks.insert(blockpos, block); + + // Unlight neighbours of node. + // This means setting light of all consequent dimmer nodes + // to 0. + + if(isValidPosition(p) == false) + throw; + + unLightNeighbors(p, lightwas, light_sources, modified_blocks); + + MapNode n = getNode(p); + n.light = 0; + setNode(p, n); + + if(node_under_sunlight) + { + s16 y = p.Y - 1; + for(;; y--){ + std::cout<<"y="<= 0.999){ + std::cout<<"doing"<::Iterator j = light_sources.begin(); + for(; j != light_sources.end(); j++) + { + lightNeighbors(*j, modified_blocks); + } + + core::map::Iterator i = modified_blocks.getIterator(); + for(; i.atEnd() == false; i++) + { + i.getNode()->getValue()->updateFastFaces(); + } +} + +/* +*/ +void Map::removeNodeAndUpdate(v3s16 p) +{ + std::cout<<"Map::removeNodeAndUpdate()"< modified_blocks; + + bool node_under_sunlight = true; + + v3s16 toppos = p + v3s16(0,1,0); + + /* + If there is a node at top and it doesn't have sunlight, + there will be no sunlight going down. + */ + try{ + MapNode topnode = getNode(toppos); + + if(topnode.light < 0.999) + node_under_sunlight = false; + } + catch(InvalidPositionException &e) + { + } + + /* + Unlight neighbors (in case the node is a light source) + */ + core::list light_sources; + unLightNeighbors(p, getNode(p).light, + light_sources, modified_blocks); + + /* + Remove the node + */ + MapNode n; + n.d = MATERIAL_AIR; + setNode(p, n); + + /* + Recalculate lighting + */ + core::list::Iterator j = light_sources.begin(); + for(; j != light_sources.end(); j++) + { + lightNeighbors(*j, modified_blocks); + } + + // Add the block of the removed node to modified_blocks + v3s16 blockpos = getNodeBlockPos(p); + MapBlock * blockref = getBlockNoCreate(blockpos); + MapBlock *block = blockref; + assert(block != NULL); + modified_blocks.insert(blockpos, block); + + if(node_under_sunlight) + { + s16 ybottom = propagateSunlight(p, modified_blocks); + /*std::cout<<"Node was under sunlight. " + "Propagating sunlight"; + std::cout<<" -> ybottom="<= ybottom; y--) + { + v3s16 p2(p.X, y, p.Z); + /*std::cout<<"lighting neighbors of node (" + <::Iterator i = modified_blocks.getIterator(); + for(; i.atEnd() == false; i++) + { + i.getNode()->getValue()->updateFastFaces(); + } +} + +core::aabbox3d Map::getDisplayedBlockArea() +{ + camera_mutex.Lock(); + core::aabbox3d box_nodes(floatToInt(camera_position)); + camera_mutex.Unlock(); + + g_viewing_range_nodes_mutex.Lock(); + s16 d = g_viewing_range_nodes; + g_viewing_range_nodes_mutex.Unlock(); + box_nodes.MinEdge -= v3s16(d,d,d); + box_nodes.MaxEdge += v3s16(d,d,d); + + return core::aabbox3d( + getNodeBlockPos(box_nodes.MinEdge), + getNodeBlockPos(box_nodes.MaxEdge)); +} + +void Map::renderMap(video::IVideoDriver* driver, + video::SMaterial *materials) +{ + //std::cout<<"Rendering map..."< blocks_displayed; + core::list< MapBlock * >::Iterator bi; + +#if 0 + /* + Get all blocks + */ + core::map::Iterator si; + + si = m_sectors.getIterator(); + for(; si.atEnd() == false; si++) + { + MapSector *sector = si.getNode()->getValue(); + core::list< MapBlock * > sectorblocks = sector->getBlocks(); + core::list< MapBlock * >::Iterator i; + for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++){ + blocks_displayed.push_back(*i); + } + } +#else + /* + Get visible blocks + */ + core::aabbox3d box_blocks = getDisplayedBlockArea(); + + for(s16 y=box_blocks.MaxEdge.Y; y>=box_blocks.MinEdge.Y; y--){ + for(s16 z=box_blocks.MinEdge.Z; z<=box_blocks.MaxEdge.Z; z++){ + for(s16 x=box_blocks.MinEdge.X; x<=box_blocks.MaxEdge.X; x++) + { + /* + Add block to list + */ + try{ + MapBlock * blockref = getBlockNoCreate(v3s16(x,y,z)); + blocks_displayed.push_back(blockref); + } + catch(InvalidPositionException &e) + { + } + } + } + } +#endif + + u8 material_in_use; + material_in_use = MATERIAL_AIR; + + if(blocks_displayed.getSize() == 0) + return; + + /* + Draw all displayed faces + */ + + u32 facecount = 0; + + for(bi = blocks_displayed.begin(); bi != blocks_displayed.end(); bi++){ + MapBlock *block = *bi; + + /* + Compare block position to camera position, skip + if not seen on display + */ +#if 1 + v3s16 blockpos_nodes = block->getPosRelative(); + + // Block center position + v3f blockpos( + ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS, + ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS, + ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS + ); + + camera_mutex.Lock(); + // Block position relative to camera + v3f blockpos_relative = blockpos - camera_position; + + // Distance in camera direction (+=front, -=back) + f32 dforward = blockpos_relative.dotProduct(camera_direction); + + camera_mutex.Unlock(); + + // Total distance + f32 d = blockpos_relative.getLength(); + + // Maximum radius of a block + f32 block_max_radius = 0.5*1.44*1.44*MAP_BLOCKSIZE*BS; + + // If block is (nearly) touching the camera, don't + // bother checking further + if(d > block_max_radius * 1.5) + { + // Cosine of the angle between the camera direction + // and the block direction (camera_direction is an unit vector) + f32 cosangle = dforward / d; + + // Compensate for the size of the block + // (as the block has to be shown even if it's a bit off FOV) + // This is an estimate. + cosangle += block_max_radius / dforward; + + // If block is not in the field of view, skip it + if(cosangle < cos(FOV_ANGLE/2)) + continue; + } +#endif + + /* + Draw the faces of the block + */ + + block->fastfaces_mutex.Lock(); + + core::list::Iterator i = + block->fastfaces->begin(); + + for(; i != block->fastfaces->end(); i++) + { + FastFace *f = *i; + + if(f->material != material_in_use){ + driver->setMaterial(materials[f->material]); + material_in_use = f->material; + } + + u16 indices[] = {0,1,2,3}; + + driver->drawVertexPrimitiveList(f->vertices, 4, indices, 1, + video::EVT_STANDARD, scene::EPT_QUADS, video::EIT_16BIT); + + facecount++; + } + + block->fastfaces_mutex.Unlock(); + } + + static s32 oldfacecount = 0; + // Cast needed for msvc + if(abs((long)(facecount - oldfacecount)) > 333){ + std::cout<<"Rendered "< box_blocks = getDisplayedBlockArea(); + core::list< MapBlock * > blocks_changed; + core::list< MapBlock * >::Iterator bi; + + for(s16 y=box_blocks.MaxEdge.Y; y>=box_blocks.MinEdge.Y; y--){ + for(s16 z=box_blocks.MinEdge.Z; z<=box_blocks.MaxEdge.Z; z++){ + for(s16 x=box_blocks.MinEdge.X; x<=box_blocks.MaxEdge.X; x++) + { + // This intentionally creates new blocks on demand + + try + { + MapBlock * block = getBlock(v3s16(x,y,z)); + if(block->getChangedFlag()) + { + if(blocks_changed.empty() == true){ + // Print initial message when first is found + std::cout<<"Updating changed MapBlocks at "; + } + + v3s16 p = block->getPosRelative(); + std::cout<<"("<resetChangedFlag(); + } + } + catch(InvalidPositionException &e) + { + } + catch(AsyncQueuedException &e) + { + } + }}} + + // Quit if nothing has changed + if(blocks_changed.empty()){ + //status.setReady(true); + return false; + } + + std::cout< modified_blocks; + + std::cout<<"Updating lighting"<::Iterator i = modified_blocks.getIterator(); + for(; i.atEnd() == false; i++) + { + MapBlock *block = i.getNode()->getValue(); + block->updateFastFaces(); + + std::cout<<"X"; + std::cout.flush(); + + //status.setDone(status.getDone()+1); + } + + std::cout<m_heightmap->generateContinued(2.0, 0.2, corners); + //sector->getHeightmap()->generateContinued(0.0, 0.0, corners); + + MapSector *sector = new MapSector(this, p2d, gen); + + sector->setHeightmap(gen->m_heightmap); + + m_sectors.insert(p2d, sector); + + /*std::cout<<"Map::getSector(("<(0,0,0, + map->getW()*BS, map->getH()*BS, map->getD()*BS);*/ + /*m_box = core::aabbox3d(0,0,0, + map->getSizeNodes().X * BS, + map->getSizeNodes().Y * BS, + map->getSizeNodes().Z * BS);*/ + m_box = core::aabbox3d(-BS*1000000,-BS*1000000,-BS*1000000, + BS*1000000,BS*1000000,BS*1000000); +} + +MapSector * ClientMap::getSector(v2s16 p2d) +{ + // Check that it doesn't exist already + try{ + MapSector *sector = getSectorNoGenerate(p2d); + return sector; + } + catch(InvalidPositionException &e) + { + } + + /* + Create a ClientBlockGenerator to fill the MapSector's + blocks from data fetched by the client from the server. + */ + ClientBlockGenerator *gen; + gen = new ClientBlockGenerator(m_client); + + MapSector *sector = new MapSector(this, p2d, gen); + + m_sectors.insert(p2d, sector); + + return sector; +} + + diff --git a/src/map.h b/src/map.h new file mode 100644 index 0000000..b3ebc1e --- /dev/null +++ b/src/map.h @@ -0,0 +1,380 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef MAP_HEADER +#define MAP_HEADER + +#include +#include +#include +#include + +#ifdef _WIN32 + #include + #define sleep_s(x) Sleep((x*1000)) +#else + #include + #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 & box, core::aabbox3d & 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 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 getNodeBox(v3s16 p) + { + return core::aabbox3d( + (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("<isValidPosition(relpos); + /*std::cout<<"Map::isValidPosition("<=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 & light_sources, + core::map & modified_blocks); + + /*void lightNeighbors(v3s16 pos, f32 oldlight, + core::map & modified_blocks);*/ + void lightNeighbors(v3s16 pos, + core::map & modified_blocks); + + v3s16 getBrightestNeighbour(v3s16 p); + + s16 propagateSunlight(v3s16 start, + core::map & modified_blocks); + + void updateLighting(core::list< MapBlock*> & a_blocks, + core::map & modified_blocks); + + void nodeAddedUpdate(v3s16 p, f32 light); + void removeNodeAndUpdate(v3s16 p); + + core::aabbox3d 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& 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 m_box; +}; + +#endif + diff --git a/src/mapblock.cpp b/src/mapblock.cpp new file mode 100644 index 0000000..2ff5ee2 --- /dev/null +++ b/src/mapblock.cpp @@ -0,0 +1,448 @@ +#include "mapblock.h" +#include "map.h" + +bool MapBlock::isValidPositionParent(v3s16 p) +{ + if(isValidPosition(p)) + { + //std::cout<<"[("<isValidPosition(getPosRelative() + p); + } +} + +MapNode MapBlock::getNodeParent(v3s16 p) +{ + if(isValidPosition(p) == false) + { + /*std::cout<<"MapBlock::getNodeParent((" + <getNode(getPosRelative() + p); + } + else + { + /*std::cout<<"MapBlock::getNodeParent((" + <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 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(0,1)); + f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c, + core::vector2d(abs_scale,1)); + f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c, + core::vector2d(abs_scale,0)); + f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c, + core::vector2d(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 &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 *fastfaces_new = new core::list; + + /* + 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 *fastfaces_old = fastfaces; + + fastfaces_mutex.Lock(); + fastfaces = fastfaces_new; + fastfaces_mutex.Unlock(); + + /*std::cout<<" Number of faces: old="<getSize() + <<" new="<getSize()<::Iterator i = fastfaces_old->begin(); + for(; i != fastfaces_old->end(); i++) + { + delete *i; + } + fastfaces_old->clear(); + delete fastfaces_old; + + //std::cout<<"added "<= 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("< *fastfaces; + JMutex fastfaces_mutex; + + MapBlock(NodeContainer *parent, v3s16 pos) + : m_parent(parent), m_pos(pos), changed(true) + { + data = NULL; + reallocate(); + fastfaces = new core::list; + fastfaces_mutex.Init(); + } + + ~MapBlock() + { + fastfaces_mutex.Lock(); + core::list::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("< getBox() + { + return core::aabbox3d(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= 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 &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 + diff --git a/src/mapnode.h b/src/mapnode.h new file mode 100644 index 0000000..1e74a2a --- /dev/null +++ b/src/mapnode.h @@ -0,0 +1,102 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef MAPNODE_HEADER +#define MAPNODE_HEADER + +#include +#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 + diff --git a/src/mapsector.cpp b/src/mapsector.cpp new file mode 100644 index 0000000..cc7c5ec --- /dev/null +++ b/src/mapsector.cpp @@ -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; z0getGroundHeight(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; y0setNode(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()"<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::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("<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 MapSector::getBlocks() +{ + JMutexAutoLock lock(m_mutex); + + core::list ref_list; + + core::map::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 diff --git a/src/mapsector.h b/src/mapsector.h new file mode 100644 index 0000000..9618cca --- /dev/null +++ b/src/mapsector.h @@ -0,0 +1,156 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef MAPSECTOR_HEADER +#define MAPSECTOR_HEADER + +#include +#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 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::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 getBlocks(); + + // These have to exist + f32 getGroundHeight(v2s16 p, bool generate=false); + void setGroundHeight(v2s16 p, f32 y, bool generate=false); +}; + +#endif + diff --git a/src/player.cpp b/src/player.cpp new file mode 100644 index 0000000..ca5db0a --- /dev/null +++ b/src/player.cpp @@ -0,0 +1,196 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#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(-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(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(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=("< 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 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 (" + < (" + < 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); +} + diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..6b5802b --- /dev/null +++ b/src/player.h @@ -0,0 +1,93 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef PLAYER_HEADER +#define PLAYER_HEADER + +#include + +using namespace irr; +typedef core::vector3df v3f; +typedef core::vector3d 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& 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 m_box; +}; + +#endif + diff --git a/src/server.cpp b/src/server.cpp new file mode 100644 index 0000000..4316ddc --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,313 @@ +#include "server.h" +#include "utility.h" +#include +#include "clientserver.h" +#include "map.h" +#include "jmutexautolock.h" +#include "main.h" + +#ifdef _WIN32 + #include + #define sleep_ms(x) Sleep(x) +#else + #include + #define sleep_ms(x) usleep(x*1000) +#endif + +void * ServerNetworkThread::Thread() +{ + ThreadStarted(); + + while(getRun()) + { + m_server->AsyncRunStep(); + + try{ + //dout_server<<"Running m_server->Receive()"<Receive(); + } + catch(con::NoIncomingDataException &e) + { + } + catch(std::exception &e) + { + dout_server<<"ServerNetworkThread: Some exception: " + < 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()=" + < 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 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 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 = 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 "< +#include +#include + +// 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)<<":" + < "; + destination.print(); + std::cout<<", size="<20) + std::cout<<"..."; + if(dumping_packet) + std::cout<<" (DUMPED BY INTERNET_SIMULATOR)"; + std::cout<20) + std::cout<<"..."; + std::cout< + #include + #include + #pragma comment(lib, "wsock32.lib") +typedef SOCKET socket_t; +typedef int socklen_t; +#else + #include + #include + #include + #include + #include +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 + diff --git a/src/test.cpp b/src/test.cpp new file mode 100644 index 0000000..939acc7 --- /dev/null +++ b/src/test.cpp @@ -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 + #define sleep_ms(x) Sleep(x) +#else + #include + #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 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 ref(&tc2); + assert(ref.get() == &tc2); + assert(tc2.getRefcount() == 1); + } + assert(tc2.getRefcount() == 0); + + Ref 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 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:"<setGroundHeight(p1, v1); + // Read from UnlimitedHeightmap + assert(fabs(hm1.getGroundHeight(p1)-v1)<0.001); + } + + void Random() + { + std::cout<<"Running random code"<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 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 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"< data = SharedBufferFromString("Hello World!"); + + std::cout<<"** running client.Send()"< data1 = SharedBufferFromString("hello1"); + SharedBuffer data2 = SharedBufferFromString("Hello2"); + + Address client_address = + server.GetPeer(peer_id_client)->address; + + std::cout<<"*** Sending packets in wrong order (2,1,2)" + <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"< 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<20) + std::cout<<"..."; + std::cout<>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; +} + diff --git a/src/utility.h b/src/utility.h new file mode 100644 index 0000000..d2a711c --- /dev/null +++ b/src/utility.h @@ -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 +class SharedPtr +{ +public: + SharedPtr(T *t) + { + assert(t != NULL); + refcount = new int; + *refcount = 1; + ptr = t; + } + SharedPtr(SharedPtr &t) + { + *this = t; + } + ~SharedPtr() + { + (*refcount)--; + if(*refcount == 0) + { + delete refcount; + delete ptr; + } + } + SharedPtr & operator=(SharedPtr &t) + { + refcount = t.refcount; + (*refcount)++; + ptr = t.ptr; + return *this; + } + T* operator->() + { + return ptr; + } + T & operator*() + { + return *ptr; + } +private: + T *ptr; + int *refcount; +}; + +template +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 +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 &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 SharedBufferFromString(const char *string) +{ + SharedBuffer b((u8*)string, strlen(string)+1); + return b; +} + +#endif +