diff options
Diffstat (limited to 'src')
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*)¤t_byte, 1); + more_count = 0; + current_byte = data[i]; + } + else + { + more_count++; + } + } + // write count and byte + os.write((char*)&more_count, 1); + os.write((char*)¤t_byte, 1); +} + +void decompress(std::istream &is, std::ostream &os, u8 version) +{ + // Read length (u32) + + u8 tmp[4]; + is.read((char*)tmp, 4); + u32 len = readU32(tmp); + + // We will be reading 8-bit pairs of more_count and byte + u32 count = 0; + for(;;) + { + u8 more_count=0; + u8 byte=0; + + is.read((char*)&more_count, 1); + + is.read((char*)&byte, 1); + + if(is.eof()) + throw SerializationError("decompress: stream ended halfway"); + + for(s32 i=0; i<(u16)more_count+1; i++) + os.write((char*)&byte, 1); + + count += (u16)more_count+1; + + if(count == len) + break; + } +} + + 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 + |