aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.cpp1639
-rw-r--r--src/client.h272
-rw-r--r--src/clientserver.h174
-rw-r--r--src/common_irrlicht.h17
-rw-r--r--src/connection.cpp1321
-rw-r--r--src/connection.h474
-rw-r--r--src/constants.h72
-rw-r--r--src/debug.cpp189
-rw-r--r--src/debug.h176
-rw-r--r--src/environment.cpp206
-rw-r--r--src/environment.h50
-rw-r--r--src/exceptions.h116
-rw-r--r--src/filesys.cpp205
-rw-r--r--src/filesys.h27
-rw-r--r--src/heightmap.cpp872
-rw-r--r--src/heightmap.h556
-rw-r--r--src/inventory.cpp320
-rw-r--r--src/inventory.h195
-rw-r--r--src/light.cpp85
-rw-r--r--src/light.h54
-rw-r--r--src/loadstatus.h144
-rw-r--r--src/main.cpp2339
-rw-r--r--src/main.h50
-rw-r--r--src/map.cpp2854
-rw-r--r--src/map.h430
-rw-r--r--src/mapblock.cpp698
-rw-r--r--src/mapblock.h404
-rw-r--r--src/mapblockobject.cpp641
-rw-r--r--src/mapblockobject.h892
-rw-r--r--src/mapnode.h280
-rw-r--r--src/mapsector.cpp652
-rw-r--r--src/mapsector.h318
-rw-r--r--src/player.cpp358
-rw-r--r--src/player.h210
-rw-r--r--src/porting.h15
-rw-r--r--src/serialization.cpp77
-rw-r--r--src/serialization.h49
-rw-r--r--src/server.cpp2115
-rw-r--r--src/server.h388
-rw-r--r--src/socket.cpp311
-rw-r--r--src/socket.h98
-rw-r--r--src/strfnd.h96
-rw-r--r--src/test.cpp920
-rw-r--r--src/test.h7
-rw-r--r--src/utility.cpp41
-rw-r--r--src/utility.h607
46 files changed, 22014 insertions, 0 deletions
diff --git a/src/client.cpp b/src/client.cpp
new file mode 100644
index 000000000..ee3269974
--- /dev/null
+++ b/src/client.cpp
@@ -0,0 +1,1639 @@
+#include "client.h"
+#include "utility.h"
+#include <iostream>
+#include "clientserver.h"
+#include "jmutexautolock.h"
+#include "main.h"
+#include <sstream>
+
+#ifdef _WIN32
+ #include <windows.h>
+ #define sleep_ms(x) Sleep(x)
+#else
+ #include <unistd.h>
+ #define sleep_ms(x) usleep(x*1000)
+#endif
+
+/*
+ FIXME: This thread can access the environment at any time
+*/
+
+void * ClientUpdateThread::Thread()
+{
+ ThreadStarted();
+
+ DSTACK(__FUNCTION_NAME);
+
+#if CATCH_UNHANDLED_EXCEPTIONS
+ try
+ {
+#endif
+ while(getRun())
+ {
+ m_client->asyncStep();
+
+ bool was = m_client->AsyncProcessData();
+
+ if(was == false)
+ sleep_ms(50);
+ }
+#if CATCH_UNHANDLED_EXCEPTIONS
+ }
+ /*
+ This is what has to be done in threads to get suitable debug info
+ */
+ catch(std::exception &e)
+ {
+ dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
+ <<e.what()<<std::endl;
+ assert(0);
+ }
+#endif
+
+ return NULL;
+}
+
+Client::Client(IrrlichtDevice *device, video::SMaterial *materials,
+ float delete_unused_sectors_timeout,
+ const char *playername):
+ m_thread(this),
+ m_env(new ClientMap(this, materials,
+ device->getSceneManager()->getRootSceneNode(),
+ device->getSceneManager(), 666),
+ dout_client),
+ m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
+ m_device(device),
+ camera_position(0,0,0),
+ camera_direction(0,0,1),
+ m_server_ser_ver(SER_FMT_VER_INVALID),
+ m_step_dtime(0.0),
+ m_delete_unused_sectors_timeout(delete_unused_sectors_timeout),
+ m_inventory_updated(false)
+{
+ //m_fetchblock_mutex.Init();
+ m_incoming_queue_mutex.Init();
+ m_env_mutex.Init();
+ m_con_mutex.Init();
+ m_step_dtime_mutex.Init();
+
+ m_thread.Start();
+
+ {
+ JMutexAutoLock envlock(m_env_mutex);
+ //m_env.getMap().StartUpdater();
+
+ Player *player = new LocalPlayer();
+
+ player->updateName(playername);
+
+ /*f32 y = BS*2 + BS*20;
+ player->setPosition(v3f(0, y, 0));*/
+ //player->setPosition(v3f(0, y, 30900*BS)); // DEBUG
+ m_env.addPlayer(player);
+ }
+}
+
+Client::~Client()
+{
+ m_thread.setRun(false);
+ while(m_thread.IsRunning())
+ sleep_ms(100);
+}
+
+void Client::connect(Address address)
+{
+ DSTACK(__FUNCTION_NAME);
+ JMutexAutoLock lock(m_con_mutex);
+ m_con.setTimeoutMs(0);
+ m_con.Connect(address);
+}
+
+bool Client::connectedAndInitialized()
+{
+ JMutexAutoLock lock(m_con_mutex);
+
+ if(m_con.Connected() == false)
+ return false;
+
+ if(m_server_ser_ver == SER_FMT_VER_INVALID)
+ return false;
+
+ return true;
+}
+
+void Client::step(float dtime)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ // Limit a bit
+ if(dtime > 2.0)
+ dtime = 2.0;
+
+ //dstream<<"Client steps "<<dtime<<std::endl;
+
+ {
+ //TimeTaker timer("ReceiveAll()", m_device);
+ // 0ms
+ ReceiveAll();
+ }
+
+ {
+ //TimeTaker timer("m_con_mutex + m_con.RunTimeouts()", m_device);
+ // 0ms
+ JMutexAutoLock lock(m_con_mutex);
+ m_con.RunTimeouts(dtime);
+ }
+
+ {
+ /*
+ Delete unused sectors
+ */
+
+ static float counter = -0.001;
+ counter -= dtime;
+ if(counter <= 0.0)
+ {
+ counter = 10.0;
+
+ JMutexAutoLock lock(m_env_mutex);
+
+ core::list<v3s16> deleted_blocks;
+
+ // Delete sector blocks
+ /*u32 num = m_env.getMap().deleteUnusedSectors
+ (m_delete_unused_sectors_timeout,
+ true, &deleted_blocks);*/
+
+ // Delete whole sectors
+ u32 num = m_env.getMap().deleteUnusedSectors
+ (m_delete_unused_sectors_timeout,
+ false, &deleted_blocks);
+
+ if(num > 0)
+ {
+ dstream<<DTIME<<"Client: Deleted blocks of "<<num
+ <<" unused sectors"<<std::endl;
+
+ /*
+ Send info to server
+ */
+
+ // Env is locked so con can be locked.
+ JMutexAutoLock lock(m_con_mutex);
+
+ core::list<v3s16>::Iterator i = deleted_blocks.begin();
+ core::list<v3s16> sendlist;
+ for(;;)
+ {
+ if(sendlist.size() == 255 || i == deleted_blocks.end())
+ {
+ if(sendlist.size() == 0)
+ break;
+ /*
+ [0] u16 command
+ [2] u8 count
+ [3] v3s16 pos_0
+ [3+6] v3s16 pos_1
+ ...
+ */
+ u32 replysize = 2+1+6*sendlist.size();
+ SharedBuffer<u8> reply(replysize);
+ writeU16(&reply[0], TOSERVER_DELETEDBLOCKS);
+ reply[2] = sendlist.size();
+ u32 k = 0;
+ for(core::list<v3s16>::Iterator
+ j = sendlist.begin();
+ j != sendlist.end(); j++)
+ {
+ writeV3S16(&reply[2+1+6*k], *j);
+ k++;
+ }
+ m_con.Send(PEER_ID_SERVER, 1, reply, true);
+
+ if(i == deleted_blocks.end())
+ break;
+
+ sendlist.clear();
+ }
+
+ sendlist.push_back(*i);
+ i++;
+ }
+ }
+ }
+ }
+
+ bool connected = connectedAndInitialized();
+
+ if(connected == false)
+ {
+ static float counter = -0.001;
+ counter -= dtime;
+ if(counter <= 0.0)
+ {
+ counter = 2.0;
+
+ JMutexAutoLock envlock(m_env_mutex);
+
+ Player *myplayer = m_env.getLocalPlayer();
+ assert(myplayer != NULL);
+
+ // Send TOSERVER_INIT
+ // [0] u16 TOSERVER_INIT
+ // [2] u8 SER_FMT_VER_HIGHEST
+ // [3] u8[20] player_name
+ SharedBuffer<u8> data(2+1+20);
+ writeU16(&data[0], TOSERVER_INIT);
+ writeU8(&data[2], SER_FMT_VER_HIGHEST);
+ memcpy(&data[3], myplayer->getName(), 20);
+ // Send as unreliable
+ Send(0, data, false);
+ }
+
+ // Not connected, return
+ return;
+ }
+
+ /*
+ Do stuff if connected
+ */
+
+ {
+ // 0ms
+ JMutexAutoLock lock(m_env_mutex);
+
+ // Control local player (0ms)
+ LocalPlayer *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ player->applyControl(dtime);
+
+ //TimeTaker envtimer("env step", m_device);
+ // Step environment
+ m_env.step(dtime);
+
+ // Step active blocks
+ for(core::map<v3s16, bool>::Iterator
+ i = m_active_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+
+ MapBlock *block = NULL;
+ try
+ {
+ block = m_env.getMap().getBlockNoCreate(p);
+ block->stepObjects(dtime, false);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+ }
+ }
+
+ {
+ // Fetch some nearby blocks
+ //fetchBlocks();
+ }
+
+ {
+ static float counter = 0.0;
+ counter += dtime;
+ if(counter >= 10)
+ {
+ counter = 0.0;
+ JMutexAutoLock lock(m_con_mutex);
+ // connectedAndInitialized() is true, peer exists.
+ con::Peer *peer = m_con.GetPeer(PEER_ID_SERVER);
+ dstream<<DTIME<<"Client: avg_rtt="<<peer->avg_rtt<<std::endl;
+ }
+ }
+ {
+ // Update at reasonable intervals (0.2s)
+ static float counter = 0.0;
+ counter += dtime;
+ if(counter >= 0.2)
+ {
+ counter = 0.0;
+ sendPlayerPos();
+ }
+ }
+
+#if 0
+ /*
+ Clear old entries from fetchblock history
+ */
+ {
+ JMutexAutoLock lock(m_fetchblock_mutex);
+
+ core::list<v3s16> remove_queue;
+ core::map<v3s16, float>::Iterator i;
+ i = m_fetchblock_history.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ float value = i.getNode()->getValue();
+ value += dtime;
+ i.getNode()->setValue(value);
+ if(value >= 60.0)
+ remove_queue.push_back(i.getNode()->getKey());
+ }
+ core::list<v3s16>::Iterator j;
+ j = remove_queue.begin();
+ for(; j != remove_queue.end(); j++)
+ {
+ m_fetchblock_history.remove(*j);
+ }
+ }
+#endif
+
+ /*{
+ JMutexAutoLock lock(m_step_dtime_mutex);
+ m_step_dtime += dtime;
+ }*/
+
+ /*
+ BEGIN TEST CODE
+ */
+
+ /*
+ END OF TEST CODE
+ */
+}
+
+float Client::asyncStep()
+{
+ DSTACK(__FUNCTION_NAME);
+ //dstream<<"Client::asyncStep()"<<std::endl;
+
+ /*float dtime;
+ {
+ JMutexAutoLock lock1(m_step_dtime_mutex);
+ dtime = m_step_dtime;
+ m_step_dtime = 0.0;
+ }
+
+ return dtime;*/
+ return 0.0;
+}
+
+// Virtual methods from con::PeerHandler
+void Client::peerAdded(con::Peer *peer)
+{
+ derr_client<<"Client::peerAdded(): peer->id="
+ <<peer->id<<std::endl;
+}
+void Client::deletingPeer(con::Peer *peer, bool timeout)
+{
+ derr_client<<"Client::deletingPeer(): "
+ "Server Peer is getting deleted "
+ <<"(timeout="<<timeout<<")"<<std::endl;
+}
+
+void Client::ReceiveAll()
+{
+ DSTACK(__FUNCTION_NAME);
+ for(;;)
+ {
+ try{
+ Receive();
+ }
+ catch(con::NoIncomingDataException &e)
+ {
+ break;
+ }
+ catch(con::InvalidIncomingDataException &e)
+ {
+ dout_client<<DTIME<<"Client::ReceiveAll(): "
+ "InvalidIncomingDataException: what()="
+ <<e.what()<<std::endl;
+ }
+ //TODO: Testing
+ //break;
+ }
+}
+
+void Client::Receive()
+{
+ DSTACK(__FUNCTION_NAME);
+ u32 data_maxsize = 10000;
+ Buffer<u8> data(data_maxsize);
+ u16 sender_peer_id;
+ u32 datasize;
+ {
+ //TimeTaker t1("con mutex and receive", m_device);
+ JMutexAutoLock lock(m_con_mutex);
+ datasize = m_con.Receive(sender_peer_id, *data, data_maxsize);
+ }
+ //TimeTaker t1("ProcessData", m_device);
+ ProcessData(*data, datasize, sender_peer_id);
+}
+
+/*
+ sender_peer_id given to this shall be quaranteed to be a valid peer
+*/
+void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Ignore packets that don't even fit a command
+ if(datasize < 2)
+ return;
+
+ ToClientCommand command = (ToClientCommand)readU16(&data[0]);
+
+ /*
+ If this check is removed, be sure to change the queue
+ system to know the ids
+ */
+ if(sender_peer_id != PEER_ID_SERVER)
+ {
+ dout_client<<DTIME<<"Client::ProcessData(): Discarding data not "
+ "coming from server: peer_id="<<sender_peer_id
+ <<std::endl;
+ return;
+ }
+
+ con::Peer *peer;
+ {
+ JMutexAutoLock lock(m_con_mutex);
+ // All data is coming from the server
+ // PeerNotFoundException is handled by caller.
+ peer = m_con.GetPeer(PEER_ID_SERVER);
+ }
+
+ u8 ser_version = m_server_ser_ver;
+
+ //dstream<<"Client received command="<<(int)command<<std::endl;
+
+ // Execute fast commands straight away
+
+ if(command == TOCLIENT_INIT)
+ {
+ if(datasize < 3)
+ return;
+
+ u8 deployed = data[2];
+
+ dout_client<<DTIME<<"Client: TOCLIENT_INIT received with "
+ "deployed="<<((int)deployed&0xff)<<std::endl;
+
+ if(deployed < SER_FMT_VER_LOWEST
+ || deployed > SER_FMT_VER_HIGHEST)
+ {
+ derr_client<<DTIME<<"Client: TOCLIENT_INIT: Server sent "
+ <<"unsupported ser_fmt_ver"<<std::endl;
+ return;
+ }
+
+ m_server_ser_ver = deployed;
+
+ // Get player position
+ v3s16 playerpos_s16(0, BS*2+BS*20, 0);
+ if(datasize >= 2+1+6)
+ playerpos_s16 = readV3S16(&data[2+1]);
+ v3f playerpos_f = intToFloat(playerpos_s16) - v3f(0, BS/2, 0);
+
+ { //envlock
+ JMutexAutoLock envlock(m_env_mutex);
+
+ // Set player position
+ Player *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ player->setPosition(playerpos_f);
+ }
+
+ // Reply to server
+ u32 replysize = 2;
+ SharedBuffer<u8> reply(replysize);
+ writeU16(&reply[0], TOSERVER_INIT2);
+ // Send as reliable
+ m_con.Send(PEER_ID_SERVER, 1, reply, true);
+
+ return;
+ }
+
+ if(ser_version == SER_FMT_VER_INVALID)
+ {
+ dout_client<<DTIME<<"WARNING: Client: Server serialization"
+ " format invalid or not initialized."
+ " Skipping incoming command="<<command<<std::endl;
+ return;
+ }
+
+ // Just here to avoid putting the two if's together when
+ // making some copypasta
+ {}
+
+ if(command == TOCLIENT_PLAYERPOS)
+ {
+ dstream<<"WARNING: Received deprecated TOCLIENT_PLAYERPOS"
+ <<std::endl;
+ /*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<<DTIME<<"TOCLIENT_PLAYERPOS cancelled: "
+ "we have no peer id"
+ <<std::endl;
+ return;
+ }*/
+
+ { //envlock
+ JMutexAutoLock envlock(m_env_mutex);
+
+ u32 player_size = 2+12+12+4+4;
+
+ u32 player_count = (datasize-2) / player_size;
+ u32 start = 2;
+ for(u32 i=0; i<player_count; i++)
+ {
+ u16 peer_id = readU16(&data[start]);
+
+ Player *player = m_env.getPlayer(peer_id);
+
+ // Skip if player doesn't exist
+ if(player == NULL)
+ {
+ start += player_size;
+ continue;
+ }
+
+ // Skip if player is local player
+ if(player->isLocal())
+ {
+ start += player_size;
+ continue;
+ }
+
+ v3s32 ps = readV3S32(&data[start+2]);
+ v3s32 ss = readV3S32(&data[start+2+12]);
+ s32 pitch_i = readS32(&data[start+2+12+12]);
+ s32 yaw_i = readS32(&data[start+2+12+12+4]);
+ /*dstream<<"Client: got "
+ <<"pitch_i="<<pitch_i
+ <<" yaw_i="<<yaw_i<<std::endl;*/
+ f32 pitch = (f32)pitch_i / 100.0;
+ f32 yaw = (f32)yaw_i / 100.0;
+ 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->setSpeed(speed);
+ player->setPitch(pitch);
+ player->setYaw(yaw);
+
+ /*dstream<<"Client: player "<<peer_id
+ <<" pitch="<<pitch
+ <<" yaw="<<yaw<<std::endl;*/
+
+ start += player_size;
+ }
+ } //envlock
+ }
+ else if(command == TOCLIENT_PLAYERINFO)
+ {
+ 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<<DTIME<<"TOCLIENT_PLAYERINFO cancelled: "
+ "we have no peer id"
+ <<std::endl;
+ return;
+ }
+
+ //dstream<<DTIME<<"Client: Server reports players:"<<std::endl;
+
+ { //envlock
+ JMutexAutoLock envlock(m_env_mutex);
+
+ u32 item_size = 2+PLAYERNAME_SIZE;
+ u32 player_count = (datasize-2) / item_size;
+ u32 start = 2;
+ // peer_ids
+ core::list<u16> players_alive;
+ for(u32 i=0; i<player_count; i++)
+ {
+ // Make sure the name ends in '\0'
+ data[start+2+20-1] = 0;
+
+ u16 peer_id = readU16(&data[start]);
+
+ players_alive.push_back(peer_id);
+
+ /*dstream<<DTIME<<"peer_id="<<peer_id
+ <<" name="<<((char*)&data[start+2])<<std::endl;*/
+
+ // Don't update the info of the local player
+ if(peer_id == our_peer_id)
+ {
+ start += item_size;
+ continue;
+ }
+
+ Player *player = m_env.getPlayer(peer_id);
+
+ // Create a player if it doesn't exist
+ if(player == NULL)
+ {
+ player = new RemotePlayer(
+ m_device->getSceneManager()->getRootSceneNode(),
+ m_device,
+ -1);
+ player->peer_id = peer_id;
+ m_env.addPlayer(player);
+ dout_client<<DTIME<<"Client: Adding new player "
+ <<peer_id<<std::endl;
+ }
+
+ player->updateName((char*)&data[start+2]);
+
+ start += item_size;
+ }
+
+ /*
+ Remove those players from the environment that
+ weren't listed by the server.
+ */
+ //dstream<<DTIME<<"Removing dead players"<<std::endl;
+ core::list<Player*> players = m_env.getPlayers();
+ core::list<Player*>::Iterator ip;
+ for(ip=players.begin(); ip!=players.end(); ip++)
+ {
+ // Ingore local player
+ if((*ip)->isLocal())
+ continue;
+
+ // Warn about a special case
+ if((*ip)->peer_id == 0)
+ {
+ dstream<<DTIME<<"WARNING: Client: Removing "
+ "dead player with id=0"<<std::endl;
+ }
+
+ bool is_alive = false;
+ core::list<u16>::Iterator i;
+ for(i=players_alive.begin(); i!=players_alive.end(); i++)
+ {
+ if((*ip)->peer_id == *i)
+ {
+ is_alive = true;
+ break;
+ }
+ }
+ /*dstream<<DTIME<<"peer_id="<<((*ip)->peer_id)
+ <<" is_alive="<<is_alive<<std::endl;*/
+ if(is_alive)
+ continue;
+ dstream<<DTIME<<"Removing dead player "<<(*ip)->peer_id
+ <<std::endl;
+ m_env.removePlayer((*ip)->peer_id);
+ }
+ } //envlock
+ }
+ else if(command == TOCLIENT_SECTORMETA)
+ {
+ /*
+ [0] u16 command
+ [2] u8 sector count
+ [3...] v2s16 pos + sector metadata
+ */
+ if(datasize < 3)
+ return;
+
+ //dstream<<"Client received TOCLIENT_SECTORMETA"<<std::endl;
+
+ { //envlock
+ JMutexAutoLock envlock(m_env_mutex);
+
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+
+ u8 buf[4];
+
+ is.read((char*)buf, 1);
+ u16 sector_count = readU8(buf);
+
+ //dstream<<"sector_count="<<sector_count<<std::endl;
+
+ for(u16 i=0; i<sector_count; i++)
+ {
+ // Read position
+ is.read((char*)buf, 4);
+ v2s16 pos = readV2S16(buf);
+ /*dstream<<"Client: deserializing sector at "
+ <<"("<<pos.X<<","<<pos.Y<<")"<<std::endl;*/
+ // Create sector
+ assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
+ ((ClientMap&)m_env.getMap()).deSerializeSector(pos, is);
+ }
+ } //envlock
+ }
+ else if(command == TOCLIENT_INVENTORY)
+ {
+ if(datasize < 3)
+ return;
+
+ //TimeTaker t1("Parsing TOCLIENT_INVENTORY", m_device);
+
+ { //envlock
+ //TimeTaker t2("mutex locking", m_device);
+ JMutexAutoLock envlock(m_env_mutex);
+ //t2.stop();
+
+ //TimeTaker t3("istringstream init", m_device);
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+ //t3.stop();
+
+ //m_env.printPlayers(dstream);
+
+ //TimeTaker t4("player get", m_device);
+ Player *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ //t4.stop();
+
+ //TimeTaker t1("inventory.deSerialize()", m_device);
+ player->inventory.deSerialize(is);
+ //t1.stop();
+
+ m_inventory_updated = true;
+
+ //dstream<<"Client got player inventory:"<<std::endl;
+ //player->inventory.print(dstream);
+ }
+ }
+ //DEBUG
+ else if(command == TOCLIENT_OBJECTDATA)
+ //else if(0)
+ {
+ // Strip command word and create a stringstream
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+
+ { //envlock
+
+ JMutexAutoLock envlock(m_env_mutex);
+
+ u8 buf[12];
+
+ /*
+ Read players
+ */
+
+ is.read((char*)buf, 2);
+ u16 playercount = readU16(buf);
+
+ for(u16 i=0; i<playercount; i++)
+ {
+ is.read((char*)buf, 2);
+ u16 peer_id = readU16(buf);
+ is.read((char*)buf, 12);
+ v3s32 p_i = readV3S32(buf);
+ is.read((char*)buf, 12);
+ v3s32 s_i = readV3S32(buf);
+ is.read((char*)buf, 4);
+ s32 pitch_i = readS32(buf);
+ is.read((char*)buf, 4);
+ s32 yaw_i = readS32(buf);
+
+ Player *player = m_env.getPlayer(peer_id);
+
+ // Skip if player doesn't exist
+ if(player == NULL)
+ {
+ continue;
+ }
+
+ // Skip if player is local player
+ if(player->isLocal())
+ {
+ continue;
+ }
+
+ f32 pitch = (f32)pitch_i / 100.0;
+ f32 yaw = (f32)yaw_i / 100.0;
+ v3f position((f32)p_i.X/100., (f32)p_i.Y/100., (f32)p_i.Z/100.);
+ v3f speed((f32)s_i.X/100., (f32)s_i.Y/100., (f32)s_i.Z/100.);
+
+ player->setPosition(position);
+ player->setSpeed(speed);
+ player->setPitch(pitch);
+ player->setYaw(yaw);
+ }
+
+ /*
+ Read block objects
+ */
+
+ // Read active block count
+ is.read((char*)buf, 2);
+ u16 blockcount = readU16(buf);
+
+ // Initialize delete queue with all active blocks
+ core::map<v3s16, bool> abs_to_delete;
+ for(core::map<v3s16, bool>::Iterator
+ i = m_active_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ /*dstream<<"adding "
+ <<"("<<p.x<<","<<p.y<<","<<p.z<<") "
+ <<" to abs_to_delete"
+ <<std::endl;*/
+ abs_to_delete.insert(p, true);
+ }
+
+ /*dstream<<"Initial delete queue size: "<<abs_to_delete.size()
+ <<std::endl;*/
+
+ for(u16 i=0; i<blockcount; i++)
+ {
+ // Read blockpos
+ is.read((char*)buf, 6);
+ v3s16 p = readV3S16(buf);
+ // Get block from somewhere
+ MapBlock *block = NULL;
+ try{
+ block = m_env.getMap().getBlockNoCreate(p);
+ }
+ catch(InvalidPositionException &e)
+ {
+ //TODO: Create a dummy block?
+ }
+ if(block == NULL)
+ {
+ dstream<<"WARNING: "
+ <<"Could not get block at blockpos "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
+ <<"in TOCLIENT_OBJECTDATA. Ignoring "
+ <<"following block object data."
+ <<std::endl;
+ return;
+ }
+
+ /*dstream<<"Client updating objects for block "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<std::endl;*/
+
+ // Insert to active block list
+ m_active_blocks.insert(p, true);
+
+ // Remove from deletion queue
+ if(abs_to_delete.find(p) != NULL)
+ abs_to_delete.remove(p);
+
+ // Update objects of block
+ block->updateObjects(is, m_server_ser_ver,
+ m_device->getSceneManager());
+ }
+
+ /*dstream<<"Final delete queue size: "<<abs_to_delete.size()
+ <<std::endl;*/
+
+ // Delete objects of blocks in delete queue
+ for(core::map<v3s16, bool>::Iterator
+ i = abs_to_delete.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ try
+ {
+ MapBlock *block = m_env.getMap().getBlockNoCreate(p);
+
+ // Clear objects
+ block->clearObjects();
+ // Remove from active blocks list
+ m_active_blocks.remove(p);
+ }
+ catch(InvalidPositionException &e)
+ {
+ dstream<<"WARNAING: Client: "
+ <<"Couldn't clear objects of active->inactive"
+ <<" block "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<" because block was not found"
+ <<std::endl;
+ // Ignore
+ }
+ }
+
+ } //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);
+ }
+}
+
+/*
+ Returns true if there was something in queue
+*/
+bool Client::AsyncProcessPacket(LazyMeshUpdater &mesh_updater)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ try //for catching con::PeerNotFoundException
+ {
+
+ con::Peer *peer;
+ {
+ JMutexAutoLock lock(m_con_mutex);
+ // All data is coming from the server
+ peer = m_con.GetPeer(PEER_ID_SERVER);
+ }
+
+ u8 ser_version = m_server_ser_ver;
+
+ 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)
+ return true;
+
+ ToClientCommand command = (ToClientCommand)readU16(&data[0]);
+
+ if(command == TOCLIENT_REMOVENODE)
+ {
+ if(datasize < 8)
+ return true;
+ v3s16 p;
+ p.X = readS16(&data[2]);
+ p.Y = readS16(&data[4]);
+ p.Z = readS16(&data[6]);
+
+ //TimeTaker t1("TOCLIENT_REMOVENODE", g_device);
+
+ core::map<v3s16, MapBlock*> modified_blocks;
+
+ try
+ {
+ JMutexAutoLock envlock(m_env_mutex);
+ //TimeTaker t("removeNodeAndUpdate", m_device);
+ m_env.getMap().removeNodeAndUpdate(p, modified_blocks);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ for(core::map<v3s16, MapBlock * >::Iterator
+ i = modified_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ //m_env.getMap().updateMeshes(p);
+ mesh_updater.add(p);
+ }
+ }
+ else if(command == TOCLIENT_ADDNODE)
+ {
+ if(datasize < 8 + MapNode::serializedLength(ser_version))
+ return true;
+
+ v3s16 p;
+ p.X = readS16(&data[2]);
+ p.Y = readS16(&data[4]);
+ p.Z = readS16(&data[6]);
+
+ //TimeTaker t1("TOCLIENT_ADDNODE", g_device);
+
+ MapNode n;
+ n.deSerialize(&data[8], ser_version);
+
+ core::map<v3s16, MapBlock*> modified_blocks;
+
+ try
+ {
+ JMutexAutoLock envlock(m_env_mutex);
+ m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
+ }
+ catch(InvalidPositionException &e)
+ {}
+
+ for(core::map<v3s16, MapBlock * >::Iterator
+ i = modified_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ //m_env.getMap().updateMeshes(p);
+ mesh_updater.add(p);
+ }
+ }
+ else if(command == TOCLIENT_BLOCKDATA)
+ {
+ // Ignore too small packet
+ if(datasize < 8)
+ return true;
+ /*if(datasize < 8 + MapBlock::serializedLength(ser_version))
+ goto getdata;*/
+
+ v3s16 p;
+ p.X = readS16(&data[2]);
+ p.Y = readS16(&data[4]);
+ p.Z = readS16(&data[6]);
+
+ /*dout_client<<DTIME<<"Client: Thread: BLOCKDATA for ("
+ <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+ /*dstream<<DTIME<<"Client: Thread: BLOCKDATA for ("
+ <<p.X<<","<<p.Y<<","<<p.Z<<"): ";*/
+
+ std::string datastring((char*)&data[8], datasize-8);
+ std::istringstream istr(datastring, std::ios_base::binary);
+
+ MapSector *sector;
+ MapBlock *block;
+
+ { //envlock
+ JMutexAutoLock envlock(m_env_mutex);
+
+ v2s16 p2d(p.X, p.Z);
+ sector = m_env.getMap().emergeSector(p2d);
+
+ v2s16 sp = sector->getPos();
+ if(sp != p2d)
+ {
+ dstream<<"ERROR: Got sector with getPos()="
+ <<"("<<sp.X<<","<<sp.Y<<"), tried to get"
+ <<"("<<p2d.X<<","<<p2d.Y<<")"<<std::endl;
+ }
+
+ assert(sp == p2d);
+ //assert(sector->getPos() == p2d);
+
+ try{
+ block = sector->getBlockNoCreate(p.Y);
+ /*
+ Update an existing block
+ */
+ //dstream<<"Updating"<<std::endl;
+ block->deSerialize(istr, ser_version);
+ //block->setChangedFlag();
+ }
+ catch(InvalidPositionException &e)
+ {
+ /*
+ Create a new block
+ */
+ //dstream<<"Creating new"<<std::endl;
+ block = new MapBlock(&m_env.getMap(), p);
+ block->deSerialize(istr, ser_version);
+ sector->insertBlock(block);
+ //block->setChangedFlag();
+ }
+ } //envlock
+
+
+ // Old version has zero lighting, update it.
+ if(ser_version == 0 || ser_version == 1)
+ {
+ derr_client<<"Client: Block in old format: "
+ "Calculating lighting"<<std::endl;
+ core::map<v3s16, MapBlock*> blocks_changed;
+ blocks_changed.insert(block->getPos(), block);
+ core::map<v3s16, MapBlock*> modified_blocks;
+ m_env.getMap().updateLighting(blocks_changed, modified_blocks);
+ }
+
+ /*
+ Update Mesh of this block and blocks at x-, y- and z-
+ */
+
+ //m_env.getMap().updateMeshes(block->getPos());
+ mesh_updater.add(block->getPos());
+
+ /*
+ Acknowledge block.
+ */
+ /*
+ [0] u16 command
+ [2] u8 count
+ [3] v3s16 pos_0
+ [3+6] v3s16 pos_1
+ ...
+ */
+ u32 replysize = 2+1+6;
+ SharedBuffer<u8> reply(replysize);
+ writeU16(&reply[0], TOSERVER_GOTBLOCKS);
+ reply[2] = 1;
+ writeV3S16(&reply[3], p);
+ // Send as reliable
+ m_con.Send(PEER_ID_SERVER, 1, reply, true);
+
+#if 0
+ /*
+ Remove from history
+ */
+ {
+ JMutexAutoLock lock(m_fetchblock_mutex);
+
+ if(m_fetchblock_history.find(p) != NULL)
+ {
+ m_fetchblock_history.remove(p);
+ }
+ else
+ {
+ /*
+ Acknowledge block.
+ */
+ /*
+ [0] u16 command
+ [2] u8 count
+ [3] v3s16 pos_0
+ [3+6] v3s16 pos_1
+ ...
+ */
+ u32 replysize = 2+1+6;
+ SharedBuffer<u8> reply(replysize);
+ writeU16(&reply[0], TOSERVER_GOTBLOCKS);
+ reply[2] = 1;
+ writeV3S16(&reply[3], p);
+ // Send as reliable
+ m_con.Send(PEER_ID_SERVER, 1, reply, true);
+ }
+ }
+#endif
+ }
+ else
+ {
+ dout_client<<DTIME<<"WARNING: Client: Ignoring unknown command "
+ <<command<<std::endl;
+ }
+
+ return true;
+
+ } //try
+ catch(con::PeerNotFoundException &e)
+ {
+ dout_client<<DTIME<<"Client::AsyncProcessData(): Cancelling: The server"
+ " connection doesn't exist (a timeout or not yet connected?)"<<std::endl;
+ return false;
+ }
+}
+
+bool Client::AsyncProcessData()
+{
+ LazyMeshUpdater mesh_updater(&m_env);
+ for(;;)
+ {
+ bool r = AsyncProcessPacket(mesh_updater);
+ if(r == false)
+ break;
+ }
+ return false;
+}
+
+void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
+{
+ JMutexAutoLock lock(m_con_mutex);
+ m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
+}
+
+#if 0
+void Client::fetchBlock(v3s16 p, u8 flags)
+{
+ if(connectedAndInitialized() == false)
+ throw ClientNotReadyException
+ ("ClientNotReadyException: connectedAndInitialized() == false");
+
+ /*dstream<<"Client::fetchBlock(): Sending GETBLOCK for ("
+ <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+ JMutexAutoLock conlock(m_con_mutex);
+
+ SharedBuffer<u8> data(9);
+ writeU16(&data[0], TOSERVER_GETBLOCK);
+ writeS16(&data[2], p.X);
+ writeS16(&data[4], p.Y);
+ writeS16(&data[6], p.Z);
+ writeU8(&data[8], flags);
+ m_con.Send(PEER_ID_SERVER, 1, data, true);
+}
+
+/*
+ Calls fetchBlock() on some nearby missing blocks.
+
+ Returns when any of various network load indicators go over limit.
+
+ Does nearly the same thing as the old updateChangedVisibleArea()
+*/
+void Client::fetchBlocks()
+{
+ if(connectedAndInitialized() == false)
+ throw ClientNotReadyException
+ ("ClientNotReadyException: connectedAndInitialized() == false");
+}
+#endif
+
+bool Client::isFetchingBlocks()
+{
+ JMutexAutoLock conlock(m_con_mutex);
+ con::Peer *peer = m_con.GetPeerNoEx(PEER_ID_SERVER);
+ // Not really fetching but can't fetch more.
+ if(peer == NULL) return true;
+
+ con::Channel *channel = &(peer->channels[1]);
+ /*
+ NOTE: Channel 0 should always be used for fetching blocks,
+ and for nothing else.
+ */
+ if(channel->incoming_reliables.size() > 0)
+ return true;
+ if(channel->outgoing_reliables.size() > 0)
+ return true;
+ return false;
+}
+
+IncomingPacket Client::getPacket()
+{
+ JMutexAutoLock lock(m_incoming_queue_mutex);
+
+ core::list<IncomingPacket>::Iterator i;
+ // Refer to first one
+ i = m_incoming_queue.begin();
+
+ // If queue is empty, return empty packet
+ if(i == m_incoming_queue.end()){
+ IncomingPacket packet;
+ return packet;
+ }
+
+ // Pop out first packet and return it
+ IncomingPacket packet = *i;
+ m_incoming_queue.erase(i);
+ return packet;
+}
+
+#if 0
+void Client::removeNode(v3s16 nodepos)
+{
+ if(connectedAndInitialized() == false){
+ dout_client<<DTIME<<"Client::removeNode() cancelled (not connected)"
+ <<std::endl;
+ return;
+ }
+
+ // Test that the position exists
+ try{
+ JMutexAutoLock envlock(m_env_mutex);
+ m_env.getMap().getNode(nodepos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ dout_client<<DTIME<<"Client::removeNode() cancelled (doesn't exist)"
+ <<std::endl;
+ return;
+ }
+
+ SharedBuffer<u8> data(8);
+ writeU16(&data[0], TOSERVER_REMOVENODE);
+ writeS16(&data[2], nodepos.X);
+ writeS16(&data[4], nodepos.Y);
+ writeS16(&data[6], nodepos.Z);
+ Send(0, data, true);
+}
+
+void Client::addNodeFromInventory(v3s16 nodepos, u16 i)
+{
+ if(connectedAndInitialized() == false){
+ dout_client<<DTIME<<"Client::addNodeFromInventory() "
+ "cancelled (not connected)"
+ <<std::endl;
+ return;
+ }
+
+ // Test that the position exists
+ try{
+ JMutexAutoLock envlock(m_env_mutex);
+ m_env.getMap().getNode(nodepos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ dout_client<<DTIME<<"Client::addNode() cancelled (doesn't exist)"
+ <<std::endl;
+ return;
+ }
+
+ //u8 ser_version = m_server_ser_ver;
+
+ // SUGGESTION: The validity of the operation could be checked here too
+
+ u8 datasize = 2 + 6 + 2;
+ SharedBuffer<u8> data(datasize);
+ writeU16(&data[0], TOSERVER_ADDNODE_FROM_INVENTORY);
+ writeS16(&data[2], nodepos.X);
+ writeS16(&data[4], nodepos.Y);
+ writeS16(&data[6], nodepos.Z);
+ writeU16(&data[8], i);
+ Send(0, data, true);
+}
+#endif
+
+void Client::clickGround(u8 button, v3s16 nodepos_undersurface,
+ v3s16 nodepos_oversurface, u16 item)
+{
+ if(connectedAndInitialized() == false){
+ dout_client<<DTIME<<"Client::clickGround() "
+ "cancelled (not connected)"
+ <<std::endl;
+ return;
+ }
+
+ /*
+ length: 19
+ [0] u16 command
+ [2] u8 button (0=left, 1=right)
+ [3] v3s16 nodepos_undersurface
+ [9] v3s16 nodepos_abovesurface
+ [15] u16 item
+ */
+ u8 datasize = 2 + 1 + 6 + 6 + 2;
+ SharedBuffer<u8> data(datasize);
+ writeU16(&data[0], TOSERVER_CLICK_GROUND);
+ writeU8(&data[2], button);
+ writeV3S16(&data[3], nodepos_undersurface);
+ writeV3S16(&data[9], nodepos_oversurface);
+ writeU16(&data[15], item);
+ Send(0, data, true);
+}
+
+void Client::clickObject(u8 button, v3s16 blockpos, s16 id, u16 item)
+{
+ if(connectedAndInitialized() == false){
+ dout_client<<DTIME<<"Client::clickObject() "
+ "cancelled (not connected)"
+ <<std::endl;
+ return;
+ }
+
+ /*
+ [0] u16 command
+ [2] u8 button (0=left, 1=right)
+ [3] v3s16 block
+ [9] s16 id
+ [11] u16 item
+ */
+ u8 datasize = 2 + 1 + 6 + 2 + 2;
+ SharedBuffer<u8> data(datasize);
+ writeU16(&data[0], TOSERVER_CLICK_OBJECT);
+ writeU8(&data[2], button);
+ writeV3S16(&data[3], blockpos);
+ writeS16(&data[9], id);
+ writeU16(&data[11], item);
+ Send(0, data, true);
+}
+
+void Client::release(u8 button)
+{
+ //TODO
+}
+
+void Client::sendSignText(v3s16 blockpos, s16 id, std::string text)
+{
+ /*
+ u16 command
+ v3s16 blockpos
+ s16 id
+ u16 textlen
+ textdata
+ */
+ std::ostringstream os(std::ios_base::binary);
+ u8 buf[12];
+
+ // Write command
+ writeU16(buf, TOSERVER_SIGNTEXT);
+ os.write((char*)buf, 2);
+
+ // Write blockpos
+ writeV3S16(buf, blockpos);
+ os.write((char*)buf, 6);
+
+ // Write id
+ writeS16(buf, id);
+ os.write((char*)buf, 2);
+
+ u16 textlen = text.size();
+ // Write text length
+ writeS16(buf, textlen);
+ os.write((char*)buf, 2);
+
+ // Write text
+ os.write((char*)text.c_str(), textlen);
+
+ // Make data buffer
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as reliable
+ Send(0, data, true);
+}
+
+void Client::sendPlayerPos()
+{
+ 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);
+
+ v3f pf = myplayer->getPosition();
+ v3s32 position(pf.X*100, pf.Y*100, pf.Z*100);
+ v3f sf = myplayer->getSpeed();
+ v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100);
+ s32 pitch = myplayer->getPitch() * 100;
+ s32 yaw = myplayer->getYaw() * 100;
+
+ /*
+ Format:
+ [0] u16 command
+ [2] v3s32 position*100
+ [2+12] v3s32 speed*100
+ [2+12+12] s32 pitch*100
+ [2+12+12+4] s32 yaw*100
+ */
+
+ SharedBuffer<u8> data(2+12+12+4+4);
+ writeU16(&data[0], TOSERVER_PLAYERPOS);
+ writeV3S32(&data[2], position);
+ writeV3S32(&data[2+12], speed);
+ writeS32(&data[2+12+12], pitch);
+ writeS32(&data[2+12+12+4], yaw);
+
+ // Send as unreliable
+ Send(0, data, false);
+}
+
+
+void Client::updateCamera(v3f pos, v3f dir)
+{
+ m_env.getMap().updateCamera(pos, dir);
+ camera_position = pos;
+ camera_direction = dir;
+}
+
+MapNode Client::getNode(v3s16 p)
+{
+ JMutexAutoLock envlock(m_env_mutex);
+ return m_env.getMap().getNode(p);
+}
+
+/*f32 Client::getGroundHeight(v2s16 p)
+{
+ JMutexAutoLock envlock(m_env_mutex);
+ return m_env.getMap().getGroundHeight(p);
+}*/
+
+bool Client::isNodeUnderground(v3s16 p)
+{
+ JMutexAutoLock envlock(m_env_mutex);
+ return m_env.getMap().isNodeUnderground(p);
+}
+
+/*Player * Client::getLocalPlayer()
+{
+ JMutexAutoLock envlock(m_env_mutex);
+ return m_env.getLocalPlayer();
+}*/
+
+/*core::list<Player*> Client::getPlayers()
+{
+ JMutexAutoLock envlock(m_env_mutex);
+ return m_env.getPlayers();
+}*/
+
+v3f Client::getPlayerPosition()
+{
+ JMutexAutoLock envlock(m_env_mutex);
+ LocalPlayer *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ return player->getPosition();
+}
+
+void Client::setPlayerControl(PlayerControl &control)
+{
+ JMutexAutoLock envlock(m_env_mutex);
+ LocalPlayer *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ player->control = control;
+}
+
+// Returns true if the inventory of the local player has been
+// updated from the server. If it is true, it is set to false.
+bool Client::getLocalInventoryUpdated()
+{
+ // m_inventory_updated is behind envlock
+ JMutexAutoLock envlock(m_env_mutex);
+ bool updated = m_inventory_updated;
+ m_inventory_updated = false;
+ return updated;
+}
+
+// Copies the inventory of the local player to parameter
+void Client::getLocalInventory(Inventory &dst)
+{
+ JMutexAutoLock envlock(m_env_mutex);
+ Player *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ dst = player->inventory;
+}
+
+MapBlockObject * Client::getSelectedObject(
+ f32 max_d,
+ v3f from_pos_f_on_map,
+ core::line3d<f32> shootline_on_map
+ )
+{
+ JMutexAutoLock envlock(m_env_mutex);
+
+ core::array<DistanceSortedObject> objects;
+
+ for(core::map<v3s16, bool>::Iterator
+ i = m_active_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+
+ MapBlock *block = NULL;
+ try
+ {
+ block = m_env.getMap().getBlockNoCreate(p);
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+
+ // Calculate from_pos relative to block
+ v3s16 block_pos_i_on_map = block->getPosRelative();
+ v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+ v3f from_pos_f_on_block = from_pos_f_on_map - block_pos_f_on_map;
+
+ block->getObjects(from_pos_f_on_block, max_d, objects);
+ }
+
+ //dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
+
+ // Sort them.
+ // After this, the closest object is the first in the array.
+ objects.sort();
+
+ for(u32 i=0; i<objects.size(); i++)
+ {
+ MapBlockObject *obj = objects[i].obj;
+ MapBlock *block = obj->getBlock();
+
+ // Calculate shootline relative to block
+ v3s16 block_pos_i_on_map = block->getPosRelative();
+ v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+ core::line3d<f32> shootline_on_block(
+ shootline_on_map.start - block_pos_f_on_map,
+ shootline_on_map.end - block_pos_f_on_map
+ );
+
+ if(obj->isSelected(shootline_on_block))
+ {
+ //dstream<<"Returning selected object"<<std::endl;
+ return obj;
+ }
+ }
+
+ //dstream<<"No object selected; returning NULL."<<std::endl;
+ return NULL;
+}
+
+void Client::printDebugInfo(std::ostream &os)
+{
+ //JMutexAutoLock lock1(m_fetchblock_mutex);
+ JMutexAutoLock lock2(m_incoming_queue_mutex);
+
+ os<<"m_incoming_queue.getSize()="<<m_incoming_queue.getSize()
+ //<<", m_fetchblock_history.size()="<<m_fetchblock_history.size()
+ //<<", m_opt_not_found_history.size()="<<m_opt_not_found_history.size()
+ <<std::endl;
+}
+
+
diff --git a/src/client.h b/src/client.h
new file mode 100644
index 000000000..60c57927b
--- /dev/null
+++ b/src/client.h
@@ -0,0 +1,272 @@
+#ifndef CLIENT_HEADER
+#define CLIENT_HEADER
+
+#include "connection.h"
+#include "environment.h"
+#include "common_irrlicht.h"
+#include "jmutex.h"
+#include <ostream>
+
+class ClientNotReadyException : public BaseException
+{
+public:
+ ClientNotReadyException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class Client;
+
+class ClientUpdateThread : public JThread
+{
+ bool run;
+ JMutex run_mutex;
+
+ Client *m_client;
+
+public:
+
+ ClientUpdateThread(Client *client) : JThread(), run(true), m_client(client)
+ {
+ run_mutex.Init();
+ }
+
+ void * Thread();
+
+ bool getRun()
+ {
+ run_mutex.Lock();
+ bool run_cached = run;
+ run_mutex.Unlock();
+ return run_cached;
+ }
+ void setRun(bool a_run)
+ {
+ run_mutex.Lock();
+ run = a_run;
+ run_mutex.Unlock();
+ }
+};
+
+struct IncomingPacket
+{
+ IncomingPacket()
+ {
+ m_data = NULL;
+ m_datalen = 0;
+ m_refcount = NULL;
+ }
+ IncomingPacket(const IncomingPacket &a)
+ {
+ m_data = a.m_data;
+ m_datalen = a.m_datalen;
+ m_refcount = a.m_refcount;
+ if(m_refcount != NULL)
+ (*m_refcount)++;
+ }
+ IncomingPacket(u8 *data, u32 datalen)
+ {
+ m_data = new u8[datalen];
+ memcpy(m_data, data, datalen);
+ m_datalen = datalen;
+ m_refcount = new s32(1);
+ }
+ ~IncomingPacket()
+ {
+ if(m_refcount != NULL){
+ assert(*m_refcount > 0);
+ (*m_refcount)--;
+ if(*m_refcount == 0){
+ if(m_data != NULL)
+ delete[] m_data;
+ }
+ }
+ }
+ /*IncomingPacket & operator=(IncomingPacket a)
+ {
+ m_data = a.m_data;
+ m_datalen = a.m_datalen;
+ m_refcount = a.m_refcount;
+ (*m_refcount)++;
+ return *this;
+ }*/
+ u8 *m_data;
+ u32 m_datalen;
+ s32 *m_refcount;
+};
+
+class LazyMeshUpdater
+{
+public:
+ LazyMeshUpdater(Environment *env)
+ {
+ m_env = env;
+ }
+ ~LazyMeshUpdater()
+ {
+ /*
+ TODO: This could be optimized. It will currently
+ double-update some blocks.
+ */
+ for(core::map<v3s16, bool>::Iterator
+ i = m_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ m_env->getMap().updateMeshes(p);
+ }
+ m_blocks.clear();
+ }
+ void add(v3s16 p)
+ {
+ m_blocks.insert(p, true);
+ }
+private:
+ Environment *m_env;
+ core::map<v3s16, bool> m_blocks;
+};
+
+class Client : public con::PeerHandler
+{
+public:
+ /*
+ NOTE: Every public method should be thread-safe
+ */
+ Client(IrrlichtDevice *device, video::SMaterial *materials,
+ float delete_unused_sectors_timeout,
+ const char *playername);
+ ~Client();
+ /*
+ The name of the local player should already be set when
+ calling this, as it is sent in the initialization.
+ */
+ void connect(Address address);
+ /*
+ returns true when
+ m_con.Connected() == true
+ AND m_server_ser_ver != SER_FMT_VER_INVALID
+ throws con::PeerNotFoundException if connection has been deleted,
+ eg. timed out.
+ */
+ bool connectedAndInitialized();
+ /*
+ Stuff that references the environment is valid only as
+ long as this is not called. (eg. Players)
+ If this throws a PeerNotFoundException, the connection has
+ timed out.
+ */
+ void step(float dtime);
+
+ // Called from updater thread
+ // Returns dtime
+ float asyncStep();
+
+ void ProcessData(u8 *data, u32 datasize, u16 sender_peer_id);
+ // Returns true if something was received
+ bool AsyncProcessPacket(LazyMeshUpdater &mesh_updater);
+ bool AsyncProcessData();
+ void Send(u16 channelnum, SharedBuffer<u8> data, bool reliable);
+
+ //TODO: Remove
+ bool isFetchingBlocks();
+
+ // Pops out a packet from the packet queue
+ IncomingPacket getPacket();
+
+ /*void removeNode(v3s16 nodepos);
+ void addNodeFromInventory(v3s16 nodepos, u16 i);*/
+ void clickGround(u8 button, v3s16 nodepos_undersurface,
+ v3s16 nodepos_oversurface, u16 item);
+ void clickObject(u8 button, v3s16 blockpos, s16 id, u16 item);
+ void release(u8 button);
+
+ void sendSignText(v3s16 blockpos, s16 id, std::string text);
+
+ void updateCamera(v3f pos, v3f dir);
+
+ // Returns InvalidPositionException if not found
+ MapNode getNode(v3s16 p);
+ // Returns InvalidPositionException if not found
+ //f32 getGroundHeight(v2s16 p);
+ // Returns InvalidPositionException if not found
+ bool isNodeUnderground(v3s16 p);
+
+ // Note: The players should not be exposed outside
+ // Return value is valid until client is destroyed
+ //Player * getLocalPlayer();
+ // Return value is valid until step()
+ //core::list<Player*> getPlayers();
+ v3f getPlayerPosition();
+
+ void setPlayerControl(PlayerControl &control);
+
+ // Returns true if the inventory of the local player has been
+ // updated from the server. If it is true, it is set to false.
+ bool getLocalInventoryUpdated();
+ // Copies the inventory of the local player to parameter
+ void getLocalInventory(Inventory &dst);
+ // TODO: Functions for sending inventory editing commands to
+ // server
+
+ // Gets closest object pointed by the shootline
+ // Returns NULL if not found
+ MapBlockObject * getSelectedObject(
+ f32 max_d,
+ v3f from_pos_f_on_map,
+ core::line3d<f32> shootline_on_map
+ );
+
+ // Prints a line or two of info
+ void printDebugInfo(std::ostream &os);
+
+private:
+
+ // Virtual methods from con::PeerHandler
+ void peerAdded(con::Peer *peer);
+ void deletingPeer(con::Peer *peer, bool timeout);
+
+ void ReceiveAll();
+ void Receive();
+
+ void sendPlayerPos();
+ // This sends the player's current name etc to the server
+ void sendPlayerInfo();
+
+ ClientUpdateThread m_thread;
+
+ // NOTE: If connection and environment are both to be locked,
+ // environment shall be locked first.
+
+ Environment m_env;
+ JMutex m_env_mutex;
+
+ con::Connection m_con;
+ JMutex m_con_mutex;
+
+ /*core::map<v3s16, float> m_fetchblock_history;
+ JMutex m_fetchblock_mutex;*/
+
+ core::list<IncomingPacket> m_incoming_queue;
+ JMutex m_incoming_queue_mutex;
+
+ IrrlichtDevice *m_device;
+
+ v3f camera_position;
+ v3f camera_direction;
+
+ // Server serialization version
+ u8 m_server_ser_ver;
+
+ float m_step_dtime;
+ JMutex m_step_dtime_mutex;
+
+ float m_delete_unused_sectors_timeout;
+
+ // This is behind m_env_mutex.
+ bool m_inventory_updated;
+
+ core::map<v3s16, bool> m_active_blocks;
+};
+
+#endif
+
diff --git a/src/clientserver.h b/src/clientserver.h
new file mode 100644
index 000000000..802069216
--- /dev/null
+++ b/src/clientserver.h
@@ -0,0 +1,174 @@
+#ifndef CLIENTSERVER_HEADER
+#define CLIENTSERVER_HEADER
+
+#define PROTOCOL_ID 0x4f457403
+
+enum ToClientCommand
+{
+ TOCLIENT_INIT=0x10,
+ /*
+ Server's reply to TOSERVER_INIT.
+ Sent second after connected.
+
+ [0] u16 TOSERVER_INIT
+ [2] u8 deployed version
+ [3] v3s16 player's position + v3f(0,BS/2,0) floatToInt'd
+ */
+
+ TOCLIENT_BLOCKDATA=0x20, //TODO: Multiple blocks
+ TOCLIENT_ADDNODE,
+ TOCLIENT_REMOVENODE,
+
+ TOCLIENT_PLAYERPOS,
+ /*
+ [0] u16 command
+ // Followed by an arbitary number of these:
+ // Number is determined from packet length.
+ [N] u16 peer_id
+ [N+2] v3s32 position*100
+ [N+2+12] v3s32 speed*100
+ [N+2+12+12] s32 pitch*100
+ [N+2+12+12+4] s32 yaw*100
+ */
+
+ TOCLIENT_PLAYERINFO,
+ /*
+ [0] u16 command
+ // Followed by an arbitary number of these:
+ // Number is determined from packet length.
+ [N] u16 peer_id
+ [N] char[20] name
+ */
+
+ TOCLIENT_OPT_BLOCK_NOT_FOUND, // Not used
+
+ TOCLIENT_SECTORMETA,
+ /*
+ [0] u16 command
+ [2] u8 sector count
+ [3...] v2s16 pos + sector metadata
+ */
+
+ TOCLIENT_INVENTORY,
+ /*
+ [0] u16 command
+ [2] serialized inventory
+ */
+
+ TOCLIENT_OBJECTDATA,
+ /*
+ Sent as unreliable.
+
+ u16 command
+ u16 number of player positions
+ for each player:
+ v3s32 position*100
+ v3s32 speed*100
+ s32 pitch*100
+ s32 yaw*100
+ u16 count of blocks
+ for each block:
+ v3s16 blockpos
+ block objects
+ */
+};
+
+enum ToServerCommand
+{
+ TOSERVER_INIT=0x10,
+ /*
+ Sent first after connected.
+
+ [0] u16 TOSERVER_INIT
+ [2] u8 SER_FMT_VER_HIGHEST
+ [3] u8[20] player_name
+ */
+
+ TOSERVER_INIT2,
+ /*
+ Sent as an ACK for TOCLIENT_INIT.
+ After this, the server can send data.
+
+ [0] u16 TOSERVER_INIT2
+ */
+
+ TOSERVER_GETBLOCK=0x20, // Not used
+ TOSERVER_ADDNODE, // Not used
+ TOSERVER_REMOVENODE, // deprecated
+
+ TOSERVER_PLAYERPOS,
+ /*
+ [0] u16 command
+ [2] v3s32 position*100
+ [2+12] v3s32 speed*100
+ [2+12+12] s32 pitch*100
+ [2+12+12+4] s32 yaw*100
+ */
+
+ TOSERVER_GOTBLOCKS,
+ /*
+ [0] u16 command
+ [2] u8 count
+ [3] v3s16 pos_0
+ [3+6] v3s16 pos_1
+ ...
+ */
+
+ TOSERVER_DELETEDBLOCKS,
+ /*
+ [0] u16 command
+ [2] u8 count
+ [3] v3s16 pos_0
+ [3+6] v3s16 pos_1
+ ...
+ */
+
+ TOSERVER_ADDNODE_FROM_INVENTORY, // deprecated
+ /*
+ [0] u16 command
+ [2] v3s16 pos
+ [8] u16 i
+ */
+
+ TOSERVER_CLICK_OBJECT,
+ /*
+ length: 13
+ [0] u16 command
+ [2] u8 button (0=left, 1=right)
+ [3] v3s16 blockpos
+ [9] s16 id
+ [11] u16 item
+ */
+
+ TOSERVER_CLICK_GROUND,
+ /*
+ length: 17
+ [0] u16 command
+ [2] u8 button (0=left, 1=right)
+ [3] v3s16 nodepos_undersurface
+ [9] v3s16 nodepos_abovesurface
+ [15] u16 item
+ */
+
+ TOSERVER_RELEASE,
+ /*
+ length: 3
+ [0] u16 command
+ [2] u8 button
+ */
+
+ TOSERVER_SIGNTEXT,
+ /*
+ u16 command
+ v3s16 blockpos
+ s16 id
+ u16 textlen
+ textdata
+ */
+};
+
+// Flags for TOSERVER_GETBLOCK
+#define TOSERVER_GETBLOCK_FLAG_OPTIONAL (1<<0)
+
+#endif
+
diff --git a/src/common_irrlicht.h b/src/common_irrlicht.h
new file mode 100644
index 000000000..69f00a752
--- /dev/null
+++ b/src/common_irrlicht.h
@@ -0,0 +1,17 @@
+#ifndef COMMON_IRRLICHT_HEADER
+#define COMMON_IRRLICHT_HEADER
+
+#include <irrlicht.h>
+using namespace irr;
+typedef core::vector3df v3f;
+typedef core::vector3d<s16> v3s16;
+typedef core::vector3d<s32> v3s32;
+
+typedef core::vector2d<f32> v2f;
+typedef core::vector2d<s16> v2s16;
+typedef core::vector2d<s32> v2s32;
+typedef core::vector2d<u32> v2u32;
+typedef core::vector2d<f32> v2f32;
+
+#endif
+
diff --git a/src/connection.cpp b/src/connection.cpp
new file mode 100644
index 000000000..42bfdfb9f
--- /dev/null
+++ b/src/connection.cpp
@@ -0,0 +1,1321 @@
+#include "connection.h"
+#include "main.h"
+#include "serialization.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<u8> &data,
+ u32 protocol_id, u16 sender_peer_id, u8 channel)
+{
+ return makePacket(address, *data, data.getSize(),
+ protocol_id, sender_peer_id, channel);
+}
+
+SharedBuffer<u8> makeOriginalPacket(
+ SharedBuffer<u8> data)
+{
+ u32 header_size = 1;
+ u32 packet_size = data.getSize() + header_size;
+ SharedBuffer<u8> b(packet_size);
+
+ writeU8(&b[0], TYPE_ORIGINAL);
+
+ memcpy(&b[header_size], *data, data.getSize());
+
+ return b;
+}
+
+core::list<SharedBuffer<u8> > makeSplitPacket(
+ SharedBuffer<u8> data,
+ u32 chunksize_max,
+ u16 seqnum)
+{
+ // Chunk packets, containing the TYPE_SPLIT header
+ core::list<SharedBuffer<u8> > 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<u8> 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<SharedBuffer<u8> >::Iterator i = chunks.begin();
+ for(; i != chunks.end(); i++)
+ {
+ // Write chunk_count
+ writeU16(&((*i)[3]), chunk_count);
+ }
+
+ return chunks;
+}
+
+core::list<SharedBuffer<u8> > makeAutoSplitPacket(
+ SharedBuffer<u8> data,
+ u32 chunksize_max,
+ u16 &split_seqnum)
+{
+ u32 original_header_size = 1;
+ core::list<SharedBuffer<u8> > 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<u8> makeReliablePacket(
+ SharedBuffer<u8> data,
+ u16 seqnum)
+{
+ /*dstream<<"BEGIN SharedBuffer<u8> makeReliablePacket()"<<std::endl;
+ dstream<<"data.getSize()="<<data.getSize()<<", data[0]="
+ <<((unsigned int)data[0]&0xff)<<std::endl;*/
+ u32 header_size = 3;
+ u32 packet_size = data.getSize() + header_size;
+ SharedBuffer<u8> b(packet_size);
+
+ writeU8(&b[0], TYPE_RELIABLE);
+ writeU16(&b[1], seqnum);
+
+ memcpy(&b[header_size], *data, data.getSize());
+
+ /*dstream<<"data.getSize()="<<data.getSize()<<", data[0]="
+ <<((unsigned int)data[0]&0xff)<<std::endl;*/
+ //dstream<<"END SharedBuffer<u8> makeReliablePacket()"<<std::endl;
+ return b;
+}
+
+/*
+ ReliablePacketBuffer
+*/
+
+void ReliablePacketBuffer::print()
+{
+ core::list<BufferedPacket>::Iterator i;
+ i = m_list.begin();
+ for(; i != m_list.end(); i++)
+ {
+ u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1]));
+ dout_con<<s<<" ";
+ }
+}
+bool ReliablePacketBuffer::empty()
+{
+ return m_list.empty();
+}
+u32 ReliablePacketBuffer::size()
+{
+ return m_list.getSize();
+}
+RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum)
+{
+ core::list<BufferedPacket>::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="<<seqnum
+ <<", comparing to s="<<s<<std::endl;*/
+ if(s == seqnum)
+ break;
+ }
+ return i;
+}
+RPBSearchResult ReliablePacketBuffer::notFound()
+{
+ return m_list.end();
+}
+u16 ReliablePacketBuffer::getFirstSeqnum()
+{
+ if(empty())
+ throw NotFoundException("Buffer is empty");
+ BufferedPacket p = *m_list.begin();
+ return readU16(&p.data[BASE_HEADER_SIZE+1]);
+}
+BufferedPacket ReliablePacketBuffer::popFirst()
+{
+ if(empty())
+ throw NotFoundException("Buffer is empty");
+ BufferedPacket p = *m_list.begin();
+ core::list<BufferedPacket>::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"<<std::endl;
+ throw NotFoundException("seqnum not found in buffer");
+ }
+ BufferedPacket p = *r;
+ m_list.erase(r);
+ return p;
+}
+void ReliablePacketBuffer::insert(BufferedPacket &p)
+{
+ assert(p.data.getSize() >= 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<BufferedPacket>::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<BufferedPacket>::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<BufferedPacket>::Iterator i;
+ i = m_list.begin();
+ for(; i != m_list.end(); i++){
+ if(i->time >= timeout)
+ i->time = 0.0;
+ }
+}
+
+bool ReliablePacketBuffer::anyTotaltimeReached(float timeout)
+{
+ core::list<BufferedPacket>::Iterator i;
+ i = m_list.begin();
+ for(; i != m_list.end(); i++){
+ if(i->totaltime >= timeout)
+ return true;
+ }
+ return false;
+}
+
+core::list<BufferedPacket> ReliablePacketBuffer::getTimedOuts(float timeout)
+{
+ core::list<BufferedPacket> timed_outs;
+ core::list<BufferedPacket>::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<u16, IncomingSplitPacket*>::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];
+
+ // TODO: These errors should be thrown or something? Dunno.
+ if(chunk_count != sp->chunk_count)
+ derr_con<<"Connection: WARNING: chunk_count="<<chunk_count
+ <<" != sp->chunk_count="<<sp->chunk_count
+ <<std::endl;
+ if(reliable != sp->reliable)
+ derr_con<<"Connection: WARNING: reliable="<<reliable
+ <<" != sp->reliable="<<sp->reliable
+ <<std::endl;
+
+ // If chunk already exists, cancel
+ if(sp->chunks.find(chunk_num) != NULL)
+ throw AlreadyExistsException("Chunk already in buffer");
+
+ // Cut chunk data out of packet
+ u32 chunkdatasize = p.data.getSize() - headersize;
+ SharedBuffer<u8> 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<u16, SharedBuffer<u8> >::Iterator i;
+ i = sp->chunks.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ totalsize += i.getNode()->getValue().getSize();
+ }
+
+ SharedBuffer<u8> fulldata(totalsize);
+
+ // Copy chunks to data buffer
+ u32 start = 0;
+ for(u32 chunk_i=0; chunk_i<sp->chunk_count;
+ chunk_i++)
+ {
+ SharedBuffer<u8> 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<u16> remove_queue;
+ core::map<u16, IncomingSplitPacket*>::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<u16>::Iterator j;
+ j = remove_queue.begin();
+ for(; j != remove_queue.end(); j++)
+ {
+ dout_con<<"NOTE: Removing timed out unreliable split packet"
+ <<std::endl;
+ delete m_buf[*j];
+ m_buf.remove(*j);
+ }
+}
+
+/*
+ Channel
+*/
+
+Channel::Channel()
+{
+ next_outgoing_seqnum = SEQNUM_INITIAL;
+ next_incoming_seqnum = SEQNUM_INITIAL;
+ next_outgoing_split_seqnum = SEQNUM_INITIAL;
+}
+Channel::~Channel()
+{
+}
+
+/*
+ Peer
+*/
+
+Peer::Peer(u16 a_id, Address a_address)
+{
+ id = a_id;
+ address = a_address;
+ timeout_counter = 0.0;
+ //resend_timeout = RESEND_TIMEOUT_MINIMUM;
+ resend_timeout = 0.5;
+ avg_rtt = -1.0;
+ has_sent_with_id = false;
+}
+Peer::~Peer()
+{
+}
+
+void Peer::reportRTT(float rtt)
+{
+ if(rtt < -0.999)
+ {}
+ else if(avg_rtt < 0.0)
+ avg_rtt = rtt;
+ else
+ avg_rtt = rtt * 0.1 + avg_rtt * 0.9;
+
+ // Calculate resend_timeout
+
+ /*int reliable_count = 0;
+ for(int i=0; i<CHANNEL_COUNT; i++)
+ {
+ reliable_count += channels[i].outgoing_reliables.size();
+ }
+ float timeout = avg_rtt * RESEND_TIMEOUT_FACTOR
+ * ((float)reliable_count * 1);*/
+
+ float timeout = avg_rtt * RESEND_TIMEOUT_FACTOR;
+ if(timeout < RESEND_TIMEOUT_MIN)
+ timeout = RESEND_TIMEOUT_MIN;
+ if(timeout > RESEND_TIMEOUT_MAX)
+ timeout = RESEND_TIMEOUT_MAX;
+ resend_timeout = timeout;
+}
+
+/*
+ Connection
+*/
+
+Connection::Connection(
+ u32 protocol_id,
+ u32 max_packet_size,
+ float timeout,
+ PeerHandler *peerhandler
+)
+{
+ assert(peerhandler != NULL);
+
+ m_protocol_id = protocol_id;
+ m_max_packet_size = max_packet_size;
+ m_timeout = timeout;
+ m_peer_id = PEER_ID_NEW;
+ //m_waiting_new_peer_id = false;
+ m_indentation = 0;
+ m_peerhandler = peerhandler;
+}
+
+Connection::~Connection()
+{
+ // Clear peers
+ core::map<u16, Peer*>::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<u16, Peer*>::Node *node = m_peers.find(PEER_ID_SERVER);
+ if(node != NULL){
+ throw ConnectionException("Already connected to a server");
+ }
+
+ Peer *peer = new Peer(PEER_ID_SERVER, address);
+ m_peers.insert(peer->id, peer);
+ m_peerhandler->peerAdded(peer);
+
+ m_socket.Bind(0);
+
+ // Send a dummy packet to server with peer_id = PEER_ID_NEW
+ m_peer_id = PEER_ID_NEW;
+ SharedBuffer<u8> 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<u16, Peer*>::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<u8> Channel::ProcessPacket(
+ SharedBuffer<u8> 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="<<peer_id
+ <<", seqnum="<<seqnum<<std::endl;
+
+ try{
+ BufferedPacket p = outgoing_reliables.popSeqnum(seqnum);
+ // Get round trip time
+ float rtt = p.totaltime;
+
+ // Let peer calculate stuff according to it
+ // (avg_rtt and resend_timeout)
+ Peer *peer = con->GetPeer(peer_id);
+ peer->reportRTT(rtt);
+
+ //con->PrintInfo(dout_con);
+ //dout_con<<"RTT = "<<rtt<<std::endl;
+
+ /*dout_con<<"OUTGOING: ";
+ con->PrintInfo();
+ outgoing_reliables.print();
+ dout_con<<std::endl;*/
+ }
+ catch(NotFoundException &e){
+ con->PrintInfo(derr_con);
+ derr_con<<"WARNING: ACKed packet not "
+ "in outgoing queue"
+ <<std::endl;
+ }
+
+ throw ProcessedSilentlyException("Got an ACK");
+ }
+ else if(controltype == CONTROLTYPE_SET_PEER_ID)
+ {
+ if(packetdata.getSize() < 4)
+ throw InvalidIncomingDataException
+ ("packetdata.getSize() < 4 (SET_PEER_ID header size)");
+ u16 peer_id_new = readU16(&packetdata[2]);
+ con->PrintInfo();
+ dout_con<<"Got new peer id: "<<peer_id_new<<"... "<<std::endl;
+
+ if(con->GetPeerID() != PEER_ID_NEW)
+ {
+ con->PrintInfo(derr_con);
+ derr_con<<"WARNING: Not changing"
+ " existing peer id."<<std::endl;
+ }
+ else
+ {
+ dout_con<<"changing."<<std::endl;
+ con->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"<<std::endl;
+ throw ProcessedSilentlyException("Got a SET_PEER_ID");
+ }
+ else{
+ con->PrintInfo(derr_con);
+ derr_con<<"INVALID TYPE_CONTROL: invalid controltype="
+ <<((int)controltype&0xff)<<std::endl;
+ throw InvalidIncomingDataException("Invalid control type");
+ }
+ }
+ else if(type == TYPE_ORIGINAL)
+ {
+ if(packetdata.getSize() < ORIGINAL_HEADER_SIZE)
+ throw InvalidIncomingDataException
+ ("packetdata.getSize() < ORIGINAL_HEADER_SIZE");
+ con->PrintInfo();
+ dout_con<<"RETURNING TYPE_ORIGINAL to user"
+ <<std::endl;
+ // Get the inside packet out and return it
+ SharedBuffer<u8> 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="<<e.getData().getSize()<<std::endl;
+ return e.getData();
+ }
+ con->PrintInfo();
+ dout_con<<"BUFFERING TYPE_SPLIT"<<std::endl;
+ throw ProcessedSilentlyException("Buffered a split packet chunk");
+ }
+ else if(type == TYPE_RELIABLE)
+ {
+ // Recursive reliable packets not allowed
+ assert(reliable == false);
+
+ if(packetdata.getSize() < RELIABLE_HEADER_SIZE)
+ throw InvalidIncomingDataException
+ ("packetdata.getSize() < RELIABLE_HEADER_SIZE");
+
+ u16 seqnum = readU16(&packetdata[1]);
+
+ bool is_future_packet = seqnum_higher(seqnum, next_incoming_seqnum);
+ bool is_old_packet = seqnum_higher(next_incoming_seqnum, seqnum);
+
+ con->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="<<seqnum
+ <<" next="<<next_incoming_seqnum;
+ dout_con<<" [sending CONTROLTYPE_ACK"
+ " to peer_id="<<peer_id<<"]";
+ dout_con<<std::endl;
+
+ //DEBUG
+ //assert(incoming_reliables.size() < 100);
+
+ // Send a CONTROLTYPE_ACK
+ SharedBuffer<u8> 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="
+ <<seqnum<<")"<<std::endl;*/
+
+ // This one comes later, buffer it.
+ // Actually we have to make a packet to buffer one.
+ // Well, we have all the ingredients, so just do it.
+ BufferedPacket packet = makePacket(
+ con->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<<std::endl;*/
+ }
+ catch(AlreadyExistsException &e)
+ {
+ }
+
+ throw ProcessedSilentlyException("Buffered future reliable packet");
+ }
+ //else if(seqnum_higher(next_incoming_seqnum, seqnum))
+ else if(is_old_packet)
+ {
+ // An old packet, dump it
+ throw InvalidIncomingDataException("Got an old reliable packet");
+ }
+
+ next_incoming_seqnum++;
+
+ // Get out the inside packet and re-process it
+ SharedBuffer<u8> 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(derr_con);
+ derr_con<<"Got invalid type="<<((int)type&0xff)<<std::endl;
+ throw InvalidIncomingDataException("Invalid packet type");
+ }
+
+ // We should never get here.
+ // If you get here, add an exception or a return to some of the
+ // above conditionals.
+ assert(0);
+ throw BaseException("Error in Channel::ProcessPacket()");
+}
+
+SharedBuffer<u8> 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="<<seqnum
+ <<" peer_id="<<peer_id
+ <<" channel="<<((int)channelnum&0xff)
+ <<std::endl;
+
+ next_incoming_seqnum++;
+
+ u32 headers_size = BASE_HEADER_SIZE + RELIABLE_HEADER_SIZE;
+ // Get out the inside packet and re-process it
+ SharedBuffer<u8> payload(p.data.getSize() - headers_size);
+ memcpy(*payload, &p.data[headers_size], payload.getSize());
+
+ return ProcessPacket(payload, con, peer_id, channelnum, true);
+ }
+ }
+
+ throw NoIncomingDataException("No relevant data in buffers");
+}
+
+SharedBuffer<u8> Connection::GetFromBuffers(u16 &peer_id)
+{
+ core::map<u16, Peer*>::Iterator j;
+ j = m_peers.getIterator();
+ for(; j.atEnd() == false; j++)
+ {
+ Peer *peer = j.getNode()->getValue();
+ for(u16 i=0; i<CHANNEL_COUNT; i++)
+ {
+ Channel *channel = &peer->channels[i];
+ try{
+ SharedBuffer<u8> 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<u8> packetdata(packet_maxsize);
+
+ for(;;)
+ {
+ try
+ {
+ /*
+ Check if some buffer has relevant data
+ */
+ try{
+ SharedBuffer<u8> 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(derr_con);
+ derr_con<<"Receive(): Invalid channel "<<channelnum<<std::endl;
+ throw InvalidIncomingDataException("Channel doesn't exist");
+ }
+
+ if(peer_id == PEER_ID_NEW)
+ {
+ /*
+ Somebody is trying to send stuff to us with no peer id.
+
+ Check if the same address and port was added to our peer
+ list before.
+ Allow only entries that have has_sent_with_id==false.
+ */
+
+ core::map<u16, Peer*>::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(derr_con);
+ derr_con<<"WARNING: Assuming unknown peer to be "
+ <<"peer_id="<<peer_id<<std::endl;
+ }
+ }
+
+ /*
+ The peer was not found in our lists. Add it.
+ */
+ if(peer_id == PEER_ID_NEW)
+ {
+ // Somebody wants to make a new connection
+
+ // Get a unique peer id (2 or higher)
+ u16 peer_id_new = 2;
+ /*
+ Find an unused peer id
+ */
+ for(;;)
+ {
+ // Check if exists
+ if(m_peers.find(peer_id_new) == NULL)
+ break;
+ // Check for overflow
+ if(peer_id_new == 65535)
+ throw ConnectionException
+ ("Connection ran out of peer ids");
+ peer_id_new++;
+ }
+
+ PrintInfo();
+ dout_con<<"Receive(): Got a packet with peer_id=PEER_ID_NEW,"
+ " giving peer_id="<<peer_id_new<<std::endl;
+
+ // Create a peer
+ Peer *peer = new Peer(peer_id_new, sender);
+ m_peers.insert(peer->id, peer);
+ m_peerhandler->peerAdded(peer);
+
+ // Create CONTROL packet to tell the peer id to the new peer.
+ SharedBuffer<u8> 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<u16, Peer*>::Node *node = m_peers.find(peer_id);
+
+ if(node == NULL)
+ {
+ // Peer not found
+ // This means that the peer id of the sender is not PEER_ID_NEW
+ // and it is invalid.
+ PrintInfo(derr_con);
+ derr_con<<"Receive(): Peer not found"<<std::endl;
+ throw InvalidIncomingDataException("Peer not found (possible timeout)");
+ }
+
+ Peer *peer = node->getValue();
+
+ // Validate peer address
+ if(peer->address != sender)
+ {
+ PrintInfo(derr_con);
+ derr_con<<"Peer "<<peer_id<<" sending from different address."
+ " Ignoring."<<std::endl;
+ throw InvalidIncomingDataException
+ ("Peer sending from different address");
+ /*// If there is more data, receive again
+ if(m_socket.WaitData(0) == true)
+ continue;
+ throw NoIncomingDataException("No incoming data (2)");*/
+ }
+
+ 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<u8> 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<u8> resultdata = channel->ProcessPacket
+ (strippeddata, this, peer_id, channelnum);
+
+ PrintInfo();
+ dout_con<<"ProcessPacket returned data of size "
+ <<resultdata.getSize()<<std::endl;
+
+ if(datasize < resultdata.getSize())
+ throw InvalidIncomingDataException
+ ("Buffer too small for received data");
+
+ memcpy(data, *resultdata, resultdata.getSize());
+ return resultdata.getSize();
+ }
+ catch(ProcessedSilentlyException &e)
+ {
+ // If there is more data, receive again
+ if(m_socket.WaitData(0) == true)
+ continue;
+ }
+ throw NoIncomingDataException("No incoming data (2)");
+ } // try
+ catch(InvalidIncomingDataException &e)
+ {
+ // If there is more data, receive again
+ if(m_socket.WaitData(0) == true)
+ continue;
+ }
+ } // for
+}
+
+void Connection::SendToAll(u8 channelnum, SharedBuffer<u8> data, bool reliable)
+{
+ core::map<u16, Peer*>::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<u8> 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<SharedBuffer<u8> > originals;
+ originals = makeAutoSplitPacket(data, chunksize_max,
+ channel->next_outgoing_split_seqnum);
+
+ core::list<SharedBuffer<u8> >::Iterator i;
+ i = originals.begin();
+ for(; i != originals.end(); i++)
+ {
+ SharedBuffer<u8> original = *i;
+
+ SendAsPacket(peer_id, channelnum, original, reliable);
+ }
+}
+
+void Connection::SendAsPacket(u16 peer_id, u8 channelnum,
+ SharedBuffer<u8> 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<u8> 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(derr_con);
+ derr_con<<"WARNING: Going to send a reliable packet "
+ "seqnum="<<seqnum<<" that is already "
+ "in outgoing buffer"<<std::endl;
+ //assert(0);
+ }
+
+ // Send the packet
+ RawSend(p);
+ }
+ else
+ {
+ // Add base headers and make a packet
+ BufferedPacket p = makePacket(peer->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<u16> timeouted_peers;
+ core::map<u16, Peer*>::Iterator j;
+ j = m_peers.getIterator();
+ for(; j.atEnd() == false; j++)
+ {
+ Peer *peer = j.getNode()->getValue();
+
+ /*
+ Check peer timeout
+ */
+ peer->timeout_counter += dtime;
+ if(peer->timeout_counter > m_timeout)
+ {
+ PrintInfo(derr_con);
+ derr_con<<"RunTimeouts(): Peer "<<peer->id
+ <<" has timed out."
+ <<" (source=peer->timeout_counter)"
+ <<std::endl;
+ // Add peer to the list
+ timeouted_peers.push_back(peer->id);
+ // Don't bother going through the buffers of this one
+ continue;
+ }
+
+ float resend_timeout = peer->resend_timeout;
+ for(u16 i=0; i<CHANNEL_COUNT; i++)
+ {
+ core::list<BufferedPacket> timed_outs;
+ core::list<BufferedPacket>::Iterator j;
+
+ Channel *channel = &peer->channels[i];
+
+ // Remove timed out incomplete unreliable split packets
+ channel->incoming_splits.removeUnreliableTimedOuts(dtime, m_timeout);
+
+ // Increment reliable packet times
+ channel->outgoing_reliables.incrementTimeouts(dtime);
+
+ // Check reliable packet total times, remove peer if
+ // over timeout.
+ if(channel->outgoing_reliables.anyTotaltimeReached(m_timeout))
+ {
+ PrintInfo(derr_con);
+ derr_con<<"RunTimeouts(): Peer "<<peer->id
+ <<" has timed out."
+ <<" (source=reliable packet totaltime)"
+ <<std::endl;
+ // Add peer to the to-be-removed list
+ timeouted_peers.push_back(peer->id);
+ goto nextpeer;
+ }
+
+ // Re-send timed out outgoing reliables
+
+ timed_outs = channel->
+ outgoing_reliables.getTimedOuts(resend_timeout);
+
+ channel->outgoing_reliables.resetTimedOuts(resend_timeout);
+
+ 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(derr_con);
+ derr_con<<"RE-SENDING timed-out RELIABLE to ";
+ j->address.print(&derr_con);
+ derr_con<<"(t/o="<<resend_timeout<<"): "
+ <<"from_peer_id="<<peer_id
+ <<", channel="<<((int)channel&0xff)
+ <<", seqnum="<<seqnum
+ <<std::endl;
+
+ RawSend(*j);
+
+ // Enlarge avg_rtt and resend_timeout:
+ // The rtt will be at least the timeout.
+ // NOTE: This won't affect the timeout of the next
+ // checked channel because it was cached.
+ peer->reportRTT(resend_timeout);
+ }
+ }
+
+ /*
+ Send pings
+ */
+ peer->ping_timer += dtime;
+ if(peer->ping_timer >= 5.0)
+ {
+ // Create and send PING packet
+ SharedBuffer<u8> data(2);
+ writeU8(&data[0], TYPE_CONTROL);
+ writeU8(&data[1], CONTROLTYPE_PING);
+ SendAsPacket(peer->id, 0, data, true);
+
+ peer->ping_timer = 0.0;
+ }
+
+nextpeer:
+ continue;
+ }
+
+ // Remove timeouted peers
+ core::list<u16>::Iterator i = timeouted_peers.begin();
+ for(; i != timeouted_peers.end(); i++)
+ {
+ PrintInfo(derr_con);
+ derr_con<<"RunTimeouts(): Removing peer "<<(*i)<<std::endl;
+ m_peerhandler->deletingPeer(m_peers[*i], true);
+ delete m_peers[*i];
+ m_peers.remove(*i);
+ }
+}
+
+Peer* Connection::GetPeer(u16 peer_id)
+{
+ core::map<u16, Peer*>::Node *node = m_peers.find(peer_id);
+
+ if(node == NULL){
+ // Peer not found
+ throw PeerNotFoundException("Peer not found (possible timeout)");
+ }
+
+ // Error checking
+ assert(node->getValue()->id == peer_id);
+
+ return node->getValue();
+}
+
+Peer* Connection::GetPeerNoEx(u16 peer_id)
+{
+ core::map<u16, Peer*>::Node *node = m_peers.find(peer_id);
+
+ if(node == NULL){
+ return NULL;
+ }
+
+ // Error checking
+ assert(node->getValue()->id == peer_id);
+
+ return node->getValue();
+}
+
+core::list<Peer*> Connection::GetPeers()
+{
+ core::list<Peer*> list;
+ core::map<u16, Peer*>::Iterator j;
+ j = m_peers.getIterator();
+ for(; j.atEnd() == false; j++)
+ {
+ Peer *peer = j.getNode()->getValue();
+ list.push_back(peer);
+ }
+ return list;
+}
+
+void Connection::PrintInfo(std::ostream &out)
+{
+ out<<m_socket.GetHandle();
+ out<<" ";
+ out<<"con "<<m_peer_id<<": ";
+ for(s16 i=0; i<(s16)m_indentation-1; i++)
+ out<<" ";
+}
+
+void Connection::PrintInfo()
+{
+ PrintInfo(dout_con);
+}
+
+} // namespace
+
diff --git a/src/connection.h b/src/connection.h
new file mode 100644
index 000000000..c67aa5feb
--- /dev/null
+++ b/src/connection.h
@@ -0,0 +1,474 @@
+#ifndef CONNECTION_HEADER
+#define CONNECTION_HEADER
+
+#include <iostream>
+#include <fstream>
+#include "debug.h"
+#include "common_irrlicht.h"
+#include "socket.h"
+#include "utility.h"
+#include "exceptions.h"
+#include "constants.h"
+
+namespace con
+{
+
+/*
+ Exceptions
+*/
+class NotFoundException : public BaseException
+{
+public:
+ NotFoundException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class PeerNotFoundException : public BaseException
+{
+public:
+ PeerNotFoundException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class ConnectionException : public BaseException
+{
+public:
+ ConnectionException(const char *s):
+ BaseException(s)
+ {}
+};
+
+/*class ThrottlingException : public BaseException
+{
+public:
+ ThrottlingException(const char *s):
+ BaseException(s)
+ {}
+};*/
+
+class InvalidIncomingDataException : public BaseException
+{
+public:
+ InvalidIncomingDataException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class InvalidOutgoingDataException : public BaseException
+{
+public:
+ InvalidOutgoingDataException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class NoIncomingDataException : public BaseException
+{
+public:
+ NoIncomingDataException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class ProcessedSilentlyException : public BaseException
+{
+public:
+ ProcessedSilentlyException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class GotSplitPacketException
+{
+ SharedBuffer<u8> m_data;
+public:
+ GotSplitPacketException(SharedBuffer<u8> data):
+ m_data(data)
+ {}
+ SharedBuffer<u8> getData()
+ {
+ return m_data;
+ }
+};
+
+inline u16 readPeerId(u8 *packetdata)
+{
+ return readU16(&packetdata[4]);
+}
+inline u8 readChannel(u8 *packetdata)
+{
+ return readU8(&packetdata[6]);
+}
+
+#define SEQNUM_MAX 65535
+inline bool seqnum_higher(u16 higher, u16 lower)
+{
+ if(lower > higher && lower - higher > SEQNUM_MAX/2){
+ return true;
+ }
+ return (higher > lower);
+}
+
+struct BufferedPacket
+{
+ BufferedPacket(u8 *a_data, u32 a_size):
+ data(a_data, a_size), time(0.0), totaltime(0.0)
+ {}
+ BufferedPacket(u32 a_size):
+ data(a_size), time(0.0), totaltime(0.0)
+ {}
+ SharedBuffer<u8> data; // Data of the packet, including headers
+ float time; // Seconds from buffering the packet or re-sending
+ float totaltime; // Seconds from buffering the packet
+ Address address; // Sender or destination
+};
+
+// This adds the base headers to the data and makes a packet out of it
+BufferedPacket makePacket(Address &address, u8 *data, u32 datasize,
+ u32 protocol_id, u16 sender_peer_id, u8 channel);
+BufferedPacket makePacket(Address &address, SharedBuffer<u8> &data,
+ u32 protocol_id, u16 sender_peer_id, u8 channel);
+
+// Add the TYPE_ORIGINAL header to the data
+SharedBuffer<u8> makeOriginalPacket(
+ SharedBuffer<u8> data);
+
+// Split data in chunks and add TYPE_SPLIT headers to them
+core::list<SharedBuffer<u8> > makeSplitPacket(
+ SharedBuffer<u8> data,
+ u32 chunksize_max,
+ u16 seqnum);
+
+// Depending on size, make a TYPE_ORIGINAL or TYPE_SPLIT packet
+// Increments split_seqnum if a split packet is made
+core::list<SharedBuffer<u8> > makeAutoSplitPacket(
+ SharedBuffer<u8> data,
+ u32 chunksize_max,
+ u16 &split_seqnum);
+
+// Add the TYPE_RELIABLE header to the data
+SharedBuffer<u8> makeReliablePacket(
+ SharedBuffer<u8> data,
+ u16 seqnum);
+
+struct IncomingSplitPacket
+{
+ IncomingSplitPacket()
+ {
+ time = 0.0;
+ reliable = false;
+ }
+ // Key is chunk number, value is data without headers
+ core::map<u16, SharedBuffer<u8> > chunks;
+ u32 chunk_count;
+ float time; // Seconds from adding
+ bool reliable; // If true, isn't deleted on timeout
+
+ bool allReceived()
+ {
+ return (chunks.size() == chunk_count);
+ }
+};
+
+/*
+=== NOTES ===
+
+A packet is sent through a channel to a peer with a basic header:
+TODO: Should we have a receiver_peer_id also?
+ Header (7 bytes):
+ [0] u32 protocol_id
+ [4] u16 sender_peer_id
+ [6] u8 channel
+sender_peer_id:
+ Unique to each peer.
+ value 0 is reserved for making new connections
+ value 1 is reserved for server
+channel:
+ The lower the number, the higher the priority is.
+ Only channels 0, 1 and 2 exist.
+*/
+#define BASE_HEADER_SIZE 7
+#define PEER_ID_NEW 0
+#define PEER_ID_SERVER 1
+#define CHANNEL_COUNT 3
+/*
+Packet types:
+
+CONTROL: This is a packet used by the protocol.
+- When this is processed, nothing is handed to the user.
+ Header (2 byte):
+ [0] u8 type
+ [1] u8 controltype
+controltype and data description:
+ CONTROLTYPE_ACK
+ [2] u16 seqnum
+ CONTROLTYPE_SET_PEER_ID
+ [2] u16 peer_id_new
+ CONTROLTYPE_PING
+ - This can be sent in a reliable packet to get a reply
+*/
+#define TYPE_CONTROL 0
+#define CONTROLTYPE_ACK 0
+#define CONTROLTYPE_SET_PEER_ID 1
+#define CONTROLTYPE_PING 2
+/*
+ORIGINAL: This is a plain packet with no control and no error
+checking at all.
+- When this is processed, it is directly handed to the user.
+ Header (1 byte):
+ [0] u8 type
+*/
+#define TYPE_ORIGINAL 1
+#define ORIGINAL_HEADER_SIZE 1
+/*
+SPLIT: These are sequences of packets forming one bigger piece of
+data.
+- When processed and all the packet_nums 0...packet_count-1 are
+ present (this should be buffered), the resulting data shall be
+ directly handed to the user.
+- If the data fails to come up in a reasonable time, the buffer shall
+ be silently discarded.
+- These can be sent as-is or atop of a RELIABLE packet stream.
+ Header (7 bytes):
+ [0] u8 type
+ [1] u16 seqnum
+ [3] u16 chunk_count
+ [5] u16 chunk_num
+*/
+#define TYPE_SPLIT 2
+/*
+RELIABLE: Delivery of all RELIABLE packets shall be forced by ACKs,
+and they shall be delivered in the same order as sent. This is done
+with a buffer in the receiving and transmitting end.
+- When this is processed, the contents of each packet is recursively
+ processed as packets.
+ Header (3 bytes):
+ [0] u8 type
+ [1] u16 seqnum
+
+*/
+#define TYPE_RELIABLE 3
+#define RELIABLE_HEADER_SIZE 3
+//#define SEQNUM_INITIAL 0x10
+#define SEQNUM_INITIAL 65500
+
+/*
+ A buffer which stores reliable packets and sorts them internally
+ for fast access to the smallest one.
+*/
+
+typedef core::list<BufferedPacket>::Iterator RPBSearchResult;
+
+class ReliablePacketBuffer
+{
+public:
+
+ void print();
+ bool empty();
+ u32 size();
+ RPBSearchResult findPacket(u16 seqnum);
+ RPBSearchResult notFound();
+ u16 getFirstSeqnum();
+ BufferedPacket popFirst();
+ BufferedPacket popSeqnum(u16 seqnum);
+ void insert(BufferedPacket &p);
+ void incrementTimeouts(float dtime);
+ void resetTimedOuts(float timeout);
+ bool anyTotaltimeReached(float timeout);
+ core::list<BufferedPacket> getTimedOuts(float timeout);
+
+private:
+ core::list<BufferedPacket> m_list;
+};
+
+/*
+ A buffer for reconstructing split packets
+*/
+
+class IncomingSplitBuffer
+{
+public:
+ ~IncomingSplitBuffer();
+ /*
+ This will throw a GotSplitPacketException when a full
+ split packet is constructed.
+ */
+ void insert(BufferedPacket &p, bool reliable);
+
+ void removeUnreliableTimedOuts(float dtime, float timeout);
+
+private:
+ // Key is seqnum
+ core::map<u16, IncomingSplitPacket*> m_buf;
+};
+
+class Connection;
+
+struct Channel
+{
+ Channel();
+ ~Channel();
+ /*
+ Processes a packet with the basic header stripped out.
+ Parameters:
+ packetdata: Data in packet (with no base headers)
+ con: The connection to which the channel is associated
+ (used for sending back stuff (ACKs))
+ peer_id: peer id of the sender of the packet in question
+ channelnum: channel on which the packet was sent
+ reliable: true if recursing into a reliable packet
+ */
+ SharedBuffer<u8> ProcessPacket(
+ SharedBuffer<u8> packetdata,
+ Connection *con,
+ u16 peer_id,
+ u8 channelnum,
+ bool reliable=false);
+
+ // Returns next data from a buffer if possible
+ // throws a NoIncomingDataException if no data is available
+ // If found, sets peer_id
+ SharedBuffer<u8> CheckIncomingBuffers(Connection *con,
+ u16 &peer_id);
+
+ u16 next_outgoing_seqnum;
+ u16 next_incoming_seqnum;
+ u16 next_outgoing_split_seqnum;
+
+ // This is for buffering the incoming packets that are coming in
+ // the wrong order
+ ReliablePacketBuffer incoming_reliables;
+ // This is for buffering the sent packets so that the sender can
+ // re-send them if no ACK is received
+ ReliablePacketBuffer outgoing_reliables;
+
+ IncomingSplitBuffer incoming_splits;
+};
+
+class Peer;
+
+class PeerHandler
+{
+public:
+ PeerHandler()
+ {
+ }
+ virtual ~PeerHandler()
+ {
+ }
+
+ /*
+ This is called after the Peer has been inserted into the
+ Connection's peer container.
+ */
+ virtual void peerAdded(Peer *peer) = 0;
+ /*
+ This is called before the Peer has been removed from the
+ Connection's peer container.
+ */
+ virtual void deletingPeer(Peer *peer, bool timeout) = 0;
+};
+
+class Peer
+{
+public:
+
+ Peer(u16 a_id, Address a_address);
+ virtual ~Peer();
+
+ /*
+ Calculates avg_rtt and resend_timeout.
+
+ rtt=-1 only recalculates resend_timeout
+ */
+ void reportRTT(float rtt);
+
+ Channel channels[CHANNEL_COUNT];
+
+ // Address of the peer
+ Address address;
+ // Unique id of the peer
+ u16 id;
+ // Seconds from last receive
+ float timeout_counter;
+ // Ping timer
+ float ping_timer;
+ // This is changed dynamically
+ float resend_timeout;
+ // Updated when an ACK is received
+ float avg_rtt;
+ // This is set to true when the peer has actually sent something
+ // with the id we have given to it
+ bool has_sent_with_id;
+
+private:
+};
+
+class Connection
+{
+public:
+ Connection(
+ u32 protocol_id,
+ u32 max_packet_size,
+ float timeout,
+ PeerHandler *peerhandler
+ );
+ ~Connection();
+ void setTimeoutMs(int timeout){ m_socket.setTimeoutMs(timeout); }
+ // Start being a server
+ void Serve(unsigned short port);
+ // Connect to a server
+ void Connect(Address address);
+ bool Connected();
+
+ // Sets peer_id
+ SharedBuffer<u8> GetFromBuffers(u16 &peer_id);
+
+ // The peer_id of sender is stored in peer_id
+ // Return value: I guess this always throws an exception or
+ // actually gets data
+ u32 Receive(u16 &peer_id, u8 *data, u32 datasize);
+
+ // These will automatically package the data as an original or split
+ void SendToAll(u8 channelnum, SharedBuffer<u8> data, bool reliable);
+ void Send(u16 peer_id, u8 channelnum, SharedBuffer<u8> data, bool reliable);
+ // Send data as a packet; it will be wrapped in base header and
+ // optionally to a reliable packet.
+ void SendAsPacket(u16 peer_id, u8 channelnum,
+ SharedBuffer<u8> data, bool reliable);
+ // Sends a raw packet
+ void RawSend(const BufferedPacket &packet);
+
+ void RunTimeouts(float dtime);
+ // Can throw a PeerNotFoundException
+ Peer* GetPeer(u16 peer_id);
+ // returns NULL if failed
+ Peer* GetPeerNoEx(u16 peer_id);
+ core::list<Peer*> GetPeers();
+
+ void SetPeerID(u16 id){ m_peer_id = id; }
+ u16 GetPeerID(){ return m_peer_id; }
+ u32 GetProtocolID(){ return m_protocol_id; }
+
+ // For debug printing
+ void PrintInfo(std::ostream &out);
+ void PrintInfo();
+ u16 m_indentation;
+
+private:
+ u32 m_protocol_id;
+ float m_timeout;
+ PeerHandler *m_peerhandler;
+ core::map<u16, Peer*> m_peers;
+ u16 m_peer_id;
+ //bool m_waiting_new_peer_id;
+ u32 m_max_packet_size;
+ UDPSocket m_socket;
+};
+
+} // namespace
+
+#endif
+
diff --git a/src/constants.h b/src/constants.h
new file mode 100644
index 000000000..e91ded0eb
--- /dev/null
+++ b/src/constants.h
@@ -0,0 +1,72 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef CONSTANTS_HEADER
+#define CONSTANTS_HEADER
+
+#define DEBUGFILE "debug.txt"
+
+// Define for simulating the quirks of sending through internet
+// WARNING: This disables unit testing of socket and connection
+#define INTERNET_SIMULATOR 0
+
+#define CONNECTION_TIMEOUT 30
+
+#define RESEND_TIMEOUT_MIN 0.333
+#define RESEND_TIMEOUT_MAX 3.0
+// resend_timeout = avg_rtt * this
+#define RESEND_TIMEOUT_FACTOR 4
+
+#define PI 3.14159
+
+#define SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT (60*10)
+#define SERVER_MAP_SAVE_INTERVAL (60)
+
+//#define SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT (5)
+//#define SERVER_MAP_SAVE_INTERVAL (5)
+
+#define FOV_ANGLE (PI/2.5)
+
+// The absolute working limit is (2^15 - viewing_range).
+#define MAP_GENERATION_LIMIT (31000)
+
+//#define MAX_SIMULTANEOUS_BLOCK_SENDS 7
+//#define MAX_SIMULTANEOUS_BLOCK_SENDS 3
+#define MAX_SIMULTANEOUS_BLOCK_SENDS 2
+//#define MAX_SIMULTANEOUS_BLOCK_SENDS 1
+
+#define FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING 2.0
+#define LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS 1
+
+// Viewing range stuff
+
+#define FPS_DEFAULT_WANTED 30
+#define FPS_DEFAULT_MAX 60
+
+#define FORCEDFETCH_RANGE 80
+
+#define HEIGHTMAP_RANGE_NODES 300
+
+// The freetime ratio is dynamically kept high enough to make this
+// dtime jitter possible
+// Allow 50% = 0.1
+/*#define DTIME_JITTER_MAX_FRACTION 0.5
+#define FREETIME_RATIO_MIN 0.05
+#define FREETIME_RATIO_MAX 0.4*/
+
+//#define FREETIME_RATIO 0.2
+#define FREETIME_RATIO 0.15
+
+#define SECTOR_HEIGHTMAP_SPLIT 2
+
+#define PLAYER_INVENTORY_SIZE (8*4)
+
+#define SIGN_TEXT_MAX_LENGTH 50
+
+#define ACTIVE_OBJECT_D_BLOCKS 2
+
+#define CATCH_UNJANDLED_EXCEPTIONS 1
+
+#endif
+
diff --git a/src/debug.cpp b/src/debug.cpp
new file mode 100644
index 000000000..248941386
--- /dev/null
+++ b/src/debug.cpp
@@ -0,0 +1,189 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "debug.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #define sleep_ms(x) Sleep(x)
+#else
+ #include <unistd.h>
+ #define sleep_ms(x) usleep(x*1000)
+#endif
+
+/*
+ Debug output
+*/
+
+FILE *g_debugstreams[DEBUGSTREAM_COUNT] = {stderr, NULL};
+
+void debugstreams_init(bool disable_stderr, const char *filename)
+{
+ if(disable_stderr)
+ g_debugstreams[0] = NULL;
+
+ if(filename)
+ g_debugstreams[1] = fopen(filename, "a");
+
+ if(g_debugstreams[1])
+ {
+ fprintf(g_debugstreams[1], "\n\n-------------\n");
+ fprintf(g_debugstreams[1], " Separator \n");
+ fprintf(g_debugstreams[1], "-------------\n\n");
+ }
+}
+
+void debugstreams_deinit()
+{
+ if(g_debugstreams[1] != NULL)
+ fclose(g_debugstreams[1]);
+}
+
+Debugbuf debugbuf(false);
+std::ostream dstream(&debugbuf);
+Debugbuf debugbuf_no_stderr(true);
+std::ostream dstream_no_stderr(&debugbuf_no_stderr);
+Nullstream dummyout;
+
+/*
+ Assert
+*/
+
+void assert_fail(const char *assertion, const char *file,
+ unsigned int line, const char *function)
+{
+ DEBUGPRINT("\nIn thread %x:\n"
+ "%s:%d: %s: Assertion '%s' failed.\n",
+ (unsigned int)get_current_thread_id(),
+ file, line, function, assertion);
+
+ debug_stacks_print();
+
+ if(g_debugstreams[1])
+ fclose(g_debugstreams[1]);
+
+ //sleep_ms(3000);
+
+ abort();
+}
+
+/*
+ DebugStack
+*/
+
+DebugStack::DebugStack(threadid_t id)
+{
+ threadid = id;
+ stack_i = 0;
+ stack_max_i = 0;
+}
+
+void DebugStack::print(FILE *file, bool everything)
+{
+ fprintf(file, "BEGIN STACK: Debug stack for thread %x:\n",
+ (unsigned int)threadid);
+
+ for(int i=0; i<stack_max_i; i++)
+ {
+ if(i == stack_i && everything == false)
+ continue;
+
+ if(everything == true && i == stack_i)
+ fprintf(file, "END OF STACK.\n"
+ "! Continuing beyond stack end:\n");
+
+ fprintf(file, "#%d %s\n", i, stack[i]);
+ }
+
+ if(stack_i == DEBUG_STACK_SIZE)
+ fprintf(file, "Probably overflown.\n");
+}
+
+
+core::map<threadid_t, DebugStack*> g_debug_stacks;
+JMutex g_debug_stacks_mutex;
+
+void debug_stacks_init()
+{
+ g_debug_stacks_mutex.Init();
+}
+
+void debug_stacks_print()
+{
+ JMutexAutoLock lock(g_debug_stacks_mutex);
+
+ DEBUGPRINT("Debug stacks:\n");
+
+ for(core::map<threadid_t, DebugStack*>::Iterator
+ i = g_debug_stacks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ DebugStack *stack = i.getNode()->getValue();
+
+ for(int i=0; i<DEBUGSTREAM_COUNT; i++)
+ {
+ if(g_debugstreams[i] != NULL)
+ stack->print(g_debugstreams[i], true);
+ }
+ }
+}
+
+DebugStacker::DebugStacker(const char *text)
+{
+ threadid_t threadid = get_current_thread_id();
+
+ JMutexAutoLock lock(g_debug_stacks_mutex);
+
+ core::map<threadid_t, DebugStack*>::Node *n;
+ n = g_debug_stacks.find(threadid);
+ if(n != NULL)
+ {
+ m_stack = n->getValue();
+ }
+ else
+ {
+ /*DEBUGPRINT("Creating new debug stack for thread %x\n",
+ (unsigned int)threadid);*/
+ m_stack = new DebugStack(threadid);
+ g_debug_stacks.insert(threadid, m_stack);
+ }
+
+ if(m_stack->stack_i >= DEBUG_STACK_SIZE)
+ {
+ m_overflowed = true;
+ }
+ else
+ {
+ m_overflowed = false;
+
+ snprintf(m_stack->stack[m_stack->stack_i],
+ DEBUG_STACK_TEXT_SIZE, "%s", text);
+ m_stack->stack_i++;
+ if(m_stack->stack_i > m_stack->stack_max_i)
+ m_stack->stack_max_i = m_stack->stack_i;
+ }
+}
+
+DebugStacker::~DebugStacker()
+{
+ JMutexAutoLock lock(g_debug_stacks_mutex);
+
+ if(m_overflowed == true)
+ return;
+
+ m_stack->stack_i--;
+
+ if(m_stack->stack_i == 0)
+ {
+ threadid_t threadid = m_stack->threadid;
+ /*DEBUGPRINT("Deleting debug stack for thread %x\n",
+ (unsigned int)threadid);*/
+ delete m_stack;
+ g_debug_stacks.remove(threadid);
+ }
+}
+
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 000000000..014456c0a
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,176 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+/*
+ Debug stack and assertion
+*/
+
+#ifndef DEBUG_HEADER
+#define DEBUG_HEADER
+
+#include <stdio.h>
+#include <jmutex.h>
+#include <jmutexautolock.h>
+#include <iostream>
+#include "common_irrlicht.h"
+
+/*
+ Compatibility stuff
+*/
+
+#if (defined(WIN32) || defined(_WIN32_WCE))
+typedef DWORD threadid_t;
+#define __NORETURN __declspec(noreturn)
+#define __FUNCTION_NAME __FUNCTION__
+#else
+typedef pthread_t threadid_t;
+#define __NORETURN __attribute__ ((__noreturn__))
+#define __FUNCTION_NAME __PRETTY_FUNCTION__
+#endif
+
+inline threadid_t get_current_thread_id()
+{
+#if (defined(WIN32) || defined(_WIN32_WCE))
+ return GetCurrentThreadId();
+#else
+ return pthread_self();
+#endif
+}
+
+/*
+ Debug output
+*/
+
+#define DEBUGSTREAM_COUNT 2
+
+extern FILE *g_debugstreams[DEBUGSTREAM_COUNT];
+
+extern void debugstreams_init(bool disable_stderr, const char *filename);
+extern void debugstreams_deinit();
+
+#define DEBUGPRINT(...)\
+{\
+ for(int i=0; i<DEBUGSTREAM_COUNT; i++)\
+ {\
+ if(g_debugstreams[i] != NULL){\
+ fprintf(g_debugstreams[i], __VA_ARGS__);\
+ fflush(g_debugstreams[i]);\
+ }\
+ }\
+}
+
+class Debugbuf : public std::streambuf
+{
+public:
+ Debugbuf(bool disable_stderr)
+ {
+ m_disable_stderr = disable_stderr;
+ }
+
+ int overflow(int c)
+ {
+ for(int i=0; i<DEBUGSTREAM_COUNT; i++)
+ {
+ if(g_debugstreams[i] == stderr && m_disable_stderr)
+ continue;
+ if(g_debugstreams[i] != NULL)
+ fwrite(&c, 1, 1, g_debugstreams[i]);
+ //TODO: Is this slow?
+ fflush(g_debugstreams[i]);
+ }
+
+ return c;
+ }
+ int xsputn(const char *s, int n)
+ {
+ for(int i=0; i<DEBUGSTREAM_COUNT; i++)
+ {
+ if(g_debugstreams[i] == stderr && m_disable_stderr)
+ continue;
+ if(g_debugstreams[i] != NULL)
+ fwrite(s, 1, n, g_debugstreams[i]);
+ //TODO: Is this slow?
+ fflush(g_debugstreams[i]);
+ }
+
+ return n;
+ }
+
+private:
+ bool m_disable_stderr;
+};
+
+// This is used to redirect output to /dev/null
+class Nullstream : public std::ostream {
+public:
+ Nullstream():
+ std::ostream(0)
+ {
+ }
+private:
+};
+
+extern Debugbuf debugbuf;
+extern std::ostream dstream;
+extern std::ostream dstream_no_stderr;
+extern Nullstream dummyout;
+
+/*
+ Assert
+*/
+
+__NORETURN extern void assert_fail(
+ const char *assertion, const char *file,
+ unsigned int line, const char *function);
+
+#define ASSERT(expr)\
+ ((expr)\
+ ? (void)(0)\
+ : assert_fail(#expr, __FILE__, __LINE__, __FUNCTION_NAME))
+
+#define assert(expr) ASSERT(expr)
+
+/*
+ DebugStack
+*/
+
+#define DEBUG_STACK_SIZE 50
+#define DEBUG_STACK_TEXT_SIZE 300
+
+struct DebugStack
+{
+ DebugStack(threadid_t id);
+ void print(FILE *file, bool everything);
+
+ threadid_t threadid;
+ char stack[DEBUG_STACK_SIZE][DEBUG_STACK_TEXT_SIZE];
+ int stack_i; // Points to the lowest empty position
+ int stack_max_i; // Highest i that was seen
+};
+
+extern core::map<threadid_t, DebugStack*> g_debug_stacks;
+extern JMutex g_debug_stacks_mutex;
+
+extern void debug_stacks_init();
+extern void debug_stacks_print();
+
+class DebugStacker
+{
+public:
+ DebugStacker(const char *text);
+ ~DebugStacker();
+
+private:
+ DebugStack *m_stack;
+ bool m_overflowed;
+};
+
+#define DSTACK(...)\
+ char __buf[DEBUG_STACK_TEXT_SIZE];\
+ snprintf(__buf,\
+ DEBUG_STACK_TEXT_SIZE, __VA_ARGS__);\
+ DebugStacker __debug_stacker(__buf);
+
+#endif
+
diff --git a/src/environment.cpp b/src/environment.cpp
new file mode 100644
index 000000000..412bb33e8
--- /dev/null
+++ b/src/environment.cpp
@@ -0,0 +1,206 @@
+#include "environment.h"
+#include "main.h" // g_device for timing debug
+
+Environment::Environment(Map *map, std::ostream &dout):
+ m_dout(dout)
+{
+ m_map = map;
+}
+
+Environment::~Environment()
+{
+ // Deallocate players
+ for(core::list<Player*>::Iterator i = m_players.begin();
+ i != m_players.end(); i++)
+ {
+ delete (*i);
+ }
+
+ delete m_map;
+}
+
+void Environment::step(float dtime)
+{
+ DSTACK(__FUNCTION_NAME);
+ /*
+ Run Map's timers
+ */
+ //TimeTaker maptimerupdatetimer("m_map->timerUpdate()", g_device);
+ // 0ms
+ m_map->timerUpdate(dtime);
+ //maptimerupdatetimer.stop();
+
+ /*
+ Get the highest speed some player is going
+ */
+ //TimeTaker playerspeed("playerspeed", g_device);
+ // 0ms
+ f32 maximum_player_speed = 0.001; // just some small value
+ for(core::list<Player*>::Iterator i = m_players.begin();
+ i != m_players.end(); i++)
+ {
+ f32 speed = (*i)->getSpeed().getLength();
+ if(speed > maximum_player_speed)
+ maximum_player_speed = speed;
+ }
+ //playerspeed.stop();
+
+ // Maximum time increment (for collision detection etc)
+ // Allow 0.1 blocks per increment
+ // time = distance / speed
+ f32 dtime_max_increment = 0.1*BS / maximum_player_speed;
+ // Maximum time increment is 10ms or lower
+ if(dtime_max_increment > 0.01)
+ dtime_max_increment = 0.01;
+
+ //TimeTaker playerupdate("playerupdate", g_device);
+
+ /*
+ Stuff that has a maximum time increment
+ */
+ // Don't allow overly huge dtime
+ if(dtime > 0.5)
+ dtime = 0.5;
+
+ u32 loopcount = 0;
+ do
+ {
+ loopcount++;
+
+ f32 dtime_part;
+ if(dtime > dtime_max_increment)
+ dtime_part = dtime_max_increment;
+ else
+ dtime_part = dtime;
+ dtime -= dtime_part;
+
+ /*
+ Handle players
+ */
+ for(core::list<Player*>::Iterator i = m_players.begin();
+ i != m_players.end(); i++)
+ {
+ Player *player = *i;
+
+ // Apply gravity to local player
+ if(player->isLocal())
+ {
+ v3f speed = player->getSpeed();
+ speed.Y -= 9.81 * BS * dtime_part * 2;
+ player->setSpeed(speed);
+ }
+
+ /*
+ Move the player.
+ For local player, this also calculates collision detection.
+ */
+ player->move(dtime_part, *m_map);
+
+ /*
+ Add footsteps to grass
+ */
+ //TimeTaker footsteptimer("footstep", g_device);
+ // 0ms
+ v3f playerpos = player->getPosition();
+ // Get node that is at BS/4 under player
+ v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0));
+ try{
+ MapNode n = m_map->getNode(bottompos);
+ if(n.d == MATERIAL_GRASS)
+ {
+ n.d = MATERIAL_GRASS_FOOTSTEPS;
+ m_map->setNode(bottompos, n);
+
+ // Update mesh on client
+ if(m_map->mapType() == MAPTYPE_CLIENT)
+ {
+ v3s16 p_blocks = getNodeBlockPos(bottompos);
+ MapBlock *b = m_map->getBlockNoCreate(p_blocks);
+ b->updateMesh();
+ }
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+ //footsteptimer.stop();
+ }
+ }
+ while(dtime > 0.001);
+
+ //std::cout<<"Looped "<<loopcount<<" times."<<std::endl;
+}
+
+Map & Environment::getMap()
+{
+ return *m_map;
+}
+
+void Environment::addPlayer(Player *player)
+{
+ DSTACK(__FUNCTION_NAME);
+ //Check that only one local player exists and peer_ids are unique
+ assert(player->isLocal() == false || getLocalPlayer() == NULL);
+ assert(getPlayer(player->peer_id) == NULL);
+ m_players.push_back(player);
+}
+
+void Environment::removePlayer(u16 peer_id)
+{
+ DSTACK(__FUNCTION_NAME);
+re_search:
+ for(core::list<Player*>::Iterator i = m_players.begin();
+ i != m_players.end(); i++)
+ {
+ Player *player = *i;
+ if(player->peer_id != peer_id)
+ continue;
+
+ delete player;
+ m_players.erase(i);
+ // See if there is an another one
+ // (shouldn't be, but just to be sure)
+ goto re_search;
+ }
+}
+
+LocalPlayer * Environment::getLocalPlayer()
+{
+ for(core::list<Player*>::Iterator i = m_players.begin();
+ i != m_players.end(); i++)
+ {
+ Player *player = *i;
+ if(player->isLocal())
+ return (LocalPlayer*)player;
+ }
+ return NULL;
+}
+
+Player * Environment::getPlayer(u16 peer_id)
+{
+ for(core::list<Player*>::Iterator i = m_players.begin();
+ i != m_players.end(); i++)
+ {
+ Player *player = *i;
+ if(player->peer_id == peer_id)
+ return player;
+ }
+ return NULL;
+}
+
+core::list<Player*> Environment::getPlayers()
+{
+ return m_players;
+}
+
+void Environment::printPlayers(std::ostream &o)
+{
+ o<<"Players in environment:"<<std::endl;
+ for(core::list<Player*>::Iterator i = m_players.begin();
+ i != m_players.end(); i++)
+ {
+ Player *player = *i;
+ o<<"Player peer_id="<<player->peer_id<<std::endl;
+ }
+}
+
diff --git a/src/environment.h b/src/environment.h
new file mode 100644
index 000000000..141511258
--- /dev/null
+++ b/src/environment.h
@@ -0,0 +1,50 @@
+#ifndef ENVIRONMENT_HEADER
+#define ENVIRONMENT_HEADER
+
+/*
+ This class is the game's environment.
+ It contains:
+ - The map
+ - Players
+ - Other objects
+ - The current time in the game, etc.
+*/
+
+#include <list>
+#include "common_irrlicht.h"
+#include "player.h"
+#include "map.h"
+#include <ostream>
+
+class Environment
+{
+public:
+ // Environment will delete the map passed to the constructor
+ Environment(Map *map, std::ostream &dout);
+ ~Environment();
+ /*
+ This can do anything to the environment, such as removing
+ timed-out players.
+ Also updates Map's timers.
+ */
+ void step(f32 dtime);
+
+ Map & getMap();
+ /*
+ Environment deallocates players after use.
+ */
+ void addPlayer(Player *player);
+ void removePlayer(u16 peer_id);
+ LocalPlayer * getLocalPlayer();
+ Player * getPlayer(u16 peer_id);
+ core::list<Player*> getPlayers();
+ void printPlayers(std::ostream &o);
+private:
+ Map *m_map;
+ core::list<Player*> m_players;
+ // Debug output goes here
+ std::ostream &m_dout;
+};
+
+#endif
+
diff --git a/src/exceptions.h b/src/exceptions.h
new file mode 100644
index 000000000..cbe13f1f3
--- /dev/null
+++ b/src/exceptions.h
@@ -0,0 +1,116 @@
+#ifndef EXCEPTIONS_HEADER
+#define EXCEPTIONS_HEADER
+
+#include <exception>
+
+class BaseException : public std::exception
+{
+public:
+ BaseException(const char *s)
+ {
+ m_s = s;
+ }
+ virtual const char * what() const throw()
+ {
+ return m_s;
+ }
+ const char *m_s;
+};
+
+class AsyncQueuedException : public BaseException
+{
+public:
+ AsyncQueuedException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class NotImplementedException : public BaseException
+{
+public:
+ NotImplementedException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class AlreadyExistsException : public BaseException
+{
+public:
+ AlreadyExistsException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class VersionMismatchException : public BaseException
+{
+public:
+ VersionMismatchException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class FileNotGoodException : public BaseException
+{
+public:
+ FileNotGoodException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class SerializationError : public BaseException
+{
+public:
+ SerializationError(const char *s):
+ BaseException(s)
+ {}
+};
+
+class LoadError : public BaseException
+{
+public:
+ LoadError(const char *s):
+ BaseException(s)
+ {}
+};
+
+class ContainerFullException : public BaseException
+{
+public:
+ ContainerFullException(const char *s):
+ BaseException(s)
+ {}
+};
+
+/*
+ Some "old-style" interrupts:
+*/
+
+class InvalidPositionException : public BaseException
+{
+public:
+ InvalidPositionException():
+ BaseException("Somebody tried to get/set something in a nonexistent position.")
+ {}
+ InvalidPositionException(const char *s):
+ BaseException(s)
+ {}
+};
+
+class TargetInexistentException : public std::exception
+{
+ virtual const char * what() const throw()
+ {
+ return "Somebody tried to refer to something that doesn't exist.";
+ }
+};
+
+class NullPointerException : public std::exception
+{
+ virtual const char * what() const throw()
+ {
+ return "NullPointerException";
+ }
+};
+
+#endif
+
diff --git a/src/filesys.cpp b/src/filesys.cpp
new file mode 100644
index 000000000..001247049
--- /dev/null
+++ b/src/filesys.cpp
@@ -0,0 +1,205 @@
+#include "filesys.h"
+#include <iostream>
+
+namespace fs
+{
+
+#ifdef _WIN32
+
+#define _WIN32_WINNT 0x0501
+#include <Windows.h>
+#include <stdio.h>
+#include <malloc.h>
+#include <tchar.h>
+#include <wchar.h>
+#include <stdio.h>
+
+#define BUFSIZE MAX_PATH
+
+std::vector<DirListNode> GetDirListing(std::string pathstring)
+{
+ std::vector<DirListNode> listing;
+
+ WIN32_FIND_DATA FindFileData;
+ HANDLE hFind = INVALID_HANDLE_VALUE;
+ DWORD dwError;
+ LPTSTR DirSpec;
+ INT retval;
+
+ DirSpec = (LPTSTR) malloc (BUFSIZE);
+
+ if( DirSpec == NULL )
+ {
+ printf( "Insufficient memory available\n" );
+ retval = 1;
+ goto Cleanup;
+ }
+
+ // Check that the input is not larger than allowed.
+ if (pathstring.size() > (BUFSIZE - 2))
+ {
+ _tprintf(TEXT("Input directory is too large.\n"));
+ retval = 3;
+ goto Cleanup;
+ }
+
+ //_tprintf (TEXT("Target directory is %s.\n"), pathstring.c_str());
+
+ sprintf(DirSpec, "%s", (pathstring + "\\*").c_str());
+
+ // Find the first file in the directory.
+ hFind = FindFirstFile(DirSpec, &FindFileData);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ _tprintf (TEXT("Invalid file handle. Error is %u.\n"),
+ GetLastError());
+ retval = (-1);
+ }
+ else
+ {
+ DirListNode node;
+ node.name = FindFileData.cFileName;
+ node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+ listing.push_back(node);
+
+ // List all the other files in the directory.
+ while (FindNextFile(hFind, &FindFileData) != 0)
+ {
+ DirListNode node;
+ node.name = FindFileData.cFileName;
+ node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+ listing.push_back(node);
+ }
+
+ dwError = GetLastError();
+ FindClose(hFind);
+ if (dwError != ERROR_NO_MORE_FILES)
+ {
+ _tprintf (TEXT("FindNextFile error. Error is %u.\n"),
+ dwError);
+ retval = (-1);
+ goto Cleanup;
+ }
+ }
+ retval = 0;
+
+Cleanup:
+ free(DirSpec);
+
+ if(retval != 0) listing.clear();
+
+ //for(unsigned int i=0; i<listing.size(); i++){
+ // std::cout<<listing[i].name<<(listing[i].dir?" (dir)":" (file)")<<std::endl;
+ //}
+
+ return listing;
+}
+
+bool CreateDir(std::string path)
+{
+ bool r = CreateDirectory(path.c_str(), NULL);
+ if(r == true)
+ return true;
+ if(GetLastError() == ERROR_ALREADY_EXISTS)
+ return true;
+ return false;
+}
+
+bool PathExists(std::string path)
+{
+ return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES);
+}
+
+#else
+
+#ifdef linux
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+std::vector<DirListNode> GetDirListing(std::string pathstring)
+{
+ std::vector<DirListNode> listing;
+
+ DIR *dp;
+ struct dirent *dirp;
+ if((dp = opendir(pathstring.c_str())) == NULL) {
+ //std::cout<<"Error("<<errno<<") opening "<<pathstring<<std::endl;
+ return listing;
+ }
+
+ while ((dirp = readdir(dp)) != NULL) {
+ if(dirp->d_name[0]!='.'){
+ DirListNode node;
+ node.name = dirp->d_name;
+ if(dirp->d_type == DT_DIR) node.dir = true;
+ else node.dir = false;
+ listing.push_back(node);
+ }
+ }
+ closedir(dp);
+
+ return listing;
+}
+
+bool CreateDir(std::string path)
+{
+ int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ if(r == 0)
+ {
+ return true;
+ }
+ else
+ {
+ // If already exists, return true
+ if(errno == EEXIST)
+ return true;
+ return false;
+ }
+}
+
+bool PathExists(std::string path)
+{
+ struct stat st;
+ return (stat(path.c_str(),&st) == 0);
+}
+
+#else
+
+#include "boost/filesystem/operations.hpp"
+namespace bfsys = boost::filesystem;
+
+std::vector<DirListNode> GetDirListing(std::string pathstring)
+{
+ std::vector<DirListNode> listing;
+
+ bfsys::path path(pathstring);
+
+ if( !exists( path ) ) return listing;
+
+ bfsys::directory_iterator end_itr; // default construction yields past-the-end
+ for( bfsys::directory_iterator itr( path ); itr != end_itr; ++itr ){
+ DirListNode node;
+ node.name = itr->leaf();
+ node.dir = is_directory(*itr);
+ listing.push_back(node);
+ }
+
+ return listing;
+}
+
+bool CreateDir(std::string path)
+{
+ std::cout<<"CreateDir not implemented in boost"<<std::endl;
+ return false;
+}
+
+#endif
+
+#endif
+
+}
+
diff --git a/src/filesys.h b/src/filesys.h
new file mode 100644
index 000000000..6222dbfda
--- /dev/null
+++ b/src/filesys.h
@@ -0,0 +1,27 @@
+#ifndef FILESYS_HEADER
+#define FILESYS_HEADER
+
+#include <string>
+#include <vector>
+#include "exceptions.h"
+
+namespace fs
+{
+
+struct DirListNode
+{
+ std::string name;
+ bool dir;
+};
+
+std::vector<DirListNode> GetDirListing(std::string path);
+
+// Returns true if already exists
+bool CreateDir(std::string path);
+
+bool PathExists(std::string path);
+
+}//fs
+
+#endif
+
diff --git a/src/heightmap.cpp b/src/heightmap.cpp
new file mode 100644
index 000000000..a4027ee7d
--- /dev/null
+++ b/src/heightmap.cpp
@@ -0,0 +1,872 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "heightmap.h"
+
+/*
+ ValueGenerator
+*/
+
+ValueGenerator* ValueGenerator::deSerialize(std::string line)
+{
+ std::istringstream ss(line);
+ //ss.imbue(std::locale("C"));
+
+ std::string name;
+ std::getline(ss, name, ' ');
+
+ if(name == "constant")
+ {
+ f32 value;
+ ss>>value;
+
+ return new ConstantGenerator(value);
+ }
+ else if(name == "linear")
+ {
+ f32 height;
+ v2f slope;
+
+ ss>>height;
+ ss>>slope.X;
+ ss>>slope.Y;
+
+ return new LinearGenerator(height, slope);
+ }
+ else if(name == "power")
+ {
+ f32 height;
+ v2f slope;
+ f32 power;
+
+ ss>>height;
+ ss>>slope.X;
+ ss>>slope.Y;
+ ss>>power;
+
+ return new PowerGenerator(height, slope, power);
+ }
+ else
+ {
+ throw SerializationError
+ ("Invalid heightmap generator (deSerialize)");
+ }
+}
+
+/*
+ FixedHeightmap
+*/
+
+f32 FixedHeightmap::avgNeighbours(v2s16 p, s16 d)
+{
+ v2s16 dirs[4] = {
+ v2s16(1,0),
+ v2s16(0,1),
+ v2s16(-1,0),
+ v2s16(0,-1)
+ };
+ f32 sum = 0.0;
+ f32 count = 0.0;
+ for(u16 i=0; i<4; i++){
+ v2s16 p2 = p + dirs[i] * d;
+ f32 n = getGroundHeightParent(p2);
+ if(n < GROUNDHEIGHT_VALID_MINVALUE)
+ continue;
+ sum += n;
+ count += 1.0;
+ }
+ assert(count > 0.001);
+ return sum / count;
+}
+
+f32 FixedHeightmap::avgDiagNeighbours(v2s16 p, s16 d)
+{
+ v2s16 dirs[4] = {
+ v2s16(1,1),
+ v2s16(-1,-1),
+ v2s16(-1,1),
+ v2s16(1,-1)
+ };
+ f32 sum = 0.0;
+ f32 count = 0.0;
+ for(u16 i=0; i<4; i++){
+ v2s16 p2 = p + dirs[i] * d;
+ f32 n = getGroundHeightParent(p2);
+ if(n < GROUNDHEIGHT_VALID_MINVALUE)
+ continue;
+ sum += n;
+ count += 1.0;
+ }
+ assert(count > 0.001);
+ return sum / count;
+}
+
+/*
+ Adds a point to transform into a diamond pattern
+ center = Center of the diamond phase (center of a square)
+ a = Side length of the existing square (2, 4, 8, ...)
+
+ Adds the center points of the next squares to next_squares as
+ dummy "true" values.
+*/
+void FixedHeightmap::makeDiamond(
+ v2s16 center,
+ s16 a,
+ f32 randmax,
+ core::map<v2s16, bool> &next_squares)
+{
+ /*dstream<<"makeDiamond(): center="
+ <<"("<<center.X<<","<<center.Y<<")"
+ <<", a="<<a<<", randmax="<<randmax
+ <<", next_squares.size()="<<next_squares.size()
+ <<std::endl;*/
+
+ f32 n = avgDiagNeighbours(center, a/2);
+ // Add (-1.0...1.0) * randmax
+ n += ((float)rand() / (float)(RAND_MAX/2) - 1.0)*randmax;
+ bool worked = setGroundHeightParent(center, n);
+
+ if(a >= 2 && worked)
+ {
+ next_squares[center + a/2*v2s16(-1,0)] = true;
+ next_squares[center + a/2*v2s16(1,0)] = true;
+ next_squares[center + a/2*v2s16(0,-1)] = true;
+ next_squares[center + a/2*v2s16(0,1)] = true;
+ }
+}
+
+/*
+ Adds a point to transform into a square pattern
+ center = The point that is added. The center of a diamond.
+ a = Diameter of the existing diamond. (2, 4, 8, 16, ...)
+
+ Adds center points of the next diamonds to next_diamonds.
+*/
+void FixedHeightmap::makeSquare(
+ v2s16 center,
+ s16 a,
+ f32 randmax,
+ core::map<v2s16, bool> &next_diamonds)
+{
+ /*dstream<<"makeSquare(): center="
+ <<"("<<center.X<<","<<center.Y<<")"
+ <<", a="<<a<<", randmax="<<randmax
+ <<", next_diamonds.size()="<<next_diamonds.size()
+ <<std::endl;*/
+
+ f32 n = avgNeighbours(center, a/2);
+ // Add (-1.0...1.0) * randmax
+ n += ((float)rand() / (float)(RAND_MAX/2) - 1.0)*randmax;
+ bool worked = setGroundHeightParent(center, n);
+
+ if(a >= 4 && worked)
+ {
+ next_diamonds[center + a/4*v2s16(1,1)] = true;
+ next_diamonds[center + a/4*v2s16(-1,1)] = true;
+ next_diamonds[center + a/4*v2s16(-1,-1)] = true;
+ next_diamonds[center + a/4*v2s16(1,-1)] = true;
+ }
+}
+
+void FixedHeightmap::DiamondSquare(f32 randmax, f32 randfactor)
+{
+ u16 a;
+ if(W < H)
+ a = W-1;
+ else
+ a = H-1;
+
+ // Check that a is a power of two
+ if((a & (a-1)) != 0)
+ throw;
+
+ core::map<v2s16, bool> next_diamonds;
+ core::map<v2s16, bool> next_squares;
+
+ next_diamonds[v2s16(a/2, a/2)] = true;
+
+ while(a >= 2)
+ {
+ next_squares.clear();
+
+ for(core::map<v2s16, bool>::Iterator
+ i = next_diamonds.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v2s16 p = i.getNode()->getKey();
+ makeDiamond(p, a, randmax, next_squares);
+ }
+
+ //print();
+
+ next_diamonds.clear();
+
+ for(core::map<v2s16, bool>::Iterator
+ i = next_squares.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v2s16 p = i.getNode()->getKey();
+ makeSquare(p, a, randmax, next_diamonds);
+ }
+
+ //print();
+
+ a /= 2;
+ randmax *= randfactor;
+ }
+}
+
+void FixedHeightmap::generateContinued(f32 randmax, f32 randfactor,
+ f32 *corners)
+{
+ DSTACK(__FUNCTION_NAME);
+ /*dstream<<"FixedHeightmap("<<m_pos_on_master.X
+ <<","<<m_pos_on_master.Y
+ <<")::generateContinued()"<<std::endl;*/
+
+ // Works only with blocksize=2,4,8,16,32,64,...
+ s16 a = m_blocksize;
+
+ // Check that a is a power of two
+ if((a & (a-1)) != 0)
+ throw;
+
+ // Overwrite with GROUNDHEIGHT_NOTFOUND_SETVALUE
+ for(s16 y=0; y<=a; y++){
+ for(s16 x=0; x<=a; x++){
+ v2s16 p(x,y);
+ setGroundHeight(p, GROUNDHEIGHT_NOTFOUND_SETVALUE);
+ }
+ }
+
+ /*
+ Seed borders from master heightmap
+ NOTE: Does this actually have any effect on the output?
+ */
+ struct SeedSpec
+ {
+ v2s16 neighbour_start;
+ v2s16 heightmap_start;
+ v2s16 dir;
+ };
+
+ SeedSpec seeds[4] =
+ {
+ { // Z- edge on X-axis
+ v2s16(0, -1), // neighbour_start
+ v2s16(0, 0), // heightmap_start
+ v2s16(1, 0) // dir
+ },
+ { // Z+ edge on X-axis
+ v2s16(0, m_blocksize),
+ v2s16(0, m_blocksize),
+ v2s16(1, 0)
+ },
+ { // X- edge on Z-axis
+ v2s16(-1, 0),
+ v2s16(0, 0),
+ v2s16(0, 1)
+ },
+ { // X+ edge on Z-axis
+ v2s16(m_blocksize, 0),
+ v2s16(m_blocksize, 0),
+ v2s16(0, 1)
+ },
+ };
+
+ for(s16 i=0; i<4; i++){
+ v2s16 npos = seeds[i].neighbour_start + m_pos_on_master * m_blocksize;
+ v2s16 hpos = seeds[i].heightmap_start;
+ for(s16 s=0; s<m_blocksize+1; s++){
+ f32 h = m_master->getGroundHeight(npos, false);
+ //dstream<<"h="<<h<<std::endl;
+ if(h < GROUNDHEIGHT_VALID_MINVALUE)
+ continue;
+ //break;
+ setGroundHeight(hpos, h);
+ hpos += seeds[i].dir;
+ npos += seeds[i].dir;
+ }
+ }
+
+ /*dstream<<"borders seeded:"<<std::endl;
+ print();*/
+
+ /*
+ Fill with corners[] (if not already set)
+ */
+ v2s16 dirs[4] = {
+ v2s16(0,0),
+ v2s16(1,0),
+ v2s16(1,1),
+ v2s16(0,1),
+ };
+ for(u16 i=0; i<4; i++){
+ v2s16 npos = dirs[i] * a;
+ // Don't replace already seeded corners
+ f32 h = getGroundHeight(npos);
+ if(h > GROUNDHEIGHT_VALID_MINVALUE)
+ continue;
+ setGroundHeight(dirs[i] * a, corners[i]);
+ }
+
+ /*dstream<<"corners filled:"<<std::endl;
+ print();*/
+
+ DiamondSquare(randmax, randfactor);
+}
+
+u32 FixedHeightmap::serializedLength(u8 version, u16 blocksize)
+{
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: FixedHeightmap format not supported");
+
+ // Any version
+ {
+ /*// [0] s32 blocksize
+ // [4] v2s16 pos_on_master
+ // [8] s32 data[W*H] (W=H=blocksize+1)
+ return 4 + 4 + (blocksize+1)*(blocksize+1)*4;*/
+
+ // [8] s32 data[W*H] (W=H=blocksize+1)
+ return (blocksize+1)*(blocksize+1)*4;
+ }
+}
+
+u32 FixedHeightmap::serializedLength(u8 version)
+{
+ return serializedLength(version, m_blocksize);
+}
+
+void FixedHeightmap::serialize(u8 *dest, u8 version)
+{
+ //dstream<<"FixedHeightmap::serialize"<<std::endl;
+
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: FixedHeightmap format not supported");
+
+ // Any version
+ {
+ /*writeU32(&dest[0], m_blocksize);
+ writeV2S16(&dest[4], m_pos_on_master);
+ u32 nodecount = W*H;
+ for(u32 i=0; i<nodecount; i++)
+ {
+ writeS32(&dest[8+i*4], (s32)(m_data[i]*1000.0));
+ }*/
+
+ u32 nodecount = W*H;
+ for(u32 i=0; i<nodecount; i++)
+ {
+ writeS32(&dest[i*4], (s32)(m_data[i]*1000.0));
+ }
+ }
+}
+
+void FixedHeightmap::deSerialize(u8 *source, u8 version)
+{
+ /*dstream<<"FixedHeightmap::deSerialize m_blocksize="
+ <<m_blocksize<<std::endl;*/
+
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: FixedHeightmap format not supported");
+
+ // Any version
+ {
+ u32 nodecount = (m_blocksize+1)*(m_blocksize+1);
+ for(u32 i=0; i<nodecount; i++)
+ {
+ m_data[i] = ((f32)readS32(&source[i*4]))/1000.0;
+ }
+
+ /*printf("source[0,1,2,3]=%x,%x,%x,%x\n",
+ (int)source[0]&0xff,
+ (int)source[1]&0xff,
+ (int)source[2]&0xff,
+ (int)source[3]&0xff);
+
+ dstream<<"m_data[0]="<<m_data[0]<<", "
+ <<"readS32(&source[0])="<<readS32(&source[0])
+ <<std::endl;
+ dstream<<"m_data[4*4]="<<m_data[4*4]<<", "
+ <<"readS32(&source[4*4])="<<readS32(&source[4*4])
+ <<std::endl;*/
+ }
+}
+
+
+void setcolor(f32 h, f32 rangemin, f32 rangemax)
+{
+#ifndef _WIN32
+ const char *colors[] =
+ {
+ "\x1b[40m",
+ "\x1b[44m",
+ "\x1b[46m",
+ "\x1b[42m",
+ "\x1b[43m",
+ "\x1b[41m",
+ };
+ u16 colorcount = sizeof(colors)/sizeof(colors[0]);
+ f32 scaled = (h - rangemin) / (rangemax - rangemin);
+ u8 color = scaled * colorcount;
+ if(color > colorcount-1)
+ color = colorcount-1;
+ /*printf("rangemin=%f, rangemax=%f, h=%f -> color=%i\n",
+ rangemin,
+ rangemax,
+ h,
+ color);*/
+ printf("%s", colors[color]);
+ //printf("\x1b[31;40m");
+ //printf("\x1b[44;1m");
+#endif
+}
+void resetcolor()
+{
+#ifndef _WIN32
+ printf("\x1b[0m");
+#endif
+}
+
+/*
+ UnlimitedHeightmap
+*/
+
+void UnlimitedHeightmap::print()
+{
+ s16 minx = 10000;
+ s16 miny = 10000;
+ s16 maxx = -10000;
+ s16 maxy = -10000;
+ core::map<v2s16, FixedHeightmap*>::Iterator i;
+ i = m_heightmaps.getIterator();
+ if(i.atEnd()){
+ printf("UnlimitedHeightmap::print(): empty.\n");
+ return;
+ }
+ for(; i.atEnd() == false; i++)
+ {
+ v2s16 p = i.getNode()->getValue()->getPosOnMaster();
+ if(p.X < minx) minx = p.X;
+ if(p.Y < miny) miny = p.Y;
+ if(p.X > maxx) maxx = p.X;
+ if(p.Y > maxy) maxy = p.Y;
+ }
+ minx = minx * m_blocksize;
+ miny = miny * m_blocksize;
+ maxx = (maxx+1) * m_blocksize;
+ maxy = (maxy+1) * m_blocksize;
+ printf("UnlimitedHeightmap::print(): from (%i,%i) to (%i,%i)\n",
+ minx, miny, maxx, maxy);
+
+ // Calculate range
+ f32 rangemin = 1e10;
+ f32 rangemax = -1e10;
+ for(s32 y=miny; y<=maxy; y++){
+ for(s32 x=minx; x<=maxx; x++){
+ f32 h = getGroundHeight(v2s16(x,y), false);
+ if(h < GROUNDHEIGHT_VALID_MINVALUE)
+ continue;
+ if(h < rangemin)
+ rangemin = h;
+ if(h > rangemax)
+ rangemax = h;
+ }
+ }
+
+ printf(" ");
+ for(s32 x=minx; x<=maxx; x++){
+ printf("% .3d ", x);
+ }
+ printf("\n");
+
+ for(s32 y=miny; y<=maxy; y++){
+ printf("% .3d ", y);
+ for(s32 x=minx; x<=maxx; x++){
+ f32 n = getGroundHeight(v2s16(x,y), false);
+ if(n < GROUNDHEIGHT_VALID_MINVALUE)
+ printf(" - ");
+ else
+ {
+ setcolor(n, rangemin, rangemax);
+ printf("% -5.1f", getGroundHeight(v2s16(x,y), false));
+ resetcolor();
+ }
+ }
+ printf("\n");
+ }
+}
+
+FixedHeightmap * UnlimitedHeightmap::getHeightmap(v2s16 p_from, bool generate)
+{
+ DSTACK("UnlimitedHeightmap::getHeightmap()");
+ /*
+ We want to check that all neighbours of the wanted heightmap
+ exist.
+ This is because generating the neighboring heightmaps will
+ modify the current one.
+ */
+
+ if(generate)
+ {
+ // Go through all neighbors (corners also) and the current one
+ // and generate every one of them.
+ for(s16 x=p_from.X-1; x<=p_from.X+1; x++)
+ for(s16 y=p_from.Y-1; y<=p_from.Y+1; y++)
+ {
+ v2s16 p(x,y);
+
+ // Check if exists
+ core::map<v2s16, FixedHeightmap*>::Node *n = m_heightmaps.find(p);
+ if(n != NULL)
+ continue;
+
+ // Doesn't exist
+ // Generate it
+
+ FixedHeightmap *heightmap = new FixedHeightmap(this, p, m_blocksize);
+
+ m_heightmaps.insert(p, heightmap);
+
+ f32 corners[4] = {
+ m_base_generator->getValue(p+v2s16(0,0)),
+ m_base_generator->getValue(p+v2s16(1,0)),
+ m_base_generator->getValue(p+v2s16(1,1)),
+ m_base_generator->getValue(p+v2s16(0,1)),
+ };
+
+ f32 randmax = m_randmax_generator->getValue(p);
+ f32 randfactor = m_randfactor_generator->getValue(p);
+
+ heightmap->generateContinued(randmax, randfactor, corners);
+ }
+ }
+
+ core::map<v2s16, FixedHeightmap*>::Node *n = m_heightmaps.find(p_from);
+
+ if(n != NULL)
+ {
+ return n->getValue();
+ }
+ else
+ {
+ throw InvalidPositionException
+ ("Something went really wrong in UnlimitedHeightmap::getHeightmap");
+ }
+}
+
+f32 UnlimitedHeightmap::getGroundHeight(v2s16 p, bool generate)
+{
+ v2s16 heightmappos = getNodeHeightmapPos(p);
+ v2s16 relpos = p - heightmappos*m_blocksize;
+ try{
+ FixedHeightmap * href = getHeightmap(heightmappos, generate);
+ f32 h = href->getGroundHeight(relpos);
+ if(h > GROUNDHEIGHT_VALID_MINVALUE)
+ return h;
+ }
+ catch(InvalidPositionException){}
+ /*
+ If on border or in the (0,0) corner, try to get from
+ overlapping heightmaps
+ */
+ if(relpos.X == 0){
+ try{
+ FixedHeightmap * href = getHeightmap(
+ heightmappos-v2s16(1,0), false);
+ f32 h = href->getGroundHeight(v2s16(m_blocksize, relpos.Y));
+ if(h > GROUNDHEIGHT_VALID_MINVALUE)
+ return h;
+ }
+ catch(InvalidPositionException){}
+ }
+ if(relpos.Y == 0){
+ try{
+ FixedHeightmap * href = getHeightmap(
+ heightmappos-v2s16(0,1), false);
+ f32 h = href->getGroundHeight(v2s16(relpos.X, m_blocksize));
+ if(h > GROUNDHEIGHT_VALID_MINVALUE)
+ return h;
+ }
+ catch(InvalidPositionException){}
+ }
+ if(relpos.X == 0 && relpos.Y == 0){
+ try{
+ FixedHeightmap * href = getHeightmap(
+ heightmappos-v2s16(1,1), false);
+ f32 h = href->getGroundHeight(v2s16(m_blocksize, m_blocksize));
+ if(h > GROUNDHEIGHT_VALID_MINVALUE)
+ return h;
+ }
+ catch(InvalidPositionException){}
+ }
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+}
+
+void UnlimitedHeightmap::setGroundHeight(v2s16 p, f32 y, bool generate)
+{
+ bool was_set = false;
+
+ v2s16 heightmappos = getNodeHeightmapPos(p);
+ v2s16 relpos = p - heightmappos*m_blocksize;
+ /*dstream<<"UnlimitedHeightmap::setGroundHeight(("
+ <<p.X<<","<<p.Y<<"), "<<y<<"): "
+ <<"heightmappos=("<<heightmappos.X<<","
+ <<heightmappos.Y<<") relpos=("
+ <<relpos.X<<","<<relpos.Y<<")"
+ <<std::endl;*/
+ try{
+ FixedHeightmap * href = getHeightmap(heightmappos, generate);
+ href->setGroundHeight(relpos, y);
+ was_set = true;
+ }catch(InvalidPositionException){}
+ // Update in neighbour heightmap if it's at border
+ if(relpos.X == 0){
+ try{
+ FixedHeightmap * href = getHeightmap(
+ heightmappos-v2s16(1,0), generate);
+ href->setGroundHeight(v2s16(m_blocksize, relpos.Y), y);
+ was_set = true;
+ }catch(InvalidPositionException){}
+ }
+ if(relpos.Y == 0){
+ try{
+ FixedHeightmap * href = getHeightmap(
+ heightmappos-v2s16(0,1), generate);
+ href->setGroundHeight(v2s16(relpos.X, m_blocksize), y);
+ was_set = true;
+ }catch(InvalidPositionException){}
+ }
+ if(relpos.X == 0 && relpos.Y == 0){
+ try{
+ FixedHeightmap * href = getHeightmap(
+ heightmappos-v2s16(1,1), generate);
+ href->setGroundHeight(v2s16(m_blocksize, m_blocksize), y);
+ was_set = true;
+ }catch(InvalidPositionException){}
+ }
+
+ if(was_set == false)
+ {
+ throw InvalidPositionException
+ ("UnlimitedHeightmap failed to set height");
+ }
+}
+
+
+void UnlimitedHeightmap::serialize(std::ostream &os, u8 version)
+{
+ //dstream<<"UnlimitedHeightmap::serialize()"<<std::endl;
+
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException
+ ("ERROR: UnlimitedHeightmap format not supported");
+
+ if(version <= 7)
+ {
+ /*if(m_base_generator->getId() != VALUE_GENERATOR_ID_CONSTANT
+ || m_randmax_generator->getId() != VALUE_GENERATOR_ID_CONSTANT
+ || m_randfactor_generator->getId() != VALUE_GENERATOR_ID_CONSTANT)*/
+ if(std::string(m_base_generator->getName()) != "constant"
+ || std::string(m_randmax_generator->getName()) != "constant"
+ || std::string(m_randfactor_generator->getName()) != "constant")
+ {
+ throw SerializationError
+ ("Cannot write UnlimitedHeightmap in old version: "
+ "Generators are not ConstantGenerators.");
+ }
+
+ f32 basevalue = ((ConstantGenerator*)m_base_generator)->m_value;
+ f32 randmax = ((ConstantGenerator*)m_randmax_generator)->m_value;
+ f32 randfactor = ((ConstantGenerator*)m_randfactor_generator)->m_value;
+
+ // Write version
+ os.write((char*)&version, 1);
+
+ /*
+ [0] u16 blocksize
+ [2] s32 randmax*1000
+ [6] s32 randfactor*1000
+ [10] s32 basevalue*1000
+ [14] u32 heightmap_count
+ [18] X * (v2s16 pos + heightmap)
+ */
+ u32 heightmap_size =
+ FixedHeightmap::serializedLength(version, m_blocksize);
+ u32 heightmap_count = m_heightmaps.size();
+
+ //dstream<<"heightmap_size="<<heightmap_size<<std::endl;
+
+ u32 datasize = 2+4+4+4+4+heightmap_count*(4+heightmap_size);
+ SharedBuffer<u8> data(datasize);
+
+ writeU16(&data[0], m_blocksize);
+ writeU32(&data[2], (s32)(randmax*1000.0));
+ writeU32(&data[6], (s32)(randfactor*1000.0));
+ writeU32(&data[10], (s32)(basevalue*1000.0));
+ writeU32(&data[14], heightmap_count);
+
+ core::map<v2s16, FixedHeightmap*>::Iterator j;
+ j = m_heightmaps.getIterator();
+ u32 i=0;
+ for(; j.atEnd() == false; j++)
+ {
+ FixedHeightmap *hm = j.getNode()->getValue();
+ v2s16 pos = j.getNode()->getKey();
+ writeV2S16(&data[18+i*(4+heightmap_size)], pos);
+ hm->serialize(&data[18+i*(4+heightmap_size)+4], version);
+ i++;
+ }
+
+ os.write((char*)*data, data.getSize());
+ }
+ else
+ {
+ // Write version
+ os.write((char*)&version, 1);
+
+ u8 buf[4];
+
+ writeU16(buf, m_blocksize);
+ os.write((char*)buf, 2);
+
+ /*m_randmax_generator->serialize(os, version);
+ m_randfactor_generator->serialize(os, version);
+ m_base_generator->serialize(os, version);*/
+ m_randmax_generator->serialize(os);
+ m_randfactor_generator->serialize(os);
+ m_base_generator->serialize(os);
+
+ u32 heightmap_count = m_heightmaps.size();
+ writeU32(buf, heightmap_count);
+ os.write((char*)buf, 4);
+
+ u32 heightmap_size =
+ FixedHeightmap::serializedLength(version, m_blocksize);
+
+ SharedBuffer<u8> hmdata(heightmap_size);
+
+ core::map<v2s16, FixedHeightmap*>::Iterator j;
+ j = m_heightmaps.getIterator();
+ u32 i=0;
+ for(; j.atEnd() == false; j++)
+ {
+ v2s16 pos = j.getNode()->getKey();
+ writeV2S16(buf, pos);
+ os.write((char*)buf, 4);
+
+ FixedHeightmap *hm = j.getNode()->getValue();
+ hm->serialize(*hmdata, version);
+ os.write((char*)*hmdata, hmdata.getSize());
+
+ i++;
+ }
+ }
+}
+
+UnlimitedHeightmap * UnlimitedHeightmap::deSerialize(std::istream &is)
+{
+ u8 version;
+ is.read((char*)&version, 1);
+
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: UnlimitedHeightmap format not supported");
+
+ if(version <= 7)
+ {
+ /*
+ [0] u16 blocksize
+ [2] s32 randmax*1000
+ [6] s32 randfactor*1000
+ [10] s32 basevalue*1000
+ [14] u32 heightmap_count
+ [18] X * (v2s16 pos + heightmap)
+ */
+ SharedBuffer<u8> data(18);
+ is.read((char*)*data, 18);
+ if(is.gcount() != 18)
+ throw SerializationError
+ ("UnlimitedHeightmap::deSerialize: no enough input data");
+ s16 blocksize = readU16(&data[0]);
+ f32 randmax = (f32)readU32(&data[2]) / 1000.0;
+ f32 randfactor = (f32)readU32(&data[6]) / 1000.0;
+ f32 basevalue = (f32)readU32(&data[10]) / 1000.0;
+ u32 heightmap_count = readU32(&data[14]);
+
+ /*dstream<<"UnlimitedHeightmap::deSerialize():"
+ <<" blocksize="<<blocksize
+ <<" heightmap_count="<<heightmap_count
+ <<std::endl;*/
+
+ u32 heightmap_size =
+ FixedHeightmap::serializedLength(version, blocksize);
+
+ //dstream<<"heightmap_size="<<heightmap_size<<std::endl;
+
+ ValueGenerator *maxgen = new ConstantGenerator(randmax);
+ ValueGenerator *factorgen = new ConstantGenerator(randfactor);
+ ValueGenerator *basegen = new ConstantGenerator(basevalue);
+
+ UnlimitedHeightmap *hm = new UnlimitedHeightmap
+ (blocksize, maxgen, factorgen, basegen);
+
+ for(u32 i=0; i<heightmap_count; i++)
+ {
+ //dstream<<"i="<<i<<std::endl;
+ SharedBuffer<u8> data(4+heightmap_size);
+ is.read((char*)*data, 4+heightmap_size);
+ if(is.gcount() != (s32)(4+heightmap_size)){
+ delete hm;
+ throw SerializationError
+ ("UnlimitedHeightmap::deSerialize: no enough input data");
+ }
+ v2s16 pos = readV2S16(&data[0]);
+ FixedHeightmap *f = new FixedHeightmap(hm, pos, blocksize);
+ f->deSerialize(&data[4], version);
+ hm->m_heightmaps.insert(pos, f);
+ }
+ return hm;
+ }
+ else
+ {
+ u8 buf[4];
+
+ is.read((char*)buf, 2);
+ s16 blocksize = readU16(buf);
+
+ ValueGenerator *maxgen = ValueGenerator::deSerialize(is);
+ ValueGenerator *factorgen = ValueGenerator::deSerialize(is);
+ ValueGenerator *basegen = ValueGenerator::deSerialize(is);
+
+ is.read((char*)buf, 4);
+ u32 heightmap_count = readU32(buf);
+
+ u32 heightmap_size =
+ FixedHeightmap::serializedLength(version, blocksize);
+
+ UnlimitedHeightmap *hm = new UnlimitedHeightmap
+ (blocksize, maxgen, factorgen, basegen);
+
+ for(u32 i=0; i<heightmap_count; i++)
+ {
+ is.read((char*)buf, 4);
+ v2s16 pos = readV2S16(buf);
+
+ SharedBuffer<u8> data(heightmap_size);
+ is.read((char*)*data, heightmap_size);
+ if(is.gcount() != (s32)(heightmap_size)){
+ delete hm;
+ throw SerializationError
+ ("UnlimitedHeightmap::deSerialize: no enough input data");
+ }
+ FixedHeightmap *f = new FixedHeightmap(hm, pos, blocksize);
+ f->deSerialize(*data, version);
+ hm->m_heightmaps.insert(pos, f);
+ }
+ return hm;
+ }
+}
+
+
diff --git a/src/heightmap.h b/src/heightmap.h
new file mode 100644
index 000000000..a5da092aa
--- /dev/null
+++ b/src/heightmap.h
@@ -0,0 +1,556 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef HEIGHTMAP_HEADER
+#define HEIGHTMAP_HEADER
+
+#include <iostream>
+#include <time.h>
+#include <sstream>
+
+#include "debug.h"
+#include "common_irrlicht.h"
+#include "exceptions.h"
+#include "utility.h"
+#include "serialization.h"
+
+#define GROUNDHEIGHT_NOTFOUND_SETVALUE (-10e6)
+#define GROUNDHEIGHT_VALID_MINVALUE ( -9e6)
+
+class Heightmappish
+{
+public:
+ virtual f32 getGroundHeight(v2s16 p, bool generate=true) = 0;
+ virtual void setGroundHeight(v2s16 p, f32 y, bool generate=true) = 0;
+
+ v2f32 getSlope(v2s16 p)
+ {
+ f32 y0 = getGroundHeight(p, false);
+
+ v2s16 dirs[] = {
+ v2s16(1,0),
+ v2s16(0,1),
+ };
+
+ v2f32 fdirs[] = {
+ v2f32(1,0),
+ v2f32(0,1),
+ };
+
+ v2f32 slopevector(0.0, 0.0);
+
+ for(u16 i=0; i<2; i++){
+ f32 y1 = 0.0;
+ f32 y2 = 0.0;
+ f32 count = 0.0;
+
+ v2s16 p1 = p - dirs[i];
+ y1 = getGroundHeight(p1, false);
+ if(y1 > GROUNDHEIGHT_VALID_MINVALUE){
+ y1 -= y0;
+ count += 1.0;
+ }
+ else
+ y1 = 0;
+
+ v2s16 p2 = p + dirs[i];
+ y2 = getGroundHeight(p2, false);
+ if(y2 > GROUNDHEIGHT_VALID_MINVALUE){
+ y2 -= y0;
+ count += 1.0;
+ }
+ else
+ y2 = 0;
+
+ if(count < 0.001)
+ return v2f32(0.0, 0.0);
+
+ /*
+ If y2 is higher than y1, slope is positive
+ */
+ f32 slope = (y2 - y1)/count;
+
+ slopevector += fdirs[i] * slope;
+ }
+
+ return slopevector;
+ }
+
+};
+
+// TODO: Get rid of this dummy wrapper
+class Heightmap : public Heightmappish /*, public ReferenceCounted*/
+{
+};
+
+class WrapperHeightmap : public Heightmap
+{
+ Heightmappish *m_target;
+public:
+
+ WrapperHeightmap(Heightmappish *target):
+ m_target(target)
+ {
+ if(target == NULL)
+ throw NullPointerException();
+ }
+
+ f32 getGroundHeight(v2s16 p, bool generate=true)
+ {
+ return m_target->getGroundHeight(p, generate);
+ }
+ void setGroundHeight(v2s16 p, f32 y, bool generate=true)
+ {
+ m_target->setGroundHeight(p, y, generate);
+ }
+};
+
+/*
+ Base class that defines a generator that gives out values at
+ positions in 2-dimensional space.
+ Can be given to UnlimitedHeightmap to feed stuff.
+
+ These are always serialized as readable text ending in "\n"
+*/
+class ValueGenerator
+{
+public:
+ ValueGenerator(){}
+ virtual ~ValueGenerator(){}
+
+ static ValueGenerator* deSerialize(std::string line);
+
+ static ValueGenerator* deSerialize(std::istream &is)
+ {
+ std::string line;
+ std::getline(is, line, '\n');
+ return deSerialize(line);
+ }
+
+ void serializeBase(std::ostream &os)
+ {
+ os<<getName()<<" ";
+ }
+
+ // Virtual methods
+ virtual const char * getName() const = 0;
+ virtual f32 getValue(v2s16 p) = 0;
+ virtual void serialize(std::ostream &os) = 0;
+};
+
+class ConstantGenerator : public ValueGenerator
+{
+public:
+ f32 m_value;
+
+ ConstantGenerator(f32 value)
+ {
+ m_value = value;
+ }
+
+ const char * getName() const
+ {
+ return "constant";
+ }
+
+ f32 getValue(v2s16 p)
+ {
+ return m_value;
+ }
+
+ void serialize(std::ostream &os)
+ {
+ serializeBase(os);
+
+ std::ostringstream ss;
+ //ss.imbue(std::locale("C"));
+
+ ss<<m_value<<"\n";
+
+ os<<ss.str();
+ }
+};
+
+class LinearGenerator : public ValueGenerator
+{
+public:
+ f32 m_height;
+ v2f m_slope;
+
+ LinearGenerator(f32 height, v2f slope)
+ {
+ m_height = height;
+ m_slope = slope;
+ }
+
+ const char * getName() const
+ {
+ return "linear";
+ }
+
+ f32 getValue(v2s16 p)
+ {
+ return m_height + m_slope.X * p.X + m_slope.Y * p.Y;
+ }
+
+ void serialize(std::ostream &os)
+ {
+ serializeBase(os);
+
+ std::ostringstream ss;
+ //ss.imbue(std::locale("C"));
+
+ ss<<m_height<<" "<<m_slope.X<<" "<<m_slope.Y<<"\n";
+
+ os<<ss.str();
+ }
+};
+
+class PowerGenerator : public ValueGenerator
+{
+public:
+ f32 m_height;
+ v2f m_slope;
+ f32 m_power;
+
+ PowerGenerator(f32 height, v2f slope, f32 power)
+ {
+ m_height = height;
+ m_slope = slope;
+ m_power = power;
+ }
+
+ const char * getName() const
+ {
+ return "power";
+ }
+
+ f32 getValue(v2s16 p)
+ {
+ return m_height
+ + m_slope.X * pow((f32)p.X, m_power)
+ + m_slope.Y * pow((f32)p.Y, m_power);
+ }
+
+ void serialize(std::ostream &os)
+ {
+ serializeBase(os);
+
+ std::ostringstream ss;
+ //ss.imbue(std::locale("C"));
+
+ ss<<m_height<<" "
+ <<m_slope.X<<" "
+ <<m_slope.Y<<" "
+ <<m_power<<"\n";
+
+ os<<ss.str();
+ }
+};
+
+class FixedHeightmap : public Heightmap
+{
+ // A meta-heightmap on which this heightmap is located
+ // (at m_pos_on_master * m_blocksize)
+ Heightmap * m_master;
+ // Position on master heightmap (in blocks)
+ v2s16 m_pos_on_master;
+ s32 m_blocksize; // This is (W-1) = (H-1)
+ // These are the actual size of the data
+ s32 W;
+ s32 H;
+ f32 *m_data;
+
+public:
+
+ FixedHeightmap(Heightmap * master,
+ v2s16 pos_on_master, s32 blocksize):
+ m_master(master),
+ m_pos_on_master(pos_on_master),
+ m_blocksize(blocksize)
+ {
+ W = m_blocksize+1;
+ H = m_blocksize+1;
+ m_data = NULL;
+ m_data = new f32[(blocksize+1)*(blocksize+1)];
+
+ for(s32 i=0; i<(blocksize+1)*(blocksize+1); i++){
+ m_data[i] = GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ }
+ }
+
+ ~FixedHeightmap()
+ {
+ if(m_data)
+ delete[] m_data;
+ }
+
+ v2s16 getPosOnMaster()
+ {
+ return m_pos_on_master;
+ }
+
+ /*
+ TODO: BorderWrapper class or something to allow defining
+ borders that wrap to an another heightmap. The algorithm
+ should be allowed to edit stuff over the border and on
+ the border in that case, too.
+ This will allow non-square heightmaps, too. (probably)
+ */
+
+ void print()
+ {
+ printf("FixedHeightmap::print(): size is %ix%i\n", W, H);
+ for(s32 y=0; y<H; y++){
+ for(s32 x=0; x<W; x++){
+ /*if(getSeeded(v2s16(x,y)))
+ printf("S");*/
+ f32 n = getGroundHeight(v2s16(x,y));
+ if(n < GROUNDHEIGHT_VALID_MINVALUE)
+ printf(" - ");
+ else
+ printf("% -5.1f ", getGroundHeight(v2s16(x,y)));
+ }
+ printf("\n");
+ }
+ }
+
+ bool overborder(v2s16 p)
+ {
+ return (p.X < 0 || p.X >= W || p.Y < 0 || p.Y >= H);
+ }
+
+ bool atborder(v2s16 p)
+ {
+ if(overborder(p))
+ return false;
+ return (p.X == 0 || p.X == W-1 || p.Y == 0 || p.Y == H-1);
+ }
+
+ void setGroundHeight(v2s16 p, f32 y, bool generate=false)
+ {
+ /*dstream<<"FixedHeightmap::setGroundHeight(("
+ <<p.X<<","<<p.Y
+ <<"), "<<y<<")"<<std::endl;*/
+ if(overborder(p))
+ throw InvalidPositionException();
+ m_data[p.Y*W + p.X] = y;
+ }
+
+ // Returns true on success, false on railure.
+ bool setGroundHeightParent(v2s16 p, f32 y, bool generate=false)
+ {
+ /*// Position on master
+ v2s16 blockpos_nodes = m_pos_on_master * m_blocksize;
+ v2s16 nodepos_master = blockpos_nodes + p;
+ dstream<<"FixedHeightmap::setGroundHeightParent(("
+ <<p.X<<","<<p.Y
+ <<"), "<<y<<"): nodepos_master=("
+ <<nodepos_master.X<<","
+ <<nodepos_master.Y<<")"<<std::endl;
+ m_master->setGroundHeight(nodepos_master, y, false);*/
+
+ // Try to set on master
+ bool master_got_it = false;
+ if(overborder(p) || atborder(p))
+ {
+ try{
+ // Position on master
+ v2s16 blockpos_nodes = m_pos_on_master * m_blocksize;
+ v2s16 nodepos_master = blockpos_nodes + p;
+ m_master->setGroundHeight(nodepos_master, y, false);
+
+ master_got_it = true;
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+ }
+
+ if(overborder(p))
+ return master_got_it;
+
+ setGroundHeight(p, y);
+
+ return true;
+ }
+
+ f32 getGroundHeight(v2s16 p, bool generate=false)
+ {
+ if(overborder(p))
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ return m_data[p.Y*W + p.X];
+ }
+
+ f32 getGroundHeightParent(v2s16 p)
+ {
+ /*v2s16 blockpos_nodes = m_pos_on_master * m_blocksize;
+ return m_master->getGroundHeight(blockpos_nodes + p, false);*/
+
+ if(overborder(p) == false){
+ f32 h = getGroundHeight(p);
+ if(h > GROUNDHEIGHT_VALID_MINVALUE)
+ return h;
+ }
+
+ // Position on master
+ v2s16 blockpos_nodes = m_pos_on_master * m_blocksize;
+ f32 h = m_master->getGroundHeight(blockpos_nodes + p, false);
+ return h;
+ }
+
+ f32 avgNeighbours(v2s16 p, s16 d);
+
+ f32 avgDiagNeighbours(v2s16 p, s16 d);
+
+ void makeDiamond(
+ v2s16 center,
+ s16 a,
+ f32 randmax,
+ core::map<v2s16, bool> &next_squares);
+
+ void makeSquare(
+ v2s16 center,
+ s16 a,
+ f32 randmax,
+ core::map<v2s16, bool> &next_diamonds);
+
+ void DiamondSquare(f32 randmax, f32 randfactor);
+
+ /*
+ corners: [i]=XY: [0]=00, [1]=10, [2]=11, [3]=10
+ */
+ void generateContinued(f32 randmax, f32 randfactor, f32 *corners);
+
+
+ static u32 serializedLength(u8 version, u16 blocksize);
+ u32 serializedLength(u8 version);
+ void serialize(u8 *dest, u8 version);
+ void deSerialize(u8 *source, u8 version);
+ /*static FixedHeightmap * deSerialize(u8 *source, u32 size,
+ u32 &usedsize, Heightmap *master, u8 version);*/
+};
+
+class OneChildHeightmap : public Heightmap
+{
+ s16 m_blocksize;
+
+public:
+
+ FixedHeightmap m_child;
+
+ OneChildHeightmap(s16 blocksize):
+ m_blocksize(blocksize),
+ m_child(this, v2s16(0,0), blocksize)
+ {
+ }
+
+ f32 getGroundHeight(v2s16 p, bool generate=true)
+ {
+ if(p.X < 0 || p.X > m_blocksize
+ || p.Y < 0 || p.Y > m_blocksize)
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ return m_child.getGroundHeight(p);
+ }
+ void setGroundHeight(v2s16 p, f32 y, bool generate=true)
+ {
+ //dstream<<"OneChildHeightmap::setGroundHeight()"<<std::endl;
+ if(p.X < 0 || p.X > m_blocksize
+ || p.Y < 0 || p.Y > m_blocksize)
+ throw InvalidPositionException();
+ m_child.setGroundHeight(p, y);
+ }
+};
+
+
+/*
+ This is a dynamic container of an arbitrary number of heightmaps
+ at arbitrary positions.
+
+ It is able to redirect queries to the corresponding heightmaps and
+ it generates new heightmaps on-the-fly according to the relevant
+ parameters.
+
+ It doesn't have a master heightmap because it is meant to be used
+ as such itself.
+
+ Child heightmaps are spaced at m_blocksize distances, and are of
+ size (m_blocksize+1)*(m_blocksize+1)
+
+ This is used as the master heightmap of a Map object.
+*/
+class UnlimitedHeightmap: public Heightmap
+{
+private:
+
+ core::map<v2s16, FixedHeightmap*> m_heightmaps;
+ s16 m_blocksize;
+
+ ValueGenerator *m_randmax_generator;
+ ValueGenerator *m_randfactor_generator;
+ ValueGenerator *m_base_generator;
+
+public:
+
+ UnlimitedHeightmap(
+ s16 blocksize,
+ ValueGenerator *randmax_generator,
+ ValueGenerator *randfactor_generator,
+ ValueGenerator *base_generator
+ ):
+ m_blocksize(blocksize),
+ m_randmax_generator(randmax_generator),
+ m_randfactor_generator(randfactor_generator),
+ m_base_generator(base_generator)
+ {
+ assert(m_randmax_generator != NULL);
+ assert(m_randfactor_generator != NULL);
+ assert(m_base_generator != NULL);
+ }
+
+ ~UnlimitedHeightmap()
+ {
+ core::map<v2s16, FixedHeightmap*>::Iterator i;
+ i = m_heightmaps.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ delete i.getNode()->getValue();
+ }
+
+ delete m_randmax_generator;
+ delete m_randfactor_generator;
+ delete m_base_generator;
+ }
+
+ /*void setParams(f32 randmax, f32 randfactor)
+ {
+ m_randmax = randmax;
+ m_randfactor = randfactor;
+ }*/
+
+ void print();
+
+ v2s16 getNodeHeightmapPos(v2s16 p)
+ {
+ return v2s16(
+ (p.X>=0 ? p.X : p.X-m_blocksize+1) / m_blocksize,
+ (p.Y>=0 ? p.Y : p.Y-m_blocksize+1) / m_blocksize);
+ }
+
+ // Can throw an InvalidPositionException
+ FixedHeightmap * getHeightmap(v2s16 p, bool generate=true);
+
+ f32 getGroundHeight(v2s16 p, bool generate=true);
+ void setGroundHeight(v2s16 p, f32 y, bool generate=true);
+
+ /*static UnlimitedHeightmap * deSerialize(u8 *source, u32 maxsize,
+ u32 &usedsize, u8 version);*/
+
+ //SharedBuffer<u8> serialize(u8 version);
+ void serialize(std::ostream &os, u8 version);
+ static UnlimitedHeightmap * deSerialize(std::istream &istr);
+};
+
+#endif
+
diff --git a/src/inventory.cpp b/src/inventory.cpp
new file mode 100644
index 000000000..4fe21b303
--- /dev/null
+++ b/src/inventory.cpp
@@ -0,0 +1,320 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "inventory.h"
+#include "serialization.h"
+#include "utility.h"
+#include "debug.h"
+#include <sstream>
+#include "main.h"
+
+/*
+ InventoryItem
+*/
+
+InventoryItem::InventoryItem()
+{
+}
+
+InventoryItem::~InventoryItem()
+{
+}
+
+InventoryItem* InventoryItem::deSerialize(std::istream &is)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ //is.imbue(std::locale("C"));
+ // Read name
+ std::string name;
+ std::getline(is, name, ' ');
+
+ if(name == "MaterialItem")
+ {
+ // u16 reads directly as a number (u8 doesn't)
+ u16 material;
+ is>>material;
+ u16 count;
+ is>>count;
+ if(material > 255)
+ throw SerializationError("Too large material number");
+ return new MaterialItem(material, count);
+ }
+ else if(name == "MBOItem")
+ {
+ std::string inventorystring;
+ std::getline(is, inventorystring, '|');
+ return new MapBlockObjectItem(inventorystring);
+ }
+ else
+ {
+ dstream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
+ throw SerializationError("Unknown InventoryItem name");
+ }
+}
+
+/*
+ MapBlockObjectItem
+*/
+
+video::ITexture * MapBlockObjectItem::getImage()
+{
+ if(m_inventorystring.substr(0,3) == "Rat")
+ return g_device->getVideoDriver()->getTexture("../data/rat.png");
+
+ if(m_inventorystring.substr(0,4) == "Sign")
+ return g_device->getVideoDriver()->getTexture("../data/sign.png");
+
+ return NULL;
+}
+std::string MapBlockObjectItem::getText()
+{
+ if(m_inventorystring.substr(0,3) == "Rat")
+ return "";
+
+ if(m_inventorystring.substr(0,4) == "Sign")
+ return "";
+
+ return "obj";
+}
+
+MapBlockObject * MapBlockObjectItem::createObject
+ (v3f pos, f32 player_yaw, f32 player_pitch)
+{
+ std::istringstream is(m_inventorystring);
+ std::string name;
+ std::getline(is, name, ' ');
+
+ if(name == "None")
+ {
+ return NULL;
+ }
+ else if(name == "Sign")
+ {
+ std::string text;
+ std::getline(is, text, '|');
+ SignObject *obj = new SignObject(NULL, -1, pos);
+ obj->setText(text);
+ obj->setYaw(-player_yaw);
+ return obj;
+ }
+ else if(name == "Rat")
+ {
+ RatObject *obj = new RatObject(NULL, -1, pos);
+ return obj;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/*
+ Inventory
+*/
+
+Inventory::Inventory(u32 size)
+{
+ m_size = size;
+ clearItems();
+}
+
+Inventory::~Inventory()
+{
+ for(u32 i=0; i<m_items.size(); i++)
+ {
+ delete m_items[i];
+ }
+}
+
+void Inventory::clearItems()
+{
+ m_items.clear();
+ for(u32 i=0; i<m_size; i++)
+ {
+ m_items.push_back(NULL);
+ }
+}
+
+void Inventory::serialize(std::ostream &os)
+{
+ //os.imbue(std::locale("C"));
+
+ for(u32 i=0; i<m_items.size(); i++)
+ {
+ InventoryItem *item = m_items[i];
+ if(item != NULL)
+ {
+ os<<"Item ";
+ item->serialize(os);
+ }
+ else
+ {
+ os<<"Empty";
+ }
+ os<<"\n";
+ }
+
+ os<<"end\n";
+}
+
+void Inventory::deSerialize(std::istream &is)
+{
+ //is.imbue(std::locale("C"));
+
+ clearItems();
+ u32 item_i = 0;
+
+ for(;;)
+ {
+ std::string line;
+ std::getline(is, line, '\n');
+
+ std::istringstream iss(line);
+ //iss.imbue(std::locale("C"));
+
+ std::string name;
+ std::getline(iss, name, ' ');
+
+ if(name == "end")
+ {
+ break;
+ }
+ else if(name == "Item")
+ {
+ if(item_i > getSize() - 1)
+ throw SerializationError("too many items");
+ InventoryItem *item = InventoryItem::deSerialize(iss);
+ m_items[item_i++] = item;
+ }
+ else if(name == "Empty")
+ {
+ if(item_i > getSize() - 1)
+ throw SerializationError("too many items");
+ m_items[item_i++] = NULL;
+ }
+ else
+ {
+ throw SerializationError("Unknown inventory identifier");
+ }
+ }
+}
+
+Inventory & Inventory::operator = (Inventory &other)
+{
+ m_size = other.m_size;
+ clearItems();
+ for(u32 i=0; i<other.m_items.size(); i++)
+ {
+ InventoryItem *item = other.m_items[i];
+ if(item != NULL)
+ {
+ m_items[i] = item->clone();
+ }
+ }
+
+ return *this;
+}
+
+u32 Inventory::getSize()
+{
+ return m_items.size();
+}
+
+u32 Inventory::getUsedSlots()
+{
+ u32 num = 0;
+ for(u32 i=0; i<m_items.size(); i++)
+ {
+ InventoryItem *item = m_items[i];
+ if(item != NULL)
+ num++;
+ }
+ return num;
+}
+
+InventoryItem * Inventory::getItem(u32 i)
+{
+ if(i > m_items.size() - 1)
+ return NULL;
+ return m_items[i];
+}
+
+InventoryItem * Inventory::changeItem(u32 i, InventoryItem *newitem)
+{
+ assert(i < m_items.size());
+
+ InventoryItem *olditem = m_items[i];
+ m_items[i] = newitem;
+ return olditem;
+}
+
+void Inventory::deleteItem(u32 i)
+{
+ assert(i < m_items.size());
+ InventoryItem *item = changeItem(i, NULL);
+ if(item)
+ delete item;
+}
+
+bool Inventory::addItem(InventoryItem *newitem)
+{
+ // If it is a MaterialItem, try to find an already existing one
+ // and just increment the counter
+ if(std::string("MaterialItem") == newitem->getName())
+ {
+ u8 material = ((MaterialItem*)newitem)->getMaterial();
+ u8 count = ((MaterialItem*)newitem)->getCount();
+ for(u32 i=0; i<m_items.size(); i++)
+ {
+ InventoryItem *item2 = m_items[i];
+ if(item2 == NULL)
+ continue;
+ if(std::string("MaterialItem") != item2->getName())
+ continue;
+ // Found one. Check if it is of the right material and has
+ // free space
+ MaterialItem *mitem2 = (MaterialItem*)item2;
+ if(mitem2->getMaterial() != material)
+ continue;
+ //TODO: Add all that can be added and add remaining part
+ // to another place
+ if(mitem2->freeSpace() < count)
+ continue;
+ // Add to the counter
+ mitem2->add(count);
+ // Dump the parameter
+ delete newitem;
+ return true;
+ }
+ }
+ // Else find an empty position
+ for(u32 i=0; i<m_items.size(); i++)
+ {
+ InventoryItem *item = m_items[i];
+ if(item != NULL)
+ continue;
+ m_items[i] = newitem;
+ return true;
+ }
+ // Failed
+ return false;
+}
+
+void Inventory::print(std::ostream &o)
+{
+ o<<"Player inventory:"<<std::endl;
+ for(u32 i=0; i<m_items.size(); i++)
+ {
+ InventoryItem *item = m_items[i];
+ if(item != NULL)
+ {
+ o<<i<<": ";
+ item->serialize(o);
+ o<<"\n";
+ }
+ }
+}
+
+//END
diff --git a/src/inventory.h b/src/inventory.h
new file mode 100644
index 000000000..8ea8bd650
--- /dev/null
+++ b/src/inventory.h
@@ -0,0 +1,195 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef INVENTORY_HEADER
+#define INVENTORY_HEADER
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "common_irrlicht.h"
+#include "debug.h"
+#include "mapblockobject.h"
+// For g_materials
+#include "main.h"
+
+class InventoryItem
+{
+public:
+ InventoryItem();
+ virtual ~InventoryItem();
+
+ static InventoryItem* deSerialize(std::istream &is);
+
+ virtual const char* getName() const = 0;
+ // Shall write the name and the parameters
+ virtual void serialize(std::ostream &os) = 0;
+ // Shall make an exact clone of the item
+ virtual InventoryItem* clone() = 0;
+ // Shall return an image to show in the GUI (or NULL)
+ virtual video::ITexture * getImage() { return NULL; }
+ // Shall return a text to show in the GUI
+ virtual std::string getText() { return ""; }
+
+private:
+};
+
+#define MATERIAL_ITEM_MAX_COUNT 99
+
+class MaterialItem : public InventoryItem
+{
+public:
+ MaterialItem(u8 material, u16 count)
+ {
+ m_material = material;
+ m_count = count;
+ }
+ /*
+ Implementation interface
+ */
+ virtual const char* getName() const
+ {
+ return "MaterialItem";
+ }
+ virtual void serialize(std::ostream &os)
+ {
+ //os.imbue(std::locale("C"));
+ os<<getName();
+ os<<" ";
+ os<<(unsigned int)m_material;
+ os<<" ";
+ os<<m_count;
+ }
+ virtual InventoryItem* clone()
+ {
+ return new MaterialItem(m_material, m_count);
+ }
+ video::ITexture * getImage()
+ {
+ return g_materials[m_material].getTexture(0);
+ }
+ std::string getText()
+ {
+ std::ostringstream os;
+ os<<m_count;
+ return os.str();
+ }
+ /*
+ Special methods
+ */
+ u8 getMaterial()
+ {
+ return m_material;
+ }
+ u16 getCount()
+ {
+ return m_count;
+ }
+ u16 freeSpace()
+ {
+ if(m_count > MATERIAL_ITEM_MAX_COUNT)
+ return 0;
+ return MATERIAL_ITEM_MAX_COUNT - m_count;
+ }
+ void add(u16 count)
+ {
+ assert(m_count + count <= MATERIAL_ITEM_MAX_COUNT);
+ m_count += count;
+ }
+ void remove(u16 count)
+ {
+ assert(m_count >= count);
+ m_count -= count;
+ }
+private:
+ u8 m_material;
+ u16 m_count;
+};
+
+class MapBlockObjectItem : public InventoryItem
+{
+public:
+ /*MapBlockObjectItem(MapBlockObject *obj)
+ {
+ m_inventorystring = obj->getInventoryString();
+ }*/
+ MapBlockObjectItem(std::string inventorystring)
+ {
+ m_inventorystring = inventorystring;
+ }
+
+ /*
+ Implementation interface
+ */
+ virtual const char* getName() const
+ {
+ return "MBOItem";
+ }
+ virtual void serialize(std::ostream &os)
+ {
+ for(;;)
+ {
+ size_t t = m_inventorystring.find('|');
+ if(t == std::string::npos)
+ break;
+ m_inventorystring[t] = '?';
+ }
+ os<<getName();
+ os<<" ";
+ os<<m_inventorystring;
+ os<<"|";
+ }
+ virtual InventoryItem* clone()
+ {
+ return new MapBlockObjectItem(m_inventorystring);
+ }
+
+ video::ITexture * getImage();
+ std::string getText();
+
+ /*
+ Special methods
+ */
+ std::string getInventoryString()
+ {
+ return m_inventorystring;
+ }
+
+ MapBlockObject * createObject(v3f pos, f32 player_yaw, f32 player_pitch);
+
+private:
+ std::string m_inventorystring;
+};
+
+//SUGGESTION: Split into ClientInventory and ServerInventory
+class Inventory
+{
+public:
+ Inventory(u32 size);
+ ~Inventory();
+ void clearItems();
+ void serialize(std::ostream &os);
+ void deSerialize(std::istream &is);
+
+ Inventory & operator = (Inventory &other);
+
+ u32 getSize();
+ u32 getUsedSlots();
+
+ InventoryItem * getItem(u32 i);
+ // Returns old item (or NULL). Parameter can be NULL.
+ InventoryItem * changeItem(u32 i, InventoryItem *newitem);
+ void deleteItem(u32 i);
+ // Adds an item to a suitable place. Returns false if failed.
+ bool addItem(InventoryItem *newitem);
+
+ void print(std::ostream &o);
+
+private:
+ core::array<InventoryItem*> m_items;
+ u32 m_size;
+};
+
+#endif
+
diff --git a/src/light.cpp b/src/light.cpp
new file mode 100644
index 000000000..95bb37a8d
--- /dev/null
+++ b/src/light.cpp
@@ -0,0 +1,85 @@
+#include "light.h"
+
+/*
+
+#!/usr/bin/python
+
+from math import *
+from sys import stdout
+
+# We want 0 at light=0 and 255 at light=LIGHT_MAX
+LIGHT_MAX = 15
+
+L = []
+for i in range(1,LIGHT_MAX+1):
+ L.append(int(round(255.0 * 0.69 ** (i-1))))
+ L.append(0)
+
+L.reverse()
+for i in L:
+ stdout.write(str(i)+",\n")
+
+*/
+
+/*
+ The first value should be 0, the last value should be 255.
+*/
+/*u8 light_decode_table[LIGHT_MAX+1] =
+{
+0,
+2,
+3,
+4,
+6,
+9,
+13,
+19,
+28,
+40,
+58,
+84,
+121,
+176,
+255,
+};*/
+
+/*
+#!/usr/bin/python
+
+from math import *
+from sys import stdout
+
+# We want 0 at light=0 and 255 at light=LIGHT_MAX
+LIGHT_MAX = 14
+#FACTOR = 0.69
+FACTOR = 0.75
+
+L = []
+for i in range(1,LIGHT_MAX+1):
+ L.append(int(round(255.0 * FACTOR ** (i-1))))
+L.append(0)
+
+L.reverse()
+for i in L:
+ stdout.write(str(i)+",\n")
+*/
+u8 light_decode_table[LIGHT_MAX+1] =
+{
+0,
+6,
+8,
+11,
+14,
+19,
+26,
+34,
+45,
+61,
+81,
+108,
+143,
+191,
+255,
+};
+
+
diff --git a/src/light.h b/src/light.h
new file mode 100644
index 000000000..b76ac3a5d
--- /dev/null
+++ b/src/light.h
@@ -0,0 +1,54 @@
+#ifndef LIGHT_HEADER
+#define LIGHT_HEADER
+
+#include "common_irrlicht.h"
+
+// This directly sets the range of light
+#define LIGHT_MAX 14
+// This brightness is reserved for sunlight
+#define LIGHT_SUN 15
+
+inline u8 diminish_light(u8 light)
+{
+ if(light == 0)
+ return 0;
+ if(light >= LIGHT_MAX)
+ return LIGHT_MAX - 1;
+
+ return light - 1;
+}
+
+inline u8 diminish_light(u8 light, u8 distance)
+{
+ if(distance >= light)
+ return 0;
+ return light - distance;
+}
+
+inline u8 undiminish_light(u8 light)
+{
+ // We don't know if light should undiminish from this particular 0.
+ // Thus, keep it at 0.
+ if(light == 0)
+ return 0;
+ if(light == LIGHT_MAX)
+ return light;
+
+ return light + 1;
+}
+
+extern u8 light_decode_table[LIGHT_MAX+1];
+
+inline u8 decode_light(u8 light)
+{
+ if(light == LIGHT_SUN)
+ return light_decode_table[LIGHT_MAX];
+
+ if(light > LIGHT_MAX)
+ throw;
+
+ return light_decode_table[light];
+}
+
+#endif
+
diff --git a/src/loadstatus.h b/src/loadstatus.h
new file mode 100644
index 000000000..a5fb6b310
--- /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 000000000..971e0eac3
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,2339 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+
+Minetest
+
+NOTE: VBO cannot be turned on for fast-changing stuff because there
+ is an apparanet memory leak in irrlicht when using it
+
+SUGGESTION: add a second lighting value to the MS nibble of param of
+ air to tell how bright the air node is when there is no sunlight.
+ When day changes to night, these two values can be interpolated.
+TODO: Fix address to be ipv6 compatible
+
+TODO: ESC Pause mode in which the cursor is not kept at the center of window.
+TODO: Stop player if focus of window is taken away (go to pause mode)
+TODO: Optimize and fix makeFastFace or whatever it's called
+ - Face calculation is the source of CPU usage on the client
+SUGGESTION: The client will calculate and send lighting changes and
+ the server will randomly check some of them and kick the client out
+ if it fails to calculate them right.
+ - Actually, it could just start ignoring them and calculate them
+ itself.
+SUGGESTION: Combine MapBlock's face caches to so big pieces that VBO
+ gets used
+ - That is >500 vertices
+
+TODO: Better dungeons
+TODO: There should be very slight natural caves also, starting from
+ only a straightened-up cliff
+
+TODO: Changing of block with mouse wheel or something
+TODO: Menus
+
+TODO: Mobs
+ - Server:
+ - One single map container with ids as keys
+ - Client:
+ - ?
+TODO: - Keep track of the place of the mob in the last few hundreth's
+ of a second - then, if a player hits it, take the value that is
+ avg_rtt/2 before the moment the packet is received.
+TODO: - Scripting
+
+SUGGESTION: Modify client to calculate single changes asynchronously
+
+TODO: Moving players more smoothly. Calculate moving animation from
+ data sent by server.
+
+TODO: There are some lighting-related todos and fixmes in
+ ServerMap::emergeBlock
+
+TODO: Make a dirt node and use it under water
+
+FIXME: When a new sector is generated, it may change the ground level
+ of it's and it's neighbors border that two blocks that are
+ above and below each other and that are generated before and
+ after the sector heightmap generation (order doesn't matter),
+ can have a small gap between each other at the border.
+SUGGESTION: Use same technique for sector heightmaps as what we're
+ using for UnlimitedHeightmap? (getting all neighbors
+ when generating)
+
+TODO: Set server to automatically find a good spawning place in some
+ place where there is water and land.
+ - Map to have a getWalkableNear(p)
+
+TODO: Transfer more blocks in a single packet
+SUGG: A blockdata combiner class, to which blocks are added and at
+ destruction it sends all the stuff in as few packets as possible.
+
+TODO: If player is on ground, mainly fetch ground-level blocks
+TODO: Fetch stuff mainly from the viewing direction
+
+TODO: Expose Connection's seqnums and ACKs to server and client.
+ - This enables saving many packets and making a faster connection
+ - This also enables server to check if client has received the
+ most recent block sent, for example.
+
+SUGG: Add a time value to the param of footstepped grass and check it
+ against a global timer when a block is accessed, to make old
+ steps fade away.
+
+FIXME: There still are *some* tiny glitches in lighting as seen from
+ the client side. The server calculates them right but sometimes
+ they don't get transferred properly.
+ - Server probably checks that a block is not sent, then continues
+ to sending it, then the emerge thread marks it as unsent and then
+ the sender sends the block as it was before emerging?
+TODO: How about adding a "revision" field to MapBlocks?
+
+TODO: More fine-grained control of client's dumping of blocks from
+ memory
+
+TODO: Somehow prioritize the sending of blocks and combine the block
+ send queue lengths
+ - Take two blocks to be sent next from each client and assign
+ a priority value to them
+ - Priority is the same as distance from player
+ - Take the highest priority ones and send them. Send as many as
+ fits in the global send queue maximum length (sum of lengths
+ of client queues)
+TODO: Make the amount of blocks sending to client and the total
+ amount of blocks dynamically limited. Transferring blocks is the
+ main network eater of this system, so it is the one that has
+ to be throttled so that RTTs stay low.
+FIXME: There is a bug that sometimes the EmergeThread bumps to
+ the client's emerge counter being already 0, and also the
+ sending queue size of the client can float to 1 or 2, which
+ stops the map from loading at all.
+ - A quick hack could be applied to ignore the error of
+ being at 0 and timing out old entries
+SUGG: Make client send GOTBLOCKS before updating meshes
+
+TODO: Server to load starting inventory from disk
+
+NOTE: iostream.imbue(std::locale("C")) is very slow
+NOTE: Global locale is now set at initialization
+
+TODO: PLayers to only be hidden when the client quits.
+TODO: - Players to be saved on disk, with inventory
+TODO: Players to be saved as text in map/players/<name>
+
+SUGGESTION: A map editing mode (similar to dedicated server mode)
+
+TODO: Maybe: Create a face calculation queue on the client that is
+ processed in a separate thread
+TODO: Make client's mesh updates to happen in a thread similar to
+ server's EmergeThread.
+ - This is not really needed, mesh update is really fast
+ - Instead, the lighting update can be slow
+ - So, this todo is not really a todo. It is a not-todo.
+SUGG: Make server to send all modified blocks after a node change
+ after all the stuff including lighting have been updated
+
+TODO: Make fetching sector's blocks more efficient when rendering
+ sectors that have very large amounts of blocks (on client)
+
+TODO: Make the video backend selectable
+
+TODO: A timestamp to blocks
+
+TODO: Client side:
+ - The server sends all active objects of the active blocks
+ at constant intervals. They should fit in a few packets.
+ - The client keeps track of what blocks at the moment are
+ having active objects in them.
+ - All blocks going in and out of the active buffer are recorded.
+ - For outgoing blocks, objects are removed from the blocks
+ and from the scene
+ - For incoming blocks, objects are added to the blocks and
+ to the scene.
+
+TODO: Server side:
+ - A "near blocks" buffer, in which some nearby blocks are stored.
+ - For all blocks in the buffer, objects are stepped(). This
+ means they are active.
+ - All blocks going in and out of the buffer are recorded.
+ - For outgoing blocks, a timestamp is written.
+ - For incoming blocks, the time difference is calculated and
+ objects are stepped according to it.
+
+TODO: Add config parameters for server's sending and generating distance
+
+TODO: Make amount of trees and other plants configurable
+ - Save to a metafile
+
+TODO: Copy the text of the last picked sign to inventory in creative
+ mode
+
+TODO: Untie client network operations from framerate
+
+TODO: Make a copy of close-range environment on client for showing
+ on screen, with minimal mutexes to slow the main loop down
+
+TODO: Make a PACKET_COMBINED which contains many subpackets. Utilize
+ it by sending more stuff in a single packet.
+
+Doing now:
+======================================================================
+
+
+======================================================================
+
+*/
+
+/*
+ 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
+
+#ifdef UNITTEST_DISABLE
+ #ifdef _WIN32
+ #pragma message ("Disabling unit tests")
+ #else
+ #warning "Disabling unit tests"
+ #endif
+ // Disable unit tests
+ #define ENABLE_TESTS 0
+#else
+ // Enable unit tests
+ #define ENABLE_TESTS 1
+#endif
+
+#ifdef _MSC_VER
+#pragma comment(lib, "Irrlicht.lib")
+#pragma comment(lib, "jthread.lib")
+// This would get rid of the console window
+//#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
+#endif
+
+#ifdef _WIN32
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #define sleep_ms(x) Sleep(x)
+#else
+ #include <unistd.h>
+ #define sleep_ms(x) usleep(x*1000)
+#endif
+
+#include <iostream>
+#include <fstream>
+#include <time.h>
+#include <jmutexautolock.h>
+#include "common_irrlicht.h"
+#include "debug.h"
+#include "map.h"
+#include "player.h"
+#include "main.h"
+#include "test.h"
+#include "environment.h"
+#include "server.h"
+#include "client.h"
+#include "serialization.h"
+#include "constants.h"
+#include "strfnd.h"
+#include "porting.h"
+#include <locale.h>
+
+IrrlichtDevice *g_device = NULL;
+
+const char *g_material_filenames[MATERIALS_COUNT] =
+{
+ "../data/stone.png",
+ "../data/grass.png",
+ "../data/water.png",
+ "../data/light.png",
+ "../data/tree.png",
+ "../data/leaves.png",
+ "../data/grass_footsteps.png",
+ "../data/mese.png"
+};
+
+video::SMaterial g_materials[MATERIALS_COUNT];
+//video::SMaterial g_mesh_materials[3];
+
+// All range-related stuff below is locked behind this
+JMutex g_range_mutex;
+
+// Blocks are generated in this range from the player
+// This is limited vertically to half by Client::fetchBlocks()
+s16 g_forcedfetch_range_nodes = FORCEDFETCH_RANGE;
+
+// Blocks are viewed in this range from the player
+s16 g_viewing_range_nodes = 60;
+
+// This is updated by the client's fetchBlocks routine
+//s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
+
+// If true, the preceding value has no meaning and all blocks
+// already existing in memory are drawn
+bool g_viewing_range_all = false;
+
+// This is the freetime ratio imposed by the dynamic viewing
+// range changing code.
+// It is controlled by the main loop to the smallest value that
+// inhibits glitches (dtime jitter) in the main loop.
+//float g_freetime_ratio = FREETIME_RATIO_MAX;
+
+
+/*
+ Settings.
+ These are loaded from the config file.
+*/
+
+std::string g_dedicated_server;
+
+// Client stuff
+float g_wanted_fps = FPS_DEFAULT_WANTED;
+float g_fps_max = FPS_DEFAULT_MAX;
+s16 g_viewing_range_nodes_max = 300;
+s16 g_viewing_range_nodes_min = 20;
+std::string g_screenW;
+std::string g_screenH;
+std::string g_host_game;
+std::string g_port;
+std::string g_address;
+std::string g_name;
+bool g_random_input = false;
+float g_client_delete_unused_sectors_timeout = 1200;
+
+// Server stuff
+bool g_creative_mode = false;
+MapgenParams g_mapgen_params;
+
+/*
+ Random stuff
+*/
+
+//u16 g_selected_material = 0;
+u16 g_selected_item = 0;
+
+bool g_esc_pressed = false;
+
+std::wstring g_text_buffer;
+bool g_text_buffer_accepted = false;
+
+// When true, the mouse and keyboard are grabbed
+bool g_game_focused = true;
+
+/*
+ Debug streams
+*/
+
+// Connection
+std::ostream *dout_con_ptr = &dummyout;
+std::ostream *derr_con_ptr = &dstream_no_stderr;
+//std::ostream *dout_con_ptr = &dstream_no_stderr;
+//std::ostream *derr_con_ptr = &dstream_no_stderr;
+//std::ostream *dout_con_ptr = &dstream;
+//std::ostream *derr_con_ptr = &dstream;
+
+// Server
+std::ostream *dout_server_ptr = &dstream;
+std::ostream *derr_server_ptr = &dstream;
+
+// Client
+std::ostream *dout_client_ptr = &dstream;
+std::ostream *derr_client_ptr = &dstream;
+
+/*
+ Config stuff
+*/
+
+// Returns false on EOF
+bool parseConfigObject(std::istream &is)
+{
+ // float g_wanted_fps
+ // s16 g_viewing_range_nodes_max
+
+ if(is.eof())
+ return false;
+
+ std::string line;
+ std::getline(is, line);
+ //dstream<<"got line: \""<<line<<"\""<<std::endl;
+
+ std::string trimmedline = trim(line);
+
+ // Ignore comments
+ if(trimmedline[0] == '#')
+ return true;
+
+ //dstream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
+
+ Strfnd sf(trim(line));
+
+ std::string name = sf.next("=");
+ name = trim(name);
+
+ if(name == "")
+ return true;
+
+ std::string value = sf.next("\n");
+ value = trim(value);
+
+ dstream<<"Config name=\""<<name<<"\" value=\""
+ <<value<<"\""<<std::endl;
+
+ if(name == "dedicated_server")
+ g_dedicated_server = value;
+
+ // Client stuff
+ else if(name == "wanted_fps")
+ {
+ g_wanted_fps = atof(value.c_str());
+ }
+ else if(name == "fps_max")
+ {
+ g_fps_max = atof(value.c_str());
+ }
+ else if(name == "viewing_range_nodes_max")
+ {
+ s32 v = atoi(value.c_str());
+ if(v < 0)
+ v = 0;
+ if(v > 32767)
+ v = 32767;
+ g_viewing_range_nodes_max = v;
+ }
+ else if(name == "viewing_range_nodes_min")
+ {
+ s32 v = atoi(value.c_str());
+ if(v < 0)
+ v = 0;
+ if(v > 32767)
+ v = 32767;
+ g_viewing_range_nodes_min = v;
+ }
+ else if(name=="screenW")
+ g_screenW = value;
+ else if(name=="screenH")
+ g_screenH = value;
+ else if(name == "host_game")
+ g_host_game = value;
+ else if(name == "port")
+ g_port = value;
+ else if(name == "address")
+ g_address = value;
+ else if(name == "name")
+ g_name = value;
+ else if(name == "random_input")
+ g_random_input = is_yes(value);
+ else if(name == "client_delete_unused_sectors_timeout")
+ {
+ std::istringstream vis(value);
+ //vis.imbue(std::locale("C"));
+ vis>>g_client_delete_unused_sectors_timeout;
+ }
+
+ // Server stuff
+ else if(name == "creative_mode")
+ g_creative_mode = is_yes(value);
+ else if(name == "mapgen_heightmap_blocksize")
+ {
+ s32 d = atoi(value.c_str());
+ if(d > 0 && (d & (d-1)) == 0)
+ g_mapgen_params.heightmap_blocksize = d;
+ else
+ dstream<<"Invalid value in config file: \""
+ <<line<<"\""<<std::endl;
+ }
+ else if(name == "mapgen_height_randmax")
+ g_mapgen_params.height_randmax = value;
+ else if(name == "mapgen_height_randfactor")
+ g_mapgen_params.height_randfactor = value;
+ else if(name == "mapgen_height_base")
+ g_mapgen_params.height_base = value;
+ else if(name == "mapgen_plants_amount")
+ g_mapgen_params.plants_amount = value;
+
+ else
+ {
+ dstream<<"Unknown option in config file: \""
+ <<line<<"\""<<std::endl;
+ }
+
+ return true;
+}
+
+// Returns true on success
+bool readConfigFile(const char *filename)
+{
+ std::ifstream is(filename);
+ if(is.good() == false)
+ {
+ dstream<<DTIME<<"Error opening configuration file: "
+ <<filename<<std::endl;
+ return false;
+ }
+
+ dstream<<DTIME<<"Parsing configuration file: "
+ <<filename<<std::endl;
+
+ while(parseConfigObject(is));
+
+ return true;
+}
+
+/*
+ Timestamp stuff
+*/
+
+JMutex g_timestamp_mutex;
+//std::string g_timestamp;
+
+std::string getTimestamp()
+{
+ if(g_timestamp_mutex.IsInitialized()==false)
+ return "";
+ JMutexAutoLock lock(g_timestamp_mutex);
+ //return g_timestamp;
+ time_t t = time(NULL);
+ struct tm *tm = localtime(&t);
+ char cs[20];
+ strftime(cs, 20, "%H:%M:%S", tm);
+ return cs;
+}
+
+class MyEventReceiver : public IEventReceiver
+{
+public:
+ // This is the one method that we have to implement
+ virtual bool OnEvent(const SEvent& event)
+ {
+ // Remember whether each key is down or up
+ if(event.EventType == irr::EET_KEY_INPUT_EVENT)
+ {
+ keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
+
+ if(event.KeyInput.PressedDown)
+ {
+ //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
+ if(g_game_focused == false)
+ {
+ s16 key = event.KeyInput.Key;
+ if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
+ {
+ g_text_buffer_accepted = true;
+ }
+ else if(key == irr::KEY_BACK)
+ {
+ if(g_text_buffer.size() > 0)
+ g_text_buffer = g_text_buffer.substr
+ (0, g_text_buffer.size()-1);
+ }
+ else
+ {
+ wchar_t wc = event.KeyInput.Char;
+ if(wc != 0)
+ g_text_buffer += wc;
+ }
+ }
+
+ if(event.KeyInput.Key == irr::KEY_ESCAPE)
+ {
+ if(g_game_focused == true)
+ {
+ dstream<<DTIME<<"ESC pressed"<<std::endl;
+ g_esc_pressed = true;
+ }
+ }
+
+ // Material selection
+ if(event.KeyInput.Key == irr::KEY_KEY_F)
+ {
+ if(g_game_focused == true)
+ {
+ if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
+ g_selected_item++;
+ else
+ g_selected_item = 0;
+ dstream<<DTIME<<"Selected item: "
+ <<g_selected_item<<std::endl;
+ }
+ }
+
+ // Viewing range selection
+ if(event.KeyInput.Key == irr::KEY_KEY_R
+ && g_game_focused)
+ {
+ JMutexAutoLock lock(g_range_mutex);
+ if(g_viewing_range_all)
+ {
+ g_viewing_range_all = false;
+ dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
+ }
+ else
+ {
+ g_viewing_range_all = true;
+ dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
+ }
+ }
+ }
+ }
+
+ if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
+ {
+ if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
+ {
+ leftclicked = true;
+ }
+ if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
+ {
+ rightclicked = true;
+ }
+ if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
+ {
+ /*dstream<<"event.MouseInput.Wheel="
+ <<event.MouseInput.Wheel<<std::endl;*/
+ if(event.MouseInput.Wheel < 0)
+ {
+ if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
+ g_selected_item++;
+ else
+ g_selected_item = 0;
+ }
+ else if(event.MouseInput.Wheel > 0)
+ {
+ if(g_selected_item > 0)
+ g_selected_item--;
+ else
+ g_selected_item = PLAYER_INVENTORY_SIZE-1;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ // This is used to check whether a key is being held down
+ virtual bool IsKeyDown(EKEY_CODE keyCode) const
+ {
+ return keyIsDown[keyCode];
+ }
+
+ MyEventReceiver()
+ {
+ for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
+ keyIsDown[i] = false;
+ leftclicked = false;
+ rightclicked = false;
+ }
+
+ bool leftclicked;
+ bool rightclicked;
+private:
+ // We use this array to store the current state of each key
+ bool keyIsDown[KEY_KEY_CODES_COUNT];
+ //s32 mouseX;
+ //s32 mouseY;
+};
+
+class InputHandler
+{
+public:
+ InputHandler()
+ {
+ }
+ virtual ~InputHandler()
+ {
+ }
+ virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
+ virtual v2s32 getMousePos() = 0;
+ virtual void setMousePos(s32 x, s32 y) = 0;
+ virtual bool getLeftClicked() = 0;
+ virtual bool getRightClicked() = 0;
+ virtual void resetLeftClicked() = 0;
+ virtual void resetRightClicked() = 0;
+
+ virtual void step(float dtime) {};
+
+ virtual void clear() {};
+};
+
+InputHandler *g_input = NULL;
+
+void focusGame()
+{
+ g_input->clear();
+ g_game_focused = true;
+}
+
+void unFocusGame()
+{
+ g_game_focused = false;
+}
+
+class RealInputHandler : public InputHandler
+{
+public:
+ RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
+ m_device(device),
+ m_receiver(receiver)
+ {
+ }
+ virtual bool isKeyDown(EKEY_CODE keyCode)
+ {
+ return m_receiver->IsKeyDown(keyCode);
+ }
+ virtual v2s32 getMousePos()
+ {
+ return m_device->getCursorControl()->getPosition();
+ }
+ virtual void setMousePos(s32 x, s32 y)
+ {
+ m_device->getCursorControl()->setPosition(x, y);
+ }
+
+ virtual bool getLeftClicked()
+ {
+ if(g_game_focused == false)
+ return false;
+ return m_receiver->leftclicked;
+ }
+ virtual bool getRightClicked()
+ {
+ if(g_game_focused == false)
+ return false;
+ return m_receiver->rightclicked;
+ }
+ virtual void resetLeftClicked()
+ {
+ m_receiver->leftclicked = false;
+ }
+ virtual void resetRightClicked()
+ {
+ m_receiver->rightclicked = false;
+ }
+
+ void clear()
+ {
+ resetRightClicked();
+ resetLeftClicked();
+ }
+private:
+ IrrlichtDevice *m_device;
+ MyEventReceiver *m_receiver;
+};
+
+class RandomInputHandler : public InputHandler
+{
+public:
+ RandomInputHandler()
+ {
+ leftclicked = false;
+ rightclicked = false;
+ for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
+ keydown[i] = false;
+ }
+ virtual bool isKeyDown(EKEY_CODE keyCode)
+ {
+ return keydown[keyCode];
+ }
+ virtual v2s32 getMousePos()
+ {
+ return mousepos;
+ }
+ virtual void setMousePos(s32 x, s32 y)
+ {
+ mousepos = v2s32(x,y);
+ }
+
+ virtual bool getLeftClicked()
+ {
+ return leftclicked;
+ }
+ virtual bool getRightClicked()
+ {
+ return rightclicked;
+ }
+ virtual void resetLeftClicked()
+ {
+ leftclicked = false;
+ }
+ virtual void resetRightClicked()
+ {
+ rightclicked = false;
+ }
+
+ virtual void step(float dtime)
+ {
+ {
+ static float counter1 = 0;
+ counter1 -= dtime;
+ if(counter1 < 0.0)
+ {
+ counter1 = 0.1*Rand(1,10);
+ /*if(g_selected_material < USEFUL_MATERIAL_COUNT-1)
+ g_selected_material++;
+ else
+ g_selected_material = 0;*/
+ if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
+ g_selected_item++;
+ else
+ g_selected_item = 0;
+ }
+ }
+ {
+ static float counter1 = 0;
+ counter1 -= dtime;
+ if(counter1 < 0.0)
+ {
+ counter1 = 0.1*Rand(1, 40);
+ keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
+ }
+ }
+ {
+ static float counter1 = 0;
+ counter1 -= dtime;
+ if(counter1 < 0.0)
+ {
+ counter1 = 0.1*Rand(1, 40);
+ keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
+ }
+ }
+ {
+ static float counter1 = 0;
+ counter1 -= dtime;
+ if(counter1 < 0.0)
+ {
+ counter1 = 0.1*Rand(1, 40);
+ keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
+ }
+ }
+ {
+ static float counter1 = 0;
+ counter1 -= dtime;
+ if(counter1 < 0.0)
+ {
+ counter1 = 0.1*Rand(1, 40);
+ keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
+ }
+ }
+ {
+ static float counter1 = 0;
+ counter1 -= dtime;
+ if(counter1 < 0.0)
+ {
+ counter1 = 0.1*Rand(1, 20);
+ mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
+ }
+ }
+ {
+ static float counter1 = 0;
+ counter1 -= dtime;
+ if(counter1 < 0.0)
+ {
+ counter1 = 0.1*Rand(1, 30);
+ leftclicked = true;
+ }
+ }
+ {
+ static float counter1 = 0;
+ counter1 -= dtime;
+ if(counter1 < 0.0)
+ {
+ counter1 = 0.1*Rand(1, 20);
+ rightclicked = true;
+ }
+ }
+ mousepos += mousespeed;
+ }
+
+ s32 Rand(s32 min, s32 max)
+ {
+ return (rand()%(max-min+1))+min;
+ }
+private:
+ bool keydown[KEY_KEY_CODES_COUNT];
+ v2s32 mousepos;
+ v2s32 mousespeed;
+ bool leftclicked;
+ bool rightclicked;
+};
+
+void updateViewingRange(f32 frametime, Client *client)
+{
+ // Range_all messes up frametime_avg
+ if(g_viewing_range_all == true)
+ return;
+
+ // Initialize to the target value
+ static float frametime_avg = 1.0/g_wanted_fps;
+ frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
+
+ static f32 counter = 0;
+ if(counter > 0){
+ counter -= frametime;
+ return;
+ }
+ //counter = 1.0; //seconds
+ counter = 0.5; //seconds
+
+ //float freetime_ratio = 0.2;
+ //float freetime_ratio = 0.4;
+ float freetime_ratio = FREETIME_RATIO;
+
+ float frametime_wanted = (1.0/(g_wanted_fps/(1.0-freetime_ratio)));
+
+ float fraction = sqrt(frametime_avg / frametime_wanted);
+
+ static bool fraction_is_good = false;
+
+ float fraction_good_threshold = 0.1;
+ float fraction_bad_threshold = 0.25;
+ float fraction_limit;
+ // Use high limit if fraction is good AND the fraction would
+ // lower the range. We want to keep the range fairly high.
+ if(fraction_is_good && fraction > 1.0)
+ fraction_limit = fraction_bad_threshold;
+ else
+ fraction_limit = fraction_good_threshold;
+
+ if(fabs(fraction - 1.0) < fraction_limit)
+ {
+ fraction_is_good = true;
+ return;
+ }
+ else
+ {
+ fraction_is_good = false;
+ }
+
+ //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
+ //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
+ /*dstream<<"fetching="<<client->isFetchingBlocks()
+ <<" faction = "<<fraction<<std::endl;*/
+
+ JMutexAutoLock lock(g_range_mutex);
+
+ s16 n = (float)g_viewing_range_nodes / fraction;
+ if(n < g_viewing_range_nodes_min)
+ n = g_viewing_range_nodes_min;
+ if(n > g_viewing_range_nodes_max)
+ n = g_viewing_range_nodes_max;
+
+ bool can_change = true;
+
+ if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
+ can_change = false;
+
+ if(can_change)
+ g_viewing_range_nodes = n;
+
+ /*dstream<<"g_viewing_range_nodes = "
+ <<g_viewing_range_nodes<<std::endl;*/
+}
+
+class GUIQuickInventory : public IEventReceiver
+{
+public:
+ GUIQuickInventory(
+ gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent,
+ v2s32 pos,
+ s32 itemcount,
+ Inventory *inventory):
+ m_itemcount(itemcount),
+ m_inventory(inventory)
+ {
+ core::rect<s32> imgsize(0,0,48,48);
+ core::rect<s32> textsize(0,0,48,16);
+ v2s32 spacing(0, 64);
+ for(s32 i=0; i<m_itemcount; i++)
+ {
+ m_images.push_back(env->addImage(
+ imgsize + pos + spacing*i
+ ));
+ m_images[i]->setScaleImage(true);
+ m_texts.push_back(env->addStaticText(
+ L"",
+ textsize + pos + spacing*i,
+ false, false
+ ));
+ m_texts[i]->setBackgroundColor(
+ video::SColor(128,0,0,0));
+ m_texts[i]->setTextAlignment(
+ gui::EGUIA_CENTER,
+ gui::EGUIA_UPPERLEFT);
+ }
+ }
+
+ virtual bool OnEvent(const SEvent& event)
+ {
+ return false;
+ }
+
+ void setSelection(s32 i)
+ {
+ m_selection = i;
+ }
+
+ void update()
+ {
+ s32 start = 0;
+
+ start = m_selection - m_itemcount / 2;
+
+ for(s32 i=0; i<m_itemcount; i++)
+ {
+ s32 j = i + start;
+
+ if(j > (s32)m_inventory->getSize() - 1)
+ j -= m_inventory->getSize();
+ if(j < 0)
+ j += m_inventory->getSize();
+
+ InventoryItem *item = m_inventory->getItem(j);
+ // Null items
+ if(item == NULL)
+ {
+ m_images[i]->setImage(NULL);
+
+ wchar_t t[10];
+ if(m_selection == j)
+ swprintf(t, 10, L"<-");
+ else
+ swprintf(t, 10, L"");
+ m_texts[i]->setText(t);
+
+ // The next ifs will segfault with a NULL pointer
+ continue;
+ }
+
+
+ m_images[i]->setImage(item->getImage());
+
+ wchar_t t[10];
+ if(m_selection == j)
+ swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
+ else
+ swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
+ m_texts[i]->setText(t);
+ }
+ }
+
+private:
+ s32 m_itemcount;
+ core::array<gui::IGUIStaticText*> m_texts;
+ core::array<gui::IGUIImage*> m_images;
+ Inventory *m_inventory;
+ s32 m_selection;
+};
+
+int main(int argc, char *argv[])
+{
+ /*
+ Low-level initialization
+ */
+
+ bool disable_stderr = false;
+#ifdef _WIN32
+ disable_stderr = true;
+#endif
+
+ debugstreams_init(disable_stderr, DEBUGFILE);
+ debug_stacks_init();
+
+
+ DSTACK(__FUNCTION_NAME);
+
+ try
+ {
+
+ /*
+ Basic initialization
+ */
+
+ dstream<<DTIME<<"minetest-c55"
+ " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
+ <<", ENABLE_TESTS="<<ENABLE_TESTS
+ <<std::endl;
+
+ // Set locale. This is for forcing '.' as the decimal point.
+ std::locale::global(std::locale("C"));
+ // This enables printing all characters in bitmap font
+ setlocale(LC_CTYPE, "en_US");
+
+ // Initialize sockets
+ sockets_init();
+ atexit(sockets_cleanup);
+
+ // Initialize timestamp mutex
+ g_timestamp_mutex.Init();
+
+ /*
+ Run unit tests
+ */
+ if(ENABLE_TESTS)
+ {
+ run_tests();
+ }
+
+ /*
+ Initialization
+ */
+
+ // Read config file
+
+ if(argc >= 2)
+ {
+ readConfigFile(argv[1]);
+ }
+ else
+ {
+ const char *filenames[2] =
+ {
+ "../minetest.conf",
+ "../../minetest.conf"
+ };
+
+ for(u32 i=0; i<2; i++)
+ {
+ bool r = readConfigFile(filenames[i]);
+ if(r)
+ break;
+ }
+ }
+
+ // Initialize random seed
+ srand(time(0));
+
+ g_range_mutex.Init();
+ assert(g_range_mutex.IsInitialized());
+
+ /*
+ Ask some stuff
+ */
+
+ std::cout<<std::endl<<std::endl;
+ char templine[100];
+
+ std::cout<<"Dedicated server? [y = yes]: ";
+ if(g_dedicated_server != "")
+ {
+ std::cout<<g_dedicated_server<<std::endl;
+ snprintf(templine, 100, "%s", g_dedicated_server.c_str());
+ }
+ else
+ {
+ std::cin.getline(templine, 100);
+ }
+
+ bool dedicated = false;
+ if(templine[0] == 'y')
+ dedicated = true;
+ if(dedicated)
+ std::cout<<"-> yes"<<std::endl;
+ else
+ std::cout<<"-> no"<<std::endl;
+
+ std::cout<<"Port [empty=30000]: ";
+ if(g_port != "")
+ {
+ std::cout<<g_port<<std::endl;
+ snprintf(templine, 100, "%s", g_port.c_str());
+ }
+ else
+ {
+ std::cin.getline(templine, 100);
+ }
+ unsigned short port;
+ if(templine[0] == 0)
+ port = 30000;
+ else
+ port = atoi(templine);
+ std::cout<<"-> "<<port<<std::endl;
+
+ if(dedicated)
+ {
+ DSTACK("Dedicated server branch");
+
+ std::cout<<std::endl;
+ std::cout<<"========================"<<std::endl;
+ std::cout<<"Running dedicated server"<<std::endl;
+ std::cout<<"========================"<<std::endl;
+ std::cout<<std::endl;
+
+ Server server("../map", g_creative_mode, g_mapgen_params);
+ server.start(port);
+
+ for(;;)
+ {
+ // This is kind of a hack but can be done like this
+ // because server.step() is very light
+ sleep_ms(100);
+ server.step(0.1);
+
+ static int counter = 0;
+ counter--;
+ if(counter <= 0)
+ {
+ counter = 10;
+
+ core::list<PlayerInfo> list = server.getPlayerInfo();
+ core::list<PlayerInfo>::Iterator i;
+ static u32 sum_old = 0;
+ u32 sum = PIChecksum(list);
+ if(sum != sum_old)
+ {
+ std::cout<<DTIME<<"Player info:"<<std::endl;
+ for(i=list.begin(); i!=list.end(); i++)
+ {
+ i->PrintLine(&std::cout);
+ }
+ }
+ sum_old = sum;
+ }
+ }
+
+ return 0;
+ }
+
+ bool hosting = false;
+ char connect_name[100] = "";
+
+ std::cout<<"Address to connect to [empty = host a game]: ";
+ if(g_address != "" && is_yes(g_host_game) == false)
+ {
+ std::cout<<g_address<<std::endl;
+ snprintf(connect_name, 100, "%s", g_address.c_str());
+ }
+ else
+ {
+ std::cin.getline(connect_name, 100);
+ }
+
+ if(connect_name[0] == 0){
+ snprintf(connect_name, 100, "127.0.0.1");
+ hosting = true;
+ }
+
+ if(hosting)
+ std::cout<<"-> hosting"<<std::endl;
+ else
+ std::cout<<"-> "<<connect_name<<std::endl;
+
+ char playername[PLAYERNAME_SIZE] = "";
+ if(g_name != "")
+ {
+ snprintf(playername, PLAYERNAME_SIZE, "%s", g_name.c_str());
+ }
+ else
+ {
+ std::cout<<"Name of player: ";
+ std::cin.getline(playername, PLAYERNAME_SIZE);
+ }
+ std::cout<<"-> \""<<playername<<"\""<<std::endl;
+
+ /*
+ Resolution selection
+ */
+
+ u16 screenW;
+ u16 screenH;
+ bool fullscreen = false;
+
+ if(g_screenW != "" && g_screenH != "")
+ {
+ screenW = atoi(g_screenW.c_str());
+ screenH = atoi(g_screenH.c_str());
+ }
+ else
+ {
+ u16 resolutions[][3] = {
+ //W, H, fullscreen
+ {640,480, 0},
+ {800,600, 0},
+ {1024,768, 0},
+ {1280,1024, 0},
+ /*{640,480, 1},
+ {800,600, 1},
+ {1024,768, 1},
+ {1280,1024, 1},*/
+ };
+
+ u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
+
+ for(u16 i=0; i<res_count; i++)
+ {
+ std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
+ <<resolutions[i][1];
+ if(resolutions[i][2])
+ std::cout<<" fullscreen"<<std::endl;
+ else
+ std::cout<<" windowed"<<std::endl;
+ }
+ std::cout<<"Select a window resolution number [empty = 2]: ";
+ std::cin.getline(templine, 100);
+
+ u16 r0;
+ if(templine[0] == 0)
+ r0 = 2;
+ else
+ r0 = atoi(templine);
+
+ if(r0 > res_count || r0 == 0)
+ r0 = 2;
+
+ {
+ u16 i = r0-1;
+ std::cout<<"-> ";
+ std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
+ <<resolutions[i][1];
+ if(resolutions[i][2])
+ std::cout<<" fullscreen"<<std::endl;
+ else
+ std::cout<<" windowed"<<std::endl;
+ }
+
+ screenW = resolutions[r0-1][0];
+ screenH = resolutions[r0-1][1];
+ fullscreen = resolutions[r0-1][2];
+ }
+
+ //
+
+ MyEventReceiver receiver;
+
+ 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
+
+ // create device and exit if creation failed
+
+ IrrlichtDevice *device;
+ device = createDevice(driverType,
+ core::dimension2d<u32>(screenW, screenH),
+ 16, fullscreen, false, false, &receiver);
+ // With vsync
+ /*device = createDevice(driverType,
+ core::dimension2d<u32>(screenW, screenH),
+ 16, fullscreen, false, true, &receiver);*/
+
+ if (device == 0)
+ return 1; // could not create selected driver.
+
+ g_device = device;
+
+ device->setResizable(true);
+
+ if(g_random_input)
+ g_input = new RandomInputHandler();
+ else
+ g_input = new RealInputHandler(device, &receiver);
+
+ /*
+ 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 );
+
+ //driver->setMinHardwareBufferVertexCount(1);
+
+ 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 and connecting...";
+ core::vector2d<s32> center(screenW/2, screenH/2);
+ core::dimension2d<u32> textd = font->getDimension(text);
+ std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
+ // Have to add a bit to disable the text from word wrapping
+ //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
+ core::vector2d<s32> textsize(300, textd.Height);
+ core::rect<s32> textrect(center - textsize/2, center + textsize/2);
+
+ gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
+ text, textrect, false, false);
+ gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
+
+ driver->beginScene(true, true, video::SColor(255,0,0,0));
+ guienv->drawAll();
+ driver->endScene();
+
+ /*
+ Initialize material array
+ */
+
+ //video::SMaterial g_materials[MATERIALS_COUNT];
+ for(u16 i=0; i<MATERIALS_COUNT; i++)
+ {
+ g_materials[i].Lighting = false;
+ g_materials[i].BackfaceCulling = false;
+
+ const char *filename = g_material_filenames[i];
+ if(filename != NULL){
+ video::ITexture *t = driver->getTexture(filename);
+ if(t == NULL){
+ std::cout<<DTIME<<"Texture could not be loaded: \""
+ <<filename<<"\""<<std::endl;
+ return 1;
+ }
+ g_materials[i].setTexture(0, driver->getTexture(filename));
+ }
+ //g_materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT);
+ g_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
+ //g_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);
+ //g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
+ if(i == MATERIAL_WATER)
+ {
+ g_materials[i].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+ //g_materials[i].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
+ }
+ }
+
+ /*g_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));
+ g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));
+ g_mesh_materials[2].setTexture(0, driver->getTexture("../data/stone.png"));
+ for(u32 i=0; i<3; i++)
+ {
+ g_mesh_materials[i].Lighting = false;
+ g_mesh_materials[i].BackfaceCulling = false;
+ g_mesh_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
+ g_mesh_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
+ }*/
+
+ // Make a scope here for the client so that it gets removed
+ // before the irrlicht device
+ {
+
+ std::cout<<DTIME<<"Creating server and client"<<std::endl;
+
+ /*
+ Create server
+ */
+ SharedPtr<Server> server;
+ if(hosting){
+ server = new Server("../map", g_creative_mode, g_mapgen_params);
+ server->start(port);
+ }
+
+ /*
+ Create client
+ */
+
+ // TODO: Get rid of the g_materials parameter or it's globalness
+ Client client(device, g_materials,
+ g_client_delete_unused_sectors_timeout,
+ playername);
+
+ Address connect_address(0,0,0,0, port);
+ try{
+ connect_address.Resolve(connect_name);
+ }
+ catch(ResolveError &e)
+ {
+ std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
+ return 0;
+ }
+
+ std::cout<<DTIME<<"Connecting to server..."<<std::endl;
+ client.connect(connect_address);
+
+ try{
+ while(client.connectedAndInitialized() == false)
+ {
+ client.step(0.1);
+ if(server != NULL){
+ server->step(0.1);
+ }
+ sleep_ms(100);
+ }
+ }
+ catch(con::PeerNotFoundException &e)
+ {
+ std::cout<<DTIME<<"Timed out."<<std::endl;
+ return 0;
+ }
+
+ /*
+ Create the camera node
+ */
+
+ scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
+ 0, // Camera parent
+ v3f(BS*100, BS*2, BS*100), // Look from
+ v3f(BS*100+1, BS*2, BS*100), // Look to
+ -1 // Camera ID
+ );
+
+ if(camera == NULL)
+ return 1;
+
+ video::SColor skycolor = video::SColor(255,90,140,200);
+
+ camera->setFOV(FOV_ANGLE);
+
+ // Just so big a value that everything rendered is visible
+ camera->setFarValue(100000*BS);
+
+ /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
+ f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
+
+ camera->setFarValue(range);
+
+ driver->setFog(
+ skycolor,
+ video::EFT_FOG_LINEAR,
+ range*0.8,
+ range,
+ 0.01,
+ false,
+ false
+ );*/
+
+ f32 camera_yaw = 0; // "right/left"
+ f32 camera_pitch = 0; // "up/down"
+
+ gui_loadingtext->remove();
+
+ /*
+ Add some gui stuff
+ */
+
+ // First line of debug text
+ gui::IGUIStaticText *guitext = guienv->addStaticText(
+ L"Minetest-c55",
+ core::rect<s32>(5, 5, 5+600, 5+textsize.Y),
+ false, false);
+ // Second line of debug text
+ gui::IGUIStaticText *guitext2 = guienv->addStaticText(
+ L"",
+ core::rect<s32>(5, 5+(textsize.Y+5)*1, 5+600, (5+textsize.Y)*2),
+ false, false);
+
+ // At the middle of the screen
+ // Object infos are shown in this
+ gui::IGUIStaticText *guitext_info = guienv->addStaticText(
+ L"test",
+ core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
+ false, false);
+
+ // This is a copy of the inventory that the client's environment has
+ Inventory local_inventory(PLAYER_INVENTORY_SIZE);
+
+ GUIQuickInventory *quick_inventory = new GUIQuickInventory
+ (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
+
+ /*
+ Some statistics are collected in these
+ */
+ u32 drawtime = 0;
+ u32 scenetime = 0;
+ u32 endscenetime = 0;
+
+ /*
+ Text input system
+ */
+
+ struct TextDest
+ {
+ virtual void sendText(std::string text) = 0;
+ };
+
+ struct TextDestSign : public TextDest
+ {
+ TextDestSign(v3s16 blockpos, s16 id, Client *client)
+ {
+ m_blockpos = blockpos;
+ m_id = id;
+ m_client = client;
+ }
+ void sendText(std::string text)
+ {
+ dstream<<"Changing text of a sign object: "
+ <<text<<std::endl;
+ m_client->sendSignText(m_blockpos, m_id, text);
+ }
+
+ v3s16 m_blockpos;
+ s16 m_id;
+ Client *m_client;
+ };
+
+ TextDest *textbuf_dest = NULL;
+
+ //gui::IGUIWindow* input_window = NULL;
+ gui::IGUIStaticText* input_guitext = NULL;
+
+ /*
+ Main loop
+ */
+
+ bool first_loop_after_window_activation = true;
+
+ // Time is in milliseconds
+ // NOTE: getRealTime() without run()s causes strange problems in wine
+ // NOTE: Have to call run() between calls of this to update the timer
+ u32 lasttime = device->getTimer()->getTime();
+
+ while(device->run())
+ {
+ // Hilight boxes collected during the loop and displayed
+ core::list< core::aabbox3d<f32> > hilightboxes;
+
+ // Info text
+ std::wstring infotext;
+
+ //TimeTaker //timer1("//timer1", device);
+
+ // Time of frame without fps limit
+ float busytime;
+ u32 busytime_u32;
+ {
+ // not using getRealTime is necessary for wine
+ u32 time = device->getTimer()->getTime();
+ if(time > lasttime)
+ busytime_u32 = time - lasttime;
+ else
+ busytime_u32 = 0;
+ busytime = busytime_u32 / 1000.0;
+ }
+
+ //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
+
+ // Absolutelu necessary for wine!
+ device->run();
+
+ /*
+ Viewing range
+ */
+
+ //updateViewingRange(dtime, &client);
+ updateViewingRange(busytime, &client);
+
+ /*
+ FPS limiter
+ */
+
+ {
+ float fps_max = g_fps_max;
+ u32 frametime_min = 1000./fps_max;
+
+ if(busytime_u32 < frametime_min)
+ {
+ u32 sleeptime = frametime_min - busytime_u32;
+ device->sleep(sleeptime);
+ }
+ }
+
+ // Absolutelu necessary for wine!
+ device->run();
+
+ /*
+ Time difference calculation
+ */
+ f32 dtime; // in seconds
+
+ u32 time = device->getTimer()->getTime();
+ if(time > lasttime)
+ dtime = (time - lasttime) / 1000.0;
+ else
+ dtime = 0;
+ lasttime = time;
+
+ /*
+ Time average and jitter calculation
+ */
+
+ static f32 dtime_avg1 = 0.0;
+ dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
+ f32 dtime_jitter1 = dtime - dtime_avg1;
+
+ static f32 dtime_jitter1_max_sample = 0.0;
+ static f32 dtime_jitter1_max_fraction = 0.0;
+ {
+ static f32 jitter1_max = 0.0;
+ static f32 counter = 0.0;
+ if(dtime_jitter1 > jitter1_max)
+ jitter1_max = dtime_jitter1;
+ counter += dtime;
+ if(counter > 0.0)
+ {
+ counter -= 3.0;
+ dtime_jitter1_max_sample = jitter1_max;
+ dtime_jitter1_max_fraction
+ = dtime_jitter1_max_sample / (dtime_avg1+0.001);
+ jitter1_max = 0.0;
+
+ /*
+ Control freetime ratio
+ */
+ /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
+ {
+ if(g_freetime_ratio < FREETIME_RATIO_MAX)
+ g_freetime_ratio += 0.01;
+ }
+ else
+ {
+ if(g_freetime_ratio > FREETIME_RATIO_MIN)
+ g_freetime_ratio -= 0.01;
+ }*/
+ }
+ }
+
+ /*
+ Busytime average and jitter calculation
+ */
+
+ static f32 busytime_avg1 = 0.0;
+ busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
+ f32 busytime_jitter1 = busytime - busytime_avg1;
+
+ static f32 busytime_jitter1_max_sample = 0.0;
+ static f32 busytime_jitter1_min_sample = 0.0;
+ {
+ static f32 jitter1_max = 0.0;
+ static f32 jitter1_min = 0.0;
+ static f32 counter = 0.0;
+ if(busytime_jitter1 > jitter1_max)
+ jitter1_max = busytime_jitter1;
+ if(busytime_jitter1 < jitter1_min)
+ jitter1_min = busytime_jitter1;
+ counter += dtime;
+ if(counter > 0.0){
+ counter -= 3.0;
+ busytime_jitter1_max_sample = jitter1_max;
+ busytime_jitter1_min_sample = jitter1_min;
+ jitter1_max = 0.0;
+ jitter1_min = 0.0;
+ }
+ }
+
+ /*
+ Debug info for client
+ */
+ {
+ static float counter = 0.0;
+ counter -= dtime;
+ if(counter < 0)
+ {
+ counter = 30.0;
+ client.printDebugInfo(std::cout);
+ }
+ }
+
+ /*
+ Input handler step()
+ */
+ g_input->step(dtime);
+
+ /*
+ Special keys
+ */
+ if(g_esc_pressed)
+ {
+ break;
+ }
+
+ /*
+ Player speed control
+ */
+
+ if(g_game_focused)
+ {
+ /*bool a_up,
+ bool a_down,
+ bool a_left,
+ bool a_right,
+ bool a_jump,
+ bool a_superspeed,
+ float a_pitch,
+ float a_yaw*/
+ PlayerControl control(
+ g_input->isKeyDown(irr::KEY_KEY_W),
+ g_input->isKeyDown(irr::KEY_KEY_S),
+ g_input->isKeyDown(irr::KEY_KEY_A),
+ g_input->isKeyDown(irr::KEY_KEY_D),
+ g_input->isKeyDown(irr::KEY_SPACE),
+ g_input->isKeyDown(irr::KEY_KEY_2),
+ camera_pitch,
+ camera_yaw
+ );
+ client.setPlayerControl(control);
+ }
+ else
+ {
+ // Set every key to inactive
+ PlayerControl control;
+ client.setPlayerControl(control);
+ }
+
+ //timer1.stop();
+ /*
+ Process environment
+ */
+
+ {
+ //TimeTaker timer("client.step(dtime)", device);
+ client.step(dtime);
+ //client.step(dtime_avg1);
+ }
+
+ if(server != NULL)
+ {
+ //TimeTaker timer("server->step(dtime)", device);
+ server->step(dtime);
+ }
+
+ v3f player_position = client.getPlayerPosition();
+
+ //TimeTaker //timer2("//timer2", device);
+
+ /*
+ Mouse and camera control
+ */
+
+ if(device->isWindowActive() && g_game_focused)
+ {
+ device->getCursorControl()->setVisible(false);
+
+ if(first_loop_after_window_activation){
+ //std::cout<<"window active, first loop"<<std::endl;
+ first_loop_after_window_activation = false;
+ }
+ else{
+ s32 dx = g_input->getMousePos().X - 320;
+ s32 dy = g_input->getMousePos().Y - 240;
+ //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
+ camera_yaw -= dx*0.2;
+ camera_pitch += dy*0.2;
+ if(camera_pitch < -89.5) camera_pitch = -89.5;
+ if(camera_pitch > 89.5) camera_pitch = 89.5;
+ }
+ g_input->setMousePos(320, 240);
+ }
+ else{
+ device->getCursorControl()->setVisible(true);
+
+ //std::cout<<"window inactive"<<std::endl;
+ first_loop_after_window_activation = true;
+ }
+
+ camera_yaw = wrapDegrees(camera_yaw);
+ camera_pitch = wrapDegrees(camera_pitch);
+
+ v3f camera_direction = v3f(0,0,1);
+ camera_direction.rotateYZBy(camera_pitch);
+ camera_direction.rotateXZBy(camera_yaw);
+
+ v3f camera_position =
+ player_position + v3f(0, BS+BS/2, 0);
+
+ camera->setPosition(camera_position);
+ // *100.0 helps in large map coordinates
+ camera->setTarget(camera_position + camera_direction * 100.0);
+
+ 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);
+ //TimeTaker timer("client.updateCamera", device);
+ client.updateCamera(camera_position, camera_direction);
+ }
+
+ //timer2.stop();
+ //TimeTaker //timer3("//timer3", device);
+
+ /*
+ Calculate what block is the crosshair pointing to
+ */
+
+ //u32 t1 = device->getTimer()->getRealTime();
+
+ //f32 d = 4; // max. distance
+ f32 d = 4; // max. distance
+ core::line3d<f32> shootline(camera_position,
+ camera_position + camera_direction * BS * (d+1));
+
+ MapBlockObject *selected_object = client.getSelectedObject
+ (d*BS, camera_position, shootline);
+
+ if(selected_object != NULL)
+ {
+ //dstream<<"Client returned selected_object != NULL"<<std::endl;
+
+ core::aabbox3d<f32> box_on_map
+ = selected_object->getSelectionBoxOnMap();
+
+ hilightboxes.push_back(box_on_map);
+
+ infotext = narrow_to_wide(selected_object->infoText());
+
+ if(g_input->getLeftClicked())
+ {
+ std::cout<<DTIME<<"Left-clicked object"<<std::endl;
+ client.clickObject(0, selected_object->getBlock()->getPos(),
+ selected_object->getId(), g_selected_item);
+ }
+ else if(g_input->getRightClicked())
+ {
+ std::cout<<DTIME<<"Right-clicked object"<<std::endl;
+ /*
+ Check if we want to modify the object ourselves
+ */
+ if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
+ {
+ dstream<<"Sign object right-clicked"<<std::endl;
+
+ unFocusGame();
+
+ input_guitext = guienv->addStaticText(L"",
+ core::rect<s32>(150,100,350,120),
+ true, // border?
+ false, // wordwrap?
+ NULL);
+
+ input_guitext->setDrawBackground(true);
+
+ g_text_buffer = L"";
+ g_text_buffer_accepted = false;
+ textbuf_dest = new TextDestSign(
+ selected_object->getBlock()->getPos(),
+ selected_object->getId(),
+ &client);
+ }
+ /*
+ Otherwise pass the event to the server as-is
+ */
+ else
+ {
+ client.clickObject(1, selected_object->getBlock()->getPos(),
+ selected_object->getId(), g_selected_item);
+ }
+ }
+ }
+ else // selected_object == NULL
+ {
+
+ bool nodefound = false;
+ v3s16 nodepos;
+ v3s16 neighbourpos;
+ core::aabbox3d<f32> nodefacebox;
+ f32 mindistance = BS * 1001;
+
+ v3s16 pos_i = floatToInt(player_position);
+
+ /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
+ <<std::endl;*/
+
+ 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.getNode(v3s16(x,y,z)).d == MATERIAL_AIR){
+ continue;
+ }
+ }catch(InvalidPositionException &e){
+ continue;
+ }
+
+ v3s16 np(x,y,z);
+ v3f npf = 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<<DTIME<<"for centerpoint=("<<centerpoint.X<<","<<centerpoint.Y<<","<<centerpoint.Z<<"): distance < mindistance"<<std::endl;
+ //std::cout<<DTIME<<"npf=("<<npf.X<<","<<npf.Y<<","<<npf.Z<<")"<<std::endl;
+ core::CMatrix4<f32> m;
+ m.buildRotateFromTo(v3f(0,0,1), dir_f);
+
+ // This is the back face
+ v3f corners[2] = {
+ v3f(BS/2, BS/2, BS/2),
+ v3f(-BS/2, -BS/2, BS/2+d)
+ };
+
+ for(u16 j=0; j<2; j++){
+ m.rotateVect(corners[j]);
+ corners[j] += npf;
+ //std::cout<<DTIME<<"box corners["<<j<<"]: ("<<corners[j].X<<","<<corners[j].Y<<","<<corners[j].Z<<")"<<std::endl;
+ }
+
+ //core::aabbox3d<f32> facebox(corners[0],corners[1]);
+ core::aabbox3d<f32> facebox(corners[0]);
+ facebox.addInternalPoint(corners[1]);
+
+ if(facebox.intersectsWithLine(shootline)){
+ nodefound = true;
+ nodepos = np;
+ neighbourpos = np + directions[i];
+ mindistance = distance;
+ nodefacebox = facebox;
+ }
+ }
+ }
+ }}}
+
+ if(nodefound)
+ {
+ //std::cout<<DTIME<<"nodefound == true"<<std::endl;
+ //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
+ //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
+
+ static v3s16 nodepos_old(-1,-1,-1);
+ if(nodepos != nodepos_old){
+ std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
+ <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
+ nodepos_old = nodepos;
+
+ /*wchar_t positiontext[20];
+ swprintf(positiontext, 20, L"(%i,%i,%i)",
+ nodepos.X, nodepos.Y, nodepos.Z);
+ positiontextgui->setText(positiontext);*/
+ }
+
+ hilightboxes.push_back(nodefacebox);
+
+ if(g_input->getLeftClicked())
+ {
+ //std::cout<<DTIME<<"Removing node"<<std::endl;
+ //client.removeNode(nodepos);
+ std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
+ client.clickGround(0, nodepos, neighbourpos, g_selected_item);
+ }
+ if(g_input->getRightClicked())
+ {
+ //std::cout<<DTIME<<"Placing node"<<std::endl;
+ //client.addNodeFromInventory(neighbourpos, g_selected_item);
+ std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
+ client.clickGround(1, nodepos, neighbourpos, g_selected_item);
+ }
+ }
+ else{
+ //std::cout<<DTIME<<"nodefound == false"<<std::endl;
+ //positiontextgui->setText(L"");
+ }
+
+ } // selected_object == NULL
+
+ g_input->resetLeftClicked();
+ g_input->resetRightClicked();
+
+ /*
+ Calculate stuff for drawing
+ */
+
+ v2u32 screensize = driver->getScreenSize();
+ core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
+
+ camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
+
+ /*
+ Update gui stuff (0ms)
+ */
+
+ //TimeTaker guiupdatetimer("Gui updating", device);
+
+ {
+ wchar_t temptext[100];
+
+ static float drawtime_avg = 0;
+ drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
+ static float scenetime_avg = 0;
+ scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
+ static float endscenetime_avg = 0;
+ endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
+
+ swprintf(temptext, 100, L"Minetest-c55 ("
+ L"F: item=%i"
+ L", R: range_all=%i"
+ L")"
+ L" drawtime=%.0f, scenetime=%.0f, endscenetime=%.0f",
+ g_selected_item,
+ g_viewing_range_all,
+ drawtime_avg,
+ scenetime_avg,
+ endscenetime_avg
+ );
+
+ guitext->setText(temptext);
+ }
+
+ {
+ wchar_t temptext[100];
+ /*swprintf(temptext, 100,
+ L"("
+ L"% .3f < btime_jitter < % .3f"
+ L", dtime_jitter = % .1f %%"
+ //L", ftime_ratio = % .3f"
+ L")",
+ busytime_jitter1_min_sample,
+ busytime_jitter1_max_sample,
+ dtime_jitter1_max_fraction * 100.0
+ //g_freetime_ratio
+ );*/
+ swprintf(temptext, 100,
+ L"(% .1f, % .1f, % .1f)"
+ L" (% .3f < btime_jitter < % .3f"
+ L", dtime_jitter = % .1f %%)",
+ player_position.X/BS,
+ player_position.Y/BS,
+ player_position.Z/BS,
+ busytime_jitter1_min_sample,
+ busytime_jitter1_max_sample,
+ dtime_jitter1_max_fraction * 100.0
+ );
+
+ guitext2->setText(temptext);
+ }
+
+ {
+ /*wchar_t temptext[100];
+ swprintf(temptext, 100,
+ SWPRINTF_CHARSTRING,
+ infotext.substr(0,99).c_str()
+ );
+
+ guitext_info->setText(temptext);*/
+
+ guitext_info->setText(infotext.c_str());
+ }
+
+ /*
+ Inventory
+ */
+
+ static u16 old_selected_item = 65535;
+ if(client.getLocalInventoryUpdated()
+ || g_selected_item != old_selected_item)
+ {
+ old_selected_item = g_selected_item;
+ //std::cout<<"Updating local inventory"<<std::endl;
+ client.getLocalInventory(local_inventory);
+ quick_inventory->setSelection(g_selected_item);
+ quick_inventory->update();
+ }
+
+ if(input_guitext != NULL)
+ {
+ /*wchar_t temptext[100];
+ swprintf(temptext, 100,
+ SWPRINTF_CHARSTRING,
+ g_text_buffer.substr(0,99).c_str()
+ );*/
+ input_guitext->setText(g_text_buffer.c_str());
+ }
+
+ /*
+ Text input stuff
+ */
+ if(input_guitext != NULL && g_text_buffer_accepted)
+ {
+ input_guitext->remove();
+ input_guitext = NULL;
+
+ if(textbuf_dest != NULL)
+ {
+ std::string text = wide_to_narrow(g_text_buffer);
+ dstream<<"Sending text: "<<text<<std::endl;
+ textbuf_dest->sendText(text);
+ delete textbuf_dest;
+ textbuf_dest = NULL;
+ }
+
+ focusGame();
+ }
+
+ //guiupdatetimer.stop();
+
+ /*
+ Drawing begins
+ */
+
+ TimeTaker drawtimer("Drawing", device);
+
+ /*
+ 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);
+ // Does this make short random delays?
+ // NOTE: no need for this, sky doesn't show underground with
+ // enough range
+ bool is_underground = client.isNodeUnderground(p0);
+ //bool is_underground = false;
+ if(is_underground == false)
+ bgcolor = video::SColor(255,90,140,200);
+ else
+ bgcolor = video::SColor(255,0,0,0);*/
+
+ //video::SColor bgcolor = video::SColor(255,90,140,200);
+ video::SColor bgcolor = skycolor;
+
+ // 0ms
+ driver->beginScene(true, true, bgcolor);
+
+ //timer3.stop();
+
+ //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
+
+ {
+ TimeTaker timer("smgr", device);
+ smgr->drawAll();
+ scenetime = timer.stop(true);
+ }
+
+ {
+ //TimeTaker timer9("auxiliary drawings", device);
+ // 0ms
+
+ driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
+ displaycenter + core::vector2d<s32>(10,0),
+ video::SColor(255,255,255,255));
+ driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
+ displaycenter + core::vector2d<s32>(0,10),
+ video::SColor(255,255,255,255));
+
+ //timer9.stop();
+ //TimeTaker //timer10("//timer10", device);
+
+ video::SMaterial m;
+ m.Thickness = 10;
+ m.Lighting = false;
+ driver->setMaterial(m);
+
+ driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
+
+ for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
+ i != hilightboxes.end(); i++)
+ {
+ /*std::cout<<"hilightbox min="
+ <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
+ <<" max="
+ <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
+ <<std::endl;*/
+ driver->draw3DBox(*i, video::SColor(255,0,0,0));
+ }
+
+ }
+
+ //timer10.stop();
+ //TimeTaker //timer11("//timer11", device);
+
+ /*
+ Draw gui
+ */
+ // 0-1ms
+ guienv->drawAll();
+
+ // End drawing
+ {
+ TimeTaker timer("endScene", device);
+ driver->endScene();
+ endscenetime = timer.stop(true);
+ }
+
+ drawtime = drawtimer.stop(true);
+
+ /*
+ Drawing ends
+ */
+
+ static s16 lastFPS = 0;
+ //u16 fps = driver->getFPS();
+ u16 fps = (1.0/dtime_avg1);
+
+ 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();*/
+ }
+
+ } // client is deleted at this point
+
+ delete g_input;
+
+ /*
+ In the end, delete the Irrlicht device.
+ */
+ device->drop();
+
+ } //try
+ catch(con::PeerNotFoundException &e)
+ {
+ dstream<<DTIME<<"Connection timed out."<<std::endl;
+ }
+#if CATCH_EXCEPTIONS
+ /*
+ This is what has to be done in every thread to get suitable debug info
+ */
+ catch(std::exception &e)
+ {
+ dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
+ <<e.what()<<std::endl;
+ assert(0);
+ }
+#endif
+
+ debugstreams_deinit();
+
+ return 0;
+}
+
+//END
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 000000000..3118e5f52
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,50 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAIN_HEADER
+#define MAIN_HEADER
+
+#include <string>
+extern std::string getTimestamp();
+#define DTIME (getTimestamp()+": ")
+
+#include <jmutex.h>
+
+extern JMutex g_range_mutex;
+extern s16 g_forcedfetch_range_nodes;
+extern s16 g_viewing_range_nodes;
+//extern s16 g_actual_viewing_range_nodes;
+extern bool g_viewing_range_all;
+
+#include <fstream>
+
+// Debug streams
+extern std::ostream *dout_con_ptr;
+extern std::ostream *derr_con_ptr;
+extern std::ostream *dout_client_ptr;
+extern std::ostream *derr_client_ptr;
+extern std::ostream *dout_server_ptr;
+extern std::ostream *derr_server_ptr;
+
+#define dout_con (*dout_con_ptr)
+#define derr_con (*derr_con_ptr)
+#define dout_client (*dout_client_ptr)
+#define derr_client (*derr_client_ptr)
+#define dout_server (*dout_server_ptr)
+#define derr_server (*derr_server_ptr)
+
+// TODO: Move somewhere else? materials.h?
+// This header is only for MATERIALS_COUNT
+#include "mapnode.h"
+extern video::SMaterial g_materials[MATERIALS_COUNT];
+//extern video::SMaterial g_mesh_materials[3];
+
+extern IrrlichtDevice *g_device;
+
+// Settings
+#include "map.h"
+extern MapgenParams g_mapgen_params;
+
+#endif
+
diff --git a/src/map.cpp b/src/map.cpp
new file mode 100644
index 000000000..c69c3f248
--- /dev/null
+++ b/src/map.cpp
@@ -0,0 +1,2854 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "map.h"
+//#include "player.h"
+#include "main.h"
+#include "jmutexautolock.h"
+#include "client.h"
+#include "filesys.h"
+#include "utility.h"
+
+#ifdef _WIN32
+ #include <windows.h>
+ #define sleep_ms(x) Sleep(x)
+#else
+ #include <unistd.h>
+ #define sleep_ms(x) usleep(x*1000)
+#endif
+
+Map::Map(std::ostream &dout):
+ m_dout(dout),
+ m_camera_position(0,0,0),
+ m_camera_direction(0,0,1),
+ m_sector_cache(NULL),
+ m_hwrapper(this),
+ drawoffset(0,0,0)
+{
+ m_sector_mutex.Init();
+ m_camera_mutex.Init();
+ assert(m_sector_mutex.IsInitialized());
+ assert(m_camera_mutex.IsInitialized());
+
+ // Get this so that the player can stay on it at first
+ //getSector(v2s16(0,0));
+}
+
+Map::~Map()
+{
+ /*
+ Stop updater thread
+ */
+ /*updater.setRun(false);
+ while(updater.IsRunning())
+ sleep_s(1);*/
+
+ /*
+ Free all MapSectors.
+ */
+ core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapSector *sector = i.getNode()->getValue();
+ delete sector;
+ }
+}
+
+/*bool Map::sectorExists(v2s16 p)
+{
+ JMutexAutoLock lock(m_sector_mutex);
+ core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
+ return (n != NULL);
+}*/
+
+MapSector * Map::getSectorNoGenerate(v2s16 p)
+{
+ JMutexAutoLock lock(m_sector_mutex);
+
+ if(m_sector_cache != NULL && p == m_sector_cache_p){
+ MapSector * sector = m_sector_cache;
+ // Reset inactivity timer
+ sector->usage_timer = 0.0;
+ return sector;
+ }
+
+ core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
+ // If sector doesn't exist, throw an exception
+ if(n == NULL)
+ {
+ throw InvalidPositionException();
+ }
+
+ MapSector *sector = n->getValue();
+
+ // Cache the last result
+ m_sector_cache_p = p;
+ m_sector_cache = sector;
+
+ //MapSector * ref(sector);
+
+ // Reset inactivity timer
+ sector->usage_timer = 0.0;
+ return sector;
+}
+
+MapBlock * Map::getBlockNoCreate(v3s16 p3d)
+{
+ v2s16 p2d(p3d.X, p3d.Z);
+ MapSector * sector = getSectorNoGenerate(p2d);
+
+ MapBlock *block = sector->getBlockNoCreate(p3d.Y);
+
+ return block;
+}
+
+/*MapBlock * Map::getBlock(v3s16 p3d, bool generate)
+{
+ dstream<<"Map::getBlock() with generate=true called"
+ <<std::endl;
+ v2s16 p2d(p3d.X, p3d.Z);
+ //MapSector * sector = getSector(p2d, generate);
+ MapSector * sector = getSectorNoGenerate(p2d);
+
+ if(sector == NULL)
+ throw InvalidPositionException();
+
+ return sector->getBlockNoCreate(p3d.Y);
+}*/
+
+f32 Map::getGroundHeight(v2s16 p, bool generate)
+{
+ try{
+ v2s16 sectorpos = getNodeSectorPos(p);
+ MapSector * sref = getSectorNoGenerate(sectorpos);
+ v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
+ f32 y = sref->getGroundHeight(relpos);
+ return y;
+ }
+ catch(InvalidPositionException &e)
+ {
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ }
+}
+
+void Map::setGroundHeight(v2s16 p, f32 y, bool generate)
+{
+ /*m_dout<<DTIME<<"Map::setGroundHeight(("
+ <<p.X<<","<<p.Y
+ <<"), "<<y<<")"<<std::endl;*/
+ v2s16 sectorpos = getNodeSectorPos(p);
+ MapSector * sref = getSectorNoGenerate(sectorpos);
+ v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
+ //sref->mutex.Lock();
+ sref->setGroundHeight(relpos, y);
+ //sref->mutex.Unlock();
+}
+
+bool Map::isNodeUnderground(v3s16 p)
+{
+ v3s16 blockpos = getNodeBlockPos(p);
+ try{
+ MapBlock * block = getBlockNoCreate(blockpos);
+ return block->getIsUnderground();
+ }
+ catch(InvalidPositionException &e)
+ {
+ return false;
+ }
+}
+
+#ifdef LKJnb
+//TODO: Remove: Not used.
+/*
+ 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: (for clearing all possible spreaded light of a lamp)
+ NOTE: This is outdated
+ core::list<v3s16> light_sources;
+ core::map<v3s16, MapBlock*> modified_blocks;
+ u8 oldlight = node_at_pos.light;
+ node_at_pos.setLight(0);
+ unLightNeighbors(pos, oldlight, light_sources, modified_blocks);
+*/
+void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
+ core::map<v3s16, bool> & light_sources,
+ core::map<v3s16, MapBlock*> & 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
+ };
+
+ /*
+ Initialize block cache
+ */
+ v3s16 blockpos_last;
+ MapBlock *block = NULL;
+ // Cache this a bit, too
+ bool block_checked_in_modified = false;
+
+ // 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);
+
+ // Only fetch a new block if the block position has changed
+ try{
+ if(block == NULL || blockpos != blockpos_last)
+ {
+ block = getBlockNoCreate(blockpos);
+ blockpos_last = blockpos;
+
+ block_checked_in_modified = false;
+ //blockchangecount++;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+
+ if(block->isDummy())
+ continue;
+
+ // Calculate relative position in block
+ v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
+ // Get node straight from the block
+ MapNode n2 = block->getNode(relpos);
+
+ /*
+ If the neighbor is dimmer than what was specified
+ as oldlight (the light of the previous node)
+ */
+ if(n2.getLight() < oldlight)
+ {
+ /*
+ And the neighbor is transparent and it has some light
+ */
+ if(n2.light_propagates() && n2.getLight() != 0)
+ {
+ /*
+ Set light to 0 and recurse.
+ */
+ u8 current_light = n2.getLight();
+ n2.setLight(0);
+ block->setNode(relpos, n2);
+ unLightNeighbors(n2pos, current_light,
+ light_sources, modified_blocks);
+
+ if(block_checked_in_modified == false)
+ {
+ // If the block is not found in modified_blocks, add.
+ if(modified_blocks.find(blockpos) == NULL)
+ {
+ modified_blocks.insert(blockpos, block);
+ }
+ block_checked_in_modified = true;
+ }
+ }
+ }
+ else{
+ //light_sources.push_back(n2pos);
+ light_sources.insert(n2pos, true);
+ }
+ }
+}
+#endif
+
+/*
+ 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.
+
+ The ending nodes of the routine are stored in light_sources.
+ This is useful when a light is removed. In such case, this
+ routine can be called for the light node and then again for
+ light_sources to re-light the area without the removed light.
+
+ values of from_nodes are lighting values.
+*/
+void Map::unspreadLight(core::map<v3s16, u8> & from_nodes,
+ core::map<v3s16, bool> & light_sources,
+ core::map<v3s16, MapBlock*> & 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
+ };
+
+ if(from_nodes.size() == 0)
+ return;
+
+ u32 blockchangecount = 0;
+
+ core::map<v3s16, u8> unlighted_nodes;
+ core::map<v3s16, u8>::Iterator j;
+ j = from_nodes.getIterator();
+
+ /*
+ Initialize block cache
+ */
+ v3s16 blockpos_last;
+ MapBlock *block = NULL;
+ // Cache this a bit, too
+ bool block_checked_in_modified = false;
+
+ for(; j.atEnd() == false; j++)
+ {
+ v3s16 pos = j.getNode()->getKey();
+ v3s16 blockpos = getNodeBlockPos(pos);
+
+ // Only fetch a new block if the block position has changed
+ try{
+ if(block == NULL || blockpos != blockpos_last){
+ block = getBlockNoCreate(blockpos);
+ blockpos_last = blockpos;
+
+ block_checked_in_modified = false;
+ blockchangecount++;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+
+ if(block->isDummy())
+ continue;
+
+ // Calculate relative position in block
+ v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
+
+ // Get node straight from the block
+ MapNode n = block->getNode(relpos);
+
+ u8 oldlight = j.getNode()->getValue();
+
+ // 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
+ try{
+ if(block == NULL || blockpos != blockpos_last){
+ block = getBlockNoCreate(blockpos);
+ blockpos_last = blockpos;
+
+ block_checked_in_modified = false;
+ blockchangecount++;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+
+ // Calculate relative position in block
+ v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
+ // Get node straight from the block
+ MapNode n2 = block->getNode(relpos);
+
+ bool changed = false;
+
+ //TODO: Optimize output by optimizing light_sources?
+
+ /*
+ If the neighbor is dimmer than what was specified
+ as oldlight (the light of the previous node)
+ */
+ if(n2.getLight() < oldlight)
+ {
+ /*
+ And the neighbor is transparent and it has some light
+ */
+ if(n2.light_propagates() && n2.getLight() != 0)
+ {
+ /*
+ Set light to 0 and add to queue
+ */
+
+ u8 current_light = n2.getLight();
+ n2.setLight(0);
+ block->setNode(relpos, n2);
+
+ unlighted_nodes.insert(n2pos, current_light);
+ changed = true;
+
+ /*
+ Remove from light_sources if it is there
+ NOTE: This doesn't happen nearly at all
+ */
+ /*if(light_sources.find(n2pos))
+ {
+ std::cout<<"Removed from light_sources"<<std::endl;
+ light_sources.remove(n2pos);
+ }*/
+ }
+ }
+ else{
+ light_sources.insert(n2pos, true);
+ }
+
+ // Add to modified_blocks
+ if(changed == true && block_checked_in_modified == false)
+ {
+ // If the block is not found in modified_blocks, add.
+ if(modified_blocks.find(blockpos) == NULL)
+ {
+ modified_blocks.insert(blockpos, block);
+ }
+ block_checked_in_modified = true;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+ }
+ }
+
+ /*dstream<<"unspreadLight(): Changed block "
+ <<blockchangecount<<" times"
+ <<" for "<<from_nodes.size()<<" nodes"
+ <<std::endl;*/
+
+ if(unlighted_nodes.size() > 0)
+ unspreadLight(unlighted_nodes, light_sources, modified_blocks);
+}
+
+/*
+ A single-node wrapper of the above
+*/
+void Map::unLightNeighbors(v3s16 pos, u8 lightwas,
+ core::map<v3s16, bool> & light_sources,
+ core::map<v3s16, MapBlock*> & modified_blocks)
+{
+ core::map<v3s16, u8> from_nodes;
+ from_nodes.insert(pos, lightwas);
+
+ unspreadLight(from_nodes, light_sources, modified_blocks);
+}
+
+/*
+ Lights neighbors of from_nodes, collects all them and then
+ goes on recursively.
+*/
+void Map::spreadLight(core::map<v3s16, bool> & from_nodes,
+ core::map<v3s16, MapBlock*> & modified_blocks)
+{
+ const 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
+ };
+
+ if(from_nodes.size() == 0)
+ return;
+
+ u32 blockchangecount = 0;
+
+ core::map<v3s16, bool> lighted_nodes;
+ core::map<v3s16, bool>::Iterator j;
+ j = from_nodes.getIterator();
+
+ /*
+ Initialize block cache
+ */
+ v3s16 blockpos_last;
+ MapBlock *block = NULL;
+ // Cache this a bit, too
+ bool block_checked_in_modified = false;
+
+ for(; j.atEnd() == false; j++)
+ //for(; j != from_nodes.end(); j++)
+ {
+ v3s16 pos = j.getNode()->getKey();
+ //v3s16 pos = *j;
+ //dstream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
+ v3s16 blockpos = getNodeBlockPos(pos);
+
+ // Only fetch a new block if the block position has changed
+ try{
+ if(block == NULL || blockpos != blockpos_last){
+ block = getBlockNoCreate(blockpos);
+ blockpos_last = blockpos;
+
+ block_checked_in_modified = false;
+ blockchangecount++;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+
+ if(block->isDummy())
+ continue;
+
+ // Calculate relative position in block
+ v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
+
+ // Get node straight from the block
+ MapNode n = block->getNode(relpos);
+
+ u8 oldlight = n.getLight();
+ u8 newlight = diminish_light(oldlight);
+
+ // 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
+ try{
+ if(block == NULL || blockpos != blockpos_last){
+ block = getBlockNoCreate(blockpos);
+ blockpos_last = blockpos;
+
+ block_checked_in_modified = false;
+ blockchangecount++;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+
+ // Calculate relative position in block
+ v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
+ // Get node straight from the block
+ MapNode n2 = block->getNode(relpos);
+
+ bool changed = false;
+ /*
+ If the neighbor is brighter than the current node,
+ add to list (it will light up this node on its turn)
+ */
+ if(n2.getLight() > undiminish_light(oldlight))
+ {
+ lighted_nodes.insert(n2pos, true);
+ //lighted_nodes.push_back(n2pos);
+ changed = true;
+ }
+ /*
+ If the neighbor is dimmer than how much light this node
+ would spread on it, add to list
+ */
+ if(n2.getLight() < newlight)
+ {
+ if(n2.light_propagates())
+ {
+ n2.setLight(newlight);
+ block->setNode(relpos, n2);
+ lighted_nodes.insert(n2pos, true);
+ //lighted_nodes.push_back(n2pos);
+ changed = true;
+ }
+ }
+
+ // Add to modified_blocks
+ if(changed == true && block_checked_in_modified == false)
+ {
+ // If the block is not found in modified_blocks, add.
+ if(modified_blocks.find(blockpos) == NULL)
+ {
+ modified_blocks.insert(blockpos, block);
+ }
+ block_checked_in_modified = true;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+ }
+ }
+
+ /*dstream<<"spreadLight(): Changed block "
+ <<blockchangecount<<" times"
+ <<" for "<<from_nodes.size()<<" nodes"
+ <<std::endl;*/
+
+ if(lighted_nodes.size() > 0)
+ spreadLight(lighted_nodes, modified_blocks);
+}
+
+/*
+ A single-node source variation of the above.
+*/
+void Map::lightNeighbors(v3s16 pos,
+ core::map<v3s16, MapBlock*> & modified_blocks)
+{
+ core::map<v3s16, bool> from_nodes;
+ from_nodes.insert(pos, true);
+ spreadLight(from_nodes, 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
+ };
+
+ u8 brightest_light = 0;
+ v3s16 brightest_pos(0,0,0);
+ bool found_something = false;
+
+ // 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.getLight() > brightest_light || found_something == false){
+ brightest_light = n2.getLight();
+ brightest_pos = n2pos;
+ found_something = true;
+ }
+ }
+
+ if(found_something == false)
+ throw InvalidPositionException();
+
+ 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<v3s16, MapBlock*> & 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->getNode(relpos);
+
+ if(n.sunlight_propagates())
+ {
+ n.setLight(LIGHT_SUN);
+ block->setNode(relpos, n);
+
+ modified_blocks.insert(blockpos, block);
+ }
+ else{
+ break;
+ }
+ }
+ return y + 1;
+}
+
+void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
+ core::map<v3s16, MapBlock*> & modified_blocks)
+{
+ /*m_dout<<DTIME<<"Map::updateLighting(): "
+ <<a_blocks.getSize()<<" blocks... ";*/
+
+ // For debugging
+ bool debug=false;
+ u32 count_was = modified_blocks.size();
+
+ /*core::list<MapBlock *>::Iterator i = a_blocks.begin();
+ for(; i != a_blocks.end(); i++)
+ {
+ MapBlock *block = *i;*/
+
+ core::map<v3s16, bool> light_sources;
+
+ core::map<v3s16, u8> unlight_from;
+
+ core::map<v3s16, MapBlock*>::Iterator i;
+ i = a_blocks.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapBlock *block = i.getNode()->getValue();
+
+ for(;;)
+ {
+ // Don't bother with dummy blocks.
+ if(block->isDummy())
+ break;
+
+ v3s16 pos = block->getPos();
+ modified_blocks.insert(pos, block);
+
+ /*
+ Clear all light from block
+ */
+ for(s16 z=0; z<MAP_BLOCKSIZE; z++)
+ for(s16 x=0; x<MAP_BLOCKSIZE; x++)
+ for(s16 y=0; y<MAP_BLOCKSIZE; y++)
+ {
+
+ try{
+ v3s16 p(x,y,z);
+ MapNode n = block->getNode(v3s16(x,y,z));
+ u8 oldlight = n.getLight();
+ n.setLight(0);
+ block->setNode(v3s16(x,y,z), n);
+
+ // Collect borders for unlighting
+ if(x==0 || x == MAP_BLOCKSIZE-1
+ || y==0 || y == MAP_BLOCKSIZE-1
+ || z==0 || z == MAP_BLOCKSIZE-1)
+ {
+ v3s16 p_map = p + v3s16(
+ MAP_BLOCKSIZE*pos.X,
+ MAP_BLOCKSIZE*pos.Y,
+ MAP_BLOCKSIZE*pos.Z);
+ unlight_from.insert(p_map, oldlight);
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ /*
+ This would happen when dealing with a
+ dummy block.
+ */
+ //assert(0);
+ dstream<<"updateLighting(): InvalidPositionException"
+ <<std::endl;
+ }
+ }
+
+ bool bottom_valid = block->propagateSunlight(light_sources);
+
+ // If bottom is valid, we're done.
+ if(bottom_valid)
+ break;
+
+ /*dstream<<"Bottom for sunlight-propagated block ("
+ <<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
+ <<std::endl;*/
+
+ // Else get the block below and loop to it
+
+ pos.Y--;
+ try{
+ block = getBlockNoCreate(pos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ assert(0);
+ }
+
+ }
+ }
+
+ {
+ //TimeTaker timer("unspreadLight", g_device);
+ unspreadLight(unlight_from, light_sources, modified_blocks);
+ }
+
+ if(debug)
+ {
+ u32 diff = modified_blocks.size() - count_was;
+ count_was = modified_blocks.size();
+ dstream<<"unspreadLight modified "<<diff<<std::endl;
+ }
+
+ // TODO: Spread light from propagated sunlight?
+ // Yes, add it to light_sources... somehow.
+ // It has to be added at somewhere above, in the loop.
+ // TODO
+
+ {
+ //TimeTaker timer("spreadLight", g_device);
+ spreadLight(light_sources, modified_blocks);
+ }
+
+ if(debug)
+ {
+ u32 diff = modified_blocks.size() - count_was;
+ count_was = modified_blocks.size();
+ dstream<<"spreadLight modified "<<diff<<std::endl;
+ }
+
+ //m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
+}
+
+/*
+ This is called after changing a node from transparent to opaque.
+ The lighting value of the node should be left as-is after changing
+ other values. This sets the lighting value to 0.
+*/
+/*void Map::nodeAddedUpdate(v3s16 p, u8 lightwas,
+ core::map<v3s16, MapBlock*> &modified_blocks)*/
+void Map::addNodeAndUpdate(v3s16 p, MapNode n,
+ core::map<v3s16, MapBlock*> &modified_blocks)
+{
+ /*PrintInfo(m_dout);
+ m_dout<<DTIME<<"Map::nodeAddedUpdate(): p=("
+ <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+ u8 lightwas = getNode(p).getLight();
+
+ //core::list<v3s16> light_sources;
+ core::map<v3s16, bool> light_sources;
+ //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.getLight() != LIGHT_SUN)
+ node_under_sunlight = false;
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ // Add the block of the added node to modified_blocks
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock * block = getBlockNoCreate(blockpos);
+ assert(block != NULL);
+ modified_blocks.insert(blockpos, block);
+
+ if(isValidPosition(p) == false)
+ throw;
+
+ // Unlight neighbours of node.
+ // This means setting light of all consequent dimmer nodes
+ // to 0.
+ // This also collects the nodes at the border which will spread
+ // light again into this.
+ unLightNeighbors(p, lightwas, light_sources, modified_blocks);
+
+ n.setLight(0);
+ setNode(p, n);
+
+ /*
+ If node is under sunlight, take all sunlighted nodes under
+ it and clear light from them and from where the light has
+ been spread.
+ */
+ if(node_under_sunlight)
+ {
+ s16 y = p.Y - 1;
+ for(;; y--){
+ //m_dout<<DTIME<<"y="<<y<<std::endl;
+ v3s16 n2pos(p.X, y, p.Z);
+
+ MapNode n2;
+ try{
+ n2 = getNode(n2pos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ break;
+ }
+
+ if(n2.getLight() == LIGHT_SUN)
+ {
+ //m_dout<<DTIME<<"doing"<<std::endl;
+ unLightNeighbors(n2pos, n2.getLight(), light_sources, modified_blocks);
+ n2.setLight(0);
+ setNode(n2pos, n2);
+ }
+ else
+ break;
+ }
+ }
+
+ /*
+ Spread light from all nodes that might be capable of doing so
+ TODO: Convert to spreadLight
+ */
+ spreadLight(light_sources, modified_blocks);
+}
+
+/*
+*/
+void Map::removeNodeAndUpdate(v3s16 p,
+ core::map<v3s16, MapBlock*> &modified_blocks)
+{
+ /*PrintInfo(m_dout);
+ m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
+ <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+ 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.getLight() != LIGHT_SUN)
+ node_under_sunlight = false;
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ /*
+ Unlight neighbors (in case the node is a light source)
+ */
+ //core::list<v3s16> light_sources;
+ core::map<v3s16, bool> light_sources;
+ unLightNeighbors(p, getNode(p).getLight(),
+ light_sources, modified_blocks);
+
+ /*
+ Remove the node
+ */
+ MapNode n;
+ n.d = MATERIAL_AIR;
+ n.setLight(0);
+ setNode(p, n);
+
+ /*
+ Recalculate lighting
+ */
+ spreadLight(light_sources, modified_blocks);
+
+ // Add the block of the removed node to modified_blocks
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock * block = getBlockNoCreate(blockpos);
+ assert(block != NULL);
+ modified_blocks.insert(blockpos, block);
+
+ /*
+ If the removed node was under sunlight, propagate the
+ sunlight down from it and then light all neighbors
+ of the propagated blocks.
+ */
+ if(node_under_sunlight)
+ {
+ s16 ybottom = propagateSunlight(p, modified_blocks);
+ /*m_dout<<DTIME<<"Node was under sunlight. "
+ "Propagating sunlight";
+ m_dout<<DTIME<<" -> ybottom="<<ybottom<<std::endl;*/
+ s16 y = p.Y;
+ for(; y >= ybottom; y--)
+ {
+ v3s16 p2(p.X, y, p.Z);
+ /*m_dout<<DTIME<<"lighting neighbors of node ("
+ <<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
+ <<std::endl;*/
+ lightNeighbors(p2, modified_blocks);
+ }
+ }
+ else
+ {
+ // Set the lighting of this node to 0
+ try{
+ MapNode n = getNode(p);
+ n.setLight(0);
+ setNode(p, n);
+ }
+ catch(InvalidPositionException &e)
+ {
+ throw;
+ }
+ }
+
+ // Get the brightest neighbour node and propagate light from it
+ v3s16 n2p = getBrightestNeighbour(p);
+ try{
+ MapNode n2 = getNode(n2p);
+ lightNeighbors(n2p, modified_blocks);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+}
+
+void Map::updateMeshes(v3s16 blockpos)
+{
+ assert(mapType() == MAPTYPE_CLIENT);
+
+ try{
+ v3s16 p = blockpos + v3s16(0,0,0);
+ MapBlock *b = getBlockNoCreate(p);
+ b->updateMesh();
+ }
+ catch(InvalidPositionException &e){}
+ try{
+ v3s16 p = blockpos + v3s16(-1,0,0);
+ MapBlock *b = getBlockNoCreate(p);
+ b->updateMesh();
+ }
+ catch(InvalidPositionException &e){}
+ try{
+ v3s16 p = blockpos + v3s16(0,-1,0);
+ MapBlock *b = getBlockNoCreate(p);
+ b->updateMesh();
+ }
+ catch(InvalidPositionException &e){}
+ try{
+ v3s16 p = blockpos + v3s16(0,0,-1);
+ MapBlock *b = getBlockNoCreate(p);
+ b->updateMesh();
+ }
+ catch(InvalidPositionException &e){}
+}
+
+/*
+ Updates usage timers
+*/
+void Map::timerUpdate(float dtime)
+{
+ JMutexAutoLock lock(m_sector_mutex);
+
+ core::map<v2s16, MapSector*>::Iterator si;
+
+ si = m_sectors.getIterator();
+ for(; si.atEnd() == false; si++)
+ {
+ MapSector *sector = si.getNode()->getValue();
+ sector->usage_timer += dtime;
+ }
+}
+
+void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
+{
+ core::list<v2s16>::Iterator j;
+ for(j=list.begin(); j!=list.end(); j++)
+ {
+ MapSector *sector = m_sectors[*j];
+ if(only_blocks)
+ {
+ sector->deleteBlocks();
+ }
+ else
+ {
+ /*
+ If sector is in sector cache, remove it from there
+ */
+ if(m_sector_cache == sector)
+ {
+ m_sector_cache = NULL;
+ }
+ /*
+ Remove from map and delete
+ */
+ m_sectors.remove(*j);
+ delete sector;
+ }
+ }
+}
+
+u32 Map::deleteUnusedSectors(float timeout, bool only_blocks,
+ core::list<v3s16> *deleted_blocks)
+{
+ JMutexAutoLock lock(m_sector_mutex);
+
+ core::list<v2s16> sector_deletion_queue;
+ core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapSector *sector = i.getNode()->getValue();
+ /*
+ Delete sector from memory if it hasn't been used in a long time
+ */
+ if(sector->usage_timer > timeout)
+ {
+ sector_deletion_queue.push_back(i.getNode()->getKey());
+
+ if(deleted_blocks != NULL)
+ {
+ // Collect positions of blocks of sector
+ MapSector *sector = i.getNode()->getValue();
+ core::list<MapBlock*> blocks;
+ sector->getBlocks(blocks);
+ for(core::list<MapBlock*>::Iterator i = blocks.begin();
+ i != blocks.end(); i++)
+ {
+ deleted_blocks->push_back((*i)->getPos());
+ }
+ }
+ }
+ }
+ deleteSectors(sector_deletion_queue, only_blocks);
+ return sector_deletion_queue.getSize();
+}
+
+void Map::PrintInfo(std::ostream &out)
+{
+ out<<"Map: ";
+}
+
+/*
+ ServerMap
+*/
+
+ServerMap::ServerMap(std::string savedir, MapgenParams params):
+ Map(dout_server),
+ m_heightmap(NULL)
+{
+ m_savedir = savedir;
+ m_map_saving_enabled = false;
+
+ try
+ {
+ // If directory exists, check contents and load if possible
+ if(fs::PathExists(m_savedir))
+ {
+ // If directory is empty, it is safe to save into it.
+ if(fs::GetDirListing(m_savedir).size() == 0)
+ {
+ dstream<<DTIME<<"Server: Empty save directory is valid."
+ <<std::endl;
+ m_map_saving_enabled = true;
+ }
+ else
+ {
+ // Load master heightmap
+ loadMasterHeightmap();
+
+ // Load sector (0,0) and throw and exception on fail
+ if(loadSectorFull(v2s16(0,0)) == false)
+ throw LoadError("Failed to load sector (0,0)");
+
+ dstream<<DTIME<<"Server: Successfully loaded master "
+ "heightmap and sector (0,0) from "<<savedir<<
+ ", assuming valid save directory."
+ <<std::endl;
+
+ m_map_saving_enabled = true;
+ // Map loaded, not creating new one
+ return;
+ }
+ }
+ // If directory doesn't exist, it is safe to save to it
+ else{
+ m_map_saving_enabled = true;
+ }
+ }
+ catch(std::exception &e)
+ {
+ dstream<<DTIME<<"Server: Failed to load map from "<<savedir
+ <<", exception: "<<e.what()<<std::endl;
+ dstream<<DTIME<<"Please remove the map or fix it."<<std::endl;
+ dstream<<DTIME<<"WARNING: Map saving will be disabled."<<std::endl;
+ }
+
+ dstream<<DTIME<<"Initializing new map."<<std::endl;
+
+ ValueGenerator *maxgen =
+ ValueGenerator::deSerialize(params.height_randmax);
+ ValueGenerator *factorgen =
+ ValueGenerator::deSerialize(params.height_randfactor);
+ ValueGenerator *basegen =
+ ValueGenerator::deSerialize(params.height_base);
+ m_heightmap = new UnlimitedHeightmap
+ (params.heightmap_blocksize, maxgen, factorgen, basegen);
+
+ // Create zero sector
+ emergeSector(v2s16(0,0));
+
+ // Initially write whole map
+ save(false);
+}
+
+ServerMap::~ServerMap()
+{
+ try
+ {
+ if(m_map_saving_enabled)
+ {
+ //save(false);
+ // Save only changed parts
+ save(true);
+ dstream<<DTIME<<"Server: saved map to "<<m_savedir<<std::endl;
+ }
+ else
+ {
+ dstream<<DTIME<<"Server: map not saved"<<std::endl;
+ }
+ }
+ catch(std::exception &e)
+ {
+ dstream<<DTIME<<"Server: Failed to save map to "<<m_savedir
+ <<", exception: "<<e.what()<<std::endl;
+ }
+
+ if(m_heightmap != NULL)
+ delete m_heightmap;
+}
+
+MapSector * ServerMap::emergeSector(v2s16 p2d)
+{
+ DSTACK("%s: p2d=(%d,%d)",
+ __FUNCTION_NAME,
+ p2d.X, p2d.Y);
+ // Check that it doesn't exist already
+ try{
+ return getSectorNoGenerate(p2d);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ /*
+ Try to load the sector from disk.
+ */
+ if(loadSectorFull(p2d) == true)
+ {
+ return getSectorNoGenerate(p2d);
+ }
+
+ /*
+ If there is no master heightmap, throw.
+ */
+ if(m_heightmap == NULL)
+ {
+ throw InvalidPositionException("emergeSector(): no heightmap");
+ }
+
+ /*
+ Do not generate over-limit
+ */
+ if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
+ throw InvalidPositionException("emergeSector(): pos. over limit");
+
+ /*
+ Generate sector and heightmaps
+ */
+
+ // Number of heightmaps in sector in each direction
+ u16 hm_split = SECTOR_HEIGHTMAP_SPLIT;
+
+ // Heightmap side width
+ s16 hm_d = MAP_BLOCKSIZE / hm_split;
+
+ ServerMapSector *sector = new ServerMapSector(this, p2d, hm_split);
+
+ /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")"
+ " heightmaps and objects"<<std::endl;*/
+
+ // Loop through sub-heightmaps
+ for(s16 y=0; y<hm_split; y++)
+ for(s16 x=0; x<hm_split; x++)
+ {
+ v2s16 p_in_sector = v2s16(x,y);
+ v2s16 mhm_p = p2d * hm_split + p_in_sector;
+ f32 corners[4] = {
+ m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
+ };
+
+ /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
+ <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
+ <<std::endl;*/
+
+ FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
+ mhm_p, hm_d);
+ sector->setHeightmap(p_in_sector, hm);
+
+ //TODO: Make these values configurable
+ hm->generateContinued(1.0, 0.2, corners);
+ //hm->generateContinued(2.0, 0.2, corners);
+
+ //hm->print();
+
+ }
+
+ /*
+ Generate objects
+ */
+
+ core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
+ sector->setObjects(objects);
+
+ v2s16 mhm_p = p2d * hm_split;
+ f32 corners[4] = {
+ m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)*hm_split),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)*hm_split),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)*hm_split),
+ };
+
+ float avgheight = (corners[0]+corners[1]+corners[2]+corners[3])/4.0;
+ float avgslope = 0.0;
+ avgslope += fabs(avgheight - corners[0]);
+ avgslope += fabs(avgheight - corners[1]);
+ avgslope += fabs(avgheight - corners[2]);
+ avgslope += fabs(avgheight - corners[3]);
+ avgslope /= 4.0;
+ avgslope /= MAP_BLOCKSIZE;
+ //dstream<<"avgslope="<<avgslope<<std::endl;
+
+ float pitness = 0.0;
+ v2f32 a;
+ a = m_heightmap->getSlope(p2d+v2s16(0,0));
+ pitness += -a.X;
+ pitness += -a.Y;
+ a = m_heightmap->getSlope(p2d+v2s16(0,1));
+ pitness += -a.X;
+ pitness += a.Y;
+ a = m_heightmap->getSlope(p2d+v2s16(1,1));
+ pitness += a.X;
+ pitness += a.Y;
+ a = m_heightmap->getSlope(p2d+v2s16(1,0));
+ pitness += a.X;
+ pitness += -a.Y;
+ pitness /= 4.0;
+ pitness /= MAP_BLOCKSIZE;
+ //dstream<<"pitness="<<pitness<<std::endl;
+
+ /*
+ Plant some trees if there is not much slope
+ */
+ {
+ // Avgslope is the derivative of a hill
+ float t = avgslope * avgslope;
+ float a = MAP_BLOCKSIZE * 2;
+ u32 tree_max;
+ if(t > 0.03)
+ tree_max = a / (t/0.03);
+ else
+ tree_max = a;
+ u32 count = (rand()%(tree_max+1));
+ //u32 count = tree_max;
+ for(u32 i=0; i<count; i++)
+ {
+ s16 x = (rand()%(MAP_BLOCKSIZE-2))+1;
+ s16 z = (rand()%(MAP_BLOCKSIZE-2))+1;
+ s16 y = sector->getGroundHeight(v2s16(x,z))+1;
+ if(y < WATER_LEVEL)
+ continue;
+ objects->insert(v3s16(x, y, z),
+ SECTOR_OBJECT_TREE_1);
+ }
+ }
+ {
+ // Pitness usually goes at around -0.5...0.5
+ u32 bush_max = 0;
+ u32 a = MAP_BLOCKSIZE * 3;
+ if(pitness > 0)
+ bush_max = (pitness*a*4);
+ if(bush_max > a)
+ bush_max = a;
+ u32 count = (rand()%(bush_max+1));
+ for(u32 i=0; i<count; i++)
+ {
+ s16 x = rand()%(MAP_BLOCKSIZE-0)+0;
+ s16 z = rand()%(MAP_BLOCKSIZE-0)+0;
+ s16 y = sector->getGroundHeight(v2s16(x,z))+1;
+ if(y < WATER_LEVEL)
+ continue;
+ objects->insert(v3s16(x, y, z),
+ SECTOR_OBJECT_BUSH_1);
+ }
+ }
+
+ /*
+ Insert to container
+ */
+ JMutexAutoLock lock(m_sector_mutex);
+ m_sectors.insert(p2d, sector);
+
+ return sector;
+}
+
+MapBlock * ServerMap::emergeBlock(
+ v3s16 p,
+ bool only_from_disk,
+ core::map<v3s16, MapBlock*> &changed_blocks,
+ core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
+)
+{
+ DSTACK("%s: p=(%d,%d,%d), only_from_disk=%d",
+ __FUNCTION_NAME,
+ p.X, p.Y, p.Z, only_from_disk);
+
+ /*dstream<<"ServerMap::emergeBlock(): "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<", only_from_disk="<<only_from_disk<<std::endl;*/
+ v2s16 p2d(p.X, p.Z);
+ s16 block_y = p.Y;
+ /*
+ This will create or load a sector if not found in memory.
+ If block exists on disk, it will be loaded.
+
+ NOTE: On old save formats, this will be slow, as it generates
+ lighting on blocks for them.
+ */
+ ServerMapSector *sector = (ServerMapSector*)emergeSector(p2d);
+ assert(sector->getId() == MAPSECTOR_SERVER);
+
+ // Try to get a block from the sector
+ MapBlock *block = NULL;
+ bool not_on_disk = false;
+ try{
+ block = sector->getBlockNoCreate(block_y);
+ if(block->isDummy() == true)
+ not_on_disk = true;
+ else
+ return block;
+ }
+ catch(InvalidPositionException &e)
+ {
+ not_on_disk = true;
+ }
+
+ /*
+ If block was not found on disk and not going to generate a
+ new one, make sure there is a dummy block in place.
+ */
+ if(not_on_disk && only_from_disk)
+ {
+ if(block == NULL)
+ {
+ // Create dummy block
+ block = new MapBlock(this, p, true);
+
+ // Add block to sector
+ sector->insertBlock(block);
+ }
+ // Done.
+ return block;
+ }
+
+ //dstream<<"Not found on disk, generating."<<std::endl;
+
+ /*
+ Do not generate over-limit
+ */
+ if(blockpos_over_limit(p))
+ throw InvalidPositionException("emergeBlock(): pos. over limit");
+
+ /*
+ OK; Not found.
+
+ Go on generating the block.
+
+ TODO: If a dungeon gets generated so that it's side gets
+ revealed to the outside air, the lighting should be
+ recalculated.
+ */
+
+ /*
+ If block doesn't exist, create one.
+ If it exists, it is a dummy. In that case unDummify() it.
+ */
+ if(block == NULL)
+ {
+ block = sector->createBlankBlockNoInsert(block_y);
+ }
+ else
+ {
+ // Remove the block so that nobody can get a half-generated one.
+ sector->removeBlock(block);
+ // Allocate the block to be a proper one.
+ block->unDummify();
+ }
+
+ // Randomize a bit. This makes dungeons.
+ bool low_block_is_empty = false;
+ if(rand() % 4 == 0)
+ low_block_is_empty = true;
+
+ // This is the basic material of what the visible flat ground
+ // will consist of
+ u8 material = MATERIAL_GRASS;
+
+ s32 lowest_ground_y = 32767;
+
+ // DEBUG
+ //sector->printHeightmaps();
+
+ for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+ for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+ {
+ //dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl;
+ float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
+
+ assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
+
+ s16 surface_y = surface_y_f;
+ //avg_ground_y += surface_y;
+ if(surface_y < lowest_ground_y)
+ lowest_ground_y = surface_y;
+
+ s32 surface_depth = 0;
+
+ float slope = sector->getSlope(v2s16(x0,z0)).getLength();
+
+ float min_slope = 0.45;
+ float max_slope = 0.85;
+ float min_slope_depth = 5.0;
+ float max_slope_depth = 0;
+ if(slope < min_slope)
+ surface_depth = min_slope_depth;
+ else if(slope > max_slope)
+ surface_depth = max_slope_depth;
+ else
+ surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
+
+ for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++){
+ s16 real_y = block_y * MAP_BLOCKSIZE + y0;
+ MapNode n;
+ /*
+ Calculate lighting
+
+ FIXME: If there are some man-made structures above the
+ newly created block, they won't be taken into account.
+ */
+ if(real_y > surface_y)
+ n.setLight(LIGHT_SUN);
+ /*
+ Calculate material
+ */
+ // If node is very low
+ if(real_y <= surface_y - 10){
+ // Create dungeons
+ if(low_block_is_empty){
+ n.d = MATERIAL_AIR;
+ }
+ else{
+ n.d = MATERIAL_STONE;
+ }
+ }
+ // If node is under surface level
+ else if(real_y <= surface_y - surface_depth)
+ n.d = MATERIAL_STONE;
+ // If node is at or under heightmap y
+ else if(real_y <= surface_y)
+ n.d = material;
+ // If node is over heightmap y
+ else{
+ // If under water level, it's water
+ if(real_y < WATER_LEVEL)
+ {
+ n.d = MATERIAL_WATER;
+ n.setLight(diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
+ }
+ // else air
+ else
+ n.d = MATERIAL_AIR;
+ }
+ block->setNode(v3s16(x0,y0,z0), n);
+ }
+ }
+
+ /*
+ Calculate is_underground
+ */
+ // Probably underground if the highest part of block is under lowest
+ // ground height
+ bool is_underground = (block_y+1) * MAP_BLOCKSIZE < lowest_ground_y;
+ block->setIsUnderground(is_underground);
+
+ /*
+ Add some minerals
+ */
+
+ if(is_underground && low_block_is_empty == false)
+ {
+ s16 underground_level = lowest_ground_y/MAP_BLOCKSIZE - block_y;
+ for(s16 i=0; i<underground_level*3; i++)
+ {
+ if(rand()%2 == 0)
+ {
+ v3s16 cp(
+ /*(rand()%(MAP_BLOCKSIZE-4))+2,
+ (rand()%(MAP_BLOCKSIZE-4))+2,
+ (rand()%(MAP_BLOCKSIZE-4))+2*/
+ (rand()%(MAP_BLOCKSIZE-2))+1,
+ (rand()%(MAP_BLOCKSIZE-2))+1,
+ (rand()%(MAP_BLOCKSIZE-2))+1
+ );
+
+ MapNode n;
+ n.d = MATERIAL_MESE;
+
+ if(rand()%8 == 0)
+ block->setNode(cp, n);
+
+ for(u16 i=0; i<26; i++)
+ {
+ if(rand()%8 == 0)
+ block->setNode(cp+g_26dirs[i], n);
+ }
+ }
+ }
+ }
+
+ /*
+ Create a few rats in empty blocks underground
+ */
+ if(is_underground && low_block_is_empty == true)
+ {
+ //for(u16 i=0; i<2; i++)
+ {
+ v3s16 pos(8, 1, 8);
+ RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
+ block->addObject(obj);
+ }
+ }
+
+ /*
+ TODO: REMOVE
+ DEBUG
+ Add some objects to the block for testing.
+ */
+ /*if(p == v3s16(0,0,0))
+ {
+ //TestObject *obj = new TestObject(NULL, -1, v3f(BS*8,BS*8,BS*8));
+ Test2Object *obj = new Test2Object(NULL, -1, v3f(BS*8,BS*15,BS*8));
+ block->addObject(obj);
+ }*/
+
+ /*
+ {
+ v3s16 pos(8, 11, 8);
+ SignObject *obj = new SignObject(NULL, -1, intToFloat(pos));
+ obj->setText("Moicka");
+ obj->setYaw(45);
+ block->addObject(obj);
+ }
+
+ {
+ v3s16 pos(8, 11, 8);
+ RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
+ block->addObject(obj);
+ }
+ */
+
+ /*
+ Add block to sector.
+ */
+ sector->insertBlock(block);
+
+ // An y-wise container if changed blocks
+ core::map<s16, MapBlock*> changed_blocks_sector;
+
+ /*
+ Check if any sector's objects can be placed now.
+ If so, place them.
+ */
+ core::map<v3s16, u8> *objects = sector->getObjects();
+ core::list<v3s16> objects_to_remove;
+ for(core::map<v3s16, u8>::Iterator i = objects->getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ u8 d = i.getNode()->getValue();
+
+ //v3s16 p = p_sector - v3s16(0, block_y*MAP_BLOCKSIZE, 0);
+
+ try
+ {
+
+ if(d == SECTOR_OBJECT_TEST)
+ {
+ if(sector->isValidArea(p + v3s16(0,0,0),
+ p + v3s16(0,0,0), &changed_blocks_sector))
+ {
+ MapNode n;
+ n.d = MATERIAL_LIGHT;
+ sector->setNode(p, n);
+ objects_to_remove.push_back(p);
+ }
+ }
+ else if(d == SECTOR_OBJECT_TREE_1)
+ {
+ v3s16 p_min = p + v3s16(-1,0,-1);
+ v3s16 p_max = p + v3s16(1,4,1);
+ if(sector->isValidArea(p_min, p_max,
+ &changed_blocks_sector))
+ {
+ MapNode n;
+ n.d = MATERIAL_TREE;
+ sector->setNode(p+v3s16(0,0,0), n);
+ sector->setNode(p+v3s16(0,1,0), n);
+ sector->setNode(p+v3s16(0,2,0), n);
+ sector->setNode(p+v3s16(0,3,0), n);
+
+ n.d = MATERIAL_LEAVES;
+
+ sector->setNode(p+v3s16(0,4,0), n);
+
+ sector->setNode(p+v3s16(-1,4,0), n);
+ sector->setNode(p+v3s16(1,4,0), n);
+ sector->setNode(p+v3s16(0,4,-1), n);
+ sector->setNode(p+v3s16(0,4,1), n);
+ sector->setNode(p+v3s16(1,4,1), n);
+ sector->setNode(p+v3s16(-1,4,1), n);
+ sector->setNode(p+v3s16(-1,4,-1), n);
+ sector->setNode(p+v3s16(1,4,-1), n);
+
+ sector->setNode(p+v3s16(-1,3,0), n);
+ sector->setNode(p+v3s16(1,3,0), n);
+ sector->setNode(p+v3s16(0,3,-1), n);
+ sector->setNode(p+v3s16(0,3,1), n);
+ sector->setNode(p+v3s16(1,3,1), n);
+ sector->setNode(p+v3s16(-1,3,1), n);
+ sector->setNode(p+v3s16(-1,3,-1), n);
+ sector->setNode(p+v3s16(1,3,-1), n);
+
+ objects_to_remove.push_back(p);
+
+ // Lighting has to be recalculated for this one.
+ sector->getBlocksInArea(p_min, p_max,
+ lighting_invalidated_blocks);
+ }
+ }
+ else if(d == SECTOR_OBJECT_BUSH_1)
+ {
+ if(sector->isValidArea(p + v3s16(0,0,0),
+ p + v3s16(0,0,0), &changed_blocks_sector))
+ {
+ MapNode n;
+ n.d = MATERIAL_LEAVES;
+ sector->setNode(p+v3s16(0,0,0), n);
+
+ objects_to_remove.push_back(p);
+ }
+ }
+ else
+ {
+ dstream<<"ServerMap::emergeBlock(): "
+ "Invalid heightmap object"
+ <<std::endl;
+ }
+
+ }//try
+ catch(InvalidPositionException &e)
+ {
+ dstream<<"WARNING: "<<__FUNCTION_NAME
+ <<": while inserting object "<<(int)d
+ <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<" InvalidPositionException.what()="
+ <<e.what()<<std::endl;
+ // This is not too fatal and seems to happen sometimes.
+ assert(0);
+ }
+ }
+
+ for(core::list<v3s16>::Iterator i = objects_to_remove.begin();
+ i != objects_to_remove.end(); i++)
+ {
+ objects->remove(*i);
+ }
+
+ for(core::map<s16, MapBlock*>::Iterator
+ i = changed_blocks_sector.getIterator();
+ i.atEnd() == false; i++)
+ {
+ MapBlock *block = i.getNode()->getValue();
+
+ changed_blocks.insert(block->getPos(), block);
+ }
+
+ return block;
+}
+
+void ServerMap::createDir(std::string path)
+{
+ if(fs::CreateDir(path) == false)
+ {
+ m_dout<<DTIME<<"ServerMap: Failed to create directory "
+ <<"\""<<path<<"\""<<std::endl;
+ throw BaseException("ServerMap failed to create directory");
+ }
+}
+
+std::string ServerMap::getSectorSubDir(v2s16 pos)
+{
+ char cc[9];
+ snprintf(cc, 9, "%.4x%.4x",
+ (unsigned int)pos.X&0xffff,
+ (unsigned int)pos.Y&0xffff);
+
+ return std::string(cc);
+}
+
+std::string ServerMap::getSectorDir(v2s16 pos)
+{
+ return m_savedir + "/sectors/" + getSectorSubDir(pos);
+}
+
+v2s16 ServerMap::getSectorPos(std::string dirname)
+{
+ if(dirname.size() != 8)
+ throw InvalidFilenameException("Invalid sector directory name");
+ unsigned int x, y;
+ int r = sscanf(dirname.c_str(), "%4x%4x", &x, &y);
+ if(r != 2)
+ throw InvalidFilenameException("Invalid sector directory name");
+ v2s16 pos((s16)x, (s16)y);
+ return pos;
+}
+
+v3s16 ServerMap::getBlockPos(std::string sectordir, std::string blockfile)
+{
+ v2s16 p2d = getSectorPos(sectordir);
+
+ if(blockfile.size() != 4){
+ throw InvalidFilenameException("Invalid block filename");
+ }
+ unsigned int y;
+ int r = sscanf(blockfile.c_str(), "%4x", &y);
+ if(r != 1)
+ throw InvalidFilenameException("Invalid block filename");
+ return v3s16(p2d.X, y, p2d.Y);
+}
+
+// Debug helpers
+#define ENABLE_SECTOR_SAVING 1
+#define ENABLE_SECTOR_LOADING 1
+#define ENABLE_BLOCK_SAVING 1
+#define ENABLE_BLOCK_LOADING 1
+
+void ServerMap::save(bool only_changed)
+{
+ DSTACK(__FUNCTION_NAME);
+ if(m_map_saving_enabled == false)
+ {
+ dstream<<DTIME<<"WARNING: Not saving map, saving disabled."<<std::endl;
+ return;
+ }
+
+ if(only_changed == false)
+ dstream<<DTIME<<"ServerMap: Saving whole map, this can take time."
+ <<std::endl;
+
+ saveMasterHeightmap();
+
+ u32 sector_meta_count = 0;
+ u32 block_count = 0;
+
+ { //sectorlock
+ JMutexAutoLock lock(m_sector_mutex);
+
+ core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ ServerMapSector *sector = (ServerMapSector*)i.getNode()->getValue();
+ assert(sector->getId() == MAPSECTOR_SERVER);
+
+ if(ENABLE_SECTOR_SAVING)
+ {
+ if(sector->differs_from_disk || only_changed == false)
+ {
+ saveSectorMeta(sector);
+ sector_meta_count++;
+ }
+ }
+ if(ENABLE_BLOCK_SAVING)
+ {
+ core::list<MapBlock*> blocks;
+ sector->getBlocks(blocks);
+ core::list<MapBlock*>::Iterator j;
+ for(j=blocks.begin(); j!=blocks.end(); j++)
+ {
+ MapBlock *block = *j;
+ if(block->getChangedFlag() || only_changed == false)
+ {
+ saveBlock(block);
+ block_count++;
+ }
+ }
+ }
+ }
+
+ }//sectorlock
+
+ u32 deleted_count = 0;
+ deleted_count = deleteUnusedSectors
+ (SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT);
+
+ /*
+ Only print if something happened or saved whole map
+ */
+ if(only_changed == false || sector_meta_count != 0
+ || block_count != 0 || deleted_count != 0)
+ {
+ dstream<<DTIME<<"ServerMap: Written: "
+ <<sector_meta_count<<" sector metadata files, "
+ <<block_count<<" block files, "
+ <<deleted_count<<" sectors unloaded from memory."
+ <<std::endl;
+ }
+}
+
+void ServerMap::loadAll()
+{
+ DSTACK(__FUNCTION_NAME);
+ dstream<<DTIME<<"ServerMap: Loading map..."<<std::endl;
+
+ loadMasterHeightmap();
+
+ std::vector<fs::DirListNode> list = fs::GetDirListing(m_savedir+"/sectors/");
+
+ dstream<<DTIME<<"There are "<<list.size()<<" sectors."<<std::endl;
+
+ JMutexAutoLock lock(m_sector_mutex);
+
+ s32 counter = 0;
+ s32 printed_counter = -100000;
+ s32 count = list.size();
+
+ std::vector<fs::DirListNode>::iterator i;
+ for(i=list.begin(); i!=list.end(); i++)
+ {
+ if(counter > printed_counter + 10)
+ {
+ dstream<<DTIME<<counter<<"/"<<count<<std::endl;
+ printed_counter = counter;
+ }
+ counter++;
+
+ MapSector *sector = NULL;
+
+ // We want directories
+ if(i->dir == false)
+ continue;
+ try{
+ sector = loadSectorMeta(i->name);
+ }
+ catch(InvalidFilenameException &e)
+ {
+ // This catches unknown crap in directory
+ }
+
+ if(ENABLE_BLOCK_LOADING)
+ {
+ std::vector<fs::DirListNode> list2 = fs::GetDirListing
+ (m_savedir+"/sectors/"+i->name);
+ std::vector<fs::DirListNode>::iterator i2;
+ for(i2=list2.begin(); i2!=list2.end(); i2++)
+ {
+ // We want files
+ if(i2->dir)
+ continue;
+ try{
+ loadBlock(i->name, i2->name, sector);
+ }
+ catch(InvalidFilenameException &e)
+ {
+ // This catches unknown crap in directory
+ }
+ }
+ }
+ }
+ dstream<<DTIME<<"ServerMap: Map loaded."<<std::endl;
+}
+
+void ServerMap::saveMasterHeightmap()
+{
+ DSTACK(__FUNCTION_NAME);
+ createDir(m_savedir);
+
+ std::string fullpath = m_savedir + "/master_heightmap";
+ std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+ if(o.good() == false)
+ throw FileNotGoodException("Cannot open master heightmap");
+
+ // Format used for writing
+ u8 version = SER_FMT_VER_HIGHEST;
+
+#if 0
+ SharedBuffer<u8> hmdata = m_heightmap->serialize(version);
+ /*
+ [0] u8 serialization version
+ [1] X master heightmap
+ */
+ u32 fullsize = 1 + hmdata.getSize();
+ SharedBuffer<u8> data(fullsize);
+
+ data[0] = version;
+ memcpy(&data[1], *hmdata, hmdata.getSize());
+
+ o.write((const char*)*data, fullsize);
+#endif
+
+ m_heightmap->serialize(o, version);
+}
+
+void ServerMap::loadMasterHeightmap()
+{
+ DSTACK(__FUNCTION_NAME);
+ std::string fullpath = m_savedir + "/master_heightmap";
+ std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+ if(is.good() == false)
+ throw FileNotGoodException("Cannot open master heightmap");
+
+ if(m_heightmap != NULL)
+ delete m_heightmap;
+
+ m_heightmap = UnlimitedHeightmap::deSerialize(is);
+}
+
+void ServerMap::saveSectorMeta(ServerMapSector *sector)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Format used for writing
+ u8 version = SER_FMT_VER_HIGHEST;
+ // Get destination
+ v2s16 pos = sector->getPos();
+ createDir(m_savedir);
+ createDir(m_savedir+"/sectors");
+ std::string dir = getSectorDir(pos);
+ createDir(dir);
+
+ std::string fullpath = dir + "/heightmap";
+ std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+ if(o.good() == false)
+ throw FileNotGoodException("Cannot open master heightmap");
+
+ sector->serialize(o, version);
+
+ sector->differs_from_disk = false;
+}
+
+MapSector* ServerMap::loadSectorMeta(std::string dirname)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Get destination
+ v2s16 p2d = getSectorPos(dirname);
+ std::string dir = m_savedir + "/sectors/" + dirname;
+
+ std::string fullpath = dir + "/heightmap";
+ std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+ if(is.good() == false)
+ throw FileNotGoodException("Cannot open sector heightmap");
+
+ ServerMapSector *sector = ServerMapSector::deSerialize
+ (is, this, p2d, &m_hwrapper, m_sectors);
+
+ sector->differs_from_disk = false;
+
+ return sector;
+}
+
+bool ServerMap::loadSectorFull(v2s16 p2d)
+{
+ DSTACK(__FUNCTION_NAME);
+ std::string sectorsubdir = getSectorSubDir(p2d);
+
+ MapSector *sector = NULL;
+
+ JMutexAutoLock lock(m_sector_mutex);
+
+ try{
+ sector = loadSectorMeta(sectorsubdir);
+ }
+ catch(InvalidFilenameException &e)
+ {
+ return false;
+ }
+ catch(FileNotGoodException &e)
+ {
+ return false;
+ }
+ catch(std::exception &e)
+ {
+ return false;
+ }
+
+ if(ENABLE_BLOCK_LOADING)
+ {
+ std::vector<fs::DirListNode> list2 = fs::GetDirListing
+ (m_savedir+"/sectors/"+sectorsubdir);
+ std::vector<fs::DirListNode>::iterator i2;
+ for(i2=list2.begin(); i2!=list2.end(); i2++)
+ {
+ // We want files
+ if(i2->dir)
+ continue;
+ try{
+ loadBlock(sectorsubdir, i2->name, sector);
+ }
+ catch(InvalidFilenameException &e)
+ {
+ // This catches unknown crap in directory
+ }
+ }
+ }
+ return true;
+}
+
+#if 0
+bool ServerMap::deFlushSector(v2s16 p2d)
+{
+ DSTACK(__FUNCTION_NAME);
+ // See if it already exists in memory
+ try{
+ MapSector *sector = getSectorNoGenerate(p2d);
+ return true;
+ }
+ catch(InvalidPositionException &e)
+ {
+ /*
+ Try to load the sector from disk.
+ */
+ if(loadSectorFull(p2d) == true)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+void ServerMap::saveBlock(MapBlock *block)
+{
+ DSTACK(__FUNCTION_NAME);
+ /*
+ Dummy blocks are not written
+ */
+ if(block->isDummy())
+ {
+ /*v3s16 p = block->getPos();
+ dstream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+ return;
+ }
+
+ // Format used for writing
+ u8 version = SER_FMT_VER_HIGHEST;
+ // Get destination
+ v3s16 p3d = block->getPos();
+ v2s16 p2d(p3d.X, p3d.Z);
+ createDir(m_savedir);
+ createDir(m_savedir+"/sectors");
+ std::string dir = getSectorDir(p2d);
+ createDir(dir);
+
+ // Block file is map/sectors/xxxxxxxx/xxxx
+ char cc[5];
+ snprintf(cc, 5, "%.4x", (unsigned int)p3d.Y&0xffff);
+ std::string fullpath = dir + "/" + cc;
+ std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+ if(o.good() == false)
+ throw FileNotGoodException("Cannot open block data");
+
+ /*
+ [0] u8 serialization version
+ [1] data
+ */
+ o.write((char*)&version, 1);
+
+ block->serialize(o, version);
+
+ /*
+ Versions up from 9 have block objects.
+ */
+ if(version >= 9)
+ {
+ block->serializeObjects(o, version);
+ }
+
+ // We just wrote it to the disk
+ block->resetChangedFlag();
+}
+
+void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSector *sector)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Block file is map/sectors/xxxxxxxx/xxxx
+ std::string fullpath = m_savedir+"/sectors/"+sectordir+"/"+blockfile;
+ std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+ if(is.good() == false)
+ throw FileNotGoodException("Cannot open block file");
+
+ v3s16 p3d = getBlockPos(sectordir, blockfile);
+ v2s16 p2d(p3d.X, p3d.Z);
+
+ assert(sector->getPos() == p2d);
+
+ u8 version = SER_FMT_VER_INVALID;
+ is.read((char*)&version, 1);
+
+ /*u32 block_size = MapBlock::serializedLength(version);
+ SharedBuffer<u8> data(block_size);
+ is.read((char*)*data, block_size);*/
+
+ // This will always return a sector because we're the server
+ //MapSector *sector = emergeSector(p2d);
+
+ MapBlock *block = NULL;
+ bool created_new = false;
+ try{
+ block = sector->getBlockNoCreate(p3d.Y);
+ }
+ catch(InvalidPositionException &e)
+ {
+ block = sector->createBlankBlockNoInsert(p3d.Y);
+ created_new = true;
+ }
+
+ block->deSerialize(is, version);
+
+ /*
+ Versions up from 9 have block objects.
+ */
+ if(version >= 9)
+ {
+ block->updateObjects(is, version, NULL);
+ }
+
+ if(created_new)
+ sector->insertBlock(block);
+
+ /*
+ Convert old formats to new and save
+ */
+
+ if(version == 0 || version == 1)
+ {
+ dstream<<"Block ("<<p3d.X<<","<<p3d.Y<<","<<p3d.Z<<")"
+ " is in old format. Updating lighting and saving"
+ " modified blocks in new format."<<std::endl;
+
+ // Old version has zero lighting, update it
+ core::map<v3s16, MapBlock*> blocks_changed;
+ blocks_changed.insert(block->getPos(), block);
+ core::map<v3s16, MapBlock*> modified_blocks;
+ updateLighting(blocks_changed, modified_blocks);
+
+ // Close input file
+ is.close();
+
+ // Save modified blocks
+ core::map<v3s16, MapBlock * >::Iterator i = modified_blocks.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapBlock *b2 = i.getNode()->getValue();
+ saveBlock(b2);
+ }
+ }
+ // Save blocks in new format
+ else if(version < SER_FMT_VER_HIGHEST)
+ {
+ saveBlock(block);
+ }
+
+ // We just loaded it from the disk, so it's up-to-date.
+ block->resetChangedFlag();
+}
+
+// Gets from master heightmap
+void ServerMap::getSectorCorners(v2s16 p2d, s16 *corners)
+{
+ assert(m_heightmap != NULL);
+ /*
+ Corner definition:
+ v2s16(0,0),
+ v2s16(1,0),
+ v2s16(1,1),
+ v2s16(0,1),
+ */
+ corners[0] = m_heightmap->getGroundHeight
+ ((p2d+v2s16(0,0))*SECTOR_HEIGHTMAP_SPLIT);
+ corners[1] = m_heightmap->getGroundHeight
+ ((p2d+v2s16(1,0))*SECTOR_HEIGHTMAP_SPLIT);
+ corners[2] = m_heightmap->getGroundHeight
+ ((p2d+v2s16(1,1))*SECTOR_HEIGHTMAP_SPLIT);
+ corners[3] = m_heightmap->getGroundHeight
+ ((p2d+v2s16(0,1))*SECTOR_HEIGHTMAP_SPLIT);
+}
+
+void ServerMap::PrintInfo(std::ostream &out)
+{
+ out<<"ServerMap: ";
+}
+
+/*
+ ClientMap
+*/
+
+ClientMap::ClientMap(
+ Client *client,
+ video::SMaterial *materials,
+ scene::ISceneNode* parent,
+ scene::ISceneManager* mgr,
+ s32 id
+):
+ Map(dout_client),
+ scene::ISceneNode(parent, mgr, id),
+ m_client(client),
+ m_materials(materials),
+ mesh(NULL)
+{
+ /*m_box = core::aabbox3d<f32>(0,0,0,
+ map->getW()*BS, map->getH()*BS, map->getD()*BS);*/
+ /*m_box = core::aabbox3d<f32>(0,0,0,
+ map->getSizeNodes().X * BS,
+ map->getSizeNodes().Y * BS,
+ map->getSizeNodes().Z * BS);*/
+ m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000,
+ BS*1000000,BS*1000000,BS*1000000);
+
+ mesh_mutex.Init();
+}
+
+ClientMap::~ClientMap()
+{
+ JMutexAutoLock lock(mesh_mutex);
+
+ if(mesh != NULL)
+ {
+ mesh->drop();
+ mesh = NULL;
+ }
+}
+
+MapSector * ClientMap::emergeSector(v2s16 p2d)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Check that it doesn't exist already
+ try{
+ return getSectorNoGenerate(p2d);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ // Create a sector with no heightmaps
+ ClientMapSector *sector = new ClientMapSector(this, p2d);
+
+ {
+ JMutexAutoLock lock(m_sector_mutex);
+ m_sectors.insert(p2d, sector);
+ }
+
+ return sector;
+}
+
+void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
+{
+ DSTACK(__FUNCTION_NAME);
+ ClientMapSector *sector = NULL;
+
+ JMutexAutoLock lock(m_sector_mutex);
+
+ core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d);
+
+ if(n != NULL)
+ {
+ sector = (ClientMapSector*)n->getValue();
+ assert(sector->getId() == MAPSECTOR_CLIENT);
+ }
+ else
+ {
+ sector = new ClientMapSector(this, p2d);
+ {
+ JMutexAutoLock lock(m_sector_mutex);
+ m_sectors.insert(p2d, sector);
+ }
+ }
+
+ sector->deSerialize(is);
+}
+
+void ClientMap::renderMap(video::IVideoDriver* driver,
+ video::SMaterial *materials, s32 pass)
+{
+ //m_dout<<DTIME<<"Rendering map..."<<std::endl;
+ DSTACK(__FUNCTION_NAME);
+
+ bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
+#if 0
+ /*
+ Draw master heightmap mesh
+ */
+
+ {
+ JMutexAutoLock lock(mesh_mutex);
+ if(mesh != NULL)
+ {
+ u32 c = mesh->getMeshBufferCount();
+
+ for(u32 i=0; i<c; i++)
+ {
+ scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+ const video::SMaterial& material = buf->getMaterial();
+ video::IMaterialRenderer* rnd =
+ driver->getMaterialRenderer(material.MaterialType);
+ bool transparent = (rnd && rnd->isTransparent());
+ // Render transparent on transparent pass and likewise.
+ if(transparent == is_transparent_pass)
+ {
+ driver->setMaterial(buf->getMaterial());
+ driver->drawMeshBuffer(buf);
+ }
+ }
+ }
+ }
+#endif
+
+ /*
+ Get time for measuring timeout.
+
+ Measuring time is very useful for long delays when the
+ machine is swapping a lot.
+ */
+ int time1 = time(0);
+
+ /*
+ Collect all blocks that are in the view range
+
+ Should not optimize more here as we want to auto-update
+ all changed nodes in viewing range at the next step.
+ */
+
+ s16 viewing_range_nodes;
+ bool viewing_range_all;
+ {
+ JMutexAutoLock lock(g_range_mutex);
+ viewing_range_nodes = g_viewing_range_nodes;
+ viewing_range_all = g_viewing_range_all;
+ }
+
+ m_camera_mutex.Lock();
+ v3f camera_position = m_camera_position;
+ v3f camera_direction = m_camera_direction;
+ m_camera_mutex.Unlock();
+
+ /*
+ Get all blocks and draw all visible ones
+ */
+
+ v3s16 cam_pos_nodes(
+ camera_position.X / BS,
+ camera_position.Y / BS,
+ camera_position.Z / BS);
+
+ v3s16 box_nodes_d = viewing_range_nodes * v3s16(1,1,1);
+
+ v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
+ v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
+
+ // Take a fair amount as we will be dropping more out later
+ v3s16 p_blocks_min(
+ p_nodes_min.X / MAP_BLOCKSIZE - 1,
+ p_nodes_min.Y / MAP_BLOCKSIZE - 1,
+ p_nodes_min.Z / MAP_BLOCKSIZE - 1);
+ v3s16 p_blocks_max(
+ p_nodes_max.X / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Z / MAP_BLOCKSIZE + 1);
+
+ u32 vertex_count = 0;
+
+ core::map<v2s16, MapSector*>::Iterator si;
+
+ //NOTE: The sectors map should be locked but we're not doing it
+ // because it'd cause too much delays
+
+ si = m_sectors.getIterator();
+ for(; si.atEnd() == false; si++)
+ {
+ {
+ static int timecheck_counter = 0;
+ timecheck_counter++;
+ if(timecheck_counter > 50)
+ {
+ int time2 = time(0);
+ if(time2 > time1 + 4)
+ {
+ dstream<<"ClientMap::renderMap(): "
+ "Rendering takes ages, returning."
+ <<std::endl;
+ return;
+ }
+ }
+ }
+
+ MapSector *sector = si.getNode()->getValue();
+ v2s16 sp = sector->getPos();
+
+ if(viewing_range_all == false)
+ {
+ if(sp.X < p_blocks_min.X
+ || sp.X > p_blocks_max.X
+ || sp.Y < p_blocks_min.Z
+ || sp.Y > p_blocks_max.Z)
+ continue;
+ }
+
+ core::list< MapBlock * > sectorblocks;
+ sector->getBlocks(sectorblocks);
+
+ /*
+ Draw blocks
+ */
+
+ core::list< MapBlock * >::Iterator i;
+ for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
+ {
+ MapBlock *block = *i;
+
+ /*
+ Compare block position to camera position, skip
+ if not seen on display
+ */
+
+ 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
+ );
+
+ // Block position relative to camera
+ v3f blockpos_relative = blockpos - camera_position;
+
+ // Distance in camera direction (+=front, -=back)
+ f32 dforward = blockpos_relative.dotProduct(camera_direction);
+
+ // Total distance
+ f32 d = blockpos_relative.getLength();
+
+ if(viewing_range_all == false)
+ {
+ // If block is far away, don't draw it
+ if(d > viewing_range_nodes * BS)
+ continue;
+ }
+
+ // 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 validating further (that is, render it anyway)
+ 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))
+ if(cosangle < cos(FOV_ANGLE/2. * 4./3.))
+ continue;
+ }
+
+ /*
+ Draw the faces of the block
+ */
+
+ {
+ JMutexAutoLock lock(block->mesh_mutex);
+
+ // Cancel if block has no mesh
+ if(block->mesh == NULL)
+ continue;
+
+ u32 c = block->mesh->getMeshBufferCount();
+
+ for(u32 i=0; i<c; i++)
+ {
+ scene::IMeshBuffer *buf = block->mesh->getMeshBuffer(i);
+ const video::SMaterial& material = buf->getMaterial();
+ video::IMaterialRenderer* rnd =
+ driver->getMaterialRenderer(material.MaterialType);
+ bool transparent = (rnd && rnd->isTransparent());
+ // Render transparent on transparent pass and likewise.
+ if(transparent == is_transparent_pass)
+ {
+ driver->setMaterial(buf->getMaterial());
+ driver->drawMeshBuffer(buf);
+ vertex_count += buf->getVertexCount();
+ }
+ }
+ }
+ } // foreach sectorblocks
+ }
+
+ /*dstream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
+ <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
+}
+
+void ClientMap::updateMesh()
+{
+#if 0
+ DSTACK(__FUNCTION_NAME);
+ //TODO
+ /*
+ Check what sectors don't draw anything useful at ground level
+ and create a mesh of the rough heightmap at those positions.
+ */
+
+ m_camera_mutex.Lock();
+ v3f camera_position = m_camera_position;
+ v3f camera_direction = m_camera_direction;
+ m_camera_mutex.Unlock();
+
+ v3s16 cam_pos_nodes(
+ camera_position.X / BS,
+ camera_position.Y / BS,
+ camera_position.Z / BS);
+
+ v3s16 box_nodes_d = HEIGHTMAP_RANGE_NODES * v3s16(1,1,1);
+
+ v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
+ v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
+
+ // Take a fair amount as we will be dropping more out later
+ v3s16 p_blocks_min(
+ p_nodes_min.X / MAP_BLOCKSIZE - 1,
+ p_nodes_min.Y / MAP_BLOCKSIZE - 1,
+ p_nodes_min.Z / MAP_BLOCKSIZE - 1);
+ v3s16 p_blocks_max(
+ p_nodes_max.X / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Z / MAP_BLOCKSIZE + 1);
+
+ /*
+ Initialize new mesh
+ */
+
+ scene::SMesh *mesh_new = new scene::SMesh();
+ //scene::IMeshBuffer *buf = NULL;
+ scene::SMeshBuffer *buf = NULL;
+
+ u8 material_in_use = 0;
+
+ /*
+ Loop through sectors
+ */
+
+ for(core::map<v2s16, MapSector*>::Iterator
+ si = m_sectors.getIterator();
+ si.atEnd() == false; si++)
+ {
+ MapSector *sector = si.getNode()->getValue();
+
+ if(sector->getId() != MAPSECTOR_CLIENT)
+ {
+ dstream<<"WARNING: Client has a non-client sector"
+ <<std::endl;
+ continue;
+ }
+
+ ClientMapSector *cs = (ClientMapSector*)sector;
+
+ v2s16 sp = sector->getPos();
+
+ if(sp.X < p_blocks_min.X
+ || sp.X > p_blocks_max.X
+ || sp.Y < p_blocks_min.Z
+ || sp.Y > p_blocks_max.Z)
+ continue;
+
+ /*
+ Get some ground level info
+ */
+
+ s16 a = -5;
+
+ s16 cn[4] =
+ {
+ cs->getCorner(0)+a,
+ cs->getCorner(1)+a,
+ cs->getCorner(2)+a,
+ cs->getCorner(3)+a,
+ };
+ s16 cn_avg = (cn[0]+cn[1]+cn[2]+cn[3])/4;
+ s16 cn_min = 32767;
+ s16 cn_max = -32768;
+ for(s16 i=0; i<4; i++)
+ {
+ if(cn[i] < cn_min)
+ cn_min = cn[i];
+ if(cn[i] > cn_max)
+ cn_max = cn[i];
+ }
+ s16 cn_slope = cn_max - cn_min;
+
+ /*
+ Generate this part of the heightmap mesh
+ */
+
+ u8 material;
+ if(cn_avg + MAP_BLOCKSIZE/4 <= WATER_LEVEL)
+ material = 0;
+ else if(cn_slope <= MAP_BLOCKSIZE)
+ material = 1;
+ else
+ material = 2;
+
+ if(material != material_in_use || buf == NULL)
+ {
+ // Try to get a meshbuffer associated with the material
+ buf = (scene::SMeshBuffer*)mesh_new->getMeshBuffer
+ (g_mesh_materials[material]);
+ // If not found, create one
+ if(buf == NULL)
+ {
+ // This is a "Standard MeshBuffer",
+ // it's a typedeffed CMeshBuffer<video::S3DVertex>
+ buf = new scene::SMeshBuffer();
+
+ // Set material
+ buf->Material = g_mesh_materials[material];
+ // Use VBO
+ //buf->setHardwareMappingHint(scene::EHM_STATIC);
+ // Add to mesh
+ mesh_new->addMeshBuffer(buf);
+ // Mesh grabbed it
+ buf->drop();
+ }
+ material_in_use = material;
+ }
+
+ // Sector side width in floating-point units
+ f32 sd = BS * MAP_BLOCKSIZE;
+ // Sector position in global floating-point units
+ v3f spf = v3f((f32)sp.X, 0, (f32)sp.Y) * sd;
+
+ //video::SColor c(255,255,255,255);
+ u8 cc = 180;
+ video::SColor c(255,cc,cc,cc);
+
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(spf.X, (f32)BS*cn[0],spf.Z, 0,0,0, c, 0,1),
+ video::S3DVertex(spf.X+sd,(f32)BS*cn[1],spf.Z, 0,0,0, c, 1,1),
+ video::S3DVertex(spf.X+sd,(f32)BS*cn[2],spf.Z+sd,0,0,0, c, 1,0),
+ video::S3DVertex(spf.X, (f32)BS*cn[3],spf.Z+sd,0,0,0, c, 0,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+
+ buf->append(vertices, 4, indices, 6);
+ }
+
+ // Set VBO on
+ //mesh_new->setHardwareMappingHint(scene::EHM_STATIC);
+
+ /*
+ Replace the mesh
+ */
+
+ mesh_mutex.Lock();
+
+ scene::SMesh *mesh_old = mesh;
+
+ //DEBUG
+ /*mesh = NULL;
+ mesh_new->drop();*/
+ mesh = mesh_new;
+
+ mesh_mutex.Unlock();
+
+ if(mesh_old != NULL)
+ {
+ /*dstream<<"mesh_old refcount="<<mesh_old->getReferenceCount()
+ <<std::endl;
+ scene::IMeshBuffer *buf = mesh_new->getMeshBuffer
+ (g_materials[MATERIAL_GRASS]);
+ if(buf != NULL)
+ dstream<<"grass buf refcount="<<buf->getReferenceCount()
+ <<std::endl;*/
+
+ mesh_old->drop();
+ }
+ else
+ {
+ dstream<<"WARNING: There was no old master heightmap mesh"<<std::endl;
+ }
+#endif
+}
+
+void ClientMap::PrintInfo(std::ostream &out)
+{
+ out<<"ClientMap: ";
+}
+
+
diff --git a/src/map.h b/src/map.h
new file mode 100644
index 000000000..ca3b0086b
--- /dev/null
+++ b/src/map.h
@@ -0,0 +1,430 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAP_HEADER
+#define MAP_HEADER
+
+#include <jmutex.h>
+#include <jthread.h>
+#include <iostream>
+#include <malloc.h>
+
+#ifdef _WIN32
+ #include <windows.h>
+ #define sleep_s(x) Sleep((x*1000))
+#else
+ #include <unistd.h>
+ #define sleep_s(x) sleep(x)
+#endif
+
+#include "common_irrlicht.h"
+#include "heightmap.h"
+#include "loadstatus.h"
+#include "mapnode.h"
+#include "mapblock.h"
+#include "mapsector.h"
+#include "constants.h"
+
+class InvalidFilenameException : public BaseException
+{
+public:
+ InvalidFilenameException(const char *s):
+ BaseException(s)
+ {}
+};
+
+#define MAPTYPE_BASE 0
+#define MAPTYPE_SERVER 1
+#define MAPTYPE_CLIENT 2
+
+class Map : public NodeContainer, public Heightmappish
+{
+protected:
+
+ std::ostream &m_dout;
+
+ core::map<v2s16, MapSector*> m_sectors;
+ JMutex m_sector_mutex;
+
+ v3f m_camera_position;
+ v3f m_camera_direction;
+ JMutex m_camera_mutex;
+
+ // Be sure to set this to NULL when the cached sector is deleted
+ MapSector *m_sector_cache;
+ v2s16 m_sector_cache_p;
+
+ WrapperHeightmap m_hwrapper;
+
+public:
+
+ v3s16 drawoffset; // for drawbox()
+
+ Map(std::ostream &dout);
+ virtual ~Map();
+
+ virtual u16 nodeContainerId() const
+ {
+ return NODECONTAINER_ID_MAP;
+ }
+
+ virtual s32 mapType() const
+ {
+ return MAPTYPE_BASE;
+ }
+
+ void updateCamera(v3f pos, v3f dir)
+ {
+ JMutexAutoLock lock(m_camera_mutex);
+ m_camera_position = pos;
+ m_camera_direction = dir;
+ }
+
+ /*void StartUpdater()
+ {
+ updater.Start();
+ }
+
+ void StopUpdater()
+ {
+ updater.setRun(false);
+ while(updater.IsRunning())
+ sleep_s(1);
+ }
+
+ bool UpdaterIsRunning()
+ {
+ return updater.IsRunning();
+ }*/
+
+ static core::aabbox3d<f32> getNodeBox(v3s16 p)
+ {
+ return core::aabbox3d<f32>(
+ (float)p.X * BS - 0.5*BS,
+ (float)p.Y * BS - 0.5*BS,
+ (float)p.Z * BS - 0.5*BS,
+ (float)p.X * BS + 0.5*BS,
+ (float)p.Y * BS + 0.5*BS,
+ (float)p.Z * BS + 0.5*BS
+ );
+ }
+
+ //bool sectorExists(v2s16 p);
+ MapSector * getSectorNoGenerate(v2s16 p2d);
+ /*
+ This is overloaded by ClientMap and ServerMap to allow
+ their differing fetch methods.
+ */
+ virtual MapSector * emergeSector(v2s16 p) = 0;
+
+ // Returns InvalidPositionException if not found
+ MapBlock * getBlockNoCreate(v3s16 p);
+ //virtual MapBlock * getBlock(v3s16 p, bool generate=true);
+
+ // Returns InvalidPositionException if not found
+ f32 getGroundHeight(v2s16 p, bool generate=false);
+ void setGroundHeight(v2s16 p, f32 y, bool generate=false);
+
+ // Returns InvalidPositionException if not found
+ bool isNodeUnderground(v3s16 p);
+
+ // virtual from NodeContainer
+ bool isValidPosition(v3s16 p)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock *blockref;
+ try{
+ blockref = getBlockNoCreate(blockpos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ return false;
+ }
+ return true;
+ /*v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+ bool is_valid = blockref->isValidPosition(relpos);
+ return is_valid;*/
+ }
+
+ // virtual from NodeContainer
+ MapNode getNode(v3s16 p)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock * blockref = getBlockNoCreate(blockpos);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+
+ return blockref->getNode(relpos);
+ }
+
+ // virtual from NodeContainer
+ void setNode(v3s16 p, MapNode & n)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock * blockref = getBlockNoCreate(blockpos);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+ blockref->setNode(relpos, n);
+ }
+
+ /*MapNode getNodeGenerate(v3s16 p)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock * blockref = getBlock(blockpos);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+
+ return blockref->getNode(relpos);
+ }*/
+
+ /*void setNodeGenerate(v3s16 p, MapNode & n)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock * blockref = getBlock(blockpos);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+ blockref->setNode(relpos, n);
+ }*/
+
+ void unspreadLight(core::map<v3s16, u8> & from_nodes,
+ core::map<v3s16, bool> & light_sources,
+ core::map<v3s16, MapBlock*> & modified_blocks);
+
+ void unLightNeighbors(v3s16 pos, u8 lightwas,
+ core::map<v3s16, bool> & light_sources,
+ core::map<v3s16, MapBlock*> & modified_blocks);
+
+ void spreadLight(core::map<v3s16, bool> & from_nodes,
+ core::map<v3s16, MapBlock*> & modified_blocks);
+
+ void lightNeighbors(v3s16 pos,
+ core::map<v3s16, MapBlock*> & modified_blocks);
+
+ v3s16 getBrightestNeighbour(v3s16 p);
+
+ s16 propagateSunlight(v3s16 start,
+ core::map<v3s16, MapBlock*> & modified_blocks);
+
+ void updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
+ core::map<v3s16, MapBlock*> & modified_blocks);
+
+ /*
+ These handle lighting but not faces.
+ */
+ void addNodeAndUpdate(v3s16 p, MapNode n,
+ core::map<v3s16, MapBlock*> &modified_blocks);
+ void removeNodeAndUpdate(v3s16 p,
+ core::map<v3s16, MapBlock*> &modified_blocks);
+
+ /*
+ Updates the faces of the given block and blocks on the
+ leading edge.
+ */
+ void updateMeshes(v3s16 blockpos);
+
+ //core::aabbox3d<s16> getDisplayedBlockArea();
+
+ //bool updateChangedVisibleArea();
+
+ virtual void save(bool only_changed){assert(0);};
+
+ /*
+ Updates usage timers
+ */
+ void timerUpdate(float dtime);
+
+ // Takes cache into account
+ // sector mutex should be locked when calling
+ void deleteSectors(core::list<v2s16> &list, bool only_blocks);
+
+ // Returns count of deleted sectors
+ u32 deleteUnusedSectors(float timeout, bool only_blocks=false,
+ core::list<v3s16> *deleted_blocks=NULL);
+
+ // For debug printing
+ virtual void PrintInfo(std::ostream &out);
+};
+
+struct MapgenParams
+{
+ MapgenParams()
+ {
+ heightmap_blocksize = 64;
+ height_randmax = "constant 70.0";
+ height_randfactor = "constant 0.6";
+ height_base = "linear 0 80 0";
+ plants_amount = "1.0";
+ }
+ s16 heightmap_blocksize;
+ std::string height_randmax;
+ std::string height_randfactor;
+ std::string height_base;
+ std::string plants_amount;
+};
+
+class ServerMap : public Map
+{
+public:
+ /*
+ savedir: directory to which map data should be saved
+ */
+ ServerMap(std::string savedir, MapgenParams params);
+ ~ServerMap();
+
+ s32 mapType() const
+ {
+ return MAPTYPE_SERVER;
+ }
+
+ /*
+ Forcefully get a sector from somewhere
+ */
+ MapSector * emergeSector(v2s16 p);
+ /*
+ Forcefully get a block from somewhere.
+
+ Exceptions:
+ - InvalidPositionException: possible if only_from_disk==true
+
+ changed_blocks:
+ - All already existing blocks that were modified are added.
+ - If found on disk, nothing will be added.
+ - If generated, the new block will not be included.
+
+ lighting_invalidated_blocks:
+ - All blocks that have heavy-to-calculate lighting changes
+ are added.
+ - updateLighting() should be called for these.
+
+ - A block that is in changed_blocks may not be in
+ lighting_invalidated_blocks.
+ */
+ MapBlock * emergeBlock(
+ v3s16 p,
+ bool only_from_disk,
+ core::map<v3s16, MapBlock*> &changed_blocks,
+ core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
+ );
+
+ void createDir(std::string path);
+ void createSaveDir();
+ // returns something like "xxxxxxxx"
+ std::string getSectorSubDir(v2s16 pos);
+ // returns something like "map/sectors/xxxxxxxx"
+ std::string getSectorDir(v2s16 pos);
+ std::string createSectorDir(v2s16 pos);
+ // dirname: final directory name
+ v2s16 getSectorPos(std::string dirname);
+ v3s16 getBlockPos(std::string sectordir, std::string blockfile);
+
+ void save(bool only_changed);
+ void loadAll();
+
+ void saveMasterHeightmap();
+ void loadMasterHeightmap();
+
+ // The sector mutex should be locked when calling most of these
+
+ // This only saves sector-specific data such as the heightmap
+ // (no MapBlocks)
+ void saveSectorMeta(ServerMapSector *sector);
+ MapSector* loadSectorMeta(std::string dirname);
+
+ // Full load of a sector including all blocks.
+ // returns true on success, false on failure.
+ bool loadSectorFull(v2s16 p2d);
+ // If sector is not found in memory, try to load it from disk.
+ // Returns true if sector now resides in memory
+ //bool deFlushSector(v2s16 p2d);
+
+ void saveBlock(MapBlock *block);
+ // This will generate a sector with getSector if not found.
+ void loadBlock(std::string sectordir, std::string blockfile, MapSector *sector);
+
+ // Gets from master heightmap
+ void getSectorCorners(v2s16 p2d, s16 *corners);
+
+ // For debug printing
+ virtual void PrintInfo(std::ostream &out);
+
+private:
+ UnlimitedHeightmap *m_heightmap;
+ std::string m_savedir;
+ bool m_map_saving_enabled;
+};
+
+class Client;
+
+class ClientMap : public Map, public scene::ISceneNode
+{
+public:
+ ClientMap(
+ Client *client,
+ video::SMaterial *materials,
+ scene::ISceneNode* parent,
+ scene::ISceneManager* mgr,
+ s32 id
+ );
+
+ ~ClientMap();
+
+ s32 mapType() const
+ {
+ return MAPTYPE_CLIENT;
+ }
+
+ /*
+ Forcefully get a sector from somewhere
+ */
+ MapSector * emergeSector(v2s16 p);
+
+ void deSerializeSector(v2s16 p2d, std::istream &is);
+
+ /*
+ ISceneNode methods
+ */
+
+ virtual void OnRegisterSceneNode()
+ {
+ if(IsVisible)
+ {
+ //SceneManager->registerNodeForRendering(this, scene::ESNRP_SKY_BOX);
+ SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
+ SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
+ }
+
+ ISceneNode::OnRegisterSceneNode();
+ }
+
+ virtual void render()
+ {
+ video::IVideoDriver* driver = SceneManager->getVideoDriver();
+ driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
+ renderMap(driver, m_materials, SceneManager->getSceneNodeRenderPass());
+ }
+
+ virtual const core::aabbox3d<f32>& getBoundingBox() const
+ {
+ return m_box;
+ }
+
+ void renderMap(video::IVideoDriver* driver,
+ video::SMaterial *materials, s32 pass);
+
+ // Update master heightmap mesh
+ void updateMesh();
+
+ // For debug printing
+ virtual void PrintInfo(std::ostream &out);
+
+private:
+ Client *m_client;
+
+ video::SMaterial *m_materials;
+
+ core::aabbox3d<f32> m_box;
+
+ // This is the master heightmap mesh
+ scene::SMesh *mesh;
+ JMutex mesh_mutex;
+};
+
+#endif
+
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
new file mode 100644
index 000000000..25561008a
--- /dev/null
+++ b/src/mapblock.cpp
@@ -0,0 +1,698 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "mapblock.h"
+#include "map.h"
+// For g_materials
+#include "main.h"
+#include "light.h"
+#include <sstream>
+
+
+/*
+ MapBlock
+*/
+
+bool MapBlock::isValidPositionParent(v3s16 p)
+{
+ if(isValidPosition(p))
+ {
+ return true;
+ }
+ else{
+ return m_parent->isValidPosition(getPosRelative() + p);
+ }
+}
+
+MapNode MapBlock::getNodeParent(v3s16 p)
+{
+ if(isValidPosition(p) == false)
+ {
+ return m_parent->getNode(getPosRelative() + p);
+ }
+ else
+ {
+ if(data == NULL)
+ throw InvalidPositionException();
+ return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
+ }
+}
+
+void MapBlock::setNodeParent(v3s16 p, MapNode & n)
+{
+ if(isValidPosition(p) == false)
+ {
+ m_parent->setNode(getPosRelative() + p, n);
+ }
+ else
+ {
+ if(data == NULL)
+ throw InvalidPositionException();
+ data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
+ }
+}
+
+FastFace * MapBlock::makeFastFace(u8 material, u8 light, v3f p,
+ v3f dir, v3f scale, v3f posRelative_f)
+{
+ FastFace *f = new FastFace;
+
+ // Position is at the center of the cube.
+ v3f pos = p * BS;
+ posRelative_f *= BS;
+
+ v3f vertex_pos[4];
+ // If looking towards z+, this is the face that is behind
+ // the center point, facing towards z+.
+ vertex_pos[0] = v3f( BS/2,-BS/2,BS/2);
+ vertex_pos[1] = v3f(-BS/2,-BS/2,BS/2);
+ vertex_pos[2] = v3f(-BS/2, BS/2,BS/2);
+ vertex_pos[3] = v3f( BS/2, BS/2,BS/2);
+
+ /*
+ TODO: Rotate it the right way (one side comes upside down)
+ */
+ core::CMatrix4<f32> m;
+ m.buildRotateFromTo(v3f(0,0,1), dir);
+
+ for(u16 i=0; i<4; i++){
+ m.rotateVect(vertex_pos[i]);
+ vertex_pos[i].X *= scale.X;
+ vertex_pos[i].Y *= scale.Y;
+ vertex_pos[i].Z *= scale.Z;
+ vertex_pos[i] += pos + posRelative_f;
+ }
+
+ f32 abs_scale = 1.;
+ if (scale.X < 0.999 || scale.X > 1.001) abs_scale = scale.X;
+ else if(scale.Y < 0.999 || scale.Y > 1.001) abs_scale = scale.Y;
+ else if(scale.Z < 0.999 || scale.Z > 1.001) abs_scale = scale.Z;
+
+ v3f zerovector = v3f(0,0,0);
+
+ u8 li = decode_light(light);
+ //u8 li = 150;
+
+ u8 alpha = 255;
+
+ if(material == MATERIAL_WATER)
+ {
+ alpha = 128;
+ }
+
+ video::SColor c = video::SColor(alpha,li,li,li);
+
+ /*f->vertices[0] = video::S3DVertex(vertex_pos[0], zerovector, c,
+ core::vector2d<f32>(0,1));
+ f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c,
+ core::vector2d<f32>(abs_scale,1));
+ f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c,
+ core::vector2d<f32>(abs_scale,0));
+ f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c,
+ core::vector2d<f32>(0,0));*/
+ f->vertices[0] = video::S3DVertex(vertex_pos[0], zerovector, c,
+ core::vector2d<f32>(0,1));
+ f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c,
+ core::vector2d<f32>(abs_scale,1));
+ f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c,
+ core::vector2d<f32>(abs_scale,0));
+ f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c,
+ core::vector2d<f32>(0,0));
+
+ f->material = material;
+
+ return f;
+}
+
+/*
+ Parameters must consist of air and !air.
+ Order doesn't matter.
+
+ If either of the nodes doesn't exist, light is 0.
+*/
+u8 MapBlock::getFaceLight(v3s16 p, v3s16 face_dir)
+{
+ try{
+ MapNode n = getNodeParent(p);
+ MapNode n2 = getNodeParent(p + face_dir);
+ u8 light;
+ if(n.solidness() < n2.solidness())
+ light = n.getLight();
+ else
+ light = n2.getLight();
+
+ // Make some nice difference to different sides
+ if(face_dir.X == 1 || face_dir.Z == 1 || face_dir.Y == -1)
+ light = diminish_light(diminish_light(light));
+ else if(face_dir.X == -1 || face_dir.Z == -1)
+ light = diminish_light(light);
+
+ return light;
+ }
+ catch(InvalidPositionException &e)
+ {
+ return 0;
+ }
+}
+
+/*
+ Gets node material from any place relative to block.
+ Returns MATERIAL_AIR if doesn't exist.
+*/
+u8 MapBlock::getNodeMaterial(v3s16 p)
+{
+ try{
+ MapNode n = getNodeParent(p);
+ return n.d;
+ }
+ catch(InvalidPositionException &e)
+ {
+ return MATERIAL_IGNORE;
+ }
+}
+
+/*
+ startpos:
+ translate_dir: unit vector with only one of x, y or z
+ face_dir: unit vector with only one of x, y or z
+*/
+void MapBlock::updateFastFaceRow(v3s16 startpos,
+ u16 length,
+ v3s16 translate_dir,
+ v3s16 face_dir,
+ core::list<FastFace*> &dest)
+{
+ /*
+ Precalculate some variables
+ */
+ v3f translate_dir_f(translate_dir.X, translate_dir.Y,
+ translate_dir.Z); // floating point conversion
+ v3f face_dir_f(face_dir.X, face_dir.Y,
+ face_dir.Z); // floating point conversion
+ v3f posRelative_f(getPosRelative().X, getPosRelative().Y,
+ getPosRelative().Z); // floating point conversion
+
+ v3s16 p = startpos;
+ /*
+ The light in the air lights the surface is taken from
+ the node that is air.
+ */
+ u8 light = getFaceLight(p, face_dir);
+
+ u16 continuous_materials_count = 0;
+
+ u8 material0 = getNodeMaterial(p);
+ u8 material1 = getNodeMaterial(p + face_dir);
+
+ for(u16 j=0; j<length; j++)
+ {
+ bool next_is_different = true;
+
+ v3s16 p_next;
+ u8 material0_next = 0;
+ u8 material1_next = 0;
+ u8 light_next = 0;
+
+ if(j != length - 1){
+ p_next = p + translate_dir;
+ material0_next = getNodeMaterial(p_next);
+ material1_next = getNodeMaterial(p_next + face_dir);
+ light_next = getFaceLight(p_next, face_dir);
+
+ if(material0_next == material0
+ && material1_next == material1
+ && light_next == light)
+ {
+ next_is_different = false;
+ }
+ }
+
+ continuous_materials_count++;
+
+ if(next_is_different)
+ {
+ /*
+ Create a face if there should be one
+ */
+ u8 mf = face_materials(material0, material1);
+
+ if(mf != 0)
+ {
+ // Floating point conversion of the position vector
+ v3f pf(p.X, p.Y, p.Z);
+ // Center point of face (kind of)
+ v3f sp = pf - ((f32)continuous_materials_count / 2. - 0.5) * translate_dir_f;
+ v3f scale(1,1,1);
+ if(translate_dir.X != 0){
+ scale.X = continuous_materials_count;
+ }
+ if(translate_dir.Y != 0){
+ scale.Y = continuous_materials_count;
+ }
+ if(translate_dir.Z != 0){
+ scale.Z = continuous_materials_count;
+ }
+
+ FastFace *f;
+
+ // If node at sp (material0) is more solid
+ if(mf == 1)
+ {
+ f = makeFastFace(material0, light,
+ sp, face_dir_f, scale,
+ posRelative_f);
+ }
+ // If node at sp is less solid (mf == 2)
+ else
+ {
+ f = makeFastFace(material1, light,
+ sp+face_dir_f, -1*face_dir_f, scale,
+ posRelative_f);
+ }
+ dest.push_back(f);
+ }
+
+ continuous_materials_count = 0;
+ material0 = material0_next;
+ material1 = material1_next;
+ light = light_next;
+ }
+
+ p = p_next;
+ }
+}
+
+void MapBlock::updateMesh()
+{
+ /*v3s16 p = getPosRelative();
+ std::cout<<"MapBlock("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<"::updateMesh(): ";*/
+ //<<"::updateMesh()"<<std::endl;
+
+ /*
+ TODO: Change this to directly generate the mesh (and get rid
+ of FastFaces)
+ */
+
+ core::list<FastFace*> *fastfaces_new = new core::list<FastFace*>;
+
+ /*
+ We are including the faces of the trailing edges of the block.
+ This means that when something changes, the caller must
+ also update the meshes of the blocks at the leading edges.
+ */
+
+ /*
+ Go through every y,z and get top faces in rows of x+
+ */
+ for(s16 y=0; y<MAP_BLOCKSIZE; y++){
+ //for(s16 y=-1; y<MAP_BLOCKSIZE; y++){
+ for(s16 z=0; z<MAP_BLOCKSIZE; z++){
+ updateFastFaceRow(v3s16(0,y,z), MAP_BLOCKSIZE,
+ v3s16(1,0,0),
+ v3s16(0,1,0),
+ *fastfaces_new);
+ }
+ }
+ /*
+ Go through every x,y and get right faces in rows of z+
+ */
+ for(s16 x=0; x<MAP_BLOCKSIZE; x++){
+ //for(s16 x=-1; x<MAP_BLOCKSIZE; x++){
+ for(s16 y=0; y<MAP_BLOCKSIZE; y++){
+ updateFastFaceRow(v3s16(x,y,0), MAP_BLOCKSIZE,
+ v3s16(0,0,1),
+ v3s16(1,0,0),
+ *fastfaces_new);
+ }
+ }
+ /*
+ Go through every y,z and get back faces in rows of x+
+ */
+ for(s16 z=0; z<MAP_BLOCKSIZE; z++){
+ //for(s16 z=-1; z<MAP_BLOCKSIZE; z++){
+ for(s16 y=0; y<MAP_BLOCKSIZE; y++){
+ updateFastFaceRow(v3s16(0,y,z), MAP_BLOCKSIZE,
+ v3s16(1,0,0),
+ v3s16(0,0,1),
+ *fastfaces_new);
+ }
+ }
+
+ scene::SMesh *mesh_new = NULL;
+
+ if(fastfaces_new->getSize() > 0)
+ {
+ mesh_new = new scene::SMesh();
+ scene::IMeshBuffer *buf = NULL;
+
+ core::list<FastFace*>::Iterator i = fastfaces_new->begin();
+
+ // MATERIAL_AIR shouldn't be used by any face
+ u8 material_in_use = MATERIAL_AIR;
+
+ for(; i != fastfaces_new->end(); i++)
+ {
+ FastFace *f = *i;
+
+ if(f->material != material_in_use || buf == NULL)
+ {
+ // Try to get a meshbuffer associated with the material
+ buf = mesh_new->getMeshBuffer(g_materials[f->material]);
+ // If not found, create one
+ if(buf == NULL)
+ {
+ // This is a "Standard MeshBuffer",
+ // it's a typedeffed CMeshBuffer<video::S3DVertex>
+ buf = new scene::SMeshBuffer();
+ // Set material
+ ((scene::SMeshBuffer*)buf)->Material = g_materials[f->material];
+ // Use VBO
+ //buf->setHardwareMappingHint(scene::EHM_STATIC);
+ // Add to mesh
+ mesh_new->addMeshBuffer(buf);
+ // Mesh grabbed it
+ buf->drop();
+ }
+ material_in_use = f->material;
+ }
+
+ u16 indices[] = {0,1,2,2,3,0};
+ buf->append(f->vertices, 4, indices, 6);
+ }
+
+ // Use VBO for mesh (this just would set this for ever buffer)
+ //mesh_new->setHardwareMappingHint(scene::EHM_STATIC);
+
+ /*std::cout<<"MapBlock has "<<fastfaces_new->getSize()<<" faces "
+ <<"and uses "<<mesh_new->getMeshBufferCount()
+ <<" materials"<<std::endl;*/
+ }
+
+ // TODO: Get rid of the FastFace stage
+ core::list<FastFace*>::Iterator i;
+ i = fastfaces_new->begin();
+ for(; i != fastfaces_new->end(); i++)
+ {
+ delete *i;
+ }
+ fastfaces_new->clear();
+ delete fastfaces_new;
+
+ /*
+ Replace the mesh
+ */
+
+ mesh_mutex.Lock();
+
+ scene::SMesh *mesh_old = mesh;
+
+ mesh = mesh_new;
+
+ if(mesh_old != NULL)
+ {
+ // Remove hardware buffers of meshbuffers of mesh
+ // NOTE: No way, this runs in a different thread and everything
+ /*u32 c = mesh_old->getMeshBufferCount();
+ for(u32 i=0; i<c; i++)
+ {
+ IMeshBuffer *buf = mesh_old->getMeshBuffer(i);
+ }*/
+ // Drop the mesh
+ mesh_old->drop();
+ //delete mesh_old;
+ }
+
+ mesh_mutex.Unlock();
+
+ //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
+}
+
+/*
+ Propagates sunlight down through the block.
+ Doesn't modify nodes that are not affected by sunlight.
+
+ Returns false if sunlight at bottom block is invalid
+ Returns true if bottom block doesn't exist.
+
+ If there is a block above, continues from it.
+ If there is no block above, assumes there is sunlight, unless
+ is_underground is set.
+
+ At the moment, all sunlighted nodes are added to light_sources.
+ TODO: This could be optimized.
+*/
+bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources)
+{
+ // Whether the sunlight at the top of the bottom block is valid
+ bool block_below_is_valid = true;
+
+ v3s16 pos_relative = getPosRelative();
+
+ for(s16 x=0; x<MAP_BLOCKSIZE; x++)
+ {
+ for(s16 z=0; z<MAP_BLOCKSIZE; z++)
+ {
+ bool no_sunlight = false;
+ bool no_top_block = false;
+ // Check if node above block has sunlight
+ try{
+ MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
+ if(n.getLight() != LIGHT_SUN)
+ {
+ /*if(is_underground)
+ {
+ no_sunlight = true;
+ }*/
+ no_sunlight = true;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ no_top_block = true;
+
+ // TODO: This makes over-ground roofed places sunlighted
+ // Assume sunlight, unless is_underground==true
+ if(is_underground)
+ {
+ no_sunlight = true;
+ }
+
+ // TODO: There has to be some way to allow this behaviour
+ // As of now, it just makes everything dark.
+ // No sunlight here
+ //no_sunlight = true;
+ }
+
+ /*std::cout<<"("<<x<<","<<z<<"): "
+ <<"no_top_block="<<no_top_block
+ <<", is_underground="<<is_underground
+ <<", no_sunlight="<<no_sunlight
+ <<std::endl;*/
+
+ s16 y = MAP_BLOCKSIZE-1;
+
+ if(no_sunlight == false)
+ {
+ // Continue spreading sunlight downwards through transparent
+ // nodes
+ for(; y >= 0; y--)
+ {
+ v3s16 pos(x, y, z);
+
+ MapNode &n = getNodeRef(pos);
+
+ if(n.sunlight_propagates())
+ {
+ n.setLight(LIGHT_SUN);
+
+ light_sources.insert(pos_relative + pos, true);
+ }
+ else{
+ break;
+ }
+ }
+ }
+
+ bool sunlight_should_go_down = (y==-1);
+
+ // Fill rest with black (only transparent ones)
+ for(; y >= 0; y--){
+ v3s16 pos(x, y, z);
+
+ MapNode &n = getNodeRef(pos);
+
+ if(n.light_propagates())
+ {
+ n.setLight(0);
+ }
+ else{
+ break;
+ }
+ }
+
+ /*
+ If the block below hasn't already been marked invalid:
+
+ Check if the node below the block has proper sunlight at top.
+ If not, the block below is invalid.
+
+ Ignore non-transparent nodes as they always have no light
+ */
+ try
+ {
+ if(block_below_is_valid)
+ {
+ MapNode n = getNodeParent(v3s16(x, -1, z));
+ if(n.light_propagates())
+ {
+ if(n.getLight() == LIGHT_SUN
+ && sunlight_should_go_down == false)
+ block_below_is_valid = false;
+ else if(n.getLight() != LIGHT_SUN
+ && sunlight_should_go_down == true)
+ block_below_is_valid = false;
+ }
+ }//if
+ }//try
+ catch(InvalidPositionException &e)
+ {
+ /*std::cout<<"InvalidBlockException for bottom block node"
+ <<std::endl;*/
+ // Just no block below, no need to panic.
+ }
+ }
+ }
+
+ return block_below_is_valid;
+}
+
+/*
+ Serialization
+*/
+
+void MapBlock::serialize(std::ostream &os, u8 version)
+{
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: MapBlock format not supported");
+
+ if(data == NULL)
+ {
+ throw SerializationError("ERROR: Not writing dummy block.");
+ }
+
+ // These have no compression
+ if(version <= 3 || version == 5 || version == 6)
+ {
+ u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+
+ u32 buflen = 1 + nodecount * MapNode::serializedLength(version);
+ SharedBuffer<u8> dest(buflen);
+
+ dest[0] = is_underground;
+ for(u32 i=0; i<nodecount; i++)
+ {
+ u32 s = 1 + i * MapNode::serializedLength(version);
+ data[i].serialize(&dest[s], version);
+ }
+
+ os.write((char*)*dest, dest.getSize());
+ }
+ // All otherversions
+ else
+ {
+ /*
+ With compression.
+ Compress the materials and the params separately.
+ */
+
+ // First byte
+ os.write((char*)&is_underground, 1);
+
+ u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+
+ // Get and compress materials
+ SharedBuffer<u8> materialdata(nodecount);
+ for(u32 i=0; i<nodecount; i++)
+ {
+ materialdata[i] = data[i].d;
+ }
+ compress(materialdata, os, version);
+
+ // Get and compress params
+ SharedBuffer<u8> paramdata(nodecount);
+ for(u32 i=0; i<nodecount; i++)
+ {
+ paramdata[i] = data[i].param;
+ }
+ compress(paramdata, os, version);
+ }
+}
+
+void MapBlock::deSerialize(std::istream &is, u8 version)
+{
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: MapBlock format not supported");
+
+ // These have no compression
+ if(version <= 3 || version == 5 || version == 6)
+ {
+ u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+ char tmp;
+ is.read(&tmp, 1);
+ if(is.gcount() != 1)
+ throw SerializationError
+ ("MapBlock::deSerialize: no enough input data");
+ is_underground = tmp;
+ for(u32 i=0; i<nodecount; i++)
+ {
+ s32 len = MapNode::serializedLength(version);
+ SharedBuffer<u8> d(len);
+ is.read((char*)*d, len);
+ if(is.gcount() != len)
+ throw SerializationError
+ ("MapBlock::deSerialize: no enough input data");
+ data[i].deSerialize(*d, version);
+ }
+ }
+ // All other versions
+ else
+ {
+ u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+
+ u8 t8;
+ is.read((char*)&t8, 1);
+ is_underground = t8;
+
+ {
+ // Uncompress and set material data
+ std::ostringstream os(std::ios_base::binary);
+ decompress(is, os, version);
+ std::string s = os.str();
+ if(s.size() != nodecount)
+ throw SerializationError
+ ("MapBlock::deSerialize: invalid format");
+ for(u32 i=0; i<s.size(); i++)
+ {
+ data[i].d = s[i];
+ }
+ }
+ {
+ // Uncompress and set param data
+ std::ostringstream os(std::ios_base::binary);
+ decompress(is, os, version);
+ std::string s = os.str();
+ if(s.size() != nodecount)
+ throw SerializationError
+ ("MapBlock::deSerialize: invalid format");
+ for(u32 i=0; i<s.size(); i++)
+ {
+ data[i].param = s[i];
+ }
+ }
+ }
+}
+
+
+//END
diff --git a/src/mapblock.h b/src/mapblock.h
new file mode 100644
index 000000000..9fcfa0793
--- /dev/null
+++ b/src/mapblock.h
@@ -0,0 +1,404 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAPBLOCK_HEADER
+#define MAPBLOCK_HEADER
+
+#include <jmutex.h>
+#include <jmutexautolock.h>
+#include <exception>
+#include "debug.h"
+#include "common_irrlicht.h"
+#include "mapnode.h"
+#include "exceptions.h"
+#include "serialization.h"
+#include "constants.h"
+#include "mapblockobject.h"
+
+#define MAP_BLOCKSIZE 16
+
+// Named by looking towards z+
+enum{
+ FACE_BACK=0,
+ FACE_TOP,
+ FACE_RIGHT,
+ FACE_FRONT,
+ FACE_BOTTOM,
+ FACE_LEFT
+};
+
+struct FastFace
+{
+ u8 material;
+ video::S3DVertex vertices[4]; // Precalculated vertices
+};
+
+enum
+{
+ NODECONTAINER_ID_MAPBLOCK,
+ NODECONTAINER_ID_MAPSECTOR,
+ NODECONTAINER_ID_MAP
+};
+
+class NodeContainer
+{
+public:
+ virtual bool isValidPosition(v3s16 p) = 0;
+ virtual MapNode getNode(v3s16 p) = 0;
+ virtual void setNode(v3s16 p, MapNode & n) = 0;
+ virtual u16 nodeContainerId() const = 0;
+};
+
+class MapBlock : public NodeContainer
+{
+private:
+
+ NodeContainer *m_parent;
+ // Position in blocks on parent
+ v3s16 m_pos;
+ /*
+ If NULL, block is a dummy block.
+ Dummy blocks are used for caching not-found-on-disk blocks.
+ */
+ MapNode * data;
+ /*
+ - On the client, this is used for checking whether to
+ recalculate the face cache. (Is it anymore?)
+ - On the server, this is used for telling whether the
+ block has been changed from the one on disk.
+ */
+ bool changed;
+ /*
+ Used for some initial lighting stuff.
+ At least /has been/ used. 8)
+ */
+ bool is_underground;
+
+ MapBlockObjectList m_objects;
+
+public:
+
+ /*
+ This used by Server's block creation stuff for not sending
+ blocks that are waiting a lighting update.
+
+ If true, the block needs some work by the one who set this
+ to true.
+
+ While true, nobody else should touch the block.
+ */
+ //bool is_incomplete;
+
+ scene::SMesh *mesh;
+ JMutex mesh_mutex;
+
+ MapBlock(NodeContainer *parent, v3s16 pos, bool dummy=false):
+ m_parent(parent),
+ m_pos(pos),
+ changed(true),
+ is_underground(false),
+ m_objects(this)
+ //is_incomplete(false)
+ {
+ data = NULL;
+ if(dummy == false)
+ reallocate();
+ mesh_mutex.Init();
+ mesh = NULL;
+ }
+
+ ~MapBlock()
+ {
+ {
+ JMutexAutoLock lock(mesh_mutex);
+
+ if(mesh != NULL)
+ {
+ mesh->drop();
+ mesh = NULL;
+ }
+ }
+
+ if(data)
+ delete[] data;
+ }
+
+ virtual u16 nodeContainerId() const
+ {
+ return NODECONTAINER_ID_MAPBLOCK;
+ }
+
+ NodeContainer * getParent()
+ {
+ return m_parent;
+ }
+
+ bool isDummy()
+ {
+ return (data == NULL);
+ }
+
+ void unDummify()
+ {
+ assert(isDummy());
+ reallocate();
+ }
+
+ bool getChangedFlag()
+ {
+ return changed;
+ }
+
+ void resetChangedFlag()
+ {
+ changed = false;
+ }
+
+ void setChangedFlag()
+ {
+ changed = true;
+ }
+
+ v3s16 getPos()
+ {
+ return m_pos;
+ }
+
+ v3s16 getPosRelative()
+ {
+ return m_pos * MAP_BLOCKSIZE;
+ }
+
+ bool getIsUnderground()
+ {
+ return is_underground;
+ }
+
+ void setIsUnderground(bool a_is_underground)
+ {
+ is_underground = a_is_underground;
+ setChangedFlag();
+ }
+
+ core::aabbox3d<s16> getBox()
+ {
+ return core::aabbox3d<s16>(getPosRelative(),
+ getPosRelative()
+ + v3s16(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE)
+ - v3s16(1,1,1));
+ }
+
+ void reallocate()
+ {
+ if(data != NULL)
+ delete[] data;
+ u32 l = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE;
+ data = new MapNode[l];
+ for(u32 i=0; i<l; i++){
+ data[i] = MapNode();
+ }
+ setChangedFlag();
+ }
+
+ bool isValidPosition(v3s16 p)
+ {
+ if(data == NULL)
+ return false;
+ return (p.X >= 0 && p.X < MAP_BLOCKSIZE
+ && p.Y >= 0 && p.Y < MAP_BLOCKSIZE
+ && p.Z >= 0 && p.Z < MAP_BLOCKSIZE);
+ }
+
+ /*
+ Regular MapNode get-setters
+ */
+
+ MapNode getNode(s16 x, s16 y, s16 z)
+ {
+ if(data == NULL)
+ throw InvalidPositionException();
+ if(x < 0 || x >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ if(y < 0 || y >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ if(z < 0 || z >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ return data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x];
+ }
+
+ MapNode getNode(v3s16 p)
+ {
+ return getNode(p.X, p.Y, p.Z);
+ }
+
+ void setNode(s16 x, s16 y, s16 z, MapNode & n)
+ {
+ if(data == NULL)
+ throw InvalidPositionException();
+ if(x < 0 || x >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ if(y < 0 || y >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ if(z < 0 || z >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x] = n;
+ setChangedFlag();
+ }
+
+ void setNode(v3s16 p, MapNode & n)
+ {
+ setNode(p.X, p.Y, p.Z, n);
+ }
+
+ /*
+ These functions consult the parent container if the position
+ is not valid on this MapBlock.
+ */
+ bool isValidPositionParent(v3s16 p);
+ MapNode getNodeParent(v3s16 p);
+ void setNodeParent(v3s16 p, MapNode & n);
+
+ void drawbox(s16 x0, s16 y0, s16 z0, s16 w, s16 h, s16 d, MapNode node)
+ {
+ for(u16 z=0; z<d; z++)
+ for(u16 y=0; y<h; y++)
+ for(u16 x=0; x<w; x++)
+ setNode(x0+x, y0+y, z0+z, node);
+ }
+
+ static FastFace * makeFastFace(u8 material, u8 light, v3f p,
+ v3f dir, v3f scale, v3f posRelative_f);
+
+ u8 getFaceLight(v3s16 p, v3s16 face_dir);
+
+ /*
+ Gets node material from any place relative to block.
+ Returns MATERIAL_AIR if doesn't exist.
+ */
+ u8 getNodeMaterial(v3s16 p);
+
+ /*
+ startpos:
+ translate_dir: unit vector with only one of x, y or z
+ face_dir: unit vector with only one of x, y or z
+ */
+ void updateFastFaceRow(v3s16 startpos,
+ u16 length,
+ v3s16 translate_dir,
+ v3s16 face_dir,
+ core::list<FastFace*> &dest);
+
+ void updateMesh();
+
+ bool propagateSunlight(core::map<v3s16, bool> & light_sources);
+
+ // Doesn't write version by itself
+ void serialize(std::ostream &os, u8 version);
+
+ void deSerialize(std::istream &is, u8 version);
+
+ void serializeObjects(std::ostream &os, u8 version)
+ {
+ m_objects.serialize(os, version);
+ }
+ // If smgr!=NULL, new objects are added to the scene
+ void updateObjects(std::istream &is, u8 version,
+ scene::ISceneManager *smgr)
+ {
+ m_objects.update(is, version, smgr);
+
+ setChangedFlag();
+ }
+ void clearObjects()
+ {
+ m_objects.clear();
+
+ setChangedFlag();
+ }
+ void addObject(MapBlockObject *object)
+ throw(ContainerFullException, AlreadyExistsException)
+ {
+ m_objects.add(object);
+
+ setChangedFlag();
+ }
+ void removeObject(s16 id)
+ {
+ m_objects.remove(id);
+
+ setChangedFlag();
+ }
+ MapBlockObject * getObject(s16 id)
+ {
+ return m_objects.get(id);
+ }
+ JMutexAutoLock * getObjectLock()
+ {
+ return m_objects.getLock();
+ }
+ void stepObjects(float dtime, bool server)
+ {
+ m_objects.step(dtime, server);
+
+ setChangedFlag();
+ }
+
+ /*void wrapObject(MapBlockObject *object)
+ {
+ m_objects.wrapObject(object);
+
+ setChangedFlag();
+ }*/
+
+ // origin is relative to block
+ void getObjects(v3f origin, f32 max_d,
+ core::array<DistanceSortedObject> &dest)
+ {
+ m_objects.getObjects(origin, max_d, dest);
+ }
+
+private:
+
+ /*
+ Used only internally, because changes can't be tracked
+ */
+
+ MapNode & getNodeRef(s16 x, s16 y, s16 z)
+ {
+ if(x < 0 || x >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ if(y < 0 || y >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ if(z < 0 || z >= MAP_BLOCKSIZE) throw InvalidPositionException();
+ return data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x];
+ }
+ MapNode & getNodeRef(v3s16 &p)
+ {
+ return getNodeRef(p.X, p.Y, p.Z);
+ }
+};
+
+inline bool blockpos_over_limit(v3s16 p)
+{
+ return
+ (p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE);
+}
+
+/*
+ Returns the position of the block where the node is located
+*/
+inline v3s16 getNodeBlockPos(v3s16 p)
+{
+ return getContainerPos(p, MAP_BLOCKSIZE);
+}
+
+inline v2s16 getNodeSectorPos(v2s16 p)
+{
+ return getContainerPos(p, MAP_BLOCKSIZE);
+}
+
+inline s16 getNodeBlockY(s16 y)
+{
+ return getContainerPos(y, MAP_BLOCKSIZE);
+}
+
+#endif
+
diff --git a/src/mapblockobject.cpp b/src/mapblockobject.cpp
new file mode 100644
index 000000000..985a01dc1
--- /dev/null
+++ b/src/mapblockobject.cpp
@@ -0,0 +1,641 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "mapblockobject.h"
+#include "mapblock.h"
+// Only for ::getNodeBox, TODO: Get rid of this
+#include "map.h"
+
+/*
+ MapBlockObject
+*/
+
+// This is here because it uses the MapBlock
+v3f MapBlockObject::getAbsolutePos()
+{
+ if(m_block == NULL)
+ return m_pos;
+
+ // getPosRelative gets nodepos relative to map origin
+ v3f blockpos = intToFloat(m_block->getPosRelative());
+ return blockpos + m_pos;
+}
+
+void MapBlockObject::setBlockChanged()
+{
+ if(m_block)
+ m_block->setChangedFlag();
+}
+
+/*
+ MovingObject
+*/
+void MovingObject::move(float dtime, v3f acceleration)
+{
+ //m_pos += dtime * 3.0;
+
+ v3s16 oldpos_i = floatToInt(m_pos);
+
+ if(m_block->isValidPosition(oldpos_i) == false)
+ {
+ // Should have wrapped, cancelling further movement.
+ return;
+ }
+
+ // No collisions if there is no collision box
+ if(m_collision_box == NULL)
+ {
+ m_speed += dtime * acceleration;
+ m_pos += m_speed * dtime;
+ return;
+ }
+
+ v3f position = m_pos;
+ v3f oldpos = position;
+
+ /*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
+ <<oldpos_i.Z<<")"<<std::endl;*/
+
+ // Maximum time increment (for collision detection etc)
+ // Allow 0.1 blocks per increment
+ // time = distance / speed
+ // NOTE: In the loop below collisions are detected at 0.15*BS radius
+ float speedlength = m_speed.getLength();
+ f32 dtime_max_increment;
+ if(fabs(speedlength) > 0.001)
+ dtime_max_increment = 0.1*BS / speedlength;
+ else
+ dtime_max_increment = 0.5;
+
+ m_touching_ground = false;
+
+ u32 loopcount = 0;
+ do
+ {
+ loopcount++;
+
+ f32 dtime_part;
+ if(dtime > dtime_max_increment)
+ dtime_part = dtime_max_increment;
+ else
+ dtime_part = dtime;
+ dtime -= dtime_part;
+
+ // Begin of dtime limited code
+
+ m_speed += acceleration * dtime_part;
+ position += m_speed * dtime_part;
+
+ /*
+ Collision detection
+ */
+
+ v3s16 pos_i = floatToInt(position);
+
+ // The loop length is limited to the object moving a distance
+ f32 d = (float)BS * 0.15;
+
+ core::aabbox3d<f32> objectbox(
+ m_collision_box->MinEdge + position,
+ m_collision_box->MaxEdge + position
+ );
+
+ core::aabbox3d<f32> objectbox_old(
+ m_collision_box->MinEdge + oldpos,
+ m_collision_box->MaxEdge + oldpos
+ );
+
+ //TODO: Get these ranges from somewhere
+ for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
+ for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
+ for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
+ {
+ try{
+ if(m_block->getNodeParent(v3s16(x,y,z)).d == MATERIAL_AIR){
+ continue;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ // Doing nothing here will block the player from
+ // walking over map borders
+ }
+
+ core::aabbox3d<f32> nodebox = Map::getNodeBox(
+ v3s16(x,y,z));
+
+ // See if the player is touching ground
+ if(
+ fabs(nodebox.MaxEdge.Y-objectbox.MinEdge.Y) < d
+ && nodebox.MaxEdge.X-d > objectbox.MinEdge.X
+ && nodebox.MinEdge.X+d < objectbox.MaxEdge.X
+ && nodebox.MaxEdge.Z-d > objectbox.MinEdge.Z
+ && nodebox.MinEdge.Z+d < objectbox.MaxEdge.Z
+ ){
+ m_touching_ground = true;
+ }
+
+ if(objectbox.intersectsWithBox(nodebox))
+ {
+
+ v3f dirs[3] = {
+ v3f(0,0,1), // back
+ v3f(0,1,0), // top
+ v3f(1,0,0), // right
+ };
+ for(u16 i=0; i<3; i++)
+ {
+ f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
+ f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
+ f32 playermax = objectbox.MaxEdge.dotProduct(dirs[i]);
+ f32 playermin = objectbox.MinEdge.dotProduct(dirs[i]);
+ f32 playermax_old = objectbox_old.MaxEdge.dotProduct(dirs[i]);
+ f32 playermin_old = objectbox_old.MinEdge.dotProduct(dirs[i]);
+
+ bool main_edge_collides =
+ ((nodemax > playermin && nodemax <= playermin_old + d
+ && m_speed.dotProduct(dirs[i]) < 0)
+ ||
+ (nodemin < playermax && nodemin >= playermax_old - d
+ && m_speed.dotProduct(dirs[i]) > 0));
+
+ bool other_edges_collide = true;
+ for(u16 j=0; j<3; j++)
+ {
+ if(j == i)
+ continue;
+ f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
+ f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
+ f32 playermax = objectbox.MaxEdge.dotProduct(dirs[j]);
+ f32 playermin = objectbox.MinEdge.dotProduct(dirs[j]);
+ if(!(nodemax - d > playermin && nodemin + d < playermax))
+ {
+ other_edges_collide = false;
+ break;
+ }
+ }
+
+ if(main_edge_collides && other_edges_collide)
+ {
+ m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
+ position -= position.dotProduct(dirs[i]) * dirs[i];
+ position += oldpos.dotProduct(dirs[i]) * dirs[i];
+ }
+
+ }
+
+ } // if(objectbox.intersectsWithBox(nodebox))
+ } // for y
+
+ } // End of dtime limited loop
+ while(dtime > 0.001);
+
+ m_pos = position;
+}
+
+/*
+ MapBlockObjectList
+*/
+
+MapBlockObjectList::MapBlockObjectList(MapBlock *block):
+ m_block(block)
+{
+ m_mutex.Init();
+}
+
+MapBlockObjectList::~MapBlockObjectList()
+{
+ clear();
+}
+
+/*
+ The serialization format:
+ [0] u16 number of entries
+ [2] entries (id, typeId, parameters)
+*/
+
+void MapBlockObjectList::serialize(std::ostream &os, u8 version)
+{
+ JMutexAutoLock lock(m_mutex);
+
+ u8 buf[2];
+ writeU16(buf, m_objects.size());
+ os.write((char*)buf, 2);
+
+ for(core::map<s16, MapBlockObject*>::Iterator
+ i = m_objects.getIterator();
+ i.atEnd() == false; i++)
+ {
+ i.getNode()->getValue()->serialize(os, version);
+ }
+}
+
+void MapBlockObjectList::update(std::istream &is, u8 version,
+ scene::ISceneManager *smgr)
+{
+ JMutexAutoLock lock(m_mutex);
+
+ /*
+ Collect all existing ids to a set.
+
+ As things are updated, they are removed from this.
+
+ All remaining ones are deleted.
+ */
+ core::map<s16, bool> ids_to_delete;
+ for(core::map<s16, MapBlockObject*>::Iterator
+ i = m_objects.getIterator();
+ i.atEnd() == false; i++)
+ {
+ ids_to_delete.insert(i.getNode()->getKey(), true);
+ }
+
+ u8 buf[6];
+
+ is.read((char*)buf, 2);
+ u16 count = readU16(buf);
+
+ for(u16 i=0; i<count; i++)
+ {
+ // Read id
+ is.read((char*)buf, 2);
+ s16 id = readS16(buf);
+
+ // Read position
+ // stored as x1000/BS v3s16
+ is.read((char*)buf, 6);
+ v3s16 pos_i = readV3S16(buf);
+ v3f pos((f32)pos_i.X/1000*BS,
+ (f32)pos_i.Y/1000*BS,
+ (f32)pos_i.Z/1000*BS);
+
+ // Read typeId
+ is.read((char*)buf, 2);
+ u16 type_id = readU16(buf);
+
+ bool create_new = false;
+
+ // Find an object with the id
+ core::map<s16, MapBlockObject*>::Node *n;
+ n = m_objects.find(id);
+ // If no entry is found for id
+ if(n == NULL)
+ {
+ // Insert dummy pointer node
+ m_objects.insert(id, NULL);
+ // Get node
+ n = m_objects.find(id);
+ // A new object will be created at this node
+ create_new = true;
+ }
+ // If type_id differs
+ else if(n->getValue()->getTypeId() != type_id)
+ {
+ // Delete old object
+ delete n->getValue();
+ // A new object will be created at this node
+ create_new = true;
+ }
+
+ MapBlockObject *obj = NULL;
+
+ if(create_new)
+ {
+ /*dstream<<"MapBlockObjectList adding new object"
+ " id="<<id
+ <<std::endl;*/
+
+ if(type_id == MAPBLOCKOBJECT_TYPE_TEST)
+ {
+ // The constructors of objects shouldn't need
+ // any more parameters than this.
+ obj = new TestObject(m_block, id, pos);
+ }
+ else if(type_id == MAPBLOCKOBJECT_TYPE_TEST2)
+ {
+ obj = new Test2Object(m_block, id, pos);
+ }
+ else if(type_id == MAPBLOCKOBJECT_TYPE_SIGN)
+ {
+ obj = new SignObject(m_block, id, pos);
+ }
+ else if(type_id == MAPBLOCKOBJECT_TYPE_RAT)
+ {
+ obj = new RatObject(m_block, id, pos);
+ }
+ else
+ {
+ throw SerializationError
+ ("MapBlockObjectList::update(): Unknown MapBlockObject type");
+ }
+
+ if(smgr != NULL)
+ obj->addToScene(smgr);
+
+ n->setValue(obj);
+ }
+ else
+ {
+ obj = n->getValue();
+ obj->updatePos(pos);
+ }
+
+ // Now there is an object in obj.
+ // Update it.
+
+ obj->update(is, version);
+
+ // Remove from deletion list
+ if(ids_to_delete.find(id) != NULL)
+ ids_to_delete.remove(id);
+ }
+
+ // Delete all objects whose ids_to_delete remain in ids_to_delete
+ for(core::map<s16, bool>::Iterator
+ i = ids_to_delete.getIterator();
+ i.atEnd() == false; i++)
+ {
+ s16 id = i.getNode()->getKey();
+
+ /*dstream<<"MapBlockObjectList deleting object"
+ " id="<<id
+ <<std::endl;*/
+
+ MapBlockObject *obj = m_objects[id];
+ obj->removeFromScene();
+ delete obj;
+ m_objects.remove(id);
+ }
+}
+
+s16 MapBlockObjectList::getFreeId() throw(ContainerFullException)
+{
+ s16 id = 0;
+ for(;;)
+ {
+ if(m_objects.find(id) == NULL)
+ return id;
+ if(id == 32767)
+ throw ContainerFullException
+ ("MapBlockObjectList doesn't fit more objects");
+ id++;
+ }
+}
+
+void MapBlockObjectList::add(MapBlockObject *object)
+ throw(ContainerFullException, AlreadyExistsException)
+{
+ if(object == NULL)
+ {
+ dstream<<"MapBlockObjectList::add(): NULL object"<<std::endl;
+ return;
+ }
+
+ JMutexAutoLock lock(m_mutex);
+
+ // Create unique id if id==-1
+ if(object->m_id == -1)
+ {
+ object->m_id = getFreeId();
+ }
+
+ if(m_objects.find(object->m_id) != NULL)
+ {
+ dstream<<"MapBlockObjectList::add(): "
+ "object with same id already exists"<<std::endl;
+ throw AlreadyExistsException
+ ("MapBlockObjectList already has given id");
+ }
+
+ object->m_block = m_block;
+
+ /*v3f p = object->m_pos;
+ dstream<<"MapBlockObjectList::add(): "
+ <<"m_block->getPos()=("
+ <<m_block->getPos().X<<","
+ <<m_block->getPos().Y<<","
+ <<m_block->getPos().Z<<")"
+ <<" inserting object with id="<<object->m_id
+ <<" pos="
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<std::endl;*/
+
+ m_objects.insert(object->m_id, object);
+}
+
+void MapBlockObjectList::clear()
+{
+ JMutexAutoLock lock(m_mutex);
+
+ for(core::map<s16, MapBlockObject*>::Iterator
+ i = m_objects.getIterator();
+ i.atEnd() == false; i++)
+ {
+ MapBlockObject *obj = i.getNode()->getValue();
+ //FIXME: This really shouldn't be NULL at any time,
+ // but this condition was added because it was.
+ if(obj != NULL)
+ {
+ obj->removeFromScene();
+ delete obj;
+ }
+ }
+
+ m_objects.clear();
+}
+
+void MapBlockObjectList::remove(s16 id)
+{
+ JMutexAutoLock lock(m_mutex);
+
+ core::map<s16, MapBlockObject*>::Node *n;
+ n = m_objects.find(id);
+ if(n == NULL)
+ return;
+
+ n->getValue()->removeFromScene();
+ delete n->getValue();
+ m_objects.remove(id);
+}
+
+MapBlockObject * MapBlockObjectList::get(s16 id)
+{
+ core::map<s16, MapBlockObject*>::Node *n;
+ n = m_objects.find(id);
+ if(n == NULL)
+ return NULL;
+ else
+ return n->getValue();
+}
+
+void MapBlockObjectList::step(float dtime, bool server)
+{
+ JMutexAutoLock lock(m_mutex);
+
+ core::map<s16, bool> ids_to_delete;
+
+ for(core::map<s16, MapBlockObject*>::Iterator
+ i = m_objects.getIterator();
+ i.atEnd() == false; i++)
+ {
+ MapBlockObject *obj = i.getNode()->getValue();
+
+ if(server)
+ {
+ bool to_delete = obj->serverStep(dtime);
+
+ if(to_delete)
+ ids_to_delete.insert(obj->m_id, true);
+ }
+ else
+ {
+ obj->clientStep(dtime);
+ }
+ }
+
+ // Delete objects in delete queue
+ for(core::map<s16, bool>::Iterator
+ i = ids_to_delete.getIterator();
+ i.atEnd() == false; i++)
+ {
+ s16 id = i.getNode()->getKey();
+
+ MapBlockObject *obj = m_objects[id];
+ obj->removeFromScene();
+ delete obj;
+ m_objects.remove(id);
+ }
+
+ /*
+ Wrap objects on server
+ */
+
+ if(server == false)
+ return;
+
+ for(core::map<s16, MapBlockObject*>::Iterator
+ i = m_objects.getIterator();
+ i.atEnd() == false; i++)
+ {
+ MapBlockObject *obj = i.getNode()->getValue();
+
+ v3s16 pos_i = floatToInt(obj->m_pos);
+
+ if(m_block->isValidPosition(pos_i))
+ {
+ // No wrap
+ continue;
+ }
+
+ bool impossible = wrapObject(obj);
+
+ if(impossible)
+ {
+ // No wrap
+ continue;
+ }
+
+ // Restart find
+ i = m_objects.getIterator();
+ }
+}
+
+bool MapBlockObjectList::wrapObject(MapBlockObject *object)
+{
+ // No lock here; this is called so that the lock is already locked.
+ //JMutexAutoLock lock(m_mutex);
+
+ assert(object->m_block == m_block);
+ assert(m_objects.find(object->m_id) != NULL);
+ assert(m_objects[object->m_id] == object);
+
+ NodeContainer *parentcontainer = m_block->getParent();
+ // This will only work if the parent is the map
+ if(parentcontainer->nodeContainerId() != NODECONTAINER_ID_MAP)
+ {
+ dstream<<"WARNING: Wrapping object not possible: "
+ "MapBlock's parent is not map"<<std::endl;
+ return true;
+ }
+ // OK, we have the map!
+ Map *map = (Map*)parentcontainer;
+
+ // Calculate blockpos on map
+ v3s16 oldblock_pos_i_on_map = m_block->getPosRelative();
+ v3f pos_f_on_oldblock = object->m_pos;
+ v3s16 pos_i_on_oldblock = floatToInt(pos_f_on_oldblock);
+ v3s16 pos_i_on_map = pos_i_on_oldblock + oldblock_pos_i_on_map;
+ v3s16 pos_blocks_on_map = getNodeBlockPos(pos_i_on_map);
+
+ // Get new block
+ MapBlock *newblock;
+ try{
+ newblock = map->getBlockNoCreate(pos_blocks_on_map);
+ }
+ catch(InvalidPositionException &e)
+ {
+ // Couldn't find block -> not wrapping
+ /*dstream<<"WARNING: Wrapping object not possible: "
+ <<"could not find new block"
+ <<"("<<pos_blocks_on_map.X
+ <<","<<pos_blocks_on_map.Y
+ <<","<<pos_blocks_on_map.Z
+ <<")"<<std::endl;*/
+ /*dstream<<"pos_f_on_oldblock=("
+ <<pos_f_on_oldblock.X<<","
+ <<pos_f_on_oldblock.Y<<","
+ <<pos_f_on_oldblock.Z<<")"
+ <<std::endl;*/
+ return true;
+ }
+
+ if(newblock == m_block)
+ {
+ dstream<<"WARNING: Wrapping object not possible: "
+ "newblock == oldblock"<<std::endl;
+ return true;
+ }
+
+ // Calculate position on new block
+ v3f oldblock_pos_f_on_map = intToFloat(oldblock_pos_i_on_map);
+ v3s16 newblock_pos_i_on_map = newblock->getPosRelative();
+ v3f newblock_pos_f_on_map = intToFloat(newblock_pos_i_on_map);
+ v3f pos_f_on_newblock = pos_f_on_oldblock
+ - newblock_pos_f_on_map + oldblock_pos_f_on_map;
+
+ // Remove object from this block
+ m_objects.remove(object->m_id);
+
+ // Add object to new block
+ object->m_pos = pos_f_on_newblock;
+ object->m_id = -1;
+ object->m_block = NULL;
+ newblock->addObject(object);
+
+ //dstream<<"NOTE: Wrapped object"<<std::endl;
+
+ return false;
+}
+
+void MapBlockObjectList::getObjects(v3f origin, f32 max_d,
+ core::array<DistanceSortedObject> &dest)
+{
+ for(core::map<s16, MapBlockObject*>::Iterator
+ i = m_objects.getIterator();
+ i.atEnd() == false; i++)
+ {
+ MapBlockObject *obj = i.getNode()->getValue();
+
+ f32 d = (obj->m_pos - origin).getLength();
+
+ if(d > max_d)
+ continue;
+
+ DistanceSortedObject dso(obj, d);
+
+ dest.push_back(dso);
+ }
+}
+
+//END
diff --git a/src/mapblockobject.h b/src/mapblockobject.h
new file mode 100644
index 000000000..1939cc896
--- /dev/null
+++ b/src/mapblockobject.h
@@ -0,0 +1,892 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAPBLOCKOBJECT_HEADER
+#define MAPBLOCKOBJECT_HEADER
+
+#include "common_irrlicht.h"
+#include <math.h>
+#include <string>
+#include "serialization.h"
+#include "mapnode.h"
+#include "constants.h"
+
+enum
+{
+ MAPBLOCKOBJECT_TYPE_TEST=0,
+ MAPBLOCKOBJECT_TYPE_TEST2=1,
+ MAPBLOCKOBJECT_TYPE_SIGN=2,
+ MAPBLOCKOBJECT_TYPE_RAT=3,
+};
+
+class MapBlock;
+
+class MapBlockObject
+{
+public:
+ MapBlockObject(MapBlock *block, s16 id, v3f pos):
+ m_collision_box(NULL),
+ m_selection_box(NULL),
+ m_block(block),
+ m_id(id),
+ m_pos(pos)
+ {
+ }
+ virtual ~MapBlockObject()
+ {
+ }
+
+ s16 getId()
+ {
+ return m_id;
+ }
+ MapBlock* getBlock()
+ {
+ return m_block;
+ }
+
+ // Writes id, pos and typeId
+ void serializeBase(std::ostream &os, u8 version)
+ {
+ u8 buf[6];
+
+ // id
+ writeS16(buf, m_id);
+ os.write((char*)buf, 2);
+
+ // position
+ // stored as x1000/BS v3s16
+ v3s16 pos_i(m_pos.X*1000/BS, m_pos.Y*1000/BS, m_pos.Z*1000/BS);
+ writeV3S16(buf, pos_i);
+ os.write((char*)buf, 6);
+
+ // typeId
+ writeU16(buf, getTypeId());
+ os.write((char*)buf, 2);
+ }
+
+ // Get floating point position on map
+ v3f getAbsolutePos();
+
+ void setBlockChanged();
+
+ // Shootline is relative to block
+ bool isSelected(core::line3d<f32> shootline)
+ {
+ if(m_selection_box == NULL)
+ return false;
+
+ core::aabbox3d<f32> offsetted_box(
+ m_selection_box->MinEdge + m_pos,
+ m_selection_box->MaxEdge + m_pos
+ );
+
+ return offsetted_box.intersectsWithLine(shootline);
+ }
+
+ core::aabbox3d<f32> getSelectionBoxOnMap()
+ {
+ v3f absolute_pos = getAbsolutePos();
+
+ core::aabbox3d<f32> box(
+ m_selection_box->MinEdge + absolute_pos,
+ m_selection_box->MaxEdge + absolute_pos
+ );
+
+ return box;
+ }
+
+ /*
+ Implementation interface
+ */
+
+ virtual u16 getTypeId() const = 0;
+ // Shall call serializeBase and then write the parameters
+ virtual void serialize(std::ostream &os, u8 version) = 0;
+ // Shall read parameters from stream
+ virtual void update(std::istream &is, u8 version) = 0;
+
+ virtual std::string getInventoryString() { return "None"; }
+
+ // Reimplementation shall call this.
+ virtual void updatePos(v3f pos)
+ {
+ m_pos = pos;
+ }
+
+ // Shall move the object around, modify it and possibly delete it.
+ // Typical dtimes are 0.2 and 10000.
+ // A return value of true requests deletion of the object by the caller.
+ // NOTE: Only server calls this.
+ virtual bool serverStep(float dtime) { return false; };
+ // This should do slight animations only or so
+ virtual void clientStep(float dtime) {};
+
+ // NOTE: These functions should do nothing if the asked state is
+ // same as the current state
+ // Shall add and remove relevant scene nodes for rendering the
+ // object in the game world
+ virtual void addToScene(scene::ISceneManager *smgr) {};
+ // Shall remove stuff from the scene
+ // Should return silently if there is nothing to remove
+ // NOTE: This has to be called before calling destructor
+ virtual void removeFromScene() {};
+
+ virtual std::string infoText() { return ""; }
+
+ // Shall be left NULL if doesn't collide
+ // Position is relative to m_pos in block
+ core::aabbox3d<f32> * m_collision_box;
+
+ // Shall be left NULL if can't be selected
+ core::aabbox3d<f32> * m_selection_box;
+
+protected:
+ MapBlock *m_block;
+ // This differentiates the instance of the object
+ // Not same as typeId.
+ s16 m_id;
+ // Position of the object inside the block
+ // Units is node coordinates * BS
+ v3f m_pos;
+
+ friend class MapBlockObjectList;
+};
+
+class TestObject : public MapBlockObject
+{
+public:
+ // The constructor of every MapBlockObject should be like this
+ TestObject(MapBlock *block, s16 id, v3f pos):
+ MapBlockObject(block, id, pos),
+ m_node(NULL)
+ {
+ }
+ virtual ~TestObject()
+ {
+ }
+
+ /*
+ Implementation interface
+ */
+ virtual u16 getTypeId() const
+ {
+ return MAPBLOCKOBJECT_TYPE_TEST;
+ }
+ virtual void serialize(std::ostream &os, u8 version)
+ {
+ serializeBase(os, version);
+
+ // Write subpos_c * 100
+ u8 buf[2];
+ writeU16(buf, m_subpos_c * 100);
+ os.write((char*)buf, 2);
+ }
+ virtual void update(std::istream &is, u8 version)
+ {
+ // Read subpos_c * 100
+ u8 buf[2];
+ is.read((char*)buf, 2);
+ m_subpos_c = (f32)readU16(buf) / 100;
+
+ updateNodePos();
+ }
+ virtual bool serverStep(float dtime)
+ {
+ m_subpos_c += dtime * 3.0;
+
+ updateNodePos();
+
+ return false;
+ }
+ virtual void addToScene(scene::ISceneManager *smgr)
+ {
+ if(m_node != NULL)
+ return;
+
+ //dstream<<"Adding to scene"<<std::endl;
+
+ video::IVideoDriver* driver = smgr->getVideoDriver();
+
+ scene::SMesh *mesh = new scene::SMesh();
+ scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+ video::SColor c(255,255,255,255);
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+ video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+ video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0),
+ video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+ buf->append(vertices, 4, indices, 6);
+ // Set material
+ buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+ buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+ buf->getMaterial().setTexture
+ (0, driver->getTexture("../data/player.png"));
+ buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+ buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+ // Add to mesh
+ mesh->addMeshBuffer(buf);
+ buf->drop();
+ m_node = smgr->addMeshSceneNode(mesh, NULL);
+ mesh->drop();
+ m_node->setPosition(getAbsolutePos());
+ }
+ virtual void removeFromScene()
+ {
+ //dstream<<"Removing from scene"<<std::endl;
+ if(m_node != NULL)
+ {
+ m_node->remove();
+ m_node = NULL;
+ }
+ }
+
+ /*
+ Special methods
+ */
+
+ void updateNodePos()
+ {
+ m_subpos = BS*2.0 * v3f(sin(m_subpos_c), sin(m_subpos_c+1.0), sin(-m_subpos_c));
+
+ if(m_node != NULL)
+ {
+ m_node->setPosition(getAbsolutePos() + m_subpos);
+ }
+ }
+
+protected:
+ scene::IMeshSceneNode *m_node;
+ std::string m_text;
+
+ v3f m_subpos;
+ f32 m_subpos_c;
+};
+
+class MovingObject : public MapBlockObject
+{
+public:
+ // The constructor of every MapBlockObject should be like this
+ MovingObject(MapBlock *block, s16 id, v3f pos):
+ MapBlockObject(block, id, pos),
+ m_speed(0,0,0)
+ {
+ m_touching_ground = false;
+ }
+ virtual ~MovingObject()
+ {
+ }
+
+ /*
+ Implementation interface
+ */
+
+ virtual u16 getTypeId() const = 0;
+
+ virtual void serialize(std::ostream &os, u8 version)
+ {
+ serializeBase(os, version);
+
+ u8 buf[6];
+
+ // Write speed
+ // stored as x100/BS v3s16
+ v3s16 speed_i(m_speed.X*100/BS, m_speed.Y*100/BS, m_speed.Z*100/BS);
+ writeV3S16(buf, speed_i);
+ os.write((char*)buf, 6);
+ }
+ virtual void update(std::istream &is, u8 version)
+ {
+ u8 buf[6];
+
+ // Read speed
+ // stored as x100/BS v3s16
+ is.read((char*)buf, 6);
+ v3s16 speed_i = readV3S16(buf);
+ v3f speed((f32)speed_i.X/100*BS,
+ (f32)speed_i.Y/100*BS,
+ (f32)speed_i.Z/100*BS);
+
+ m_speed = speed;
+ }
+
+ virtual bool serverStep(float dtime) { return false; };
+ virtual void clientStep(float dtime) {};
+
+ virtual void addToScene(scene::ISceneManager *smgr) = 0;
+ virtual void removeFromScene() = 0;
+
+ /*
+ Special methods
+ */
+
+ // Moves with collision detection
+ void move(float dtime, v3f acceleration);
+
+protected:
+ v3f m_speed;
+ bool m_touching_ground;
+};
+
+class Test2Object : public MovingObject
+{
+public:
+ // The constructor of every MapBlockObject should be like this
+ Test2Object(MapBlock *block, s16 id, v3f pos):
+ MovingObject(block, id, pos),
+ m_node(NULL)
+ {
+ m_collision_box = new core::aabbox3d<f32>
+ (-BS*0.3,0,-BS*0.3, BS*0.3,BS*1.7,BS*0.3);
+ }
+ virtual ~Test2Object()
+ {
+ delete m_collision_box;
+ }
+
+ /*
+ Implementation interface
+ */
+ virtual u16 getTypeId() const
+ {
+ return MAPBLOCKOBJECT_TYPE_TEST2;
+ }
+ virtual void serialize(std::ostream &os, u8 version)
+ {
+ MovingObject::serialize(os, version);
+ }
+ virtual void update(std::istream &is, u8 version)
+ {
+ MovingObject::update(is, version);
+
+ updateNodePos();
+ }
+
+ virtual bool serverStep(float dtime)
+ {
+ m_speed.X = 2*BS;
+ m_speed.Z = 0;
+
+ if(m_touching_ground)
+ {
+ static float count = 0;
+ count -= dtime;
+ if(count < 0.0)
+ {
+ count += 1.0;
+ m_speed.Y = 6.5*BS;
+ }
+ }
+
+ move(dtime, v3f(0, -9.81*BS, 0));
+
+ updateNodePos();
+
+ return false;
+ }
+
+ virtual void clientStep(float dtime)
+ {
+ m_pos += m_speed * dtime;
+
+ updateNodePos();
+ }
+
+ virtual void addToScene(scene::ISceneManager *smgr)
+ {
+ if(m_node != NULL)
+ return;
+
+ //dstream<<"Adding to scene"<<std::endl;
+
+ video::IVideoDriver* driver = smgr->getVideoDriver();
+
+ scene::SMesh *mesh = new scene::SMesh();
+ scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+ video::SColor c(255,255,255,255);
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+ video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+ video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0),
+ video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+ buf->append(vertices, 4, indices, 6);
+ // Set material
+ buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+ buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+ buf->getMaterial().setTexture
+ (0, driver->getTexture("../data/player.png"));
+ buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+ buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+ // Add to mesh
+ mesh->addMeshBuffer(buf);
+ buf->drop();
+ m_node = smgr->addMeshSceneNode(mesh, NULL);
+ mesh->drop();
+ m_node->setPosition(getAbsolutePos());
+ }
+ virtual void removeFromScene()
+ {
+ //dstream<<"Removing from scene"<<std::endl;
+ if(m_node != NULL)
+ {
+ m_node->remove();
+ m_node = NULL;
+ }
+ }
+
+ /*
+ Special methods
+ */
+
+ void updateNodePos()
+ {
+ //m_subpos = BS*2.0 * v3f(sin(m_subpos_c), sin(m_subpos_c+1.0), sin(-m_subpos_c));
+
+ if(m_node != NULL)
+ {
+ //m_node->setPosition(getAbsolutePos() + m_subpos);
+ m_node->setPosition(getAbsolutePos());
+ }
+ }
+
+protected:
+ scene::IMeshSceneNode *m_node;
+};
+
+class RatObject : public MovingObject
+{
+public:
+ RatObject(MapBlock *block, s16 id, v3f pos):
+ MovingObject(block, id, pos),
+ m_node(NULL)
+ {
+ m_collision_box = new core::aabbox3d<f32>
+ (-BS*0.3,0,-BS*0.3, BS*0.3,BS*0.5,BS*0.3);
+ m_selection_box = new core::aabbox3d<f32>
+ (-BS*0.3,0,-BS*0.3, BS*0.3,BS*0.5,BS*0.3);
+
+ m_counter1 = 0;
+ m_counter2 = 0;
+ }
+ virtual ~RatObject()
+ {
+ delete m_collision_box;
+ delete m_selection_box;
+ }
+
+ /*
+ Implementation interface
+ */
+ virtual u16 getTypeId() const
+ {
+ return MAPBLOCKOBJECT_TYPE_RAT;
+ }
+ virtual void serialize(std::ostream &os, u8 version)
+ {
+ MovingObject::serialize(os, version);
+ u8 buf[2];
+
+ // Write yaw * 10
+ writeS16(buf, m_yaw * 10);
+ os.write((char*)buf, 2);
+
+ }
+ virtual void update(std::istream &is, u8 version)
+ {
+ MovingObject::update(is, version);
+ u8 buf[2];
+
+ // Read yaw * 10
+ is.read((char*)buf, 2);
+ s16 yaw_i = readS16(buf);
+ m_yaw = (f32)yaw_i / 10;
+
+ updateNodePos();
+ }
+
+ virtual bool serverStep(float dtime)
+ {
+ v3f dir(cos(m_yaw/180*PI),0,sin(m_yaw/180*PI));
+
+ f32 speed = 2*BS;
+
+ m_speed.X = speed * dir.X;
+ m_speed.Z = speed * dir.Z;
+
+ if(m_touching_ground && (m_oldpos - m_pos).getLength() < dtime*speed/2)
+ {
+ m_counter1 -= dtime;
+ if(m_counter1 < 0.0)
+ {
+ m_counter1 += 1.0;
+ m_speed.Y = 5.0*BS;
+ }
+ }
+
+ {
+ m_counter2 -= dtime;
+ if(m_counter2 < 0.0)
+ {
+ m_counter2 += (float)(rand()%100)/100*3.0;
+ m_yaw += ((float)(rand()%200)-100)/100*180;
+ m_yaw = wrapDegrees(m_yaw);
+ }
+ }
+
+ m_oldpos = m_pos;
+
+ //m_yaw += dtime*90;
+
+ move(dtime, v3f(0, -9.81*BS, 0));
+
+ updateNodePos();
+
+ return false;
+ }
+
+ virtual void clientStep(float dtime)
+ {
+ m_pos += m_speed * dtime;
+
+ updateNodePos();
+ }
+
+ virtual void addToScene(scene::ISceneManager *smgr)
+ {
+ if(m_node != NULL)
+ return;
+
+ video::IVideoDriver* driver = smgr->getVideoDriver();
+
+ scene::SMesh *mesh = new scene::SMesh();
+ scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+ video::SColor c(255,255,255,255);
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+ video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+ video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 1,0),
+ video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 0,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+ buf->append(vertices, 4, indices, 6);
+ // Set material
+ buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+ buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+ buf->getMaterial().setTexture
+ (0, driver->getTexture("../data/rat.png"));
+ buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+ buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+ // Add to mesh
+ mesh->addMeshBuffer(buf);
+ buf->drop();
+ m_node = smgr->addMeshSceneNode(mesh, NULL);
+ mesh->drop();
+ m_node->setPosition(getAbsolutePos());
+ }
+ virtual void removeFromScene()
+ {
+ if(m_node != NULL)
+ {
+ m_node->remove();
+ m_node = NULL;
+ }
+ }
+
+ virtual std::string getInventoryString()
+ {
+ // There must be a space after the name
+ // Or does there?
+ return std::string("Rat ");
+ }
+
+ /*
+ Special methods
+ */
+
+ void updateNodePos()
+ {
+ if(m_node != NULL)
+ {
+ m_node->setPosition(getAbsolutePos());
+ m_node->setRotation(v3f(0, -m_yaw+180, 0));
+ }
+ }
+
+protected:
+ scene::IMeshSceneNode *m_node;
+ float m_yaw;
+
+ float m_counter1;
+ float m_counter2;
+ v3f m_oldpos;
+};
+
+class SignObject : public MapBlockObject
+{
+public:
+ // The constructor of every MapBlockObject should be like this
+ SignObject(MapBlock *block, s16 id, v3f pos):
+ MapBlockObject(block, id, pos),
+ m_node(NULL)
+ {
+ m_selection_box = new core::aabbox3d<f32>
+ (-BS*0.4,-BS*0.5,-BS*0.4, BS*0.4,BS*0.5,BS*0.4);
+ }
+ virtual ~SignObject()
+ {
+ delete m_selection_box;
+ }
+
+ /*
+ Implementation interface
+ */
+ virtual u16 getTypeId() const
+ {
+ return MAPBLOCKOBJECT_TYPE_SIGN;
+ }
+ virtual void serialize(std::ostream &os, u8 version)
+ {
+ serializeBase(os, version);
+ u8 buf[2];
+
+ // Write yaw * 10
+ writeS16(buf, m_yaw * 10);
+ os.write((char*)buf, 2);
+
+ // Write text length
+ writeU16(buf, m_text.size());
+ os.write((char*)buf, 2);
+
+ // Write text
+ os.write(m_text.c_str(), m_text.size());
+ }
+ virtual void update(std::istream &is, u8 version)
+ {
+ u8 buf[2];
+
+ // Read yaw * 10
+ is.read((char*)buf, 2);
+ s16 yaw_i = readS16(buf);
+ m_yaw = (f32)yaw_i / 10;
+
+ // Read text length
+ is.read((char*)buf, 2);
+ u16 size = readU16(buf);
+
+ // Read text
+ m_text.clear();
+ for(u16 i=0; i<size; i++)
+ {
+ is.read((char*)buf, 1);
+ m_text += buf[0];
+ }
+
+ updateSceneNode();
+ }
+ virtual bool serverStep(float dtime)
+ {
+ return false;
+ }
+ virtual void addToScene(scene::ISceneManager *smgr)
+ {
+ if(m_node != NULL)
+ return;
+
+ video::IVideoDriver* driver = smgr->getVideoDriver();
+
+ scene::SMesh *mesh = new scene::SMesh();
+ { // Front
+ scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+ video::SColor c(255,255,255,255);
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c, 0,1),
+ video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c, 1,1),
+ video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 1,0),
+ video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 0,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+ buf->append(vertices, 4, indices, 6);
+ // Set material
+ buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+ //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+ buf->getMaterial().setTexture
+ (0, driver->getTexture("../data/sign.png"));
+ buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+ buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+ // Add to mesh
+ mesh->addMeshBuffer(buf);
+ buf->drop();
+ }
+ { // Back
+ scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+ video::SColor c(255,255,255,255);
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c, 0,1),
+ video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c, 1,1),
+ video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 1,0),
+ video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 0,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+ buf->append(vertices, 4, indices, 6);
+ // Set material
+ buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+ //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+ buf->getMaterial().setTexture
+ (0, driver->getTexture("../data/sign_back.png"));
+ buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+ buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+ // Add to mesh
+ mesh->addMeshBuffer(buf);
+ buf->drop();
+ }
+ m_node = smgr->addMeshSceneNode(mesh, NULL);
+ mesh->drop();
+
+ updateSceneNode();
+ }
+ virtual void removeFromScene()
+ {
+ if(m_node != NULL)
+ {
+ m_node->remove();
+ m_node = NULL;
+ }
+ }
+
+ virtual std::string infoText()
+ {
+ return std::string("\"") + m_text + "\"";
+ }
+
+ virtual std::string getInventoryString()
+ {
+ return std::string("Sign ")+m_text;
+ }
+
+ /*
+ Special methods
+ */
+
+ void updateSceneNode()
+ {
+ if(m_node != NULL)
+ {
+ m_node->setPosition(getAbsolutePos());
+ m_node->setRotation(v3f(0, m_yaw, 0));
+ }
+ }
+
+ void setText(std::string text)
+ {
+ if(text.size() > SIGN_TEXT_MAX_LENGTH)
+ text = text.substr(0, SIGN_TEXT_MAX_LENGTH);
+ m_text = text;
+
+ setBlockChanged();
+ }
+
+ void setYaw(f32 yaw)
+ {
+ m_yaw = yaw;
+
+ setBlockChanged();
+ }
+
+protected:
+ scene::IMeshSceneNode *m_node;
+ std::string m_text;
+ f32 m_yaw;
+};
+
+struct DistanceSortedObject
+{
+ DistanceSortedObject(MapBlockObject *a_obj, f32 a_d)
+ {
+ obj = a_obj;
+ d = a_d;
+ }
+
+ MapBlockObject *obj;
+ f32 d;
+
+ bool operator < (DistanceSortedObject &other)
+ {
+ return d < other.d;
+ }
+};
+
+class MapBlockObjectList
+{
+public:
+ MapBlockObjectList(MapBlock *block);
+ ~MapBlockObjectList();
+ // Writes the count, id, the type id and the parameters of all objects
+ void serialize(std::ostream &os, u8 version);
+ // Reads ids, type_ids and parameters.
+ // Creates, updates and deletes objects.
+ // If smgr!=NULL, new objects are added to the scene
+ void update(std::istream &is, u8 version, scene::ISceneManager *smgr);
+ // Finds a new unique id
+ s16 getFreeId() throw(ContainerFullException);
+ /*
+ Adds an object.
+ Set id to -1 to have this set it to a suitable one.
+ The block pointer member is set to this block.
+ */
+ void add(MapBlockObject *object)
+ throw(ContainerFullException, AlreadyExistsException);
+
+ // Deletes and removes all objects
+ void clear();
+
+ /*
+ Removes an object.
+ Ignores inexistent objects
+ */
+ void remove(s16 id);
+ /*
+ References an object.
+ The object will not be valid after step() or of course if
+ it is removed.
+ Grabbing the lock is recommended while processing.
+ */
+ MapBlockObject * get(s16 id);
+
+ // You'll want to grab this in a SharedPtr
+ JMutexAutoLock * getLock()
+ {
+ return new JMutexAutoLock(m_mutex);
+ }
+
+ // Steps all objects and if server==true, removes those that
+ // want to be removed
+ void step(float dtime, bool server);
+
+ // Wraps an object that wants to move onto this block from an another
+ // Returns true if wrapping was impossible
+ bool wrapObject(MapBlockObject *object);
+
+ // origin is relative to block
+ void getObjects(v3f origin, f32 max_d,
+ core::array<DistanceSortedObject> &dest);
+
+private:
+ JMutex m_mutex;
+ // Key is id
+ core::map<s16, MapBlockObject*> m_objects;
+ MapBlock *m_block;
+};
+
+
+#endif
+
diff --git a/src/mapnode.h b/src/mapnode.h
new file mode 100644
index 000000000..68e669161
--- /dev/null
+++ b/src/mapnode.h
@@ -0,0 +1,280 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAPNODE_HEADER
+#define MAPNODE_HEADER
+
+#include <iostream>
+#include "common_irrlicht.h"
+#include "light.h"
+#include "utility.h"
+#include "exceptions.h"
+#include "serialization.h"
+
+// Size of node in rendering units
+#define BS 10
+
+#define MATERIALS_COUNT 256
+
+// This is completely ignored. It doesn't create faces with anything.
+#define MATERIAL_IGNORE 255
+// This is the common material through which the player can walk
+// and which is transparent to light
+#define MATERIAL_AIR 254
+
+/*
+ Materials-todo:
+
+ GRAVEL
+ - Dynamics of gravel: if there is a drop of more than two
+ blocks on any side, it will drop in there. Is this doable?
+*/
+
+enum Material
+{
+ MATERIAL_STONE=0,
+
+ MATERIAL_GRASS,
+
+ /*
+ For water, the param is water pressure. 0...127.
+ TODO: No, at least the lowest nibble is used for lighting.
+
+ - Water will be a bit like light, but with different flow
+ behavior.
+ - Water blocks will fall down if there is empty space below.
+ - If there is water below, the pressure of the block below is
+ the pressure of the current block + 1, or higher.
+ - If there is any pressure in a horizontally neighboring
+ block, a water block will try to move away from it.
+ - If there is >=2 of pressure in a block below, water will
+ try to move upwards.
+ - NOTE: To keep large operations fast, we have to keep a
+ cache of the water-air-surfaces, just like with light
+ */
+ MATERIAL_WATER,
+
+ MATERIAL_LIGHT,
+
+ MATERIAL_TREE,
+ MATERIAL_LEAVES,
+
+ MATERIAL_GRASS_FOOTSTEPS,
+
+ MATERIAL_MESE,
+
+ // This is set to the number of the actual values in this enum
+ USEFUL_MATERIAL_COUNT
+};
+
+/*
+ If true, the material allows light propagation and brightness is stored
+ in param.
+*/
+inline bool light_propagates_material(u8 m)
+{
+ return (m == MATERIAL_AIR || m == MATERIAL_LIGHT || m == MATERIAL_WATER);
+}
+
+/*
+ If true, the material allows lossless sunlight propagation.
+*/
+inline bool sunlight_propagates_material(u8 m)
+{
+ return (m == MATERIAL_AIR);
+}
+
+/*
+ On a node-node surface, the material of the node with higher solidness
+ is used for drawing.
+ 0: Invisible
+ 1: Transparent
+ 2: Opaque
+*/
+inline u8 material_solidness(u8 m)
+{
+ if(m == MATERIAL_AIR)
+ return 0;
+ if(m == MATERIAL_WATER)
+ return 1;
+ return 2;
+}
+
+/*
+ Nodes make a face if materials differ and solidness differs.
+ Return value:
+ 0: No face
+ 1: Face uses m1's material
+ 2: Face uses m2's material
+*/
+inline u8 face_materials(u8 m1, u8 m2)
+{
+ if(m1 == MATERIAL_IGNORE || m2 == MATERIAL_IGNORE)
+ return 0;
+
+ bool materials_differ = (m1 != m2);
+ bool solidness_differs = (material_solidness(m1) != material_solidness(m2));
+ bool makes_face = materials_differ && solidness_differs;
+
+ if(makes_face == false)
+ return 0;
+
+ if(material_solidness(m1) > material_solidness(m2))
+ return 1;
+ else
+ return 2;
+}
+
+struct MapNode
+{
+ //TODO: block type to differ from material
+ // (e.g. grass edges or something)
+ // block type
+ u8 d;
+
+ // Removed because light is now stored in param for air
+ // f32 light;
+
+ /*
+ Misc parameter. Initialized to 0.
+ - For light_propagates() blocks, this is light intensity,
+ stored logarithmically from 0 to LIGHT_MAX.
+ Sunlight is LIGHT_SUN, which is LIGHT_MAX+1.
+ */
+ s8 param;
+
+ MapNode(const MapNode & n)
+ {
+ *this = n;
+ }
+
+ MapNode(u8 data=MATERIAL_AIR, u8 a_param=0)
+ {
+ d = data;
+ param = a_param;
+ }
+
+ bool light_propagates()
+ {
+ return light_propagates_material(d);
+ }
+
+ bool sunlight_propagates()
+ {
+ return sunlight_propagates_material(d);
+ }
+
+ u8 solidness()
+ {
+ return material_solidness(d);
+ }
+
+ u8 light_source()
+ {
+ /*
+ Note that a block that isn't light_propagates() can be a light source.
+ */
+ if(d == MATERIAL_LIGHT)
+ return LIGHT_MAX;
+
+ return 0;
+ }
+
+ u8 getLight()
+ {
+ // Select the brightest of [light_source, transparent_light]
+ u8 light = 0;
+ if(light_propagates())
+ light = param & 0x0f;
+ if(light_source() > light)
+ light = light_source();
+ return light;
+ }
+
+ void setLight(u8 a_light)
+ {
+ // If not transparent, can't set light
+ if(light_propagates() == false)
+ return;
+ param = a_light;
+ }
+
+ static u32 serializedLength(u8 version)
+ {
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: MapNode format not supported");
+
+ if(version == 0)
+ return 1;
+ else
+ return 2;
+ }
+ void serialize(u8 *dest, u8 version)
+ {
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: MapNode format not supported");
+
+ if(version == 0)
+ {
+ dest[0] = d;
+ }
+ else
+ {
+ dest[0] = d;
+ dest[1] = param;
+ }
+ }
+ void deSerialize(u8 *source, u8 version)
+ {
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: MapNode format not supported");
+
+ if(version == 0)
+ {
+ d = source[0];
+ }
+ else if(version == 1)
+ {
+ d = source[0];
+ // This version doesn't support saved lighting
+ if(light_propagates() || light_source() > 0)
+ param = 0;
+ else
+ param = source[1];
+ }
+ else
+ {
+ d = source[0];
+ param = source[1];
+ }
+ }
+};
+
+/*
+ Returns integer position of the node in given
+ floating point position.
+*/
+inline v3s16 floatToInt(v3f p)
+{
+ v3s16 p2(
+ (p.X + (p.X>0 ? BS/2 : -BS/2))/BS,
+ (p.Y + (p.Y>0 ? BS/2 : -BS/2))/BS,
+ (p.Z + (p.Z>0 ? BS/2 : -BS/2))/BS);
+ return p2;
+}
+
+inline v3f intToFloat(v3s16 p)
+{
+ v3f p2(
+ p.X * BS,
+ p.Y * BS,
+ p.Z * BS
+ );
+ return p2;
+}
+
+
+
+#endif
+
diff --git a/src/mapsector.cpp b/src/mapsector.cpp
new file mode 100644
index 000000000..8dd1c8c91
--- /dev/null
+++ b/src/mapsector.cpp
@@ -0,0 +1,652 @@
+#include "mapsector.h"
+#include "jmutexautolock.h"
+#include "client.h"
+#include "exceptions.h"
+
+MapSector::MapSector(NodeContainer *parent, v2s16 pos):
+ differs_from_disk(true),
+ usage_timer(0.0),
+ m_parent(parent),
+ m_pos(pos),
+ m_block_cache(NULL)
+{
+ m_mutex.Init();
+ assert(m_mutex.IsInitialized());
+}
+
+MapSector::~MapSector()
+{
+ deleteBlocks();
+}
+
+void MapSector::deleteBlocks()
+{
+ JMutexAutoLock lock(m_mutex);
+
+ // Clear cache
+ m_block_cache = NULL;
+
+ // Delete all
+ core::map<s16, MapBlock*>::Iterator i = m_blocks.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ delete i.getNode()->getValue();
+ }
+
+ // Clear container
+ m_blocks.clear();
+}
+
+MapBlock * MapSector::getBlockBuffered(s16 y)
+{
+ MapBlock *block;
+
+ if(m_block_cache != NULL && y == m_block_cache_y){
+ return m_block_cache;
+ }
+
+ // If block doesn't exist, return NULL
+ core::map<s16, MapBlock*>::Node *n = m_blocks.find(y);
+ if(n == NULL)
+ {
+ block = NULL;
+ }
+ // If block exists, return it
+ else{
+ block = n->getValue();
+ }
+
+ // Cache the last result
+ m_block_cache_y = y;
+ m_block_cache = block;
+
+ return block;
+}
+
+MapBlock * MapSector::getBlockNoCreate(s16 y)
+{
+ JMutexAutoLock lock(m_mutex);
+
+ MapBlock *block = getBlockBuffered(y);
+
+ if(block == NULL)
+ throw InvalidPositionException();
+
+ return block;
+}
+
+MapBlock * MapSector::createBlankBlockNoInsert(s16 y)
+{
+ // There should not be a block at this position
+ if(getBlockBuffered(y) != NULL)
+ throw AlreadyExistsException("Block already exists");
+
+ v3s16 blockpos_map(m_pos.X, y, m_pos.Y);
+
+ MapBlock *block = new MapBlock(m_parent, blockpos_map);
+
+ return block;
+}
+
+MapBlock * MapSector::createBlankBlock(s16 y)
+{
+ JMutexAutoLock lock(m_mutex);
+
+ MapBlock *block = createBlankBlockNoInsert(y);
+
+ m_blocks.insert(y, block);
+
+ return block;
+}
+
+void MapSector::insertBlock(MapBlock *block)
+{
+ s16 block_y = block->getPos().Y;
+
+ {
+ JMutexAutoLock lock(m_mutex);
+
+ MapBlock *block2 = getBlockBuffered(block_y);
+ if(block2 != NULL){
+ throw AlreadyExistsException("Block already exists");
+ }
+
+ v2s16 p2d(block->getPos().X, block->getPos().Z);
+ assert(p2d == m_pos);
+
+ // Insert into container
+ m_blocks.insert(block_y, block);
+ }
+}
+
+void MapSector::removeBlock(MapBlock *block)
+{
+ s16 block_y = block->getPos().Y;
+
+ JMutexAutoLock lock(m_mutex);
+
+ // Clear from cache
+ m_block_cache = NULL;
+
+ // Remove from container
+ m_blocks.remove(block_y);
+}
+
+void MapSector::getBlocks(core::list<MapBlock*> &dest)
+{
+ JMutexAutoLock lock(m_mutex);
+
+ core::list<MapBlock*> ref_list;
+
+ core::map<s16, MapBlock*>::Iterator bi;
+
+ bi = m_blocks.getIterator();
+ for(; bi.atEnd() == false; bi++)
+ {
+ MapBlock *b = bi.getNode()->getValue();
+ dest.push_back(b);
+ }
+}
+
+/*
+ ServerMapSector
+*/
+
+ServerMapSector::ServerMapSector(NodeContainer *parent, v2s16 pos, u16 hm_split):
+ MapSector(parent, pos),
+ m_hm_split(hm_split),
+ m_objects(NULL)
+{
+ // hm_split has to be 1 or 2^x
+ assert(hm_split == 0 || hm_split == 1 || (hm_split & (hm_split-1)) == 0);
+ assert(hm_split * hm_split <= MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT);
+
+ for(u16 i=0; i<hm_split*hm_split; i++)
+ m_heightmaps[i] = NULL;
+}
+
+ServerMapSector::~ServerMapSector()
+{
+ u16 hm_count = m_hm_split * m_hm_split;
+
+ // Write heightmaps
+ for(u16 i=0; i<hm_count; i++)
+ {
+ if(m_heightmaps[i])
+ delete m_heightmaps[i];
+ }
+
+ if(m_objects)
+ delete m_objects;
+}
+
+void ServerMapSector::setHeightmap(v2s16 hm_p, FixedHeightmap *hm)
+{
+ assert(isInArea(hm_p, m_hm_split));
+
+ s16 i = hm_p.Y * m_hm_split + hm_p.X;
+
+ // Don't allow setting already set heightmaps as of now
+ assert(m_heightmaps[i] == NULL);
+
+ /*std::cout<<"MapSector::setHeightmap for sector "
+ <<"("<<m_pos.X<<","<<m_pos.Y<<"): "
+ <<"Setting heightmap "
+ <<"("<<hm_p.X<<","<<hm_p.Y<<")"
+ <<" which is i="<<i
+ <<" to pointer "<<(long long)hm
+ <<std::endl;*/
+
+ m_heightmaps[i] = hm;
+
+ differs_from_disk = true;
+}
+
+FixedHeightmap * ServerMapSector::getHeightmap(v2s16 hm_p)
+{
+ assert(isInArea(hm_p, m_hm_split));
+
+ s16 i = hm_p.Y * m_hm_split + hm_p.X;
+
+ return m_heightmaps[i];
+}
+
+f32 ServerMapSector::getGroundHeight(v2s16 p, bool generate)
+{
+ // If no heightmaps
+ if(m_hm_split == 0)
+ {
+ /*std::cout<<"Sector has no heightmap"
+ <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+ <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+ <<std::endl;*/
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ }
+
+ // Side length of heightmap
+ s16 hm_d = MAP_BLOCKSIZE / m_hm_split;
+
+ // Position of selected heightmap
+ v2s16 hm_p = getContainerPos(p, hm_d);
+ if(isInArea(hm_p, m_hm_split) == false)
+ {
+ /*std::cout<<"Sector has no heightmap ("<<hm_p.X<<","<<hm_p.Y<<")"
+ <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+ <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+ <<std::endl;*/
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ }
+
+ // Selected heightmap
+ FixedHeightmap *hm = m_heightmaps[hm_p.Y * m_hm_split + hm_p.X];
+
+ if(hm == NULL)
+ {
+ /*std::cout<<"Sector heightmap ("<<hm_p.X<<","<<hm_p.Y<<")"
+ " is NULL"
+ <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+ <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+ <<std::endl;*/
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ }
+
+ // Position in selected heighmap
+ v2s16 p_in_hm = p - hm_p * hm_d;
+ if(isInArea(p_in_hm, hm_d+1) == false)
+ {
+ /*std::cout<<"Position ("<<p_in_hm.X<<","<<p_in_hm.Y<<")"
+ " not in sector heightmap area"
+ <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+ <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+ <<std::endl;*/
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ }
+
+ f32 h = hm->getGroundHeight(p_in_hm);
+
+ /*if(h < GROUNDHEIGHT_VALID_MINVALUE)
+ {
+ std::cout<<"Sector heightmap ("<<hm_p.X<<","<<hm_p.Y<<")"
+ " returned invalid value"
+ <<" while trying to get height at ("<<p.X<<","<<p.Y<<")"
+ <<" which is ("<<p_in_hm.X<<","<<p_in_hm.Y<<") in heightmap"
+ <<" for sector ("<<m_pos.X<<","<<m_pos.Y<<")"
+ <<std::endl;
+ }*/
+
+ return h;
+}
+
+void ServerMapSector::setGroundHeight(v2s16 p, f32 y, bool generate)
+{
+ /*
+ NOTE:
+ This causes glitches because the sector cannot be actually
+ modified according to heightmap changes.
+
+ This is useful when generating continued sub-heightmaps
+ inside the sector.
+ */
+
+ // If no heightmaps
+ if(m_hm_split == 0)
+ return;
+
+ // Side length of heightmap
+ s16 hm_d = MAP_BLOCKSIZE / m_hm_split;
+
+ // Position of selected heightmap
+ v2s16 hm_p = getContainerPos(p, hm_d);
+ if(isInArea(hm_p, m_hm_split) == false)
+ return;
+
+ // Selected heightmap
+ FixedHeightmap *hm = m_heightmaps[hm_p.Y * m_hm_split + hm_p.X];
+
+ if(hm == NULL)
+ return;
+
+ // Position in selected heighmap
+ v2s16 p_in_hm = p - hm_p * hm_d;
+ if(isInArea(p_in_hm, hm_d) == false)
+ return;
+
+ hm->setGroundHeight(p_in_hm, y);
+
+ differs_from_disk = true;
+}
+
+void ServerMapSector::serialize(std::ostream &os, u8 version)
+{
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: MapSector format not supported");
+
+ /*
+ [0] u8 serialization version
+ + heightmap data
+ */
+
+ // Server has both of these, no need to support not having them.
+ assert(m_objects != NULL);
+
+ // Write version
+ os.write((char*)&version, 1);
+
+ /*
+ Serialize heightmap(s)
+ */
+
+ // Version with single heightmap
+ if(version <= 7)
+ {
+ u32 heightmap_size =
+ FixedHeightmap::serializedLength(version, MAP_BLOCKSIZE);
+
+ SharedBuffer<u8> data(heightmap_size);
+ m_heightmaps[0]->serialize(*data, version);
+
+ os.write((const char*)*data, heightmap_size);
+
+ if(version >= 5)
+ {
+ /*
+ Write objects
+ */
+
+ u16 object_count;
+ if(m_objects->size() > 65535)
+ object_count = 65535;
+ else
+ object_count = m_objects->size();
+
+ u8 b[2];
+ writeU16(b, object_count);
+ os.write((char*)b, 2);
+
+ core::map<v3s16, u8>::Iterator i;
+ i = m_objects->getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ u8 d = i.getNode()->getValue();
+ u8 b[7];
+ writeV3S16(&b[0], p);
+ b[6] = d;
+ os.write((char*)b, 7);
+ }
+ }
+ }
+ // Version with multiple heightmaps
+ else
+ {
+ u8 buf[2];
+
+ if(m_hm_split > 255)
+ throw SerializationError("Sector has too many heightmaps");
+
+ // Write heightmap split ratio
+ writeU8(buf, m_hm_split);
+ os.write((char*)buf, 1);
+
+ // If there are heightmaps, write them
+ if(m_hm_split != 0)
+ {
+ u16 hm_d = MAP_BLOCKSIZE / m_hm_split;
+
+ u32 hm_size = FixedHeightmap::serializedLength(version, hm_d);
+ SharedBuffer<u8> data(hm_size);
+
+ u16 hm_count = m_hm_split * m_hm_split;
+
+ // Write heightmaps
+ for(u16 i=0; i<hm_count; i++)
+ {
+ m_heightmaps[i]->serialize(*data, version);
+ os.write((const char*)*data, hm_size);
+ }
+ }
+
+ /*
+ Write objects
+ */
+
+ u16 object_count;
+ if(m_objects->size() > 65535)
+ object_count = 65535;
+ else
+ object_count = m_objects->size();
+
+ u8 b[2];
+ writeU16(b, object_count);
+ os.write((char*)b, 2);
+
+ core::map<v3s16, u8>::Iterator i;
+ i = m_objects->getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ u8 d = i.getNode()->getValue();
+ u8 b[7];
+ writeV3S16(&b[0], p);
+ b[6] = d;
+ os.write((char*)b, 7);
+ }
+ }
+}
+
+ServerMapSector* ServerMapSector::deSerialize(
+ std::istream &is,
+ NodeContainer *parent,
+ v2s16 p2d,
+ Heightmap *master_hm,
+ core::map<v2s16, MapSector*> & sectors
+ )
+{
+ /*
+ [0] u8 serialization version
+ + heightmap data
+ */
+
+ /*
+ Read stuff
+ */
+
+ // Read version
+ u8 version = SER_FMT_VER_INVALID;
+ is.read((char*)&version, 1);
+
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: MapSector format not supported");
+
+ /*
+ Read heightmap(s)
+ */
+
+ FixedHeightmap *hms[MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT];
+ u16 hm_split = 0;
+
+ // Version with a single heightmap
+ if(version <= 7)
+ {
+ hm_split = 1;
+
+ u32 hm_size =
+ FixedHeightmap::serializedLength(version, MAP_BLOCKSIZE);
+
+ SharedBuffer<u8> data(hm_size);
+ is.read((char*)*data, hm_size);
+
+ hms[0] = new FixedHeightmap(master_hm, p2d, MAP_BLOCKSIZE);
+ hms[0]->deSerialize(*data, version);
+ }
+ // Version with multiple heightmaps
+ else
+ {
+ u8 buf[2];
+
+ // Read split ratio
+ is.read((char*)buf, 1);
+ hm_split = readU8(buf);
+
+ // If there are heightmaps, read them
+ if(hm_split != 0)
+ {
+ u16 hm_count = hm_split * hm_split;
+
+ if(hm_count > MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT)
+ throw SerializationError("Sector has too many heightmaps");
+
+ u16 hm_d = MAP_BLOCKSIZE / hm_split;
+
+ u32 hm_size = FixedHeightmap::serializedLength(version, hm_d);
+
+ u16 i=0;
+ for(s16 y=0; y<hm_split; y++)
+ for(s16 x=0; x<hm_split; x++)
+ {
+ SharedBuffer<u8> data(hm_size);
+ is.read((char*)*data, hm_size);
+
+ hms[i] = new FixedHeightmap(master_hm, p2d+v2s16(x,y), hm_d);
+ hms[i]->deSerialize(*data, version);
+ i++;
+ }
+ }
+ }
+
+ /*
+ Read objects
+ */
+
+ core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
+
+ if(version >= 5)
+ {
+ u8 b[2];
+ is.read((char*)b, 2);
+ u16 object_count = readU16(b);
+
+ for(u16 i=0; i<object_count; i++)
+ {
+ u8 b[7];
+ is.read((char*)b, 7);
+ v3s16 p = readV3S16(&b[0]);
+ u8 d = b[6];
+ objects->insert(p, d);
+ }
+ }
+
+ /*
+ Get or create sector
+ */
+
+ ServerMapSector *sector = NULL;
+
+ core::map<v2s16, MapSector*>::Node *n = sectors.find(p2d);
+
+ if(n != NULL)
+ {
+ dstream<<"deSerializing existent sectors not supported "
+ "at the moment, because code hasn't been tested."
+ <<std::endl;
+ assert(0);
+ // NOTE: At least hm_split mismatch would have to be checked
+
+ //sector = n->getValue();
+ }
+ else
+ {
+ sector = new ServerMapSector(parent, p2d, hm_split);
+ sectors.insert(p2d, sector);
+ }
+
+ /*
+ Set stuff in sector
+ */
+
+ // Set heightmaps
+
+ sector->m_hm_split = hm_split;
+
+ u16 hm_count = hm_split * hm_split;
+
+ for(u16 i=0; i<hm_count; i++)
+ {
+ // Set (or change) heightmap
+ FixedHeightmap *oldhm = sector->m_heightmaps[i];
+ sector->m_heightmaps[i] = hms[i];
+ if(oldhm != NULL)
+ delete oldhm;
+ }
+
+ // Set (or change) objects
+ core::map<v3s16, u8> *oldfo = sector->m_objects;
+ sector->m_objects = objects;
+ if(oldfo)
+ delete oldfo;
+
+ return sector;
+}
+
+/*
+ ClientMapSector
+*/
+
+ClientMapSector::ClientMapSector(NodeContainer *parent, v2s16 pos):
+ MapSector(parent, pos)
+{
+}
+
+ClientMapSector::~ClientMapSector()
+{
+}
+
+void ClientMapSector::deSerialize(std::istream &is)
+{
+ /*
+ [0] u8 serialization version
+ [1] s16 corners[0]
+ [3] s16 corners[1]
+ [5] s16 corners[2]
+ [7] s16 corners[3]
+ size = 9
+
+ In which corners are in these positions
+ v2s16(0,0),
+ v2s16(1,0),
+ v2s16(1,1),
+ v2s16(0,1),
+ */
+
+ // Read version
+ u8 version = SER_FMT_VER_INVALID;
+ is.read((char*)&version, 1);
+
+ if(!ser_ver_supported(version))
+ throw VersionMismatchException("ERROR: MapSector format not supported");
+ if(version <= 7)
+ throw VersionMismatchException("ERROR: MapSector format not supported");
+
+ u8 buf[2];
+
+ // Read corners
+ is.read((char*)buf, 2);
+ s16 c0 = readU16(buf);
+ is.read((char*)buf, 2);
+ s16 c1 = readU16(buf);
+ is.read((char*)buf, 2);
+ s16 c2 = readU16(buf);
+ is.read((char*)buf, 2);
+ s16 c3 = readU16(buf);
+
+ /*
+ Set stuff in sector
+ */
+
+ m_corners[0] = c0;
+ m_corners[1] = c1;
+ m_corners[2] = c2;
+ m_corners[3] = c3;
+}
+
+//END
diff --git a/src/mapsector.h b/src/mapsector.h
new file mode 100644
index 000000000..196a129c3
--- /dev/null
+++ b/src/mapsector.h
@@ -0,0 +1,318 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef MAPSECTOR_HEADER
+#define MAPSECTOR_HEADER
+
+#include <jmutex.h>
+#include "common_irrlicht.h"
+#include "mapblock.h"
+#include "heightmap.h"
+#include "exceptions.h"
+
+/*
+ This is an Y-wise stack of MapBlocks.
+*/
+
+#define WATER_LEVEL (-5)
+
+#define SECTOR_OBJECT_TEST 0
+#define SECTOR_OBJECT_TREE_1 1
+#define SECTOR_OBJECT_BUSH_1 2
+
+#define MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT 4
+
+#define MAPSECTOR_SERVER 0
+#define MAPSECTOR_CLIENT 1
+
+class MapSector: public NodeContainer, public Heightmappish
+{
+public:
+
+ MapSector(NodeContainer *parent, v2s16 pos);
+ virtual ~MapSector();
+
+ virtual u16 nodeContainerId() const
+ {
+ return NODECONTAINER_ID_MAPSECTOR;
+ }
+
+ virtual u32 getId() const = 0;
+
+ void deleteBlocks();
+
+ v2s16 getPos()
+ {
+ return m_pos;
+ }
+
+ MapBlock * getBlockNoCreate(s16 y);
+ MapBlock * createBlankBlockNoInsert(s16 y);
+ MapBlock * createBlankBlock(s16 y);
+ //MapBlock * getBlock(s16 y, bool generate=true);
+
+ void insertBlock(MapBlock *block);
+
+ // This is used to remove a dummy from the sector while generating it.
+ // Block is only removed from internal container, not deleted.
+ void removeBlock(MapBlock *block);
+
+ /*
+ This might not be a thread-safe depending on the day.
+ See the implementation.
+ */
+ void getBlocks(core::list<MapBlock*> &dest);
+
+ /*
+ If all nodes in area can be accessed, returns true and
+ adds all blocks in area to blocks.
+
+ If all nodes in area cannot be accessed, returns false.
+
+ The implementation of this is quite slow
+
+ if blocks==NULL; it is not accessed at all.
+ */
+ bool isValidArea(v3s16 p_min_nodes, v3s16 p_max_nodes,
+ core::map<s16, MapBlock*> *blocks)
+ {
+ core::map<s16, MapBlock*> bs;
+
+ v3s16 p_min = getNodeBlockPos(p_min_nodes);
+ v3s16 p_max = getNodeBlockPos(p_max_nodes);
+ if(p_min.X != 0 || p_min.Z != 0
+ || p_max.X != 0 || p_max.Z != 0)
+ return false;
+ v3s16 y;
+ for(s16 y=p_min.Y; y<=p_max.Y; y++)
+ {
+ try{
+ MapBlock *block = getBlockNoCreate(y);
+ if(block->isDummy())
+ return false;
+ if(blocks!=NULL)
+ bs[y] = block;
+ }
+ catch(InvalidPositionException &e)
+ {
+ return false;
+ }
+ }
+
+ if(blocks!=NULL)
+ {
+ for(core::map<s16, MapBlock*>::Iterator i=bs.getIterator();
+ i.atEnd()==false; i++)
+ {
+ MapBlock *block = i.getNode()->getValue();
+ s16 y = i.getNode()->getKey();
+ blocks->insert(y, block);
+ }
+ }
+ return true;
+ }
+
+ void getBlocksInArea(v3s16 p_min_nodes, v3s16 p_max_nodes,
+ core::map<v3s16, MapBlock*> &blocks)
+ {
+ v3s16 p_min = getNodeBlockPos(p_min_nodes);
+ v3s16 p_max = getNodeBlockPos(p_max_nodes);
+ v3s16 y;
+ for(s16 y=p_min.Y; y<=p_max.Y; y++)
+ {
+ try{
+ MapBlock *block = getBlockNoCreate(y);
+ blocks.insert(block->getPos(), block);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+ }
+ }
+
+ // virtual from NodeContainer
+ bool isValidPosition(v3s16 p)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+
+ if(blockpos.X != 0 || blockpos.Z != 0)
+ return false;
+
+ MapBlock *blockref;
+ try{
+ blockref = getBlockNoCreate(blockpos.Y);
+ }
+ catch(InvalidPositionException &e)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ // virtual from NodeContainer
+ MapNode getNode(v3s16 p)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+ if(blockpos.X != 0 || blockpos.Z != 0)
+ throw InvalidPositionException
+ ("MapSector only allows Y");
+
+ MapBlock * blockref = getBlockNoCreate(blockpos.Y);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+
+ return blockref->getNode(relpos);
+ }
+ // virtual from NodeContainer
+ void setNode(v3s16 p, MapNode & n)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+ if(blockpos.X != 0 || blockpos.Z != 0)
+ throw InvalidPositionException
+ ("MapSector only allows Y");
+
+ MapBlock * blockref = getBlockNoCreate(blockpos.Y);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+ blockref->setNode(relpos, n);
+ }
+
+ virtual f32 getGroundHeight(v2s16 p, bool generate=false)
+ {
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ }
+ virtual void setGroundHeight(v2s16 p, f32 y, bool generate=false)
+ {
+ }
+
+ // When true, sector metadata is changed from the one on disk
+ // (sector metadata = all but blocks)
+ // Basically, this should be changed to true in every setter method
+ bool differs_from_disk;
+
+ // Counts seconds from last usage.
+ // Sector can be deleted from memory after some time of inactivity.
+ // NOTE: It has to be made very sure no other thread is accessing
+ // the sector and it doesn't remain in any cache when
+ // deleting it.
+ float usage_timer;
+
+protected:
+
+ // The pile of MapBlocks
+ core::map<s16, MapBlock*> m_blocks;
+ //JMutex m_blocks_mutex; // For public access functions
+
+ NodeContainer *m_parent;
+ // Position on parent (in MapBlock widths)
+ v2s16 m_pos;
+
+ // Be sure to set this to NULL when the cached block is deleted
+ MapBlock *m_block_cache;
+ s16 m_block_cache_y;
+
+ // This is used for protecting m_blocks
+ JMutex m_mutex;
+
+ /*
+ Private methods
+ */
+ MapBlock *getBlockBuffered(s16 y);
+
+};
+
+class ServerMapSector : public MapSector
+{
+public:
+ ServerMapSector(NodeContainer *parent, v2s16 pos, u16 hm_split);
+ ~ServerMapSector();
+
+ u32 getId() const
+ {
+ return MAPSECTOR_SERVER;
+ }
+
+ void setHeightmap(v2s16 hm_p, FixedHeightmap *hm);
+ FixedHeightmap * getHeightmap(v2s16 hm_p);
+
+ void printHeightmaps()
+ {
+ for(s16 y=0; y<m_hm_split; y++)
+ for(s16 x=0; x<m_hm_split; x++)
+ {
+ std::cout<<"Sector "
+ <<"("<<m_pos.X<<","<<m_pos.Y<<")"
+ " heightmap "
+ "("<<x<<","<<y<<"):"
+ <<std::endl;
+ FixedHeightmap *hm = getHeightmap(v2s16(x,y));
+ hm->print();
+ }
+ }
+
+ void setObjects(core::map<v3s16, u8> *objects)
+ {
+ m_objects = objects;
+ differs_from_disk = true;
+ }
+
+ core::map<v3s16, u8> * getObjects()
+ {
+ differs_from_disk = true;
+ return m_objects;
+ }
+
+ f32 getGroundHeight(v2s16 p, bool generate=false);
+ void setGroundHeight(v2s16 p, f32 y, bool generate=false);
+
+ /*
+ These functions handle metadata.
+ They do not handle blocks.
+ */
+ void serialize(std::ostream &os, u8 version);
+
+ static ServerMapSector* deSerialize(
+ std::istream &is,
+ NodeContainer *parent,
+ v2s16 p2d,
+ Heightmap *master_hm,
+ core::map<v2s16, MapSector*> & sectors
+ );
+
+private:
+ // Heightmap(s) for the sector
+ FixedHeightmap *m_heightmaps[MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT];
+ // Sector is split in m_hm_split^2 heightmaps.
+ // Value of 0 means there is no heightmap.
+ u16 m_hm_split;
+ // These are removed when they are drawn to blocks.
+ // - Each is drawn when generating blocks; When the last one of
+ // the needed blocks is being generated.
+ core::map<v3s16, u8> *m_objects;
+};
+
+class ClientMapSector : public MapSector
+{
+public:
+ ClientMapSector(NodeContainer *parent, v2s16 pos);
+ ~ClientMapSector();
+
+ u32 getId() const
+ {
+ return MAPSECTOR_CLIENT;
+ }
+
+ void deSerialize(std::istream &is);
+
+ s16 getCorner(u16 i)
+ {
+ return m_corners[i];
+ }
+
+private:
+ // The ground height of the corners is stored in here
+ s16 m_corners[4];
+};
+
+#endif
+
diff --git a/src/player.cpp b/src/player.cpp
new file mode 100644
index 000000000..080de6067
--- /dev/null
+++ b/src/player.cpp
@@ -0,0 +1,358 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "player.h"
+#include "map.h"
+#include "connection.h"
+#include "constants.h"
+
+Player::Player():
+ touching_ground(false),
+ inventory(PLAYER_INVENTORY_SIZE),
+ peer_id(PEER_ID_NEW),
+ m_speed(0,0,0),
+ m_position(0,0,0)
+{
+ updateName("<not set>");
+}
+
+Player::~Player()
+{
+}
+
+void Player::move(f32 dtime, Map &map)
+{
+ v3f position = getPosition();
+ v3f oldpos = position;
+ v3s16 oldpos_i = floatToInt(oldpos);
+
+ /*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
+ <<oldpos_i.Z<<")"<<std::endl;*/
+
+ position += m_speed * dtime;
+
+ // Skip collision detection if player is non-local
+ if(isLocal() == false)
+ {
+ setPosition(position);
+ return;
+ }
+
+ /*
+ Collision detection
+ */
+
+ v3s16 pos_i = floatToInt(position);
+
+ // The frame length is limited to the player going 0.1*BS per call
+ f32 d = (float)BS * 0.15;
+
+#define PLAYER_RADIUS (BS*0.3)
+#define PLAYER_HEIGHT (BS*1.7)
+
+ core::aabbox3d<f32> playerbox(
+ position.X - PLAYER_RADIUS,
+ position.Y - 0.0,
+ position.Z - PLAYER_RADIUS,
+ position.X + PLAYER_RADIUS,
+ position.Y + PLAYER_HEIGHT,
+ position.Z + PLAYER_RADIUS
+ );
+ core::aabbox3d<f32> playerbox_old(
+ oldpos.X - PLAYER_RADIUS,
+ oldpos.Y - 0.0,
+ oldpos.Z - PLAYER_RADIUS,
+ oldpos.X + PLAYER_RADIUS,
+ oldpos.Y + PLAYER_HEIGHT,
+ oldpos.Z + PLAYER_RADIUS
+ );
+
+ //hilightboxes.push_back(playerbox);
+
+ touching_ground = false;
+
+ /*std::cout<<"Checking collisions for ("
+ <<oldpos_i.X<<","<<oldpos_i.Y<<","<<oldpos_i.Z
+ <<") -> ("
+ <<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z
+ <<"):"<<std::endl;*/
+
+ for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++){
+ for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++){
+ for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++){
+ //std::cout<<"with ("<<x<<","<<y<<","<<z<<"): ";
+ try{
+ if(map.getNode(v3s16(x,y,z)).d == MATERIAL_AIR){
+ //std::cout<<"air."<<std::endl;
+ continue;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ // Doing nothing here will block the player from
+ // walking over map borders
+ }
+
+ core::aabbox3d<f32> nodebox = Map::getNodeBox(
+ v3s16(x,y,z));
+
+ // See if the player is touching ground
+ if(
+ fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < d
+ && nodebox.MaxEdge.X-d > playerbox.MinEdge.X
+ && nodebox.MinEdge.X+d < playerbox.MaxEdge.X
+ && nodebox.MaxEdge.Z-d > playerbox.MinEdge.Z
+ && nodebox.MinEdge.Z+d < playerbox.MaxEdge.Z
+ ){
+ touching_ground = true;
+ }
+
+ if(playerbox.intersectsWithBox(nodebox))
+ {
+
+ v3f dirs[3] = {
+ v3f(0,0,1), // back
+ v3f(0,1,0), // top
+ v3f(1,0,0), // right
+ };
+ for(u16 i=0; i<3; i++)
+ {
+ f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
+ f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
+ f32 playermax = playerbox.MaxEdge.dotProduct(dirs[i]);
+ f32 playermin = playerbox.MinEdge.dotProduct(dirs[i]);
+ f32 playermax_old = playerbox_old.MaxEdge.dotProduct(dirs[i]);
+ f32 playermin_old = playerbox_old.MinEdge.dotProduct(dirs[i]);
+
+ bool main_edge_collides =
+ ((nodemax > playermin && nodemax <= playermin_old + d
+ && m_speed.dotProduct(dirs[i]) < 0)
+ ||
+ (nodemin < playermax && nodemin >= playermax_old - d
+ && m_speed.dotProduct(dirs[i]) > 0));
+
+ bool other_edges_collide = true;
+ for(u16 j=0; j<3; j++)
+ {
+ if(j == i)
+ continue;
+ f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
+ f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
+ f32 playermax = playerbox.MaxEdge.dotProduct(dirs[j]);
+ f32 playermin = playerbox.MinEdge.dotProduct(dirs[j]);
+ if(!(nodemax - d > playermin && nodemin + d < playermax))
+ {
+ other_edges_collide = false;
+ break;
+ }
+ }
+
+ if(main_edge_collides && other_edges_collide)
+ {
+ m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
+ position -= position.dotProduct(dirs[i]) * dirs[i];
+ position += oldpos.dotProduct(dirs[i]) * dirs[i];
+ }
+
+ }
+ } // if(playerbox.intersectsWithBox(nodebox))
+ } // for x
+ } // for z
+ } // for y
+
+ setPosition(position);
+}
+
+// Y direction is ignored
+void Player::accelerate(v3f target_speed, f32 max_increase)
+{
+ if(m_speed.X < target_speed.X - max_increase)
+ m_speed.X += max_increase;
+ else if(m_speed.X > target_speed.X + max_increase)
+ m_speed.X -= max_increase;
+ else if(m_speed.X < target_speed.X)
+ m_speed.X = target_speed.X;
+ else if(m_speed.X > target_speed.X)
+ m_speed.X = target_speed.X;
+
+ if(m_speed.Z < target_speed.Z - max_increase)
+ m_speed.Z += max_increase;
+ else if(m_speed.Z > target_speed.Z + max_increase)
+ m_speed.Z -= max_increase;
+ else if(m_speed.Z < target_speed.Z)
+ m_speed.Z = target_speed.Z;
+ else if(m_speed.Z > target_speed.Z)
+ m_speed.Z = target_speed.Z;
+}
+
+/*
+ RemotePlayer
+*/
+
+RemotePlayer::RemotePlayer(
+ scene::ISceneNode* parent,
+ IrrlichtDevice *device,
+ s32 id):
+ scene::ISceneNode(parent, (device==NULL)?NULL:device->getSceneManager(), id),
+ m_text(NULL)
+{
+ m_box = core::aabbox3d<f32>(-BS/2,0,-BS/2,BS/2,BS*2,BS/2);
+
+ if(parent != NULL && device != NULL)
+ {
+ // ISceneNode stores a member called SceneManager
+ scene::ISceneManager* mgr = SceneManager;
+ video::IVideoDriver* driver = mgr->getVideoDriver();
+ gui::IGUIEnvironment* gui = device->getGUIEnvironment();
+
+ // Add a text node for showing the name
+ wchar_t wname[1] = {0};
+ m_text = mgr->addTextSceneNode(gui->getBuiltInFont(),
+ wname, video::SColor(255,255,255,255), this);
+ m_text->setPosition(v3f(0, (f32)BS*2.1, 0));
+
+ // Attach a simple mesh to the player for showing an image
+ scene::SMesh *mesh = new scene::SMesh();
+ { // Front
+ scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+ video::SColor c(255,255,255,255);
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+ video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+ video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0),
+ video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+ buf->append(vertices, 4, indices, 6);
+ // Set material
+ buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+ //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+ buf->getMaterial().setTexture(0, driver->getTexture("../data/player.png"));
+ buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+ //buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+ buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+ // Add to mesh
+ mesh->addMeshBuffer(buf);
+ buf->drop();
+ }
+ { // Back
+ scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+ video::SColor c(255,255,255,255);
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+ video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1),
+ video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0),
+ video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+ buf->append(vertices, 4, indices, 6);
+ // Set material
+ buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+ //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+ buf->getMaterial().setTexture(0, driver->getTexture("../data/player_back.png"));
+ buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+ buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+ // Add to mesh
+ mesh->addMeshBuffer(buf);
+ buf->drop();
+ }
+ scene::IMeshSceneNode *node = mgr->addMeshSceneNode(mesh, this);
+ mesh->drop();
+ node->setPosition(v3f(0,0,0));
+ }
+}
+
+RemotePlayer::~RemotePlayer()
+{
+ if(SceneManager != NULL)
+ ISceneNode::remove();
+}
+
+void RemotePlayer::updateName(const char *name)
+{
+ Player::updateName(name);
+ if(m_text != NULL)
+ {
+ wchar_t wname[PLAYERNAME_SIZE];
+ mbstowcs(wname, m_name, strlen(m_name)+1);
+ m_text->setText(wname);
+ }
+}
+
+/*
+ LocalPlayer
+*/
+
+LocalPlayer::LocalPlayer()
+{
+}
+
+LocalPlayer::~LocalPlayer()
+{
+}
+
+void LocalPlayer::applyControl(float dtime)
+{
+ // Random constants
+#define WALK_ACCELERATION (4.0 * BS)
+#define WALKSPEED_MAX (4.0 * BS)
+ f32 walk_acceleration = WALK_ACCELERATION;
+ f32 walkspeed_max = WALKSPEED_MAX;
+
+ setPitch(control.pitch);
+ setYaw(control.yaw);
+
+ v3f move_direction = v3f(0,0,1);
+ move_direction.rotateXZBy(getYaw());
+
+ v3f speed = v3f(0,0,0);
+
+ // Superspeed mode
+ bool superspeed = false;
+ if(control.superspeed)
+ {
+ speed += move_direction;
+ superspeed = true;
+ }
+
+ if(control.up)
+ {
+ speed += move_direction;
+ }
+ if(control.down)
+ {
+ speed -= move_direction;
+ }
+ if(control.left)
+ {
+ speed += move_direction.crossProduct(v3f(0,1,0));
+ }
+ if(control.right)
+ {
+ speed += move_direction.crossProduct(v3f(0,-1,0));
+ }
+ if(control.jump)
+ {
+ if(touching_ground){
+ v3f speed = getSpeed();
+ speed.Y = 6.5*BS;
+ setSpeed(speed);
+ }
+ }
+
+ // The speed of the player (Y is ignored)
+ if(superspeed)
+ speed = speed.normalize() * walkspeed_max * 5;
+ else
+ speed = speed.normalize() * walkspeed_max;
+
+ f32 inc = walk_acceleration * BS * dtime;
+
+ // Accelerate to target speed with maximum increment
+ accelerate(speed, inc);
+}
+
+
diff --git a/src/player.h b/src/player.h
new file mode 100644
index 000000000..e692f55b1
--- /dev/null
+++ b/src/player.h
@@ -0,0 +1,210 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef PLAYER_HEADER
+#define PLAYER_HEADER
+
+#include "common_irrlicht.h"
+#include "inventory.h"
+
+#define PLAYERNAME_SIZE 20
+
+class Map;
+
+class Player
+{
+public:
+ Player();
+ virtual ~Player();
+
+ void move(f32 dtime, Map &map);
+
+ v3f getSpeed()
+ {
+ return m_speed;
+ }
+
+ void setSpeed(v3f speed)
+ {
+ m_speed = speed;
+ }
+
+ // Y direction is ignored
+ void accelerate(v3f target_speed, f32 max_increase);
+
+ v3f getPosition()
+ {
+ return m_position;
+ }
+
+ virtual void setPosition(v3f position)
+ {
+ m_position = position;
+ }
+
+ void setPitch(f32 pitch)
+ {
+ m_pitch = pitch;
+ }
+
+ virtual void setYaw(f32 yaw)
+ {
+ m_yaw = yaw;
+ }
+
+ f32 getPitch()
+ {
+ return m_pitch;
+ }
+
+ f32 getYaw()
+ {
+ return m_yaw;
+ }
+
+ virtual void updateName(const char *name)
+ {
+ snprintf(m_name, PLAYERNAME_SIZE, "%s", name);
+ }
+
+ const char * getName()
+ {
+ return m_name;
+ }
+
+ virtual bool isLocal() const = 0;
+
+ bool touching_ground;
+
+ Inventory inventory;
+
+ u16 peer_id;
+
+protected:
+ char m_name[PLAYERNAME_SIZE];
+ f32 m_pitch;
+ f32 m_yaw;
+ v3f m_speed;
+ v3f m_position;
+};
+
+class RemotePlayer : public Player, public scene::ISceneNode
+{
+public:
+ RemotePlayer(
+ scene::ISceneNode* parent=NULL,
+ IrrlichtDevice *device=NULL,
+ s32 id=0);
+
+ virtual ~RemotePlayer();
+
+ /*
+ ISceneNode methods
+ */
+
+ virtual void OnRegisterSceneNode()
+ {
+ if (IsVisible)
+ SceneManager->registerNodeForRendering(this);
+
+ ISceneNode::OnRegisterSceneNode();
+ }
+
+ virtual void render()
+ {
+ // Do nothing
+ }
+
+ virtual const core::aabbox3d<f32>& getBoundingBox() const
+ {
+ return m_box;
+ }
+
+ void setPosition(v3f position)
+ {
+ Player::setPosition(position);
+ ISceneNode::setPosition(position);
+ }
+
+ virtual void setYaw(f32 yaw)
+ {
+ Player::setYaw(yaw);
+ ISceneNode::setRotation(v3f(0, -yaw, 0));
+ }
+
+ bool isLocal() const
+ {
+ return false;
+ }
+
+ void updateName(const char *name);
+
+private:
+ scene::ITextSceneNode* m_text;
+ core::aabbox3d<f32> m_box;
+};
+
+struct PlayerControl
+{
+ PlayerControl()
+ {
+ up = false;
+ down = false;
+ left = false;
+ right = false;
+ jump = false;
+ superspeed = false;
+ pitch = 0;
+ yaw = 0;
+ }
+ PlayerControl(
+ bool a_up,
+ bool a_down,
+ bool a_left,
+ bool a_right,
+ bool a_jump,
+ bool a_superspeed,
+ float a_pitch,
+ float a_yaw
+ )
+ {
+ up = a_up;
+ down = a_down;
+ left = a_left;
+ right = a_right;
+ jump = a_jump;
+ superspeed = a_superspeed;
+ pitch = a_pitch;
+ yaw = a_yaw;
+ }
+ bool up;
+ bool down;
+ bool left;
+ bool right;
+ bool jump;
+ bool superspeed;
+ float pitch;
+ float yaw;
+};
+
+class LocalPlayer : public Player
+{
+public:
+ LocalPlayer();
+ virtual ~LocalPlayer();
+
+ bool isLocal() const
+ {
+ return true;
+ }
+
+ void applyControl(float dtime);
+
+ PlayerControl control;
+
+private:
+};
+
+#endif
+
diff --git a/src/porting.h b/src/porting.h
new file mode 100644
index 000000000..3213ec9c0
--- /dev/null
+++ b/src/porting.h
@@ -0,0 +1,15 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef PORTING_HEADER
+#define PORTING_HEADER
+
+#ifdef _WIN32
+ #define SWPRINTF_CHARSTRING L"%S"
+#else
+ #define SWPRINTF_CHARSTRING L"%s"
+#endif
+
+#endif
+
diff --git a/src/serialization.cpp b/src/serialization.cpp
new file mode 100644
index 000000000..72f675594
--- /dev/null
+++ b/src/serialization.cpp
@@ -0,0 +1,77 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "serialization.h"
+#include "utility.h"
+
+void compress(SharedBuffer<u8> data, std::ostream &os, u8 version)
+{
+ if(data.getSize() == 0)
+ return;
+
+ // Write length (u32)
+
+ u8 tmp[4];
+ writeU32(tmp, data.getSize());
+ os.write((char*)tmp, 4);
+
+ // We will be writing 8-bit pairs of more_count and byte
+ u8 more_count = 0;
+ u8 current_byte = data[0];
+ for(u32 i=1; i<data.getSize(); i++)
+ {
+ if(
+ data[i] != current_byte
+ || more_count == 255
+ )
+ {
+ // write count and byte
+ os.write((char*)&more_count, 1);
+ os.write((char*)&current_byte, 1);
+ more_count = 0;
+ current_byte = data[i];
+ }
+ else
+ {
+ more_count++;
+ }
+ }
+ // write count and byte
+ os.write((char*)&more_count, 1);
+ os.write((char*)&current_byte, 1);
+}
+
+void decompress(std::istream &is, std::ostream &os, u8 version)
+{
+ // Read length (u32)
+
+ u8 tmp[4];
+ is.read((char*)tmp, 4);
+ u32 len = readU32(tmp);
+
+ // We will be reading 8-bit pairs of more_count and byte
+ u32 count = 0;
+ for(;;)
+ {
+ u8 more_count=0;
+ u8 byte=0;
+
+ is.read((char*)&more_count, 1);
+
+ is.read((char*)&byte, 1);
+
+ if(is.eof())
+ throw SerializationError("decompress: stream ended halfway");
+
+ for(s32 i=0; i<(u16)more_count+1; i++)
+ os.write((char*)&byte, 1);
+
+ count += (u16)more_count+1;
+
+ if(count == len)
+ break;
+ }
+}
+
+
diff --git a/src/serialization.h b/src/serialization.h
new file mode 100644
index 000000000..fd9b39535
--- /dev/null
+++ b/src/serialization.h
@@ -0,0 +1,49 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef SERIALIZATION_HEADER
+#define SERIALIZATION_HEADER
+
+#include "common_irrlicht.h"
+#include "exceptions.h"
+#include <iostream>
+#include "utility.h"
+
+/*
+ NOTE: The goal is to increment this so that saved maps will be
+ loadable by any version. Other compatibility is not
+ maintained.
+ Serialization format versions:
+ 0: original networked test with 1-byte nodes
+ 1: update with 2-byte nodes
+ 2: lighting is transmitted in param
+ 3: optional fetching of far blocks
+ 4: block compression
+ 5: sector objects NOTE: block compression was left accidentally out
+ 6: failed attempt at switching block compression on again
+ 7: block compression switched on again
+ 8: (dev) server-initiated block transfers and all kinds of stuff
+ 9: (dev) block objects
+*/
+// This represents an uninitialized or invalid format
+#define SER_FMT_VER_INVALID 255
+// Highest supported serialization version
+#define SER_FMT_VER_HIGHEST 9
+// Lowest supported serialization version
+#define SER_FMT_VER_LOWEST 0
+
+#define ser_ver_supported(v) (v >= SER_FMT_VER_LOWEST && v <= SER_FMT_VER_HIGHEST)
+
+void compress(SharedBuffer<u8> data, std::ostream &os, u8 version);
+void decompress(std::istream &is, std::ostream &os, u8 version);
+
+/*class Serializable
+{
+public:
+ void serialize(std::ostream &os, u8 version) = 0;
+ void deSerialize(std::istream &istr);
+};*/
+
+#endif
+
diff --git a/src/server.cpp b/src/server.cpp
new file mode 100644
index 000000000..a5f55ab5d
--- /dev/null
+++ b/src/server.cpp
@@ -0,0 +1,2115 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "server.h"
+#include "utility.h"
+#include <iostream>
+#include "clientserver.h"
+#include "map.h"
+#include "jmutexautolock.h"
+#include "main.h"
+#include "constants.h"
+
+void * ServerThread::Thread()
+{
+ ThreadStarted();
+
+ DSTACK(__FUNCTION_NAME);
+
+ while(getRun())
+ {
+ try{
+ m_server->AsyncRunStep();
+
+ //dout_server<<"Running m_server->Receive()"<<std::endl;
+ m_server->Receive();
+ }
+ catch(con::NoIncomingDataException &e)
+ {
+ }
+#if CATCH_UNHANDLED_EXCEPTIONS
+ /*
+ This is what has to be done in threads to get suitable debug info
+ */
+ catch(std::exception &e)
+ {
+ dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
+ <<e.what()<<std::endl;
+ assert(0);
+ }
+#endif
+ }
+
+
+ return NULL;
+}
+
+void * EmergeThread::Thread()
+{
+ ThreadStarted();
+
+ DSTACK(__FUNCTION_NAME);
+
+ bool debug=false;
+#if CATCH_UNHANDLED_EXCEPTIONS
+ try
+ {
+#endif
+
+ /*
+ Get block info from queue, emerge them and send them
+ to clients.
+
+ After queue is empty, exit.
+ */
+ while(getRun())
+ {
+ QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
+ if(qptr == NULL)
+ break;
+
+ SharedPtr<QueuedBlockEmerge> q(qptr);
+
+ v3s16 &p = q->pos;
+
+ //derr_server<<"EmergeThread::Thread(): running"<<std::endl;
+
+ /*
+ Try to emerge it from somewhere.
+
+ If it is only wanted as optional, only loading from disk
+ will be allowed.
+ */
+
+ /*
+ Check if any peer wants it as non-optional. In that case it
+ will be generated.
+
+ Also decrement the emerge queue count in clients.
+ */
+
+ bool optional = true;
+
+ {
+ core::map<u16, u8>::Iterator i;
+ for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++)
+ {
+ //u16 peer_id = i.getNode()->getKey();
+
+ // Check flags
+ u8 flags = i.getNode()->getValue();
+ if((flags & TOSERVER_GETBLOCK_FLAG_OPTIONAL) == false)
+ optional = false;
+
+ }
+ }
+
+ /*dstream<<"EmergeThread: p="
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
+ <<"optional="<<optional<<std::endl;*/
+
+ ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
+
+ core::map<v3s16, MapBlock*> changed_blocks;
+ core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
+
+ MapBlock *block = NULL;
+ bool got_block = true;
+ core::map<v3s16, MapBlock*> modified_blocks;
+
+ {//envlock
+
+ JMutexAutoLock envlock(m_server->m_env_mutex);
+
+ //TimeTaker timer("block emerge envlock", g_device);
+
+ try{
+ bool only_from_disk = false;
+
+ if(optional)
+ only_from_disk = true;
+
+ block = map.emergeBlock(
+ p,
+ only_from_disk,
+ changed_blocks,
+ lighting_invalidated_blocks);
+
+ // If it is a dummy, block was not found on disk
+ if(block->isDummy())
+ {
+ //dstream<<"EmergeThread: Got a dummy block"<<std::endl;
+ got_block = false;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ // Block not found.
+ // This happens when position is over limit.
+ got_block = false;
+ }
+
+ if(got_block)
+ {
+ if(debug && changed_blocks.size() > 0)
+ {
+ dout_server<<DTIME<<"Got changed_blocks: ";
+ for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ MapBlock *block = i.getNode()->getValue();
+ v3s16 p = block->getPos();
+ dout_server<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") ";
+ }
+ dout_server<<std::endl;
+ }
+
+ /*
+ Collect a list of blocks that have been modified in
+ addition to the fetched one.
+ */
+
+ // Add all the "changed blocks"
+ for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ MapBlock *block = i.getNode()->getValue();
+ modified_blocks.insert(block->getPos(), block);
+ }
+
+ //TimeTaker timer("** updateLighting", g_device);
+ // Update lighting without locking the environment mutex,
+ // add modified blocks to changed blocks
+ map.updateLighting(lighting_invalidated_blocks, modified_blocks);
+ }
+ // If we got no block, there should be no invalidated blocks
+ else
+ {
+ assert(lighting_invalidated_blocks.size() == 0);
+ }
+
+ }//envlock
+
+ /*
+ Set sent status of modified blocks on clients
+ */
+
+ // NOTE: Server's clients are also behind the connection mutex
+ JMutexAutoLock lock(m_server->m_con_mutex);
+
+ /*
+ Add the originally fetched block to the modified list
+ */
+ if(got_block)
+ {
+ modified_blocks.insert(p, block);
+ }
+
+ /*
+ Set the modified blocks unsent for all the clients
+ */
+
+ for(core::map<u16, RemoteClient*>::Iterator
+ i = m_server->m_clients.getIterator();
+ i.atEnd() == false; i++)
+ {
+ RemoteClient *client = i.getNode()->getValue();
+
+ if(modified_blocks.size() > 0)
+ {
+ // Remove block from sent history
+ client->SetBlocksNotSent(modified_blocks);
+ }
+
+ if(q->peer_ids.find(client->peer_id) != NULL)
+ {
+ // Decrement emerge queue count of client
+ client->BlockEmerged();
+ }
+ }
+
+ }
+#if CATCH_UNHANDLED_EXCEPTIONS
+ }//try
+ /*
+ This is what has to be done in threads to get suitable debug info
+ */
+ catch(std::exception &e)
+ {
+ dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
+ <<e.what()<<std::endl;
+ assert(0);
+ }
+#endif
+
+ return NULL;
+}
+
+void RemoteClient::SendBlocks(Server *server, float dtime)
+{
+ DSTACK(__FUNCTION_NAME);
+ /*
+ Find what blocks to send to the client next, and send them.
+
+ Throttling is based on limiting the amount of blocks "flying"
+ at a given time.
+ */
+
+ // Can't send anything without knowing version
+ if(serialization_version == SER_FMT_VER_INVALID)
+ {
+ dstream<<"RemoteClient::SendBlocks(): Not sending, no version."
+ <<std::endl;
+ return;
+ }
+
+ {
+ JMutexAutoLock lock(m_blocks_sending_mutex);
+
+ if(m_blocks_sending.size() >= MAX_SIMULTANEOUS_BLOCK_SENDS)
+ {
+ //dstream<<"Not sending any blocks, Queue full."<<std::endl;
+ return;
+ }
+ }
+
+ Player *player = server->m_env.getPlayer(peer_id);
+
+ v3f playerpos = player->getPosition();
+ v3f playerspeed = player->getSpeed();
+
+ v3s16 center_nodepos = floatToInt(playerpos);
+
+ v3s16 center = getNodeBlockPos(center_nodepos);
+
+ /*
+ Find out what block the player is going to next and set
+ center to it.
+
+ Don't react to speeds under the initial value of highest_speed
+ */
+ /*f32 highest_speed = 0.1 * BS;
+ v3s16 dir(0,0,0);
+ if(abs(playerspeed.X) > highest_speed)
+ {
+ highest_speed = playerspeed.X;
+ if(playerspeed.X > 0)
+ dir = v3s16(1,0,0);
+ else
+ dir = v3s16(-1,0,0);
+ }
+ if(abs(playerspeed.Y) > highest_speed)
+ {
+ highest_speed = playerspeed.Y;
+ if(playerspeed.Y > 0)
+ dir = v3s16(0,1,0);
+ else
+ dir = v3s16(0,-1,0);
+ }
+ if(abs(playerspeed.Z) > highest_speed)
+ {
+ highest_speed = playerspeed.Z;
+ if(playerspeed.Z > 0)
+ dir = v3s16(0,0,1);
+ else
+ dir = v3s16(0,0,-1);
+ }
+
+ center += dir;*/
+
+ /*
+ Calculate the starting value of the block finder radius.
+
+ The radius shall be the last used value minus the
+ maximum moved distance.
+ */
+ /*s16 d_start = m_last_block_find_d;
+ if(max_moved >= d_start)
+ {
+ d_start = 0;
+ }
+ else
+ {
+ d_start -= max_moved;
+ }*/
+
+ s16 last_nearest_unsent_d;
+ s16 d_start;
+ {
+ JMutexAutoLock lock(m_blocks_sent_mutex);
+
+ if(m_last_center != center)
+ {
+ m_nearest_unsent_d = 0;
+ m_last_center = center;
+ }
+
+ static float reset_counter = 0;
+ reset_counter += dtime;
+ if(reset_counter > 5.0)
+ {
+ reset_counter = 0;
+ m_nearest_unsent_d = 0;
+ }
+
+ last_nearest_unsent_d = m_nearest_unsent_d;
+
+ d_start = m_nearest_unsent_d;
+ }
+
+ u16 maximum_simultaneous_block_sends = MAX_SIMULTANEOUS_BLOCK_SENDS;
+
+ {
+ SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
+ m_time_from_building.m_value += dtime;
+ /*
+ Check the time from last addNode/removeNode.
+ Decrease send rate if player is building stuff.
+ */
+ if(m_time_from_building.m_value
+ < FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)
+ {
+ maximum_simultaneous_block_sends
+ = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
+ }
+ }
+
+ // Serialization version used
+ //u8 ser_version = serialization_version;
+
+ //bool has_incomplete_blocks = false;
+
+ /*
+ TODO: Get this from somewhere
+ TODO: Values more than 7 make placing and removing blocks very
+ sluggish when the map is being generated. This is
+ because d is looped every time from 0 to d_max if no
+ blocks are found for sending.
+ */
+ //s16 d_max = 7;
+ s16 d_max = 8;
+
+ //TODO: Get this from somewhere (probably a bigger value)
+ s16 d_max_gen = 5;
+
+ //dstream<<"Starting from "<<d_start<<std::endl;
+
+ for(s16 d = d_start; d <= d_max; d++)
+ {
+ //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
+
+ //if(has_incomplete_blocks == false)
+ {
+ JMutexAutoLock lock(m_blocks_sent_mutex);
+ /*
+ If m_nearest_unsent_d was changed by the EmergeThread
+ (it can change it to 0 through SetBlockNotSent),
+ update our d to it.
+ Else update m_nearest_unsent_d
+ */
+ if(m_nearest_unsent_d != last_nearest_unsent_d)
+ {
+ d = m_nearest_unsent_d;
+ }
+ else
+ {
+ m_nearest_unsent_d = d;
+ }
+ last_nearest_unsent_d = m_nearest_unsent_d;
+ }
+
+ /*
+ Get the border/face dot coordinates of a "d-radiused"
+ box
+ */
+ core::list<v3s16> list;
+ getFacePositions(list, d);
+
+ core::list<v3s16>::Iterator li;
+ for(li=list.begin(); li!=list.end(); li++)
+ {
+ v3s16 p = *li + center;
+
+ /*
+ Send throttling
+ - Don't allow too many simultaneous transfers
+
+ Also, don't send blocks that are already flying.
+ */
+ {
+ JMutexAutoLock lock(m_blocks_sending_mutex);
+
+ if(m_blocks_sending.size()
+ >= maximum_simultaneous_block_sends)
+ {
+ /*dstream<<"Not sending more blocks. Queue full. "
+ <<m_blocks_sending.size()
+ <<std::endl;*/
+ return;
+ }
+
+ if(m_blocks_sending.find(p) != NULL)
+ continue;
+ }
+
+ /*
+ Do not go over-limit
+ */
+ if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
+ continue;
+
+ bool generate = d <= d_max_gen;
+
+ // Limit the generating area vertically to half
+ if(abs(p.Y - center.Y) > d_max_gen / 2)
+ generate = false;
+
+ /*
+ Don't send already sent blocks
+ */
+ {
+ JMutexAutoLock lock(m_blocks_sent_mutex);
+
+ if(m_blocks_sent.find(p) != NULL)
+ continue;
+ }
+
+ /*
+ Check if map has this block
+ */
+ MapBlock *block = NULL;
+ try
+ {
+ block = server->m_env.getMap().getBlockNoCreate(p);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ bool surely_not_found_on_disk = false;
+ if(block != NULL)
+ {
+ /*if(block->isIncomplete())
+ {
+ has_incomplete_blocks = true;
+ continue;
+ }*/
+
+ if(block->isDummy())
+ {
+ surely_not_found_on_disk = true;
+ }
+ }
+
+ /*
+ If block has been marked to not exist on disk (dummy)
+ and generating new ones is not wanted, skip block. TODO
+ */
+ if(generate == false && surely_not_found_on_disk == true)
+ {
+ // get next one.
+ continue;
+ }
+
+ /*
+ Add inexistent block to emerge queue.
+ */
+ if(block == NULL || surely_not_found_on_disk)
+ {
+ // Block not found.
+ SharedPtr<JMutexAutoLock> lock
+ (m_num_blocks_in_emerge_queue.getLock());
+
+ //TODO: Get value from somewhere
+ //TODO: Balance between clients
+ //if(server->m_emerge_queue.size() < 1)
+
+ // Allow only one block in emerge queue
+ if(m_num_blocks_in_emerge_queue.m_value == 0)
+ {
+ // Add it to the emerge queue and trigger the thread
+
+ u8 flags = 0;
+ if(generate == false)
+ flags |= TOSERVER_GETBLOCK_FLAG_OPTIONAL;
+
+ {
+ m_num_blocks_in_emerge_queue.m_value++;
+ }
+
+ server->m_emerge_queue.addBlock(peer_id, p, flags);
+ server->m_emergethread.trigger();
+ }
+
+ // get next one.
+ continue;
+ }
+
+ /*
+ Send block
+ */
+
+ /*dstream<<"RemoteClient::SendBlocks(): d="<<d<<", p="
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<" sending queue size: "<<m_blocks_sending.size()<<std::endl;*/
+
+ server->SendBlockNoLock(peer_id, block, serialization_version);
+
+ /*
+ Add to history
+ */
+ SentBlock(p);
+ }
+ }
+
+ // Don't add anything here. The loop breaks by returning.
+}
+
+void RemoteClient::SendObjectData(
+ Server *server,
+ float dtime,
+ core::map<v3s16, bool> &stepped_blocks
+ )
+{
+ DSTACK(__FUNCTION_NAME);
+
+ // Can't send anything without knowing version
+ if(serialization_version == SER_FMT_VER_INVALID)
+ {
+ dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
+ <<std::endl;
+ return;
+ }
+
+ /*
+ Send a TOCLIENT_OBJECTDATA packet.
+ Sent as unreliable.
+
+ u16 command
+ u16 number of player positions
+ for each player:
+ v3s32 position*100
+ v3s32 speed*100
+ s32 pitch*100
+ s32 yaw*100
+ u16 count of blocks
+ for each block:
+ block objects
+ */
+
+ std::ostringstream os(std::ios_base::binary);
+ u8 buf[12];
+
+ // Write command
+ writeU16(buf, TOCLIENT_OBJECTDATA);
+ os.write((char*)buf, 2);
+
+ /*
+ Get and write player data
+ */
+
+ core::list<Player*> players = server->m_env.getPlayers();
+
+ // Write player count
+ u16 playercount = players.size();
+ writeU16(buf, playercount);
+ os.write((char*)buf, 2);
+
+ core::list<Player*>::Iterator i;
+ for(i = players.begin();
+ i != players.end(); i++)
+ {
+ Player *player = *i;
+
+ v3f pf = player->getPosition();
+ v3f sf = player->getSpeed();
+
+ v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100);
+ v3s32 speed_i (sf.X*100, sf.Y*100, sf.Z*100);
+ s32 pitch_i (player->getPitch() * 100);
+ s32 yaw_i (player->getYaw() * 100);
+
+ writeU16(buf, player->peer_id);
+ os.write((char*)buf, 2);
+ writeV3S32(buf, position_i);
+ os.write((char*)buf, 12);
+ writeV3S32(buf, speed_i);
+ os.write((char*)buf, 12);
+ writeS32(buf, pitch_i);
+ os.write((char*)buf, 4);
+ writeS32(buf, yaw_i);
+ os.write((char*)buf, 4);
+ }
+
+ /*
+ Get and write object data
+ */
+
+ /*
+ Get nearby blocks.
+
+ For making players to be able to build to their nearby
+ environment (building is not possible on blocks that are not
+ in memory):
+ - Set blocks changed
+ - Add blocks to emerge queue if they are not found
+ */
+
+ Player *player = server->m_env.getPlayer(peer_id);
+
+ v3f playerpos = player->getPosition();
+ v3f playerspeed = player->getSpeed();
+
+ v3s16 center_nodepos = floatToInt(playerpos);
+ v3s16 center = getNodeBlockPos(center_nodepos);
+
+ s16 d_max = ACTIVE_OBJECT_D_BLOCKS;
+
+ core::map<v3s16, MapBlock*> blocks;
+
+ for(s16 d = 0; d <= d_max; d++)
+ {
+ core::list<v3s16> list;
+ getFacePositions(list, d);
+
+ core::list<v3s16>::Iterator li;
+ for(li=list.begin(); li!=list.end(); li++)
+ {
+ v3s16 p = *li + center;
+
+ /*
+ Ignore blocks that haven't been sent to the client
+ */
+ {
+ JMutexAutoLock sentlock(m_blocks_sent_mutex);
+ if(m_blocks_sent.find(p) == NULL)
+ continue;
+ }
+
+ try
+ {
+
+ // Get block
+ MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);
+
+ // Step block if not in stepped_blocks and add to stepped_blocks
+ if(stepped_blocks.find(p) == NULL)
+ {
+ block->stepObjects(dtime, true);
+ stepped_blocks.insert(p, true);
+ block->setChangedFlag();
+ }
+
+ // Add block to queue
+ blocks.insert(p, block);
+
+ } //try
+ catch(InvalidPositionException &e)
+ {
+ // Not in memory
+ // Add it to the emerge queue and trigger the thread.
+ // Fetch the block only if it is on disk.
+
+ // Grab and increment counter
+ SharedPtr<JMutexAutoLock> lock
+ (m_num_blocks_in_emerge_queue.getLock());
+ m_num_blocks_in_emerge_queue.m_value++;
+
+ // Add to queue as an anonymous fetch from disk
+ u8 flags = TOSERVER_GETBLOCK_FLAG_OPTIONAL;
+ server->m_emerge_queue.addBlock(0, p, flags);
+ server->m_emergethread.trigger();
+ }
+ }
+ }
+
+ /*
+ Write objects
+ */
+
+ u16 blockcount = blocks.size();
+
+ // Write block count
+ writeU16(buf, blockcount);
+ os.write((char*)buf, 2);
+
+ for(core::map<v3s16, MapBlock*>::Iterator
+ i = blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ // Write blockpos
+ writeV3S16(buf, p);
+ os.write((char*)buf, 6);
+ // Write objects
+ MapBlock *block = i.getNode()->getValue();
+ block->serializeObjects(os, serialization_version);
+ }
+
+ /*
+ Send data
+ */
+
+ // Make data buffer
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as unreliable
+ server->m_con.Send(peer_id, 0, data, false);
+}
+
+void RemoteClient::GotBlock(v3s16 p)
+{
+ JMutexAutoLock lock(m_blocks_sending_mutex);
+ JMutexAutoLock lock2(m_blocks_sent_mutex);
+ if(m_blocks_sending.find(p) != NULL)
+ m_blocks_sending.remove(p);
+ else
+ dstream<<"RemoteClient::GotBlock(): Didn't find in"
+ " m_blocks_sending"<<std::endl;
+ m_blocks_sent.insert(p, true);
+}
+
+void RemoteClient::SentBlock(v3s16 p)
+{
+ JMutexAutoLock lock(m_blocks_sending_mutex);
+ if(m_blocks_sending.size() > 15)
+ {
+ dstream<<"RemoteClient::SentBlock(): "
+ <<"m_blocks_sending.size()="
+ <<m_blocks_sending.size()<<std::endl;
+ }
+ if(m_blocks_sending.find(p) == NULL)
+ m_blocks_sending.insert(p, 0.0);
+ else
+ dstream<<"RemoteClient::SentBlock(): Sent block"
+ " already in m_blocks_sending"<<std::endl;
+}
+
+void RemoteClient::SetBlockNotSent(v3s16 p)
+{
+ JMutexAutoLock sendinglock(m_blocks_sending_mutex);
+ JMutexAutoLock sentlock(m_blocks_sent_mutex);
+
+ m_nearest_unsent_d = 0;
+
+ if(m_blocks_sending.find(p) != NULL)
+ m_blocks_sending.remove(p);
+ if(m_blocks_sent.find(p) != NULL)
+ m_blocks_sent.remove(p);
+}
+
+void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
+{
+ JMutexAutoLock sendinglock(m_blocks_sending_mutex);
+ JMutexAutoLock sentlock(m_blocks_sent_mutex);
+
+ m_nearest_unsent_d = 0;
+
+ for(core::map<v3s16, MapBlock*>::Iterator
+ i = blocks.getIterator();
+ i.atEnd()==false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+
+ if(m_blocks_sending.find(p) != NULL)
+ m_blocks_sending.remove(p);
+ if(m_blocks_sent.find(p) != NULL)
+ m_blocks_sent.remove(p);
+ }
+}
+
+void RemoteClient::BlockEmerged()
+{
+ SharedPtr<JMutexAutoLock> lock(m_num_blocks_in_emerge_queue.getLock());
+ assert(m_num_blocks_in_emerge_queue.m_value > 0);
+ m_num_blocks_in_emerge_queue.m_value--;
+}
+
+/*void RemoteClient::RunSendingTimeouts(float dtime, float timeout)
+{
+ JMutexAutoLock sendinglock(m_blocks_sending_mutex);
+
+ core::list<v3s16> remove_queue;
+ for(core::map<v3s16, float>::Iterator
+ i = m_blocks_sending.getIterator();
+ i.atEnd()==false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ float t = i.getNode()->getValue();
+ t += dtime;
+ i.getNode()->setValue(t);
+
+ if(t > timeout)
+ {
+ remove_queue.push_back(p);
+ }
+ }
+ for(core::list<v3s16>::Iterator
+ i = remove_queue.begin();
+ i != remove_queue.end(); i++)
+ {
+ m_blocks_sending.remove(*i);
+ }
+}*/
+
+/*
+ PlayerInfo
+*/
+
+PlayerInfo::PlayerInfo()
+{
+ name[0] = 0;
+}
+
+void PlayerInfo::PrintLine(std::ostream *s)
+{
+ (*s)<<id<<": \""<<name<<"\" ("
+ <<position.X<<","<<position.Y
+ <<","<<position.Z<<") ";
+ address.print(s);
+ (*s)<<" avg_rtt="<<avg_rtt;
+ (*s)<<std::endl;
+}
+
+u32 PIChecksum(core::list<PlayerInfo> &l)
+{
+ core::list<PlayerInfo>::Iterator i;
+ u32 checksum = 1;
+ u32 a = 10;
+ for(i=l.begin(); i!=l.end(); i++)
+ {
+ checksum += a * (i->id+1);
+ checksum ^= 0x435aafcd;
+ a *= 10;
+ }
+ return checksum;
+}
+
+/*
+ Server
+*/
+
+Server::Server(
+ std::string mapsavedir,
+ bool creative_mode,
+ MapgenParams mapgen_params
+ ):
+ m_env(new ServerMap(mapsavedir, mapgen_params), dout_server),
+ m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
+ m_thread(this),
+ m_emergethread(this),
+ m_creative_mode(creative_mode)
+{
+ m_env_mutex.Init();
+ m_con_mutex.Init();
+ m_step_dtime_mutex.Init();
+ m_step_dtime = 0.0;
+}
+
+Server::~Server()
+{
+ // Stop threads
+ stop();
+
+ JMutexAutoLock clientslock(m_con_mutex);
+
+ for(core::map<u16, RemoteClient*>::Iterator
+ i = m_clients.getIterator();
+ i.atEnd() == false; i++)
+ {
+ u16 peer_id = i.getNode()->getKey();
+
+ // Delete player
+ {
+ JMutexAutoLock envlock(m_env_mutex);
+ m_env.removePlayer(peer_id);
+ }
+
+ // Delete client
+ delete i.getNode()->getValue();
+ }
+}
+
+void Server::start(unsigned short port)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Stop thread if already running
+ m_thread.stop();
+
+ // Initialize connection
+ m_con.setTimeoutMs(50);
+ m_con.Serve(port);
+
+ // Start thread
+ m_thread.setRun(true);
+ m_thread.Start();
+
+ dout_server<<"Server started on port "<<port<<std::endl;
+}
+
+void Server::stop()
+{
+ DSTACK(__FUNCTION_NAME);
+ // Stop threads (set run=false first so both start stopping)
+ m_thread.setRun(false);
+ m_emergethread.setRun(false);
+ m_thread.stop();
+ m_emergethread.stop();
+
+ dout_server<<"Server threads stopped"<<std::endl;
+}
+
+void Server::step(float dtime)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Limit a bit
+ if(dtime > 2.0)
+ dtime = 2.0;
+ {
+ JMutexAutoLock lock(m_step_dtime_mutex);
+ m_step_dtime += dtime;
+ }
+}
+
+void Server::AsyncRunStep()
+{
+ DSTACK(__FUNCTION_NAME);
+ float dtime;
+ {
+ JMutexAutoLock lock1(m_step_dtime_mutex);
+ dtime = m_step_dtime;
+ if(dtime < 0.001)
+ return;
+ m_step_dtime = 0.0;
+ }
+
+ //dstream<<"Server steps "<<dtime<<std::endl;
+
+ //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
+ {
+ // Has to be locked for peerAdded/Removed
+ JMutexAutoLock lock1(m_env_mutex);
+ // Process connection's timeouts
+ JMutexAutoLock lock2(m_con_mutex);
+ m_con.RunTimeouts(dtime);
+ }
+ {
+ // Step environment
+ // This also runs Map's timers
+ JMutexAutoLock lock(m_env_mutex);
+ m_env.step(dtime);
+ }
+
+ /*
+ Do background stuff
+ */
+
+ // Periodically print some info
+ {
+ static float counter = 0.0;
+ counter += dtime;
+ if(counter >= 30.0)
+ {
+ counter = 0.0;
+
+ JMutexAutoLock lock2(m_con_mutex);
+
+ for(core::map<u16, RemoteClient*>::Iterator
+ i = m_clients.getIterator();
+ i.atEnd() == false; i++)
+ {
+ //u16 peer_id = i.getNode()->getKey();
+ RemoteClient *client = i.getNode()->getValue();
+ client->PrintInfo(std::cout);
+ }
+ }
+ }
+
+ // Run time- and client- related stuff
+ // NOTE: If you intend to add something here, check that it
+ // doesn't fit in RemoteClient::SendBlocks for exampel.
+ /*{
+ // Clients are behind connection lock
+ JMutexAutoLock lock(m_con_mutex);
+
+ for(core::map<u16, RemoteClient*>::Iterator
+ i = m_clients.getIterator();
+ i.atEnd() == false; i++)
+ {
+ RemoteClient *client = i.getNode()->getValue();
+ //con::Peer *peer = m_con.GetPeer(client->peer_id);
+ //client->RunSendingTimeouts(dtime, peer->resend_timeout);
+ }
+ }*/
+
+ // Send blocks to clients
+ SendBlocks(dtime);
+
+ // Send object positions
+ {
+ static float counter = 0.0;
+ counter += dtime;
+ //TODO: Get value from somewhere
+ if(counter >= 0.1)
+ {
+ JMutexAutoLock lock1(m_env_mutex);
+ JMutexAutoLock lock2(m_con_mutex);
+ SendObjectData(counter);
+
+ counter = 0.0;
+ }
+ }
+
+ {
+ // Save map
+ static float counter = 0.0;
+ counter += dtime;
+ if(counter >= SERVER_MAP_SAVE_INTERVAL)
+ {
+ counter = 0.0;
+
+ JMutexAutoLock lock(m_env_mutex);
+ // Save only changed parts
+ m_env.getMap().save(true);
+ }
+ }
+}
+
+void Server::Receive()
+{
+ DSTACK(__FUNCTION_NAME);
+ u32 data_maxsize = 10000;
+ Buffer<u8> data(data_maxsize);
+ u16 peer_id;
+ u32 datasize;
+ try{
+ {
+ JMutexAutoLock lock(m_con_mutex);
+ datasize = m_con.Receive(peer_id, *data, data_maxsize);
+ }
+ ProcessData(*data, datasize, peer_id);
+ }
+ catch(con::InvalidIncomingDataException &e)
+ {
+ derr_server<<"Server::Receive(): "
+ "InvalidIncomingDataException: what()="
+ <<e.what()<<std::endl;
+ }
+ catch(con::PeerNotFoundException &e)
+ {
+ //NOTE: This is not needed anymore
+
+ // The peer has been disconnected.
+ // Find the associated player and remove it.
+
+ /*JMutexAutoLock envlock(m_env_mutex);
+
+ dout_server<<"ServerThread: peer_id="<<peer_id
+ <<" has apparently closed connection. "
+ <<"Removing player."<<std::endl;
+
+ m_env.removePlayer(peer_id);*/
+ }
+}
+
+void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Environment is locked first.
+ JMutexAutoLock envlock(m_env_mutex);
+ JMutexAutoLock conlock(m_con_mutex);
+
+ con::Peer *peer;
+ try{
+ peer = m_con.GetPeer(peer_id);
+ }
+ catch(con::PeerNotFoundException &e)
+ {
+ derr_server<<DTIME<<"Server::ProcessData(): Cancelling: peer "
+ <<peer_id<<" not found"<<std::endl;
+ return;
+ }
+
+ //u8 peer_ser_ver = peer->serialization_version;
+ u8 peer_ser_ver = getClient(peer->id)->serialization_version;
+
+ try
+ {
+
+ if(datasize < 2)
+ return;
+
+ ToServerCommand command = (ToServerCommand)readU16(&data[0]);
+
+ if(command == TOSERVER_INIT)
+ {
+ // [0] u16 TOSERVER_INIT
+ // [2] u8 SER_FMT_VER_HIGHEST
+ // [3] u8[20] player_name
+
+ if(datasize < 3)
+ return;
+
+ derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
+ <<peer->id<<std::endl;
+
+ // First byte after command is maximum supported
+ // serialization version
+ u8 client_max = data[2];
+ u8 our_max = SER_FMT_VER_HIGHEST;
+ // Use the highest version supported by both
+ u8 deployed = core::min_(client_max, our_max);
+ // If it's lower than the lowest supported, give up.
+ if(deployed < SER_FMT_VER_LOWEST)
+ deployed = SER_FMT_VER_INVALID;
+
+ //peer->serialization_version = deployed;
+ getClient(peer->id)->pending_serialization_version = deployed;
+
+ if(deployed == SER_FMT_VER_INVALID)
+ {
+ derr_server<<DTIME<<"Server: Cannot negotiate "
+ "serialization version with peer "
+ <<peer_id<<std::endl;
+ return;
+ }
+
+ /*
+ Set up player
+ */
+
+ Player *player = m_env.getPlayer(peer_id);
+
+ // Check if player doesn't exist
+ if(player == NULL)
+ throw con::InvalidIncomingDataException
+ ("Server::ProcessData(): INIT: Player doesn't exist");
+
+ // update name if it was supplied
+ if(datasize >= 20+3)
+ {
+ data[20+3-1] = 0;
+ player->updateName((const char*)&data[3]);
+ }
+
+ // Now answer with a TOCLIENT_INIT
+
+ SharedBuffer<u8> reply(2+1+6);
+ writeU16(&reply[0], TOCLIENT_INIT);
+ writeU8(&reply[2], deployed);
+ writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0)));
+ // Send as reliable
+ m_con.Send(peer_id, 0, reply, true);
+
+ return;
+ }
+ if(command == TOSERVER_INIT2)
+ {
+ derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
+ <<peer->id<<std::endl;
+
+
+ getClient(peer->id)->serialization_version
+ = getClient(peer->id)->pending_serialization_version;
+
+ /*
+ Send some initialization data
+ */
+
+ // Send player info to all players
+ SendPlayerInfos();
+
+ // Send inventory to player
+ SendInventory(peer->id);
+
+ return;
+ }
+
+ if(peer_ser_ver == SER_FMT_VER_INVALID)
+ {
+ derr_server<<DTIME<<"Server::ProcessData(): Cancelling: Peer"
+ " serialization format invalid or not initialized."
+ " Skipping incoming command="<<command<<std::endl;
+ return;
+ }
+
+ Player *player = m_env.getPlayer(peer_id);
+
+ if(player == NULL){
+ derr_server<<"Server::ProcessData(): Cancelling: "
+ "No player for peer_id="<<peer_id
+ <<std::endl;
+ return;
+ }
+ if(command == TOSERVER_PLAYERPOS)
+ {
+ if(datasize < 2+12+12+4+4)
+ return;
+
+ u32 start = 0;
+ v3s32 ps = readV3S32(&data[start+2]);
+ v3s32 ss = readV3S32(&data[start+2+12]);
+ f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0;
+ f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0;
+ 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.);
+ pitch = wrapDegrees(pitch);
+ yaw = wrapDegrees(yaw);
+ player->setPosition(position);
+ player->setSpeed(speed);
+ player->setPitch(pitch);
+ player->setYaw(yaw);
+
+ /*dout_server<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
+ <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
+ <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
+ }
+ else if(command == TOSERVER_GOTBLOCKS)
+ {
+ if(datasize < 2+1)
+ return;
+
+ /*
+ [0] u16 command
+ [2] u8 count
+ [3] v3s16 pos_0
+ [3+6] v3s16 pos_1
+ ...
+ */
+
+ u16 count = data[2];
+ for(u16 i=0; i<count; i++)
+ {
+ if((s16)datasize < 2+1+(i+1)*6)
+ throw con::InvalidIncomingDataException
+ ("GOTBLOCKS length is too short");
+ v3s16 p = readV3S16(&data[2+1+i*6]);
+ /*dstream<<"Server: GOTBLOCKS ("
+ <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+ RemoteClient *client = getClient(peer_id);
+ client->GotBlock(p);
+ }
+ }
+ else if(command == TOSERVER_DELETEDBLOCKS)
+ {
+ if(datasize < 2+1)
+ return;
+
+ /*
+ [0] u16 command
+ [2] u8 count
+ [3] v3s16 pos_0
+ [3+6] v3s16 pos_1
+ ...
+ */
+
+ u16 count = data[2];
+ for(u16 i=0; i<count; i++)
+ {
+ if((s16)datasize < 2+1+(i+1)*6)
+ throw con::InvalidIncomingDataException
+ ("DELETEDBLOCKS length is too short");
+ v3s16 p = readV3S16(&data[2+1+i*6]);
+ /*dstream<<"Server: DELETEDBLOCKS ("
+ <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+ RemoteClient *client = getClient(peer_id);
+ client->SetBlockNotSent(p);
+ }
+ }
+ else if(command == TOSERVER_CLICK_OBJECT)
+ {
+ if(datasize < 13)
+ return;
+
+ /*
+ [0] u16 command
+ [2] u8 button (0=left, 1=right)
+ [3] v3s16 block
+ [9] s16 id
+ [11] u16 item
+ */
+ u8 button = readU8(&data[2]);
+ v3s16 p;
+ p.X = readS16(&data[3]);
+ p.Y = readS16(&data[5]);
+ p.Z = readS16(&data[7]);
+ s16 id = readS16(&data[9]);
+ //u16 item_i = readU16(&data[11]);
+
+ MapBlock *block = NULL;
+ try
+ {
+ block = m_env.getMap().getBlockNoCreate(p);
+ }
+ catch(InvalidPositionException &e)
+ {
+ derr_server<<"PICK_OBJECT block not found"<<std::endl;
+ return;
+ }
+
+ MapBlockObject *obj = block->getObject(id);
+
+ if(obj == NULL)
+ {
+ derr_server<<"PICK_OBJECT object not found"<<std::endl;
+ return;
+ }
+
+ //TODO: Check that object is reasonably close
+
+ // Left click
+ if(button == 0)
+ {
+ if(m_creative_mode == false)
+ {
+
+ // Skip if inventory has no free space
+ if(player->inventory.getUsedSlots() == player->inventory.getSize())
+ {
+ dout_server<<"Player inventory has no free space"<<std::endl;
+ return;
+ }
+
+ // Add to inventory and send inventory
+ InventoryItem *item = new MapBlockObjectItem
+ (obj->getInventoryString());
+ player->inventory.addItem(item);
+ SendInventory(player->peer_id);
+ }
+
+ // Remove from block
+ block->removeObject(id);
+ }
+ }
+ else if(command == TOSERVER_CLICK_GROUND)
+ {
+ if(datasize < 17)
+ return;
+ /*
+ length: 17
+ [0] u16 command
+ [2] u8 button (0=left, 1=right)
+ [3] v3s16 nodepos_undersurface
+ [9] v3s16 nodepos_abovesurface
+ [15] u16 item
+ */
+ u8 button = readU8(&data[2]);
+ v3s16 p_under;
+ p_under.X = readS16(&data[3]);
+ p_under.Y = readS16(&data[5]);
+ p_under.Z = readS16(&data[7]);
+ v3s16 p_over;
+ p_over.X = readS16(&data[9]);
+ p_over.Y = readS16(&data[11]);
+ p_over.Z = readS16(&data[13]);
+ u16 item_i = readU16(&data[15]);
+
+ //TODO: Check that target is reasonably close
+
+ /*
+ Left button digs ground
+ */
+ if(button == 0)
+ {
+
+ core::map<v3s16, MapBlock*> modified_blocks;
+
+ u8 material;
+
+ try
+ {
+ // Get material at position
+ material = m_env.getMap().getNode(p_under).d;
+ // If it's air, do nothing
+ if(material == MATERIAL_AIR)
+ {
+ return;
+ }
+ // Otherwise remove it
+ m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
+ }
+ catch(InvalidPositionException &e)
+ {
+ derr_server<<"Server: Ignoring REMOVENODE: Node not found"
+ <<std::endl;
+ return;
+ }
+
+ // Reset build time counter
+ getClient(peer->id)->m_time_from_building.set(0.0);
+
+ // Create packet
+ u32 replysize = 8;
+ SharedBuffer<u8> reply(replysize);
+ writeU16(&reply[0], TOCLIENT_REMOVENODE);
+ writeS16(&reply[2], p_under.X);
+ writeS16(&reply[4], p_under.Y);
+ writeS16(&reply[6], p_under.Z);
+ // Send as reliable
+ m_con.SendToAll(0, reply, true);
+
+ if(m_creative_mode == false)
+ {
+ // Add to inventory and send inventory
+ InventoryItem *item = new MaterialItem(material, 1);
+ player->inventory.addItem(item);
+ SendInventory(player->peer_id);
+ }
+
+ } // button == 0
+ /*
+ Right button places blocks and stuff
+ */
+ else if(button == 1)
+ {
+
+ // Get item
+ InventoryItem *item = player->inventory.getItem(item_i);
+
+ // If there is no item, it is not possible to add it anywhere
+ if(item == NULL)
+ return;
+
+ /*
+ Handle material items
+ */
+ if(std::string("MaterialItem") == item->getName())
+ {
+ MaterialItem *mitem = (MaterialItem*)item;
+
+ MapNode n;
+ n.d = mitem->getMaterial();
+
+ try{
+ // Don't add a node if there isn't air
+ MapNode n2 = m_env.getMap().getNode(p_over);
+ if(n2.d != MATERIAL_AIR)
+ return;
+
+ core::map<v3s16, MapBlock*> modified_blocks;
+ m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
+ }
+ catch(InvalidPositionException &e)
+ {
+ derr_server<<"Server: Ignoring ADDNODE: Node not found"
+ <<std::endl;
+ return;
+ }
+
+ // Reset build time counter
+ getClient(peer->id)->m_time_from_building.set(0.0);
+
+ if(m_creative_mode == false)
+ {
+ // Remove from inventory and send inventory
+ if(mitem->getCount() == 1)
+ player->inventory.deleteItem(item_i);
+ else
+ mitem->remove(1);
+ // Send inventory
+ SendInventory(peer_id);
+ }
+
+ // Create packet
+ u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
+ SharedBuffer<u8> reply(replysize);
+ writeU16(&reply[0], TOCLIENT_ADDNODE);
+ writeS16(&reply[2], p_over.X);
+ writeS16(&reply[4], p_over.Y);
+ writeS16(&reply[6], p_over.Z);
+ n.serialize(&reply[8], peer_ser_ver);
+ // Send as reliable
+ m_con.SendToAll(0, reply, true);
+ }
+ /*
+ Handle block object items
+ */
+ else if(std::string("MBOItem") == item->getName())
+ {
+ MapBlockObjectItem *oitem = (MapBlockObjectItem*)item;
+
+ /*dout_server<<"Trying to place a MapBlockObjectItem: "
+ "inventorystring=\""
+ <<oitem->getInventoryString()
+ <<"\""<<std::endl;*/
+
+ v3s16 blockpos = getNodeBlockPos(p_over);
+
+ MapBlock *block = NULL;
+ try
+ {
+ block = m_env.getMap().getBlockNoCreate(blockpos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ derr_server<<"Error while placing object: "
+ "block not found"<<std::endl;
+ return;
+ }
+
+ v3s16 block_pos_i_on_map = block->getPosRelative();
+ v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+
+ v3f pos = intToFloat(p_over);
+ pos -= block_pos_f_on_map;
+
+ /*dout_server<<"pos="
+ <<"("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
+ <<std::endl;*/
+
+
+ MapBlockObject *obj = oitem->createObject
+ (pos, player->getYaw(), player->getPitch());
+
+ if(obj == NULL)
+ derr_server<<"WARNING: oitem created NULL object"
+ <<std::endl;
+
+ block->addObject(obj);
+
+ //dout_server<<"Placed object"<<std::endl;
+
+ if(m_creative_mode == false)
+ {
+ // Remove from inventory and send inventory
+ player->inventory.deleteItem(item_i);
+ // Send inventory
+ SendInventory(peer_id);
+ }
+ }
+
+ } // button == 1
+ /*
+ Catch invalid buttons
+ */
+ else
+ {
+ derr_server<<"WARNING: Server: Invalid button "
+ <<button<<std::endl;
+ }
+ }
+ else if(command == TOSERVER_RELEASE)
+ {
+ if(datasize < 3)
+ return;
+ /*
+ length: 3
+ [0] u16 command
+ [2] u8 button
+ */
+ //TODO
+ }
+ else if(command == TOSERVER_SIGNTEXT)
+ {
+ /*
+ u16 command
+ v3s16 blockpos
+ s16 id
+ u16 textlen
+ textdata
+ */
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+ u8 buf[6];
+ // Read stuff
+ is.read((char*)buf, 6);
+ v3s16 blockpos = readV3S16(buf);
+ is.read((char*)buf, 2);
+ s16 id = readS16(buf);
+ is.read((char*)buf, 2);
+ u16 textlen = readU16(buf);
+ std::string text;
+ for(u16 i=0; i<textlen; i++)
+ {
+ is.read((char*)buf, 1);
+ text += (char)buf[0];
+ }
+
+ MapBlock *block = NULL;
+ try
+ {
+ block = m_env.getMap().getBlockNoCreate(blockpos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ derr_server<<"Error while setting sign text: "
+ "block not found"<<std::endl;
+ return;
+ }
+
+ MapBlockObject *obj = block->getObject(id);
+ if(obj == NULL)
+ {
+ derr_server<<"Error while setting sign text: "
+ "object not found"<<std::endl;
+ return;
+ }
+
+ if(obj->getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN)
+ {
+ derr_server<<"Error while setting sign text: "
+ "object is not a sign"<<std::endl;
+ return;
+ }
+
+ ((SignObject*)obj)->setText(text);
+
+ obj->getBlock()->setChangedFlag();
+ }
+ else
+ {
+ derr_server<<"WARNING: Server::ProcessData(): Ignoring "
+ "unknown command "<<command<<std::endl;
+ }
+
+ } //try
+ catch(SendFailedException &e)
+ {
+ derr_server<<"Server::ProcessData(): SendFailedException: "
+ <<"what="<<e.what()
+ <<std::endl;
+ }
+}
+
+/*void Server::Send(u16 peer_id, u16 channelnum,
+ SharedBuffer<u8> data, bool reliable)
+{
+ JMutexAutoLock lock(m_con_mutex);
+ m_con.Send(peer_id, channelnum, data, reliable);
+}*/
+
+void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
+{
+ DSTACK(__FUNCTION_NAME);
+ /*
+ Create a packet with the block in the right format
+ */
+
+ std::ostringstream os(std::ios_base::binary);
+ block->serialize(os, ver);
+ std::string s = os.str();
+ SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
+
+ u32 replysize = 8 + blockdata.getSize();
+ SharedBuffer<u8> reply(replysize);
+ v3s16 p = block->getPos();
+ writeU16(&reply[0], TOCLIENT_BLOCKDATA);
+ writeS16(&reply[2], p.X);
+ writeS16(&reply[4], p.Y);
+ writeS16(&reply[6], p.Z);
+ memcpy(&reply[8], *blockdata, blockdata.getSize());
+
+ /*
+ Send packet
+ */
+ m_con.Send(peer_id, 1, reply, true);
+}
+
+/*void Server::SendBlock(u16 peer_id, MapBlock *block, u8 ver)
+{
+ JMutexAutoLock conlock(m_con_mutex);
+
+ SendBlockNoLock(peer_id, block, ver);
+}*/
+
+#if 0
+void Server::SendSectorMeta(u16 peer_id, core::list<v2s16> ps, u8 ver)
+{
+ DSTACK(__FUNCTION_NAME);
+ dstream<<"Server sending sector meta of "
+ <<ps.getSize()<<" sectors"<<std::endl;
+
+ core::list<v2s16>::Iterator i = ps.begin();
+ core::list<v2s16> sendlist;
+ for(;;)
+ {
+ if(sendlist.size() == 255 || i == ps.end())
+ {
+ if(sendlist.size() == 0)
+ break;
+ /*
+ [0] u16 command
+ [2] u8 sector count
+ [3...] v2s16 pos + sector metadata
+ */
+ std::ostringstream os(std::ios_base::binary);
+ u8 buf[4];
+
+ writeU16(buf, TOCLIENT_SECTORMETA);
+ os.write((char*)buf, 2);
+
+ writeU8(buf, sendlist.size());
+ os.write((char*)buf, 1);
+
+ for(core::list<v2s16>::Iterator
+ j = sendlist.begin();
+ j != sendlist.end(); j++)
+ {
+ // Write position
+ writeV2S16(buf, *j);
+ os.write((char*)buf, 4);
+
+ /*
+ Write ClientMapSector metadata
+ */
+
+ /*
+ [0] u8 serialization version
+ [1] s16 corners[0]
+ [3] s16 corners[1]
+ [5] s16 corners[2]
+ [7] s16 corners[3]
+ size = 9
+
+ In which corners are in these positions
+ v2s16(0,0),
+ v2s16(1,0),
+ v2s16(1,1),
+ v2s16(0,1),
+ */
+
+ // Write version
+ writeU8(buf, ver);
+ os.write((char*)buf, 1);
+
+ // Write corners
+ // TODO: Get real values
+ s16 corners[4];
+ ((ServerMap&)m_env.getMap()).getSectorCorners(*j, corners);
+
+ writeS16(buf, corners[0]);
+ os.write((char*)buf, 2);
+ writeS16(buf, corners[1]);
+ os.write((char*)buf, 2);
+ writeS16(buf, corners[2]);
+ os.write((char*)buf, 2);
+ writeS16(buf, corners[3]);
+ os.write((char*)buf, 2);
+ }
+
+ SharedBuffer<u8> data((u8*)os.str().c_str(), os.str().size());
+
+ /*dstream<<"Server::SendSectorMeta(): sending packet"
+ " with "<<sendlist.size()<<" sectors"<<std::endl;*/
+
+ m_con.Send(peer_id, 1, data, true);
+
+ if(i == ps.end())
+ break;
+
+ sendlist.clear();
+ }
+
+ sendlist.push_back(*i);
+ i++;
+ }
+}
+#endif
+
+core::list<PlayerInfo> Server::getPlayerInfo()
+{
+ DSTACK(__FUNCTION_NAME);
+ JMutexAutoLock envlock(m_env_mutex);
+ JMutexAutoLock conlock(m_con_mutex);
+
+ core::list<PlayerInfo> list;
+
+ core::list<Player*> players = m_env.getPlayers();
+
+ core::list<Player*>::Iterator i;
+ for(i = players.begin();
+ i != players.end(); i++)
+ {
+ PlayerInfo info;
+
+ Player *player = *i;
+ try{
+ con::Peer *peer = m_con.GetPeer(player->peer_id);
+ info.id = peer->id;
+ info.address = peer->address;
+ info.avg_rtt = peer->avg_rtt;
+ }
+ catch(con::PeerNotFoundException &e)
+ {
+ // Outdated peer info
+ info.id = 0;
+ info.address = Address(0,0,0,0,0);
+ info.avg_rtt = 0.0;
+ }
+
+ snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
+ info.position = player->getPosition();
+
+ list.push_back(info);
+ }
+
+ return list;
+}
+
+void Server::peerAdded(con::Peer *peer)
+{
+ DSTACK(__FUNCTION_NAME);
+ dout_server<<"Server::peerAdded(): peer->id="
+ <<peer->id<<std::endl;
+
+ // Connection is already locked when this is called.
+ //JMutexAutoLock lock(m_con_mutex);
+
+ // Error check
+ core::map<u16, RemoteClient*>::Node *n;
+ n = m_clients.find(peer->id);
+ // The client shouldn't already exist
+ assert(n == NULL);
+
+ // Create client
+ RemoteClient *client = new RemoteClient();
+ client->peer_id = peer->id;
+ m_clients.insert(client->peer_id, client);
+
+ // Create player
+ {
+ // Already locked when called
+ //JMutexAutoLock envlock(m_env_mutex);
+
+ Player *player = m_env.getPlayer(peer->id);
+
+ // The player shouldn't already exist
+ assert(player == NULL);
+
+ player = new RemotePlayer();
+ player->peer_id = peer->id;
+
+ /*
+ Set player position
+ */
+
+ // Get zero sector (it could have been unloaded to disk)
+ m_env.getMap().emergeSector(v2s16(0,0));
+ // Get ground height at origin
+ f32 groundheight = m_env.getMap().getGroundHeight(v2s16(0,0), true);
+ // The zero sector should have been generated
+ assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
+ // Don't go underwater
+ if(groundheight < WATER_LEVEL)
+ groundheight = WATER_LEVEL;
+
+ player->setPosition(intToFloat(v3s16(
+ 0,
+ groundheight + 1,
+ 0
+ )));
+
+ /*
+ Add player to environment
+ */
+
+ m_env.addPlayer(player);
+
+ /*
+ Add stuff to inventory
+ */
+
+ if(m_creative_mode)
+ {
+ // Give all materials
+ assert(USEFUL_MATERIAL_COUNT <= PLAYER_INVENTORY_SIZE);
+ for(u16 i=0; i<USEFUL_MATERIAL_COUNT; i++)
+ {
+ InventoryItem *item = new MaterialItem(i, 1);
+ player->inventory.addItem(item);
+ }
+ // Sign
+ {
+ InventoryItem *item = new MapBlockObjectItem("Sign Example text");
+ bool r = player->inventory.addItem(item);
+ assert(r == true);
+ }
+ // Rat
+ {
+ InventoryItem *item = new MapBlockObjectItem("Rat");
+ bool r = player->inventory.addItem(item);
+ assert(r == true);
+ }
+ }
+ else
+ {
+ // Give some lights
+ {
+ InventoryItem *item = new MaterialItem(3, 999);
+ bool r = player->inventory.addItem(item);
+ assert(r == true);
+ }
+ // and some signs
+ for(u16 i=0; i<4; i++)
+ {
+ InventoryItem *item = new MapBlockObjectItem("Sign Example text");
+ bool r = player->inventory.addItem(item);
+ assert(r == true);
+ }
+ /*// and some rats
+ for(u16 i=0; i<4; i++)
+ {
+ InventoryItem *item = new MapBlockObjectItem("Rat");
+ bool r = player->inventory.addItem(item);
+ assert(r == true);
+ }*/
+ }
+ }
+}
+
+void Server::deletingPeer(con::Peer *peer, bool timeout)
+{
+ DSTACK(__FUNCTION_NAME);
+ dout_server<<"Server::deletingPeer(): peer->id="
+ <<peer->id<<", timeout="<<timeout<<std::endl;
+
+ // Connection is already locked when this is called.
+ //JMutexAutoLock lock(m_con_mutex);
+
+ // Error check
+ core::map<u16, RemoteClient*>::Node *n;
+ n = m_clients.find(peer->id);
+ // The client should exist
+ assert(n != NULL);
+
+ // Delete player
+ {
+ // Already locked when called
+ //JMutexAutoLock envlock(m_env_mutex);
+ m_env.removePlayer(peer->id);
+ }
+
+ // Delete client
+ delete m_clients[peer->id];
+ m_clients.remove(peer->id);
+
+ // Send player info to all clients
+ SendPlayerInfos();
+}
+
+void Server::SendObjectData(float dtime)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ core::map<v3s16, bool> stepped_blocks;
+
+ for(core::map<u16, RemoteClient*>::Iterator
+ i = m_clients.getIterator();
+ i.atEnd() == false; i++)
+ {
+ u16 peer_id = i.getNode()->getKey();
+ RemoteClient *client = i.getNode()->getValue();
+ assert(client->peer_id == peer_id);
+
+ if(client->serialization_version == SER_FMT_VER_INVALID)
+ continue;
+
+ client->SendObjectData(this, dtime, stepped_blocks);
+ }
+}
+
+void Server::SendPlayerInfos()
+{
+ DSTACK(__FUNCTION_NAME);
+
+ //JMutexAutoLock envlock(m_env_mutex);
+
+ core::list<Player*> players = m_env.getPlayers();
+
+ u32 player_count = players.getSize();
+ u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count;
+
+ SharedBuffer<u8> data(datasize);
+ writeU16(&data[0], TOCLIENT_PLAYERINFO);
+
+ u32 start = 2;
+ core::list<Player*>::Iterator i;
+ for(i = players.begin();
+ i != players.end(); i++)
+ {
+ Player *player = *i;
+
+ /*dstream<<"Server sending player info for player with "
+ "peer_id="<<player->peer_id<<std::endl;*/
+
+ writeU16(&data[start], player->peer_id);
+ snprintf((char*)&data[start+2], PLAYERNAME_SIZE, "%s", player->getName());
+ start += 2+PLAYERNAME_SIZE;
+ }
+
+ //JMutexAutoLock conlock(m_con_mutex);
+
+ // Send as reliable
+ m_con.SendToAll(0, data, true);
+}
+
+void Server::SendInventory(u16 peer_id)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ //JMutexAutoLock envlock(m_env_mutex);
+
+ Player* player = m_env.getPlayer(peer_id);
+
+ std::ostringstream os;
+ //os.imbue(std::locale("C"));
+
+ player->inventory.serialize(os);
+
+ std::string s = os.str();
+
+ SharedBuffer<u8> data(s.size()+2);
+ writeU16(&data[0], TOCLIENT_INVENTORY);
+ memcpy(&data[2], s.c_str(), s.size());
+
+ //JMutexAutoLock conlock(m_con_mutex);
+
+ // Send as reliable
+ m_con.Send(peer_id, 0, data, true);
+}
+
+void Server::SendBlocks(float dtime)
+{
+ DSTACK(__FUNCTION_NAME);
+ //dstream<<"Server::SendBlocks(): BEGIN"<<std::endl;
+
+ JMutexAutoLock envlock(m_env_mutex);
+ JMutexAutoLock conlock(m_con_mutex);
+
+ for(core::map<u16, RemoteClient*>::Iterator
+ i = m_clients.getIterator();
+ i.atEnd() == false; i++)
+ {
+ RemoteClient *client = i.getNode()->getValue();
+ assert(client->peer_id == i.getNode()->getKey());
+
+ if(client->serialization_version == SER_FMT_VER_INVALID)
+ continue;
+
+ //dstream<<"Server::SendBlocks(): sending blocks for client "<<client->peer_id<<std::endl;
+
+ //u16 peer_id = client->peer_id;
+ client->SendBlocks(this, dtime);
+ }
+
+ //dstream<<"Server::SendBlocks(): END"<<std::endl;
+}
+
+RemoteClient* Server::getClient(u16 peer_id)
+{
+ DSTACK(__FUNCTION_NAME);
+ //JMutexAutoLock lock(m_con_mutex);
+ core::map<u16, RemoteClient*>::Node *n;
+ n = m_clients.find(peer_id);
+ // A client should exist for all peers
+ assert(n != NULL);
+ return n->getValue();
+}
+
+
diff --git a/src/server.h b/src/server.h
new file mode 100644
index 000000000..5c856333a
--- /dev/null
+++ b/src/server.h
@@ -0,0 +1,388 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef SERVER_HEADER
+#define SERVER_HEADER
+
+#include "connection.h"
+#include "environment.h"
+#include "common_irrlicht.h"
+#include <string>
+
+#ifdef _WIN32
+ #include <windows.h>
+ #define sleep_ms(x) Sleep(x)
+#else
+ #include <unistd.h>
+ #define sleep_ms(x) usleep(x*1000)
+#endif
+
+struct QueuedBlockEmerge
+{
+ v3s16 pos;
+ // key = peer_id, value = flags
+ core::map<u16, u8> peer_ids;
+};
+
+/*
+ This is a thread-safe class.
+*/
+class BlockEmergeQueue
+{
+public:
+ BlockEmergeQueue()
+ {
+ m_mutex.Init();
+ }
+
+ ~BlockEmergeQueue()
+ {
+ JMutexAutoLock lock(m_mutex);
+
+ core::list<QueuedBlockEmerge*>::Iterator i;
+ for(i=m_queue.begin(); i!=m_queue.end(); i++)
+ {
+ QueuedBlockEmerge *q = *i;
+ delete q;
+ }
+ }
+
+ /*
+ peer_id=0 adds with nobody to send to
+ */
+ void addBlock(u16 peer_id, v3s16 pos, u8 flags)
+ {
+ JMutexAutoLock lock(m_mutex);
+
+ if(peer_id != 0)
+ {
+ /*
+ Find if block is already in queue.
+ If it is, update the peer to it and quit.
+ */
+ core::list<QueuedBlockEmerge*>::Iterator i;
+ for(i=m_queue.begin(); i!=m_queue.end(); i++)
+ {
+ QueuedBlockEmerge *q = *i;
+ if(q->pos == pos)
+ {
+ q->peer_ids[peer_id] = flags;
+ return;
+ }
+ }
+ }
+
+ /*
+ Add the block
+ */
+ QueuedBlockEmerge *q = new QueuedBlockEmerge;
+ q->pos = pos;
+ if(peer_id != 0)
+ q->peer_ids[peer_id] = flags;
+ m_queue.push_back(q);
+ }
+
+ // Returned pointer must be deleted
+ // Returns NULL if queue is empty
+ QueuedBlockEmerge * pop()
+ {
+ JMutexAutoLock lock(m_mutex);
+
+ core::list<QueuedBlockEmerge*>::Iterator i = m_queue.begin();
+ if(i == m_queue.end())
+ return NULL;
+ QueuedBlockEmerge *q = *i;
+ m_queue.erase(i);
+ return q;
+ }
+
+ u32 size()
+ {
+ JMutexAutoLock lock(m_mutex);
+ return m_queue.size();
+ }
+
+private:
+ core::list<QueuedBlockEmerge*> m_queue;
+ JMutex m_mutex;
+};
+
+class SimpleThread : public JThread
+{
+ bool run;
+ JMutex run_mutex;
+
+public:
+
+ SimpleThread():
+ JThread(),
+ run(true)
+ {
+ run_mutex.Init();
+ }
+
+ virtual ~SimpleThread()
+ {}
+
+ virtual void * Thread() = 0;
+
+ bool getRun()
+ {
+ JMutexAutoLock lock(run_mutex);
+ return run;
+ }
+ void setRun(bool a_run)
+ {
+ JMutexAutoLock lock(run_mutex);
+ run = a_run;
+ }
+
+ void stop()
+ {
+ setRun(false);
+ while(IsRunning())
+ sleep_ms(100);
+ }
+};
+
+class Server;
+
+class ServerThread : public SimpleThread
+{
+ Server *m_server;
+
+public:
+
+ ServerThread(Server *server):
+ SimpleThread(),
+ m_server(server)
+ {
+ }
+
+ void * Thread();
+};
+
+class EmergeThread : public SimpleThread
+{
+ Server *m_server;
+
+public:
+
+ EmergeThread(Server *server):
+ SimpleThread(),
+ m_server(server)
+ {
+ }
+
+ void * Thread();
+
+ void trigger()
+ {
+ setRun(true);
+ if(IsRunning() == false)
+ {
+ Start();
+ }
+ }
+};
+
+struct PlayerInfo
+{
+ u16 id;
+ char name[PLAYERNAME_SIZE];
+ v3f position;
+ Address address;
+ float avg_rtt;
+
+ PlayerInfo();
+ void PrintLine(std::ostream *s);
+};
+
+u32 PIChecksum(core::list<PlayerInfo> &l);
+
+class RemoteClient
+{
+public:
+ // peer_id=0 means this client has no associated peer
+ // NOTE: If client is made allowed to exist while peer doesn't,
+ // this has to be set to 0 when there is no peer.
+ // Also, the client must be moved to some other container.
+ u16 peer_id;
+ // The serialization version to use with the client
+ u8 serialization_version;
+ // Version is stored in here after INIT before INIT2
+ u8 pending_serialization_version;
+
+ RemoteClient():
+ m_time_from_building(0.0),
+ m_num_blocks_in_emerge_queue(0)
+ {
+ peer_id = 0;
+ serialization_version = SER_FMT_VER_INVALID;
+ pending_serialization_version = SER_FMT_VER_INVALID;
+ m_nearest_unsent_d = 0;
+
+ m_blocks_sent_mutex.Init();
+ m_blocks_sending_mutex.Init();
+ }
+ ~RemoteClient()
+ {
+ }
+
+ // Connection and environment should be locked when this is called
+ void SendBlocks(Server *server, float dtime);
+
+ // Connection and environment should be locked when this is called
+ // steps() objects of blocks not found in active_blocks, then
+ // adds those blocks to active_blocks
+ void SendObjectData(
+ Server *server,
+ float dtime,
+ core::map<v3s16, bool> &stepped_blocks
+ );
+
+ void GotBlock(v3s16 p);
+
+ void SentBlock(v3s16 p);
+
+ void SetBlockNotSent(v3s16 p);
+ void SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks);
+
+ void BlockEmerged();
+
+ // Increments timeouts and removes timed-out blocks from list
+ //void RunSendingTimeouts(float dtime, float timeout);
+
+ void PrintInfo(std::ostream &o)
+ {
+ JMutexAutoLock l2(m_blocks_sent_mutex);
+ JMutexAutoLock l3(m_blocks_sending_mutex);
+ o<<"RemoteClient "<<peer_id<<": "
+ <<"m_num_blocks_in_emerge_queue="
+ <<m_num_blocks_in_emerge_queue.get()
+ <<", m_blocks_sent.size()="<<m_blocks_sent.size()
+ <<", m_blocks_sending.size()="<<m_blocks_sending.size()
+ <<", m_nearest_unsent_d="<<m_nearest_unsent_d
+ <<std::endl;
+ }
+
+ // Time from last placing or removing blocks
+ MutexedVariable<float> m_time_from_building;
+
+private:
+ /*
+ All members that are accessed by many threads should
+ obviously be behind a mutex. The threads include:
+ - main thread (calls step())
+ - server thread (calls AsyncRunStep() and Receive())
+ - emerge thread
+ */
+
+ //TODO: core::map<v3s16, MapBlock*> m_active_blocks
+
+ // Number of blocks in the emerge queue that have this client as
+ // a receiver. Used for throttling network usage.
+ MutexedVariable<s16> m_num_blocks_in_emerge_queue;
+
+ /*
+ Blocks that have been sent to client.
+ - These don't have to be sent again.
+ - A block is cleared from here when client says it has
+ deleted it from it's memory
+
+ Key is position, value is dummy.
+ No MapBlock* is stored here because the blocks can get deleted.
+ */
+ core::map<v3s16, bool> m_blocks_sent;
+ s16 m_nearest_unsent_d;
+ v3s16 m_last_center;
+ JMutex m_blocks_sent_mutex;
+ /*
+ Blocks that are currently on the line.
+ This is used for throttling the sending of blocks.
+ - The size of this list is limited to some value
+ Block is added when it is sent with BLOCKDATA.
+ Block is removed when GOTBLOCKS is received.
+ Value is time from sending. (not used at the moment)
+ */
+ core::map<v3s16, float> m_blocks_sending;
+ JMutex m_blocks_sending_mutex;
+};
+
+class Server : public con::PeerHandler
+{
+public:
+ /*
+ NOTE: Every public method should be thread-safe
+ */
+ Server(
+ std::string mapsavedir,
+ bool creative_mode,
+ MapgenParams mapgen_params
+ );
+ ~Server();
+ void start(unsigned short port);
+ void stop();
+ void step(float dtime);
+ void AsyncRunStep();
+ void Receive();
+ void ProcessData(u8 *data, u32 datasize, u16 peer_id);
+
+ /*void Send(u16 peer_id, u16 channelnum,
+ SharedBuffer<u8> data, bool reliable);*/
+
+ // Environment and Connection must be locked when called
+ void SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver);
+ //void SendBlock(u16 peer_id, MapBlock *block, u8 ver);
+ //TODO: Sending of many blocks in a single packet
+
+ // Environment and Connection must be locked when called
+ //void SendSectorMeta(u16 peer_id, core::list<v2s16> ps, u8 ver);
+
+ core::list<PlayerInfo> getPlayerInfo();
+
+private:
+
+ // Virtual methods from con::PeerHandler.
+ // As of now, these create and remove clients and players.
+ // TODO: Make it possible to leave players on server.
+ void peerAdded(con::Peer *peer);
+ void deletingPeer(con::Peer *peer, bool timeout);
+
+ // Envlock and conlock should be locked when calling these
+ void SendObjectData(float dtime);
+ void SendPlayerInfos();
+ void SendInventory(u16 peer_id);
+ // Sends blocks to clients
+ void SendBlocks(float dtime);
+
+ // When called, connection mutex should be locked
+ RemoteClient* getClient(u16 peer_id);
+
+ // NOTE: If connection and environment are both to be locked,
+ // environment shall be locked first.
+
+ JMutex m_env_mutex;
+ Environment m_env;
+
+ JMutex m_con_mutex;
+ con::Connection m_con;
+ core::map<u16, RemoteClient*> m_clients; // Behind the con mutex
+
+ float m_step_dtime;
+ JMutex m_step_dtime_mutex;
+
+ ServerThread m_thread;
+ EmergeThread m_emergethread;
+
+ BlockEmergeQueue m_emerge_queue;
+
+ bool m_creative_mode;
+
+ friend class EmergeThread;
+ friend class RemoteClient;
+};
+
+#endif
+
diff --git a/src/socket.cpp b/src/socket.cpp
new file mode 100644
index 000000000..91b2ef73a
--- /dev/null
+++ b/src/socket.cpp
@@ -0,0 +1,311 @@
+#include "socket.h"
+#include "debug.h"
+#include <stdio.h>
+#include <iostream>
+#include <stdlib.h>
+#include <errno.h>
+
+// Debug printing options
+#define DP 0
+#define DPS ""
+
+bool g_sockets_initialized = false;
+
+void sockets_init()
+{
+#ifdef _WIN32
+ WSADATA WsaData;
+ if(WSAStartup( MAKEWORD(2,2), &WsaData ) != NO_ERROR)
+ throw SocketException("WSAStartup failed");
+#else
+#endif
+ g_sockets_initialized = true;
+}
+
+void sockets_cleanup()
+{
+#ifdef _WIN32
+ WSACleanup();
+#endif
+}
+
+Address::Address()
+{
+}
+
+Address::Address(unsigned int address, unsigned short port)
+{
+ m_address = address;
+ m_port = port;
+}
+
+Address::Address(unsigned int a, unsigned int b,
+ unsigned int c, unsigned int d,
+ unsigned short port)
+{
+ m_address = (a<<24) | (b<<16) | ( c<<8) | d;
+ m_port = port;
+}
+
+bool Address::operator==(Address &address)
+{
+ return (m_address == address.m_address
+ && m_port == address.m_port);
+}
+
+bool Address::operator!=(Address &address)
+{
+ return !(*this == address);
+}
+
+void Address::Resolve(const char *name)
+{
+ struct addrinfo *resolved;
+ int e = getaddrinfo(name, NULL, NULL, &resolved);
+ if(e != 0)
+ throw ResolveError("");
+ /*
+ FIXME: This is an ugly hack; change the whole class
+ to store the address as sockaddr
+ */
+ struct sockaddr_in *t = (struct sockaddr_in*)resolved->ai_addr;
+ m_address = ntohl(t->sin_addr.s_addr);
+ freeaddrinfo(resolved);
+}
+
+unsigned int Address::getAddress() const
+{
+ return m_address;
+}
+
+unsigned short Address::getPort() const
+{
+ return m_port;
+}
+
+void Address::setAddress(unsigned int address)
+{
+ m_address = address;
+}
+
+void Address::setPort(unsigned short port)
+{
+ m_port = port;
+}
+
+void Address::print(std::ostream *s) const
+{
+ (*s)<<((m_address>>24)&0xff)<<"."
+ <<((m_address>>16)&0xff)<<"."
+ <<((m_address>>8)&0xff)<<"."
+ <<((m_address>>0)&0xff)<<":"
+ <<m_port;
+}
+
+void Address::print() const
+{
+ print(&dstream);
+}
+
+UDPSocket::UDPSocket()
+{
+ if(g_sockets_initialized == false)
+ throw SocketException("Sockets not initialized");
+
+ m_handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ if(DP)
+ dstream<<DPS<<"UDPSocket("<<(int)m_handle<<")::UDPSocket()"<<std::endl;
+
+ if(m_handle <= 0)
+ {
+ throw SocketException("Failed to create socket");
+ }
+
+/*#ifdef _WIN32
+ DWORD nonblocking = 0;
+ if(ioctlsocket(m_handle, FIONBIO, &nonblocking) != 0)
+ {
+ throw SocketException("Failed set non-blocking mode");
+ }
+#else
+ int nonblocking = 0;
+ if(fcntl(m_handle, F_SETFL, O_NONBLOCK, nonblocking) == -1)
+ {
+ throw SocketException("Failed set non-blocking mode");
+ }
+#endif*/
+
+ setTimeoutMs(0);
+}
+
+UDPSocket::~UDPSocket()
+{
+ if(DP)
+ dstream<<DPS<<"UDPSocket("<<(int)m_handle<<")::~UDPSocket()"<<std::endl;
+
+#ifdef _WIN32
+ closesocket(m_handle);
+#else
+ close(m_handle);
+#endif
+}
+
+void UDPSocket::Bind(unsigned short port)
+{
+ if(DP)
+ dstream<<DPS<<"UDPSocket("<<(int)m_handle
+ <<")::Bind(): port="<<port<<std::endl;
+
+ sockaddr_in address;
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = INADDR_ANY;
+ address.sin_port = htons(port);
+
+ if(bind(m_handle, (const sockaddr*)&address, sizeof(sockaddr_in)) < 0)
+ {
+ dstream<<(int)m_handle<<": Bind failed: "<<strerror(errno)<<std::endl;
+ throw SocketException("Failed to bind socket");
+ }
+}
+
+void UDPSocket::Send(const Address & destination, const void * data, int size)
+{
+ bool dumping_packet = false;
+ if(INTERNET_SIMULATOR)
+ dumping_packet = (rand()%10==0); //easy
+ //dumping_packet = (rand()%4==0); // hard
+
+ if(DP){
+ /*dstream<<DPS<<"UDPSocket("<<(int)m_handle
+ <<")::Send(): destination=";*/
+ dstream<<DPS;
+ dstream<<(int)m_handle<<" -> ";
+ destination.print();
+ dstream<<", size="<<size<<", data=";
+ for(int i=0; i<size && i<20; i++){
+ if(i%2==0) printf(" ");
+ DEBUGPRINT("%.2X", ((int)((const char*)data)[i])&0xff);
+ }
+ if(size>20)
+ dstream<<"...";
+ if(dumping_packet)
+ dstream<<" (DUMPED BY INTERNET_SIMULATOR)";
+ dstream<<std::endl;
+ }
+ else if(dumping_packet)
+ {
+ // Lol let's forget it
+ dstream<<"UDPSocket::Send(): "
+ "INTERNET_SIMULATOR: dumping packet."
+ <<std::endl;
+ }
+
+ if(dumping_packet)
+ return;
+
+ sockaddr_in address;
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = htonl(destination.getAddress());
+ address.sin_port = htons(destination.getPort());
+
+ int sent = sendto(m_handle, (const char*)data, size,
+ 0, (sockaddr*)&address, sizeof(sockaddr_in));
+
+ if(sent != size)
+ {
+ throw SendFailedException("Failed to send packet");
+ }
+}
+
+int UDPSocket::Receive(Address & sender, void * data, int size)
+{
+ if(WaitData(m_timeout_ms) == false)
+ {
+ return -1;
+ }
+
+ sockaddr_in address;
+ socklen_t address_len = sizeof(address);
+
+ int received = recvfrom(m_handle, (char*)data,
+ size, 0, (sockaddr*)&address, &address_len);
+
+ if(received < 0)
+ return -1;
+
+ unsigned int address_ip = ntohl(address.sin_addr.s_addr);
+ unsigned int address_port = ntohs(address.sin_port);
+
+ sender = Address(address_ip, address_port);
+
+ if(DP){
+ //dstream<<DPS<<"UDPSocket("<<(int)m_handle<<")::Receive(): sender=";
+ dstream<<DPS<<(int)m_handle<<" <- ";
+ sender.print();
+ //dstream<<", received="<<received<<std::endl;
+ dstream<<", size="<<received<<", data=";
+ for(int i=0; i<received && i<20; i++){
+ if(i%2==0) printf(" ");
+ DEBUGPRINT("%.2X", ((int)((const char*)data)[i])&0xff);
+ }
+ if(received>20)
+ dstream<<"...";
+ dstream<<std::endl;
+ }
+
+ return received;
+}
+
+int UDPSocket::GetHandle()
+{
+ return m_handle;
+}
+
+void UDPSocket::setTimeoutMs(int timeout_ms)
+{
+ m_timeout_ms = timeout_ms;
+}
+
+bool UDPSocket::WaitData(int timeout_ms)
+{
+ fd_set readset;
+ int result;
+
+ // Initialize the set
+ FD_ZERO(&readset);
+ FD_SET(m_handle, &readset);
+
+ // Initialize time out struct
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = timeout_ms * 1000;
+ // select()
+ result = select(m_handle+1, &readset, NULL, NULL, &tv);
+
+ if(result == 0){
+ // Timeout
+ /*dstream<<"Select timed out (timeout_ms="
+ <<timeout_ms<<")"<<std::endl;*/
+ return false;
+ }
+ else if(result < 0){
+ // Error
+ dstream<<(int)m_handle<<": Select failed: "<<strerror(errno)<<std::endl;
+#ifdef _WIN32
+ dstream<<(int)m_handle<<": WSAGetLastError()="<<WSAGetLastError()<<std::endl;
+#endif
+ throw SocketException("Select failed");
+ }
+ else if(FD_ISSET(m_handle, &readset) == false){
+ // No data
+ //dstream<<"Select reported no data in m_handle"<<std::endl;
+ return false;
+ }
+
+ // There is data
+ //dstream<<"Select reported data in m_handle"<<std::endl;
+ return true;
+}
+
+
diff --git a/src/socket.h b/src/socket.h
new file mode 100644
index 000000000..1cbed91cf
--- /dev/null
+++ b/src/socket.h
@@ -0,0 +1,98 @@
+#ifndef SOCKET_HEADER
+#define SOCKET_HEADER
+
+#ifdef _WIN32
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+ #pragma comment(lib, "wsock32.lib")
+typedef SOCKET socket_t;
+typedef int socklen_t;
+#else
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+ #include <fcntl.h>
+ #include <netdb.h>
+ #include <unistd.h>
+typedef int socket_t;
+#endif
+
+#include <ostream>
+#include "exceptions.h"
+#include "constants.h"
+
+class SocketException : public BaseException
+{
+public:
+ SocketException(const char *s):
+ BaseException(s)
+ {
+ }
+};
+
+class ResolveError : public BaseException
+{
+public:
+ ResolveError(const char *s):
+ BaseException(s)
+ {
+ }
+};
+
+class SendFailedException : public BaseException
+{
+public:
+ SendFailedException(const char *s):
+ BaseException(s)
+ {
+ }
+};
+
+void sockets_init();
+void sockets_cleanup();
+
+class Address
+{
+public:
+ Address();
+ Address(unsigned int address, unsigned short port);
+ Address(unsigned int a, unsigned int b,
+ unsigned int c, unsigned int d,
+ unsigned short port);
+ bool operator==(Address &address);
+ bool operator!=(Address &address);
+ void Resolve(const char *name);
+ unsigned int getAddress() const;
+ unsigned short getPort() const;
+ void setAddress(unsigned int address);
+ void setPort(unsigned short port);
+ void print(std::ostream *s) const;
+ void print() const;
+private:
+ unsigned int m_address;
+ unsigned short m_port;
+};
+
+class UDPSocket
+{
+public:
+ UDPSocket();
+ ~UDPSocket();
+ void Bind(unsigned short port);
+ //void Close();
+ //bool IsOpen();
+ void Send(const Address & destination, const void * data, int size);
+ // Returns -1 if there is no data
+ int Receive(Address & sender, void * data, int size);
+ int GetHandle(); // For debugging purposes only
+ void setTimeoutMs(int timeout_ms);
+ // Returns true if there is data, false if timeout occurred
+ bool WaitData(int timeout_ms);
+private:
+ int m_handle;
+ int m_timeout_ms;
+};
+
+#endif
+
diff --git a/src/strfnd.h b/src/strfnd.h
new file mode 100644
index 000000000..dbbec11a6
--- /dev/null
+++ b/src/strfnd.h
@@ -0,0 +1,96 @@
+#ifndef STRFND_HEADER
+#define STRFND_HEADER
+
+#include <string>
+
+std::string trim(std::string str);
+
+class Strfnd{
+ std::string tek;
+ unsigned int p;
+public:
+ void start(std::string niinq){
+ tek = niinq;
+ p=0;
+ }
+ unsigned int where(){
+ return p;
+ }
+ void to(unsigned int i){
+ p = i;
+ }
+ std::string what(){
+ return tek;
+ }
+ std::string next(std::string plop){
+ //std::cout<<"tek=\""<<tek<<"\" plop=\""<<plop<<"\""<<std::endl;
+ size_t n;
+ std::string palautus;
+ if (p < tek.size())
+ {
+ //std::cout<<"\tp<tek.size()"<<std::endl;
+ if ((n = tek.find(plop, p)) == std::string::npos || plop == "")
+ {
+ //std::cout<<"\t\tn == string::npos || plop == \"\""<<std::endl;
+ n = tek.size();
+ }
+ else
+ {
+ //std::cout<<"\t\tn != string::npos"<<std::endl;
+ }
+ palautus = tek.substr(p, n-p);
+ p = n + plop.length();
+ }
+ //else
+ //std::cout<<"\tp>=tek.size()"<<std::endl;
+ //std::cout<<"palautus=\""<<palautus<<"\""<<std::endl;
+ return palautus;
+ }
+ bool atend(){
+ if(p>=tek.size()) return true;
+ return false;
+ }
+ Strfnd(std::string s){
+ start(s);
+ }
+};
+
+inline std::string trim(std::string str)
+{
+ while(
+ str.length()>0
+ &&
+ (
+ str.substr(0, 1)==" " ||
+ str.substr(0, 1)=="\t" ||
+ str.substr(0, 1)=="\r" ||
+ str.substr(0, 1)=="\n" ||
+ str.substr(str.length()-1, 1)==" " ||
+ str.substr(str.length()-1, 1)=="\t" ||
+ str.substr(str.length()-1, 1)=="\r" ||
+ str.substr(str.length()-1, 1)=="\n"
+ )
+ )
+ {
+ if (str.substr(0, 1)==" ")
+ str = str.substr(1,str.length()-1);
+ else if (str.substr(0, 1)=="\t")
+ str = str.substr(1,str.length()-1);
+ else if (str.substr(0, 1)=="\r")
+ str = str.substr(1,str.length()-1);
+ else if (str.substr(0, 1)=="\n")
+ str = str.substr(1,str.length()-1);
+ else if (str.substr(str.length()-1, 1)==" ")
+ str = str.substr(0,str.length()-1);
+ else if (str.substr(str.length()-1, 1)=="\t")
+ str = str.substr(0,str.length()-1);
+ else if (str.substr(str.length()-1, 1)=="\r")
+ str = str.substr(0,str.length()-1);
+ else if (str.substr(str.length()-1, 1)=="\n")
+ str = str.substr(0,str.length()-1);
+ }
+ return str;
+}
+
+#endif
+
diff --git a/src/test.cpp b/src/test.cpp
new file mode 100644
index 000000000..8cdd84426
--- /dev/null
+++ b/src/test.cpp
@@ -0,0 +1,920 @@
+#include "test.h"
+#include "common_irrlicht.h"
+
+#include "debug.h"
+#include "map.h"
+#include "player.h"
+#include "main.h"
+#include "heightmap.h"
+#include "socket.h"
+#include "connection.h"
+#include "utility.h"
+#include "serialization.h"
+#include <sstream>
+
+#ifdef _WIN32
+ #include <windows.h>
+ #define sleep_ms(x) Sleep(x)
+#else
+ #include <unistd.h>
+ #define sleep_ms(x) usleep(x*1000)
+#endif
+
+/*
+ Asserts that the exception occurs
+*/
+#define EXCEPTION_CHECK(EType, code)\
+{\
+ bool exception_thrown = false;\
+ try{ code; }\
+ catch(EType &e) { exception_thrown = true; }\
+ assert(exception_thrown);\
+}
+
+struct TestUtilities
+{
+ void Run()
+ {
+ /*dstream<<"wrapDegrees(100.0) = "<<wrapDegrees(100.0)<<std::endl;
+ dstream<<"wrapDegrees(720.5) = "<<wrapDegrees(720.5)<<std::endl;
+ dstream<<"wrapDegrees(-0.5) = "<<wrapDegrees(-0.5)<<std::endl;*/
+ assert(fabs(wrapDegrees(100.0) - 100.0) < 0.001);
+ assert(fabs(wrapDegrees(720.5) - 0.5) < 0.001);
+ assert(fabs(wrapDegrees(-0.5) - (-0.5)) < 0.001);
+ assert(fabs(wrapDegrees(-365.5) - (-5.5)) < 0.001);
+ assert(lowercase("Foo bAR") == "foo bar");
+ assert(is_yes("YeS") == true);
+ assert(is_yes("") == false);
+ assert(is_yes("FAlse") == false);
+ }
+};
+
+struct TestCompress
+{
+ void Run()
+ {
+ SharedBuffer<u8> fromdata(4);
+ fromdata[0]=1;
+ fromdata[1]=5;
+ fromdata[2]=5;
+ fromdata[3]=1;
+
+ std::ostringstream os(std::ios_base::binary);
+ compress(fromdata, os, 0);
+
+ std::string str_out = os.str();
+
+ dstream<<"str_out.size()="<<str_out.size()<<std::endl;
+ dstream<<"TestCompress: 1,5,5,1 -> ";
+ for(u32 i=0; i<str_out.size(); i++)
+ {
+ dstream<<(u32)str_out[i]<<",";
+ }
+ dstream<<std::endl;
+
+ assert(str_out.size() == 10);
+
+ assert(str_out[0] == 0);
+ assert(str_out[1] == 0);
+ assert(str_out[2] == 0);
+ assert(str_out[3] == 4);
+ assert(str_out[4] == 0);
+ assert(str_out[5] == 1);
+ assert(str_out[6] == 1);
+ assert(str_out[7] == 5);
+ assert(str_out[8] == 0);
+ assert(str_out[9] == 1);
+
+ std::istringstream is(str_out, std::ios_base::binary);
+ std::ostringstream os2(std::ios_base::binary);
+
+ decompress(is, os2, 0);
+ std::string str_out2 = os2.str();
+
+ dstream<<"decompress: ";
+ for(u32 i=0; i<str_out2.size(); i++)
+ {
+ dstream<<(u32)str_out2[i]<<",";
+ }
+ dstream<<std::endl;
+
+ assert(str_out2.size() == fromdata.getSize());
+
+ for(u32 i=0; i<str_out2.size(); i++)
+ {
+ assert(str_out2[i] == fromdata[i]);
+ }
+ }
+};
+
+struct TestMapNode
+{
+ void Run()
+ {
+ MapNode n;
+
+ // Default values
+ assert(n.d == MATERIAL_AIR);
+ assert(n.getLight() == 0);
+
+ // Transparency
+ n.d = MATERIAL_AIR;
+ assert(n.light_propagates() == true);
+ n.d = 0;
+ assert(n.light_propagates() == false);
+ }
+};
+
+struct TestMapBlock
+{
+ class TC : public NodeContainer
+ {
+ public:
+
+ MapNode node;
+ bool position_valid;
+ core::list<v3s16> validity_exceptions;
+
+ TC()
+ {
+ position_valid = true;
+ }
+
+ virtual bool isValidPosition(v3s16 p)
+ {
+ //return position_valid ^ (p==position_valid_exception);
+ bool exception = false;
+ for(core::list<v3s16>::Iterator i=validity_exceptions.begin();
+ i != validity_exceptions.end(); i++)
+ {
+ if(p == *i)
+ {
+ exception = true;
+ break;
+ }
+ }
+ return exception ? !position_valid : position_valid;
+ }
+
+ virtual MapNode getNode(v3s16 p)
+ {
+ if(isValidPosition(p) == false)
+ throw InvalidPositionException();
+ return node;
+ }
+
+ virtual void setNode(v3s16 p, MapNode & n)
+ {
+ if(isValidPosition(p) == false)
+ throw InvalidPositionException();
+ };
+
+ virtual u16 nodeContainerId() const
+ {
+ return 666;
+ }
+ };
+
+ void Run()
+ {
+ TC parent;
+
+ MapBlock b(&parent, v3s16(1,1,1));
+ v3s16 relpos(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
+
+ assert(b.getPosRelative() == relpos);
+
+ assert(b.getBox().MinEdge.X == MAP_BLOCKSIZE);
+ assert(b.getBox().MaxEdge.X == MAP_BLOCKSIZE*2-1);
+ assert(b.getBox().MinEdge.Y == MAP_BLOCKSIZE);
+ assert(b.getBox().MaxEdge.Y == MAP_BLOCKSIZE*2-1);
+ assert(b.getBox().MinEdge.Z == MAP_BLOCKSIZE);
+ assert(b.getBox().MaxEdge.Z == MAP_BLOCKSIZE*2-1);
+
+ assert(b.isValidPosition(v3s16(0,0,0)) == true);
+ assert(b.isValidPosition(v3s16(-1,0,0)) == false);
+ assert(b.isValidPosition(v3s16(-1,-142,-2341)) == false);
+ assert(b.isValidPosition(v3s16(-124,142,2341)) == false);
+ assert(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
+ assert(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE,MAP_BLOCKSIZE-1)) == false);
+
+ /*
+ TODO: this method should probably be removed
+ if the block size isn't going to be set variable
+ */
+ /*assert(b.getSizeNodes() == v3s16(MAP_BLOCKSIZE,
+ MAP_BLOCKSIZE, MAP_BLOCKSIZE));*/
+
+ // Changed flag should be initially set
+ assert(b.getChangedFlag() == true);
+ b.resetChangedFlag();
+ assert(b.getChangedFlag() == false);
+
+ // All nodes should have been set to
+ // .d=MATERIAL_AIR and .getLight() = 0
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++)
+ for(u16 y=0; y<MAP_BLOCKSIZE; y++)
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++){
+ assert(b.getNode(v3s16(x,y,z)).d == MATERIAL_AIR);
+ assert(b.getNode(v3s16(x,y,z)).getLight() == 0);
+ }
+
+ /*
+ Parent fetch functions
+ */
+ parent.position_valid = false;
+ parent.node.d = 5;
+
+ MapNode n;
+
+ // Positions in the block should still be valid
+ assert(b.isValidPositionParent(v3s16(0,0,0)) == true);
+ assert(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
+ n = b.getNodeParent(v3s16(0,MAP_BLOCKSIZE-1,0));
+ assert(n.d == MATERIAL_AIR);
+
+ // ...but outside the block they should be invalid
+ assert(b.isValidPositionParent(v3s16(-121,2341,0)) == false);
+ assert(b.isValidPositionParent(v3s16(-1,0,0)) == false);
+ assert(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == false);
+
+ {
+ bool exception_thrown = false;
+ try{
+ // This should throw an exception
+ MapNode n = b.getNodeParent(v3s16(0,0,-1));
+ }
+ catch(InvalidPositionException &e)
+ {
+ exception_thrown = true;
+ }
+ assert(exception_thrown);
+ }
+
+ parent.position_valid = true;
+ // Now the positions outside should be valid
+ assert(b.isValidPositionParent(v3s16(-121,2341,0)) == true);
+ assert(b.isValidPositionParent(v3s16(-1,0,0)) == true);
+ assert(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == true);
+ n = b.getNodeParent(v3s16(0,0,MAP_BLOCKSIZE));
+ assert(n.d == 5);
+
+ /*
+ Set a node
+ */
+ v3s16 p(1,2,0);
+ n.d = 4;
+ b.setNode(p, n);
+ assert(b.getNode(p).d == 4);
+ assert(b.getNodeMaterial(p) == 4);
+ assert(b.getNodeMaterial(v3s16(-1,-1,0)) == 5);
+
+ /*
+ propagateSunlight()
+ */
+ // Set lighting of all nodes to 0
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++){
+ for(u16 y=0; y<MAP_BLOCKSIZE; y++){
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++){
+ MapNode n = b.getNode(v3s16(x,y,z));
+ n.setLight(0);
+ b.setNode(v3s16(x,y,z), n);
+ }
+ }
+ }
+ {
+ /*
+ Check how the block handles being a lonely sky block
+ */
+ parent.position_valid = true;
+ b.setIsUnderground(false);
+ parent.node.d = MATERIAL_AIR;
+ parent.node.setLight(LIGHT_SUN);
+ core::map<v3s16, bool> light_sources;
+ // The bottom block is invalid, because we have a shadowing node
+ assert(b.propagateSunlight(light_sources) == false);
+ assert(b.getNode(v3s16(1,4,0)).getLight() == LIGHT_SUN);
+ assert(b.getNode(v3s16(1,3,0)).getLight() == LIGHT_SUN);
+ assert(b.getNode(v3s16(1,2,0)).getLight() == 0);
+ assert(b.getNode(v3s16(1,1,0)).getLight() == 0);
+ assert(b.getNode(v3s16(1,0,0)).getLight() == 0);
+ assert(b.getNode(v3s16(1,2,3)).getLight() == LIGHT_SUN);
+ assert(b.getFaceLight(p, v3s16(0,1,0)) == LIGHT_SUN);
+ assert(b.getFaceLight(p, v3s16(0,-1,0)) == 0);
+ // According to MapBlock::getFaceLight,
+ // The face on the z+ side should have double-diminished light
+ assert(b.getFaceLight(p, v3s16(0,0,1)) == diminish_light(diminish_light(LIGHT_MAX)));
+ }
+ /*
+ Check how the block handles being in between blocks with some non-sunlight
+ while being underground
+ */
+ {
+ // Make neighbours to exist and set some non-sunlight to them
+ parent.position_valid = true;
+ b.setIsUnderground(true);
+ parent.node.setLight(LIGHT_MAX/2);
+ core::map<v3s16, bool> light_sources;
+ // The block below should be valid because there shouldn't be
+ // sunlight in there either
+ assert(b.propagateSunlight(light_sources) == true);
+ // Should not touch nodes that are not affected (that is, all of them)
+ //assert(b.getNode(v3s16(1,2,3)).getLight() == LIGHT_SUN);
+ // Should set light of non-sunlighted blocks to 0.
+ assert(b.getNode(v3s16(1,2,3)).getLight() == 0);
+ }
+ /*
+ Set up a situation where:
+ - There is only air in this block
+ - There is a valid non-sunlighted block at the bottom, and
+ - Invalid blocks elsewhere.
+ - the block is not underground.
+
+ This should result in bottom block invalidity
+ */
+ {
+ b.setIsUnderground(false);
+ // Clear block
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++){
+ for(u16 y=0; y<MAP_BLOCKSIZE; y++){
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++){
+ MapNode n;
+ n.d = MATERIAL_AIR;
+ n.setLight(0);
+ b.setNode(v3s16(x,y,z), n);
+ }
+ }
+ }
+ // Make neighbours invalid
+ parent.position_valid = false;
+ // Add exceptions to the top of the bottom block
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++)
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++)
+ {
+ parent.validity_exceptions.push_back(v3s16(MAP_BLOCKSIZE+x, MAP_BLOCKSIZE-1, MAP_BLOCKSIZE+z));
+ }
+ // Lighting value for the valid nodes
+ parent.node.setLight(LIGHT_MAX/2);
+ core::map<v3s16, bool> light_sources;
+ // Bottom block is not valid
+ assert(b.propagateSunlight(light_sources) == false);
+ }
+ }
+};
+
+struct TestMapSector
+{
+ class TC : public NodeContainer
+ {
+ public:
+
+ MapNode node;
+ bool position_valid;
+
+ TC()
+ {
+ position_valid = true;
+ }
+
+ virtual bool isValidPosition(v3s16 p)
+ {
+ return position_valid;
+ }
+
+ virtual MapNode getNode(v3s16 p)
+ {
+ if(position_valid == false)
+ throw InvalidPositionException();
+ return node;
+ }
+
+ virtual void setNode(v3s16 p, MapNode & n)
+ {
+ if(position_valid == false)
+ throw InvalidPositionException();
+ };
+
+ virtual u16 nodeContainerId() const
+ {
+ return 666;
+ }
+ };
+
+ void Run()
+ {
+ TC parent;
+ parent.position_valid = false;
+
+ // Create one with no heightmaps
+ ServerMapSector sector(&parent, v2s16(1,1), 0);
+ //ConstantGenerator *dummyheightmap = new ConstantGenerator();
+ //sector->setHeightmap(dummyheightmap);
+
+ EXCEPTION_CHECK(InvalidPositionException, sector.getBlockNoCreate(0));
+ EXCEPTION_CHECK(InvalidPositionException, sector.getBlockNoCreate(1));
+
+ MapBlock * bref = sector.createBlankBlock(-2);
+
+ EXCEPTION_CHECK(InvalidPositionException, sector.getBlockNoCreate(0));
+ assert(sector.getBlockNoCreate(-2) == bref);
+
+ //TODO: Check for AlreadyExistsException
+
+ /*bool exception_thrown = false;
+ try{
+ sector.getBlock(0);
+ }
+ catch(InvalidPositionException &e){
+ exception_thrown = true;
+ }
+ assert(exception_thrown);*/
+
+ }
+};
+
+struct TestHeightmap
+{
+ void TestSingleFixed()
+ {
+ const s16 BS1 = 4;
+ OneChildHeightmap hm1(BS1);
+
+ // Test that it is filled with < GROUNDHEIGHT_VALID_MINVALUE
+ for(s16 y=0; y<=BS1; y++){
+ for(s16 x=0; x<=BS1; x++){
+ v2s16 p(x,y);
+ assert(hm1.m_child.getGroundHeight(p)
+ < GROUNDHEIGHT_VALID_MINVALUE);
+ }
+ }
+
+ hm1.m_child.setGroundHeight(v2s16(1,0), 2.0);
+ //hm1.m_child.print();
+ assert(fabs(hm1.getGroundHeight(v2s16(1,0))-2.0)<0.001);
+ hm1.setGroundHeight(v2s16(0,1), 3.0);
+ assert(fabs(hm1.m_child.getGroundHeight(v2s16(0,1))-3.0)<0.001);
+
+ // Fill with -1.0
+ for(s16 y=0; y<=BS1; y++){
+ for(s16 x=0; x<=BS1; x++){
+ v2s16 p(x,y);
+ hm1.m_child.setGroundHeight(p, -1.0);
+ }
+ }
+
+ f32 corners[] = {0.0, 0.0, 1.0, 1.0};
+ hm1.m_child.generateContinued(0.0, 0.0, corners);
+
+ hm1.m_child.print();
+ assert(fabs(hm1.m_child.getGroundHeight(v2s16(1,0))-0.2)<0.05);
+ assert(fabs(hm1.m_child.getGroundHeight(v2s16(4,3))-0.7)<0.05);
+ assert(fabs(hm1.m_child.getGroundHeight(v2s16(4,4))-1.0)<0.05);
+ }
+
+ void TestUnlimited()
+ {
+ //g_heightmap_debugprint = true;
+ const s16 BS1 = 4;
+ UnlimitedHeightmap hm1(BS1,
+ new ConstantGenerator(0.0),
+ new ConstantGenerator(0.0),
+ new ConstantGenerator(5.0));
+ // Go through it so it generates itself
+ for(s16 y=0; y<=BS1; y++){
+ for(s16 x=0; x<=BS1; x++){
+ v2s16 p(x,y);
+ hm1.getGroundHeight(p);
+ }
+ }
+ // Print it
+ dstream<<"UnlimitedHeightmap hm1:"<<std::endl;
+ hm1.print();
+
+ dstream<<"testing UnlimitedHeightmap set/get"<<std::endl;
+ v2s16 p1(0,3);
+ f32 v1(234.01);
+ // Get first heightmap and try setGroundHeight
+ FixedHeightmap * href = hm1.getHeightmap(v2s16(0,0));
+ href->setGroundHeight(p1, v1);
+ // Read from UnlimitedHeightmap
+ assert(fabs(hm1.getGroundHeight(p1)-v1)<0.001);
+ }
+
+ void Random()
+ {
+ dstream<<"Running random code (get a human to check this)"<<std::endl;
+ dstream<<"rand() values: ";
+ for(u16 i=0; i<5; i++)
+ dstream<<(u16)rand()<<" ";
+ dstream<<std::endl;
+
+ const s16 BS1 = 8;
+ UnlimitedHeightmap hm1(BS1,
+ new ConstantGenerator(10.0),
+ new ConstantGenerator(0.3),
+ new ConstantGenerator(0.0));
+
+ // Force hm1 to generate a some heightmap
+ hm1.getGroundHeight(v2s16(0,0));
+ hm1.getGroundHeight(v2s16(0,BS1));
+ /*hm1.getGroundHeight(v2s16(BS1,-1));
+ hm1.getGroundHeight(v2s16(BS1-1,-1));*/
+ hm1.print();
+
+ // Get the (0,0) and (1,0) heightmaps
+ /*FixedHeightmap * hr00 = hm1.getHeightmap(v2s16(0,0));
+ FixedHeightmap * hr01 = hm1.getHeightmap(v2s16(1,0));
+ f32 corners[] = {1.0, 1.0, 1.0, 1.0};
+ hr00->generateContinued(0.0, 0.0, corners);
+ hm1.print();*/
+
+ //assert(0);
+ }
+
+ void Run()
+ {
+ //srand(7); // Get constant random
+ srand(time(0)); // Get better random
+
+ TestSingleFixed();
+ TestUnlimited();
+ Random();
+ }
+};
+
+struct TestSocket
+{
+ void Run()
+ {
+ const int port = 30003;
+ UDPSocket socket;
+ socket.Bind(port);
+
+ const char sendbuffer[] = "hello world!";
+ socket.Send(Address(127,0,0,1,port), sendbuffer, sizeof(sendbuffer));
+
+ sleep_ms(50);
+
+ char rcvbuffer[256];
+ memset(rcvbuffer, 0, sizeof(rcvbuffer));
+ Address sender;
+ for(;;)
+ {
+ int bytes_read = socket.Receive(sender, rcvbuffer, sizeof(rcvbuffer));
+ if(bytes_read < 0)
+ break;
+ }
+ //FIXME: This fails on some systems
+ assert(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer))==0);
+ assert(sender.getAddress() == Address(127,0,0,1, 0).getAddress());
+ }
+};
+
+struct TestConnection
+{
+ void TestHelpers()
+ {
+ /*
+ Test helper functions
+ */
+
+ // Some constants for testing
+ u32 proto_id = 0x12345678;
+ u16 peer_id = 123;
+ u8 channel = 2;
+ SharedBuffer<u8> data1(1);
+ data1[0] = 100;
+ Address a(127,0,0,1, 10);
+ u16 seqnum = 34352;
+
+ con::BufferedPacket p1 = con::makePacket(a, data1,
+ proto_id, peer_id, channel);
+ /*
+ We should now have a packet with this data:
+ Header:
+ [0] u32 protocol_id
+ [4] u16 sender_peer_id
+ [6] u8 channel
+ Data:
+ [7] u8 data1[0]
+ */
+ assert(readU32(&p1.data[0]) == proto_id);
+ assert(readU16(&p1.data[4]) == peer_id);
+ assert(readU8(&p1.data[6]) == channel);
+ assert(readU8(&p1.data[7]) == data1[0]);
+
+ //dstream<<"initial data1[0]="<<((u32)data1[0]&0xff)<<std::endl;
+
+ SharedBuffer<u8> p2 = con::makeReliablePacket(data1, seqnum);
+
+ /*dstream<<"p2.getSize()="<<p2.getSize()<<", data1.getSize()="
+ <<data1.getSize()<<std::endl;
+ dstream<<"readU8(&p2[3])="<<readU8(&p2[3])
+ <<" p2[3]="<<((u32)p2[3]&0xff)<<std::endl;
+ dstream<<"data1[0]="<<((u32)data1[0]&0xff)<<std::endl;*/
+
+ assert(p2.getSize() == 3 + data1.getSize());
+ assert(readU8(&p2[0]) == TYPE_RELIABLE);
+ assert(readU16(&p2[1]) == seqnum);
+ assert(readU8(&p2[3]) == data1[0]);
+ }
+
+ struct Handler : public con::PeerHandler
+ {
+ Handler(const char *a_name)
+ {
+ count = 0;
+ last_id = 0;
+ name = a_name;
+ }
+ void peerAdded(con::Peer *peer)
+ {
+ dstream<<"Handler("<<name<<")::peerAdded(): "
+ "id="<<peer->id<<std::endl;
+ last_id = peer->id;
+ count++;
+ }
+ void deletingPeer(con::Peer *peer, bool timeout)
+ {
+ dstream<<"Handler("<<name<<")::deletingPeer(): "
+ "id="<<peer->id
+ <<", timeout="<<timeout<<std::endl;
+ last_id = peer->id;
+ count--;
+ }
+
+ s32 count;
+ u16 last_id;
+ const char *name;
+ };
+
+ void Run()
+ {
+ DSTACK("TestConnection::Run");
+
+ TestHelpers();
+
+ /*
+ Test some real connections
+ */
+ u32 proto_id = 0xad26846a;
+
+ Handler hand_server("server");
+ Handler hand_client("client");
+
+ dstream<<"** Creating server Connection"<<std::endl;
+ con::Connection server(proto_id, 512, 5.0, &hand_server);
+ server.Serve(30001);
+
+ dstream<<"** Creating client Connection"<<std::endl;
+ con::Connection client(proto_id, 512, 5.0, &hand_client);
+
+ assert(hand_server.count == 0);
+ assert(hand_client.count == 0);
+
+ sleep_ms(50);
+
+ Address server_address(127,0,0,1, 30001);
+ dstream<<"** running client.Connect()"<<std::endl;
+ client.Connect(server_address);
+
+ sleep_ms(50);
+
+ // Client should have added server now
+ assert(hand_client.count == 1);
+ assert(hand_client.last_id == 1);
+ // But server should not have added client
+ assert(hand_server.count == 0);
+
+ try
+ {
+ u16 peer_id;
+ u8 data[100];
+ dstream<<"** running server.Receive()"<<std::endl;
+ u32 size = server.Receive(peer_id, data, 100);
+ dstream<<"** Server received: peer_id="<<peer_id
+ <<", size="<<size
+ <<std::endl;
+ }
+ catch(con::NoIncomingDataException &e)
+ {
+ // No actual data received, but the client has
+ // probably been connected
+ }
+
+ // Client should be the same
+ assert(hand_client.count == 1);
+ assert(hand_client.last_id == 1);
+ // Server should have the client
+ assert(hand_server.count == 1);
+ assert(hand_server.last_id == 2);
+
+ //sleep_ms(50);
+
+ while(client.Connected() == false)
+ {
+ try
+ {
+ u16 peer_id;
+ u8 data[100];
+ dstream<<"** running client.Receive()"<<std::endl;
+ u32 size = client.Receive(peer_id, data, 100);
+ dstream<<"** Client received: peer_id="<<peer_id
+ <<", size="<<size
+ <<std::endl;
+ }
+ catch(con::NoIncomingDataException &e)
+ {
+ }
+ sleep_ms(50);
+ }
+
+ sleep_ms(50);
+
+ try
+ {
+ u16 peer_id;
+ u8 data[100];
+ dstream<<"** running server.Receive()"<<std::endl;
+ u32 size = server.Receive(peer_id, data, 100);
+ dstream<<"** Server received: peer_id="<<peer_id
+ <<", size="<<size
+ <<std::endl;
+ }
+ catch(con::NoIncomingDataException &e)
+ {
+ }
+
+ {
+ /*u8 data[] = "Hello World!";
+ u32 datasize = sizeof(data);*/
+ SharedBuffer<u8> data = SharedBufferFromString("Hello World!");
+
+ dstream<<"** running client.Send()"<<std::endl;
+ client.Send(PEER_ID_SERVER, 0, data, true);
+
+ sleep_ms(50);
+
+ u16 peer_id;
+ u8 recvdata[100];
+ dstream<<"** running server.Receive()"<<std::endl;
+ u32 size = server.Receive(peer_id, recvdata, 100);
+ dstream<<"** Server received: peer_id="<<peer_id
+ <<", size="<<size
+ <<", data="<<*data
+ <<std::endl;
+ assert(memcmp(*data, recvdata, data.getSize()) == 0);
+ }
+
+ u16 peer_id_client = 2;
+
+ {
+ /*
+ Send consequent packets in different order
+ */
+ //u8 data1[] = "hello1";
+ //u8 data2[] = "hello2";
+ SharedBuffer<u8> data1 = SharedBufferFromString("hello1");
+ SharedBuffer<u8> data2 = SharedBufferFromString("Hello2");
+
+ Address client_address =
+ server.GetPeer(peer_id_client)->address;
+
+ dstream<<"*** Sending packets in wrong order (2,1,2)"
+ <<std::endl;
+
+ u8 chn = 0;
+ con::Channel *ch = &server.GetPeer(peer_id_client)->channels[chn];
+ u16 sn = ch->next_outgoing_seqnum;
+ ch->next_outgoing_seqnum = sn+1;
+ server.Send(peer_id_client, chn, data2, true);
+ ch->next_outgoing_seqnum = sn;
+ server.Send(peer_id_client, chn, data1, true);
+ ch->next_outgoing_seqnum = sn+1;
+ server.Send(peer_id_client, chn, data2, true);
+
+ sleep_ms(50);
+
+ dstream<<"*** Receiving the packets"<<std::endl;
+
+ u16 peer_id;
+ u8 recvdata[20];
+ u32 size;
+
+ dstream<<"** running client.Receive()"<<std::endl;
+ peer_id = 132;
+ size = client.Receive(peer_id, recvdata, 20);
+ dstream<<"** Client received: peer_id="<<peer_id
+ <<", size="<<size
+ <<", data="<<recvdata
+ <<std::endl;
+ assert(size == data1.getSize());
+ assert(memcmp(*data1, recvdata, data1.getSize()) == 0);
+ assert(peer_id == PEER_ID_SERVER);
+
+ dstream<<"** running client.Receive()"<<std::endl;
+ peer_id = 132;
+ size = client.Receive(peer_id, recvdata, 20);
+ dstream<<"** Client received: peer_id="<<peer_id
+ <<", size="<<size
+ <<", data="<<recvdata
+ <<std::endl;
+ assert(size == data2.getSize());
+ assert(memcmp(*data2, recvdata, data2.getSize()) == 0);
+ assert(peer_id == PEER_ID_SERVER);
+
+ bool got_exception = false;
+ try
+ {
+ dstream<<"** running client.Receive()"<<std::endl;
+ peer_id = 132;
+ size = client.Receive(peer_id, recvdata, 20);
+ dstream<<"** Client received: peer_id="<<peer_id
+ <<", size="<<size
+ <<", data="<<recvdata
+ <<std::endl;
+ }
+ catch(con::NoIncomingDataException &e)
+ {
+ dstream<<"** No incoming data for client"<<std::endl;
+ got_exception = true;
+ }
+ assert(got_exception);
+ }
+ {
+ //u8 data1[1100];
+ SharedBuffer<u8> data1(1100);
+ for(u16 i=0; i<1100; i++){
+ data1[i] = i/4;
+ }
+
+ dstream<<"Sending data (size="<<1100<<"):";
+ for(int i=0; i<1100 && i<20; i++){
+ if(i%2==0) printf(" ");
+ printf("%.2X", ((int)((const char*)*data1)[i])&0xff);
+ }
+ if(1100>20)
+ dstream<<"...";
+ dstream<<std::endl;
+
+ server.Send(peer_id_client, 0, data1, true);
+
+ sleep_ms(50);
+
+ u8 recvdata[2000];
+ dstream<<"** running client.Receive()"<<std::endl;
+ u16 peer_id = 132;
+ u16 size = client.Receive(peer_id, recvdata, 2000);
+ dstream<<"** Client received: peer_id="<<peer_id
+ <<", size="<<size
+ <<std::endl;
+
+ dstream<<"Received data (size="<<size<<"):";
+ for(int i=0; i<size && i<20; i++){
+ if(i%2==0) printf(" ");
+ printf("%.2X", ((int)((const char*)recvdata)[i])&0xff);
+ }
+ if(size>20)
+ dstream<<"...";
+ dstream<<std::endl;
+
+ assert(memcmp(*data1, recvdata, data1.getSize()) == 0);
+ assert(peer_id == PEER_ID_SERVER);
+ }
+
+ // Check peer handlers
+ assert(hand_client.count == 1);
+ assert(hand_client.last_id == 1);
+ assert(hand_server.count == 1);
+ assert(hand_server.last_id == 2);
+
+ //assert(0);
+ }
+};
+
+#define TEST(X)\
+{\
+ X x;\
+ dstream<<"Running " #X <<std::endl;\
+ x.Run();\
+}
+
+void run_tests()
+{
+ DSTACK(__FUNCTION_NAME);
+ dstream<<"run_tests() started"<<std::endl;
+ TEST(TestUtilities);
+ TEST(TestCompress);
+ TEST(TestMapNode);
+ TEST(TestMapBlock);
+ TEST(TestMapSector);
+ TEST(TestHeightmap);
+ if(INTERNET_SIMULATOR == false){
+ TEST(TestSocket);
+ dout_con<<"=== BEGIN RUNNING UNIT TESTS FOR CONNECTION ==="<<std::endl;
+ TEST(TestConnection);
+ dout_con<<"=== END RUNNING UNIT TESTS FOR CONNECTION ==="<<std::endl;
+ }
+ dstream<<"run_tests() passed"<<std::endl;
+}
+
diff --git a/src/test.h b/src/test.h
new file mode 100644
index 000000000..79560aea8
--- /dev/null
+++ b/src/test.h
@@ -0,0 +1,7 @@
+#ifndef TEST_HEADER
+#define TEST_HEADER
+
+void run_tests();
+
+#endif
+
diff --git a/src/utility.cpp b/src/utility.cpp
new file mode 100644
index 000000000..f3b98f062
--- /dev/null
+++ b/src/utility.cpp
@@ -0,0 +1,41 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "utility.h"
+
+const v3s16 g_26dirs[26] =
+{
+ // +right, +top, +back
+ v3s16( 0, 0, 1), // back
+ v3s16( 0, 1, 0), // top
+ v3s16( 1, 0, 0), // right
+ v3s16( 0, 0,-1), // front
+ v3s16( 0,-1, 0), // bottom
+ v3s16(-1, 0, 0), // left
+ // 6
+ v3s16(-1, 1, 0), // top left
+ v3s16( 1, 1, 0), // top right
+ v3s16( 0, 1, 1), // top back
+ v3s16( 0, 1,-1), // top front
+ v3s16(-1, 0, 1), // back left
+ v3s16( 1, 0, 1), // back right
+ v3s16(-1, 0,-1), // front left
+ v3s16( 1, 0,-1), // front right
+ v3s16(-1,-1, 0), // bottom left
+ v3s16( 1,-1, 0), // bottom right
+ v3s16( 0,-1, 1), // bottom back
+ v3s16( 0,-1,-1), // bottom front
+ // 18
+ v3s16(-1, 1, 1), // top back-left
+ v3s16( 1, 1, 1), // top back-right
+ v3s16(-1, 1,-1), // top front-left
+ v3s16( 1, 1,-1), // top front-right
+ v3s16(-1,-1, 1), // bottom back-left
+ v3s16( 1,-1, 1), // bottom back-right
+ v3s16(-1,-1,-1), // bottom front-left
+ v3s16( 1,-1,-1), // bottom front-right
+ // 26
+};
+
+
diff --git a/src/utility.h b/src/utility.h
new file mode 100644
index 000000000..4178e9d95
--- /dev/null
+++ b/src/utility.h
@@ -0,0 +1,607 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#ifndef UTILITY_HEADER
+#define UTILITY_HEADER
+
+#include "common_irrlicht.h"
+#include "debug.h"
+#include "strfnd.h"
+#include <iostream>
+#include <string>
+
+extern const v3s16 g_26dirs[26];
+
+inline void writeU32(u8 *data, u32 i)
+{
+ data[0] = ((i>>24)&0xff);
+ data[1] = ((i>>16)&0xff);
+ data[2] = ((i>> 8)&0xff);
+ data[3] = ((i>> 0)&0xff);
+}
+
+inline void writeU16(u8 *data, u16 i)
+{
+ data[0] = ((i>> 8)&0xff);
+ data[1] = ((i>> 0)&0xff);
+}
+
+inline void writeU8(u8 *data, u8 i)
+{
+ data[0] = ((i>> 0)&0xff);
+}
+
+inline u32 readU32(u8 *data)
+{
+ return (data[0]<<24) | (data[1]<<16) | (data[2]<<8) | (data[3]<<0);
+}
+
+inline u16 readU16(u8 *data)
+{
+ return (data[0]<<8) | (data[1]<<0);
+}
+
+inline u8 readU8(u8 *data)
+{
+ return (data[0]<<0);
+}
+
+// Signed variants of the above
+
+inline void writeS32(u8 *data, s32 i){
+ writeU32(data, (u32)i);
+}
+inline s32 readS32(u8 *data){
+ return (s32)readU32(data);
+}
+
+inline void writeS16(u8 *data, s16 i){
+ writeU16(data, (u16)i);
+}
+inline s16 readS16(u8 *data){
+ return (s16)readU16(data);
+}
+
+inline void writeV3S32(u8 *data, v3s32 p)
+{
+ writeS32(&data[0], p.X);
+ writeS32(&data[4], p.Y);
+ writeS32(&data[8], p.Z);
+}
+
+inline v3s32 readV3S32(u8 *data)
+{
+ v3s32 p;
+ p.X = readS32(&data[0]);
+ p.Y = readS32(&data[4]);
+ p.Z = readS32(&data[8]);
+ return p;
+}
+
+inline void writeV2S16(u8 *data, v2s16 p)
+{
+ writeS16(&data[0], p.X);
+ writeS16(&data[2], p.Y);
+}
+
+inline v2s16 readV2S16(u8 *data)
+{
+ v2s16 p;
+ p.X = readS16(&data[0]);
+ p.Y = readS16(&data[2]);
+ return p;
+}
+
+inline void writeV2S32(u8 *data, v2s32 p)
+{
+ writeS32(&data[0], p.X);
+ writeS32(&data[2], p.Y);
+}
+
+inline v2s32 readV2S32(u8 *data)
+{
+ v2s32 p;
+ p.X = readS32(&data[0]);
+ p.Y = readS32(&data[2]);
+ return p;
+}
+
+inline void writeV3S16(u8 *data, v3s16 p)
+{
+ writeS16(&data[0], p.X);
+ writeS16(&data[2], p.Y);
+ writeS16(&data[4], p.Z);
+}
+
+inline v3s16 readV3S16(u8 *data)
+{
+ v3s16 p;
+ p.X = readS16(&data[0]);
+ p.Y = readS16(&data[2]);
+ p.Z = readS16(&data[4]);
+ return p;
+}
+
+/*
+ None of these are used at the moment
+*/
+
+template <typename T>
+class SharedPtr
+{
+public:
+ SharedPtr(T *t=NULL)
+ {
+ refcount = new int;
+ *refcount = 1;
+ ptr = t;
+ }
+ SharedPtr(SharedPtr<T> &t)
+ {
+ //*this = t;
+ drop();
+ refcount = t.refcount;
+ (*refcount)++;
+ ptr = t.ptr;
+ }
+ ~SharedPtr()
+ {
+ drop();
+ }
+ SharedPtr<T> & operator=(T *t)
+ {
+ drop();
+ refcount = new int;
+ *refcount = 1;
+ ptr = t;
+ return *this;
+ }
+ SharedPtr<T> & operator=(SharedPtr<T> &t)
+ {
+ drop();
+ refcount = t.refcount;
+ (*refcount)++;
+ ptr = t.ptr;
+ return *this;
+ }
+ T* operator->()
+ {
+ return ptr;
+ }
+ T & operator*()
+ {
+ return *ptr;
+ }
+ bool operator!=(T *t)
+ {
+ return ptr != t;
+ }
+ bool operator==(T *t)
+ {
+ return ptr == t;
+ }
+private:
+ void drop()
+ {
+ assert((*refcount) > 0);
+ (*refcount)--;
+ if(*refcount == 0)
+ {
+ delete refcount;
+ if(ptr != NULL)
+ delete ptr;
+ }
+ }
+ T *ptr;
+ int *refcount;
+};
+
+template <typename T>
+class Buffer
+{
+public:
+ Buffer(unsigned int size)
+ {
+ m_size = size;
+ data = new T[size];
+ }
+ Buffer(const Buffer &buffer)
+ {
+ m_size = buffer.m_size;
+ data = new T[buffer.m_size];
+ memcpy(data, buffer.data, buffer.m_size);
+ }
+ Buffer(T *t, unsigned int size)
+ {
+ m_size = size;
+ data = new T[size];
+ memcpy(data, t, size);
+ }
+ ~Buffer()
+ {
+ delete[] data;
+ }
+ T & operator[](unsigned int i) const
+ {
+ return data[i];
+ }
+ T * operator*() const
+ {
+ return data;
+ }
+ unsigned int getSize() const
+ {
+ return m_size;
+ }
+private:
+ T *data;
+ unsigned int m_size;
+};
+
+template <typename T>
+class SharedBuffer
+{
+public:
+ SharedBuffer(unsigned int size)
+ {
+ m_size = size;
+ data = new T[size];
+ refcount = new unsigned int;
+ (*refcount) = 1;
+ }
+ SharedBuffer(const SharedBuffer &buffer)
+ {
+ //std::cout<<"SharedBuffer(const SharedBuffer &buffer)"<<std::endl;
+ m_size = buffer.m_size;
+ data = buffer.data;
+ refcount = buffer.refcount;
+ (*refcount)++;
+ }
+ SharedBuffer & operator=(const SharedBuffer & buffer)
+ {
+ //std::cout<<"SharedBuffer & operator=(const SharedBuffer & buffer)"<<std::endl;
+ if(this == &buffer)
+ return *this;
+ drop();
+ m_size = buffer.m_size;
+ data = buffer.data;
+ refcount = buffer.refcount;
+ (*refcount)++;
+ return *this;
+ }
+ /*
+ Copies whole buffer
+ */
+ SharedBuffer(T *t, unsigned int size)
+ {
+ m_size = size;
+ data = new T[size];
+ memcpy(data, t, size);
+ refcount = new unsigned int;
+ (*refcount) = 1;
+ }
+ /*
+ Copies whole buffer
+ */
+ SharedBuffer(const Buffer<T> &buffer)
+ {
+ m_size = buffer.m_size;
+ data = new T[buffer.getSize()];
+ memcpy(data, *buffer, buffer.getSize());
+ refcount = new unsigned int;
+ (*refcount) = 1;
+ }
+ ~SharedBuffer()
+ {
+ drop();
+ }
+ T & operator[](unsigned int i) const
+ {
+ return data[i];
+ }
+ T * operator*() const
+ {
+ return data;
+ }
+ unsigned int getSize() const
+ {
+ return m_size;
+ }
+private:
+ void drop()
+ {
+ assert((*refcount) > 0);
+ (*refcount)--;
+ if(*refcount == 0)
+ {
+ delete[] data;
+ delete refcount;
+ }
+ }
+ T *data;
+ unsigned int m_size;
+ unsigned int *refcount;
+};
+
+inline SharedBuffer<u8> SharedBufferFromString(const char *string)
+{
+ SharedBuffer<u8> b((u8*)string, strlen(string)+1);
+ return b;
+}
+
+template<typename T>
+class MutexedVariable
+{
+public:
+ MutexedVariable(T value):
+ m_value(value)
+ {
+ m_mutex.Init();
+ }
+
+ T get()
+ {
+ JMutexAutoLock lock(m_mutex);
+ return m_value;
+ }
+
+ void set(T value)
+ {
+ JMutexAutoLock lock(m_mutex);
+ m_value = value;
+ }
+
+ // You'll want to grab this in a SharedPtr
+ JMutexAutoLock * getLock()
+ {
+ return new JMutexAutoLock(m_mutex);
+ }
+
+ // You pretty surely want to grab the lock when accessing this
+ T m_value;
+
+private:
+ JMutex m_mutex;
+};
+
+/*
+ TimeTaker
+*/
+
+class TimeTaker
+{
+public:
+ TimeTaker(const char *name, IrrlichtDevice *dev)
+ {
+ m_name = name;
+ m_dev = dev;
+ m_time1 = m_dev->getTimer()->getRealTime();
+ m_running = true;
+ }
+ ~TimeTaker()
+ {
+ stop();
+ }
+ u32 stop(bool quiet=false)
+ {
+ if(m_running)
+ {
+ u32 time2 = m_dev->getTimer()->getRealTime();
+ u32 dtime = time2 - m_time1;
+ if(quiet == false)
+ std::cout<<m_name<<" took "<<dtime<<"ms"<<std::endl;
+ m_running = false;
+ return dtime;
+ }
+ return 0;
+ }
+private:
+ const char *m_name;
+ IrrlichtDevice *m_dev;
+ u32 m_time1;
+ bool m_running;
+};
+
+// Calculates the borders of a "d-radius" cube
+inline void getFacePositions(core::list<v3s16> &list, u16 d)
+{
+ if(d == 0)
+ {
+ list.push_back(v3s16(0,0,0));
+ return;
+ }
+ if(d == 1)
+ {
+ /*
+ This is an optimized sequence of coordinates.
+ */
+ list.push_back(v3s16( 0, 0, 1)); // back
+ list.push_back(v3s16(-1, 0, 0)); // left
+ list.push_back(v3s16( 1, 0, 0)); // right
+ list.push_back(v3s16( 0, 0,-1)); // front
+ list.push_back(v3s16( 0,-1, 0)); // bottom
+ list.push_back(v3s16( 0, 1, 0)); // top
+ // 6
+ list.push_back(v3s16(-1, 0, 1)); // back left
+ list.push_back(v3s16( 1, 0, 1)); // back right
+ list.push_back(v3s16(-1, 0,-1)); // front left
+ list.push_back(v3s16( 1, 0,-1)); // front right
+ list.push_back(v3s16(-1,-1, 0)); // bottom left
+ list.push_back(v3s16( 1,-1, 0)); // bottom right
+ list.push_back(v3s16( 0,-1, 1)); // bottom back
+ list.push_back(v3s16( 0,-1,-1)); // bottom front
+ list.push_back(v3s16(-1, 1, 0)); // top left
+ list.push_back(v3s16( 1, 1, 0)); // top right
+ list.push_back(v3s16( 0, 1, 1)); // top back
+ list.push_back(v3s16( 0, 1,-1)); // top front
+ // 18
+ list.push_back(v3s16(-1, 1, 1)); // top back-left
+ list.push_back(v3s16( 1, 1, 1)); // top back-right
+ list.push_back(v3s16(-1, 1,-1)); // top front-left
+ list.push_back(v3s16( 1, 1,-1)); // top front-right
+ list.push_back(v3s16(-1,-1, 1)); // bottom back-left
+ list.push_back(v3s16( 1,-1, 1)); // bottom back-right
+ list.push_back(v3s16(-1,-1,-1)); // bottom front-left
+ list.push_back(v3s16( 1,-1,-1)); // bottom front-right
+ // 26
+ return;
+ }
+
+ // Take blocks in all sides, starting from y=0 and going +-y
+ for(s16 y=0; y<=d-1; y++)
+ {
+ // Left and right side, including borders
+ for(s16 z=-d; z<=d; z++)
+ {
+ list.push_back(v3s16(d,y,z));
+ list.push_back(v3s16(-d,y,z));
+ if(y != 0)
+ {
+ list.push_back(v3s16(d,-y,z));
+ list.push_back(v3s16(-d,-y,z));
+ }
+ }
+ // Back and front side, excluding borders
+ for(s16 x=-d+1; x<=d-1; x++)
+ {
+ list.push_back(v3s16(x,y,d));
+ list.push_back(v3s16(x,y,-d));
+ if(y != 0)
+ {
+ list.push_back(v3s16(x,-y,d));
+ list.push_back(v3s16(x,-y,-d));
+ }
+ }
+ }
+
+ // Take the bottom and top face with borders
+ // -d<x<d, y=+-d, -d<z<d
+ for(s16 x=-d; x<=d; x++)
+ for(s16 z=-d; z<=d; z++)
+ {
+ list.push_back(v3s16(x,-d,z));
+ list.push_back(v3s16(x,d,z));
+ }
+}
+
+class IndentationRaiser
+{
+public:
+ IndentationRaiser(u16 *indentation)
+ {
+ m_indentation = indentation;
+ (*m_indentation)++;
+ }
+ ~IndentationRaiser()
+ {
+ (*m_indentation)--;
+ }
+private:
+ u16 *m_indentation;
+};
+
+inline s16 getContainerPos(s16 p, s16 d)
+{
+ return (p>=0 ? p : p-d+1) / d;
+}
+
+inline v2s16 getContainerPos(v2s16 p, s16 d)
+{
+ return v2s16(
+ getContainerPos(p.X, d),
+ getContainerPos(p.Y, d)
+ );
+}
+
+inline v3s16 getContainerPos(v3s16 p, s16 d)
+{
+ return v3s16(
+ getContainerPos(p.X, d),
+ getContainerPos(p.Y, d),
+ getContainerPos(p.Z, d)
+ );
+}
+
+inline bool isInArea(v3s16 p, s16 d)
+{
+ return (
+ p.X >= 0 && p.X < d &&
+ p.Y >= 0 && p.Y < d &&
+ p.Z >= 0 && p.Z < d
+ );
+}
+
+inline bool isInArea(v2s16 p, s16 d)
+{
+ return (
+ p.X >= 0 && p.X < d &&
+ p.Y >= 0 && p.Y < d
+ );
+}
+
+inline std::wstring narrow_to_wide(const std::string& mbs)
+{
+ size_t wcl = mbs.size();
+ SharedBuffer<wchar_t> wcs(wcl+1);
+ size_t l = mbstowcs(*wcs, mbs.c_str(), wcl);
+ wcs[l] = 0;
+ return *wcs;
+}
+
+inline std::string wide_to_narrow(const std::wstring& wcs)
+{
+ size_t mbl = wcs.size()*4;
+ SharedBuffer<char> mbs(mbl+1);
+ size_t l = wcstombs(*mbs, wcs.c_str(), mbl);
+ if((int)l == -1)
+ mbs[0] = 0;
+ else
+ mbs[l] = 0;
+ return *mbs;
+}
+
+/*
+ See test.cpp for example cases.
+ wraps degrees to the range of -360...360
+ NOTE: Wrapping to 0...360 is not used because pitch needs negative values.
+*/
+inline float wrapDegrees(float f)
+{
+ // Take examples of f=10, f=720.5, f=-0.5, f=-360.5
+ // This results in
+ // 10, 720, -1, -361
+ int i = floor(f);
+ // 0, 2, 0, -1
+ int l = i / 360;
+ // NOTE: This would be used for wrapping to 0...360
+ // 0, 2, -1, -2
+ /*if(i < 0)
+ l -= 1;*/
+ // 0, 720, 0, -360
+ int k = l * 360;
+ // 10, 0.5, -0.5, -0.5
+ f -= float(k);
+ return f;
+}
+
+inline std::string lowercase(std::string s)
+{
+ for(size_t i=0; i<s.size(); i++)
+ {
+ if(s[i] >= 'A' && s[i] <= 'Z')
+ s[i] -= 'A' - 'a';
+ }
+ return s;
+}
+
+inline bool is_yes(std::string s)
+{
+ s = lowercase(trim(s));
+ if(s == "y" || s == "yes" || s == "true")
+ return true;
+ return false;
+}
+
+#endif
+