From: Perttu Ahola Date: Fri, 26 Nov 2010 23:02:21 +0000 (+0200) Subject: Initial files X-Git-Url: http://81.2.79.47:8989/gitweb/?a=commitdiff_plain;h=4e249fb3fbf75f0359758760d88e22aa5b14533c;p=zefram%2Fminetest%2Fminetest_engine.git Initial files --- 4e249fb3fbf75f0359758760d88e22aa5b14533c diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..c8912d1f --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +# Makefile for Irrlicht Examples +# It's usually sufficient to change just the target name and source file list +# and be sure that CXX is set to a valid compiler +TARGET = test +SOURCE_FILES = mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp client.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp main.cpp test.cpp +SOURCES = $(addprefix src/, $(SOURCE_FILES)) +OBJECTS = $(SOURCES:.cpp=.o) +FASTTARGET = fasttest + +IRRLICHTPATH = ../irrlicht/irrlicht-1.7.1 +JTHREADPATH = ../jthread/jthread-1.2.1 + +CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src + +#CXXFLAGS = -O2 -ffast-math -Wall -fomit-frame-pointer -pipe +CXXFLAGS = -O2 -ffast-math -Wall -g +#CXXFLAGS = -O1 -ffast-math -Wall -g +#CXXFLAGS = -Wall -g -O0 + +#CXXFLAGS = -O3 -ffast-math -Wall +#CXXFLAGS = -O3 -ffast-math -Wall -g +#CXXFLAGS = -O2 -ffast-math -Wall -g + +#FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=pentium3 +FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=i686 + +#Default target + +all: all_linux + +ifeq ($(HOSTTYPE), x86_64) +LIBSELECT=64 +endif + +# Target specific settings + +all_linux fast_linux: LDFLAGS = -L/usr/X11R6/lib$(LIBSELECT) -L$(IRRLICHTPATH)/lib/Linux -L$(JTHREADPATH)/src/.libs -lIrrlicht -lGL -lXxf86vm -lXext -lX11 -ljthread +all_linux fast_linux clean_linux: SYSTEM=Linux + +all_win32: LDFLAGS = -L$(IRRLICHTPATH)/lib/Win32-gcc -L$(JTHREADPATH)/Debug -lIrrlicht -lopengl32 -lm -ljthread +all_win32 clean_win32: SYSTEM=Win32-gcc +all_win32 clean_win32: SUF=.exe + +# Name of the binary - only valid for targets which set SYSTEM + +DESTPATH = bin/$(TARGET)$(SUF) +FASTDESTPATH = bin/$(FASTTARGET)$(SUF) + +# Build commands + +all_linux all_win32: $(DESTPATH) + +fast_linux: $(FASTDESTPATH) + +$(FASTDESTPATH): $(SOURCES) + $(CXX) -o $(FASTDESTPATH) $(SOURCES) $(CPPFLAGS) $(FASTCXXFLAGS) $(LDFLAGS) -DUNITTEST_DISABLE + +$(DESTPATH): $(OBJECTS) + $(CXX) -o $@ $(OBJECTS) $(LDFLAGS) + +.cpp.o: + $(CXX) -c -o $@ $< $(CPPFLAGS) $(CXXFLAGS) + +clean: clean_linux clean_win32 clean_fast_linux + +clean_linux clean_win32: + @$(RM) $(OBJECTS) $(DESTPATH) + +clean_fast_linux: + @$(RM) $(FASTDESTPATH) + +.PHONY: all all_win32 clean clean_linux clean_win32 diff --git a/bin/Irrlicht.dll b/bin/Irrlicht.dll new file mode 100644 index 00000000..32cb47f9 Binary files /dev/null and b/bin/Irrlicht.dll differ diff --git a/data/fontlucida.png b/data/fontlucida.png new file mode 100644 index 00000000..c63fa02b Binary files /dev/null and b/data/fontlucida.png differ diff --git a/data/grass.png b/data/grass.png new file mode 100644 index 00000000..56362053 Binary files /dev/null and b/data/grass.png differ diff --git a/data/grass2.png b/data/grass2.png new file mode 100644 index 00000000..66b3e58b Binary files /dev/null and b/data/grass2.png differ diff --git a/data/grass_footsteps.png b/data/grass_footsteps.png new file mode 100644 index 00000000..36573ada Binary files /dev/null and b/data/grass_footsteps.png differ diff --git a/data/leaves.png b/data/leaves.png new file mode 100644 index 00000000..086da809 Binary files /dev/null and b/data/leaves.png differ diff --git a/data/light.png b/data/light.png new file mode 100644 index 00000000..24091a37 Binary files /dev/null and b/data/light.png differ diff --git a/data/mese.png b/data/mese.png new file mode 100644 index 00000000..4c876cdc Binary files /dev/null and b/data/mese.png differ diff --git a/data/player.png b/data/player.png new file mode 100644 index 00000000..90adf974 Binary files /dev/null and b/data/player.png differ diff --git a/data/player_back.png b/data/player_back.png new file mode 100644 index 00000000..530aa751 Binary files /dev/null and b/data/player_back.png differ diff --git a/data/rat.png b/data/rat.png new file mode 100644 index 00000000..d1a0e2ae Binary files /dev/null and b/data/rat.png differ diff --git a/data/sign.png b/data/sign.png new file mode 100644 index 00000000..fc9081ad Binary files /dev/null and b/data/sign.png differ diff --git a/data/sign_back.png b/data/sign_back.png new file mode 100644 index 00000000..779e4bc2 Binary files /dev/null and b/data/sign_back.png differ diff --git a/data/stone.png b/data/stone.png new file mode 100644 index 00000000..7b578215 Binary files /dev/null and b/data/stone.png differ diff --git a/data/stone_with_arrow.png b/data/stone_with_arrow.png new file mode 100644 index 00000000..d6447067 Binary files /dev/null and b/data/stone_with_arrow.png differ diff --git a/data/tf.jpg b/data/tf.jpg new file mode 100644 index 00000000..d08b6c95 Binary files /dev/null and b/data/tf.jpg differ diff --git a/data/tree.png b/data/tree.png new file mode 100644 index 00000000..98f6f54d Binary files /dev/null and b/data/tree.png differ diff --git a/data/water.png b/data/water.png new file mode 100644 index 00000000..d4457bcb Binary files /dev/null and b/data/water.png differ diff --git a/data/water_old.png b/data/water_old.png new file mode 100644 index 00000000..37b2898f Binary files /dev/null and b/data/water_old.png differ diff --git a/doc/README.txt b/doc/README.txt new file mode 100644 index 00000000..ff07fc4a --- /dev/null +++ b/doc/README.txt @@ -0,0 +1,115 @@ +Minetest-c55 +--------------- + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +Copyright (c) 2010 Perttu Ahola + +An InfiniMiner/Minecraft inspired game. + +This is a development version: +- Don't expect it to work as well as a finished game will. +- Please report any bugs to me. That way I can fix them to the next release. + +Server information: +- I usually have a server running at celer.oni.biz on port 30000 +- If you want to run a server, I can list you on my website and in here. + +Features, as of now: +- Almost Infinite Map (limited to +-31000 blocks in any direction at the moment) + - Minecraft alpha has a height restriction of 128 blocks +- Map Generator capable of taking advantage of the infinite map + +Controls: +- WASD+mouse: Move +- Mouse L: Dig +- Mouse R: Place block +- Mouse Wheel: Change item +- F: Change item +- R: Toggle full view range + +Configuration file: +- Can be passed as parameter to the executable. +- If not given as parameter, these are checked, in order: + ../minetest.conf + ../../minetest.conf + +Running on Windows: +- The working directory should be ./bin + +Running on GNU/Linux: +- fasttest is a linux binary compiled on a recent Arch Linux installation. + It should run on most recent GNU/Linux distributions. +- Browse to the game ./bin directory and type: + LD_LIBRARY_PATH=. ./fasttest +- If it doesn't work, use wine. I aim at 100% compatibility with wine. + +COPYING (License of Minetest-c55): +- This version of the game is FREEWARE. +- You can play this version of the game without charge. +- You can redistribute this version of the game without charge, only with + these same license terms you are reading now, and provided that you give + proper information about who has made the game (me) and where to get the + original copy and new versions (http://celer.oni.biz/~celeron55/minetest). +- You are not allowed to sell this game for a price. + + +Irrlicht +--------------- + +This program uses the Irrlicht Engine. http://irrlicht.sourceforge.net/ + + The Irrlicht Engine License + +Copyright © 2002-2005 Nikolaus Gebhardt + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. + + +JThread +--------------- + +This program uses the JThread library. License for JThread follows: + +Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + + diff --git a/makepackage_binary.sh b/makepackage_binary.sh new file mode 100755 index 00000000..c11c45b1 --- /dev/null +++ b/makepackage_binary.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +PACKAGEDIR=../minetest-packages +PACKAGENAME=minetest-c55-binary-`date +%y%m%d%H%M%S` +PACKAGEPATH=$PACKAGEDIR/$PACKAGENAME + +mkdir -p $PACKAGEPATH +mkdir -p $PACKAGEPATH/bin +mkdir -p $PACKAGEPATH/data +mkdir -p $PACKAGEPATH/doc + +cp minetest.conf.example $PACKAGEPATH/ + +cp bin/minetest.exe $PACKAGEPATH/bin/ +cp bin/Irrlicht.dll $PACKAGEPATH/bin/ +#cp bin/test $PACKAGEPATH/bin/ +cp bin/fasttest $PACKAGEPATH/bin/ +cp ../irrlicht/irrlicht-1.7.1/lib/Linux/libIrrlicht.a $PACKAGEPATH/bin/ +cp ../jthread/jthread-1.2.1/src/.libs/libjthread-1.2.1.so $PACKAGEPATH/bin/ + +cp -r data/fontlucida.png $PACKAGEPATH/data/ +cp -r data/player.png $PACKAGEPATH/data/ +cp -r data/player_back.png $PACKAGEPATH/data/ +cp -r data/stone.png $PACKAGEPATH/data/ +cp -r data/grass.png $PACKAGEPATH/data/ +cp -r data/grass_footsteps.png $PACKAGEPATH/data/ +cp -r data/water.png $PACKAGEPATH/data/ +cp -r data/tree.png $PACKAGEPATH/data/ +cp -r data/leaves.png $PACKAGEPATH/data/ +cp -r data/mese.png $PACKAGEPATH/data/ +cp -r data/light.png $PACKAGEPATH/data/ +cp -r data/sign.png $PACKAGEPATH/data/ +cp -r data/sign_back.png $PACKAGEPATH/data/ +cp -r data/rat.png $PACKAGEPATH/data/ + +cp -r doc/README.txt $PACKAGEPATH/doc/README.txt + +cd $PACKAGEDIR +rm $PACKAGENAME.zip +zip -r $PACKAGENAME.zip $PACKAGENAME + diff --git a/minetest.conf.example b/minetest.conf.example new file mode 100644 index 00000000..3dd4a1c5 --- /dev/null +++ b/minetest.conf.example @@ -0,0 +1,44 @@ +# This file is read by default from: +# ../minetest.conf +# ../../minetest.conf +# Any other path can be chosen by passing the path as a parameter +# to the program, eg. "minetest.exe ../minetest.conf.example" + +dedicated_server = + +# Client side stuff + +wanted_fps = 30 +fps_max = 60 +viewing_range_nodes_max = 300 +viewing_range_nodes_min = 20 +screenW = +screenH = +host_game = +port = 30000 +address = celer.oni.biz +name = + +random_input = false +client_delete_unused_sectors_timeout = 1200 + +# Server side stuff + +# - The possible generators are: +# (Indeed you can do all of them with only "power" 8)) +# H=value: +# constant +# H=slope.dot(pos): +# linear +# H=slope.dot(pos^power): +# power + +mapgen_heightmap_blocksize = 64 +mapgen_height_randmax = constant 70.0 +mapgen_height_randfactor = constant 0.6 +mapgen_height_base = linear 0 80 0 +mapgen_plants_amount = constant 1.0 + +creative_mode = false + + diff --git a/minetest.sln b/minetest.sln new file mode 100644 index 00000000..d1f144cb --- /dev/null +++ b/minetest.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minetest", "minetest.vcproj", "{AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Debug|Win32.ActiveCfg = Debug|Win32 + {AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Debug|Win32.Build.0 = Debug|Win32 + {AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Release|Win32.ActiveCfg = Release|Win32 + {AE3BF173-1D74-4294-AAB8-5A0ACDE9990D}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/minetest.vcproj b/minetest.vcproj new file mode 100644 index 00000000..c60d9a1f --- /dev/null +++ b/minetest.vcproj @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 00000000..ee326997 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,1639 @@ +#include "client.h" +#include "utility.h" +#include +#include "clientserver.h" +#include "jmutexautolock.h" +#include "main.h" +#include + +#ifdef _WIN32 + #include + #define sleep_ms(x) Sleep(x) +#else + #include + #define sleep_ms(x) usleep(x*1000) +#endif + +/* + FIXME: This thread can access the environment at any time +*/ + +void * ClientUpdateThread::Thread() +{ + ThreadStarted(); + + 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<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 "< 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<::Iterator i = deleted_blocks.begin(); + core::list 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 reply(replysize); + writeU16(&reply[0], TOSERVER_DELETEDBLOCKS); + reply[2] = sendlist.size(); + u32 k = 0; + for(core::list::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 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::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<id=" + <id<= 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 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<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="< players_alive; + for(u32 i=0; iupdateName((char*)&data[start+2]); + + start += item_size; + } + + /* + Remove those players from the environment that + weren't listed by the server. + */ + //dstream< players = m_env.getPlayers(); + core::list::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<::Iterator i; + for(i=players_alive.begin(); i!=players_alive.end(); i++) + { + if((*ip)->peer_id == *i) + { + is_alive = true; + break; + } + } + /*dstream<peer_id) + <<" is_alive="<peer_id + <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"<inventory.deSerialize(is); + //t1.stop(); + + m_inventory_updated = true; + + //dstream<<"Client got player inventory:"<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; iisLocal()) + { + 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 abs_to_delete; + for(core::map::Iterator + i = m_active_blocks.getIterator(); + i.atEnd() == false; i++) + { + v3s16 p = i.getNode()->getKey(); + /*dstream<<"adding " + <<"("<updateObjects(is, m_server_ser_ver, + m_device->getSceneManager()); + } + + /*dstream<<"Final delete queue size: "<::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 " + <<"("< 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::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 modified_blocks; + + try + { + JMutexAutoLock envlock(m_env_mutex); + m_env.getMap().addNodeAndUpdate(p, n, modified_blocks); + } + catch(InvalidPositionException &e) + {} + + for(core::map::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<getPos(); + if(sp != p2d) + { + dstream<<"ERROR: Got sector with getPos()=" + <<"("<getPos() == p2d); + + try{ + block = sector->getBlockNoCreate(p.Y); + /* + Update an existing block + */ + //dstream<<"Updating"<deSerialize(istr, ser_version); + //block->setChangedFlag(); + } + catch(InvalidPositionException &e) + { + /* + Create a new block + */ + //dstream<<"Creating new"<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"< blocks_changed; + blocks_changed.insert(block->getPos(), block); + core::map 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 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 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< 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 (" + < 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::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< 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< 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< 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< 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 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 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 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 shootline_on_map + ) +{ + JMutexAutoLock envlock(m_env_mutex); + + core::array objects; + + for(core::map::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 "<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 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"< + +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::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 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 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 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 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 m_fetchblock_history; + JMutex m_fetchblock_mutex;*/ + + core::list 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 m_active_blocks; +}; + +#endif + diff --git a/src/clientserver.h b/src/clientserver.h new file mode 100644 index 00000000..80206921 --- /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 00000000..69f00a75 --- /dev/null +++ b/src/common_irrlicht.h @@ -0,0 +1,17 @@ +#ifndef COMMON_IRRLICHT_HEADER +#define COMMON_IRRLICHT_HEADER + +#include +using namespace irr; +typedef core::vector3df v3f; +typedef core::vector3d v3s16; +typedef core::vector3d v3s32; + +typedef core::vector2d v2f; +typedef core::vector2d v2s16; +typedef core::vector2d v2s32; +typedef core::vector2d v2u32; +typedef core::vector2d v2f32; + +#endif + diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 00000000..42bfdfb9 --- /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 &data, + u32 protocol_id, u16 sender_peer_id, u8 channel) +{ + return makePacket(address, *data, data.getSize(), + protocol_id, sender_peer_id, channel); +} + +SharedBuffer makeOriginalPacket( + SharedBuffer data) +{ + u32 header_size = 1; + u32 packet_size = data.getSize() + header_size; + SharedBuffer b(packet_size); + + writeU8(&b[0], TYPE_ORIGINAL); + + memcpy(&b[header_size], *data, data.getSize()); + + return b; +} + +core::list > makeSplitPacket( + SharedBuffer data, + u32 chunksize_max, + u16 seqnum) +{ + // Chunk packets, containing the TYPE_SPLIT header + core::list > chunks; + + u32 chunk_header_size = 7; + u32 maximum_data_size = chunksize_max - chunk_header_size; + u32 start = 0; + u32 end = 0; + u32 chunk_num = 0; + do{ + end = start + maximum_data_size - 1; + if(end > data.getSize() - 1) + end = data.getSize() - 1; + + u32 payload_size = end - start + 1; + u32 packet_size = chunk_header_size + payload_size; + + SharedBuffer chunk(packet_size); + + writeU8(&chunk[0], TYPE_SPLIT); + writeU16(&chunk[1], seqnum); + // [3] u16 chunk_count is written at next stage + writeU16(&chunk[5], chunk_num); + memcpy(&chunk[chunk_header_size], &data[start], payload_size); + + chunks.push_back(chunk); + + start = end + 1; + chunk_num++; + } + while(end != data.getSize() - 1); + + u16 chunk_count = chunks.getSize(); + + core::list >::Iterator i = chunks.begin(); + for(; i != chunks.end(); i++) + { + // Write chunk_count + writeU16(&((*i)[3]), chunk_count); + } + + return chunks; +} + +core::list > makeAutoSplitPacket( + SharedBuffer data, + u32 chunksize_max, + u16 &split_seqnum) +{ + u32 original_header_size = 1; + core::list > list; + if(data.getSize() + original_header_size > chunksize_max) + { + list = makeSplitPacket(data, chunksize_max, split_seqnum); + split_seqnum++; + return list; + } + else + { + list.push_back(makeOriginalPacket(data)); + } + return list; +} + +SharedBuffer makeReliablePacket( + SharedBuffer data, + u16 seqnum) +{ + /*dstream<<"BEGIN SharedBuffer makeReliablePacket()"< makeReliablePacket()"<::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++) + { + u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + dout_con<::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++) + { + u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + /*dout_con<<"findPacket(): finding seqnum="<::Iterator i = m_list.begin(); + m_list.erase(i); + return p; +} +BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) +{ + RPBSearchResult r = findPacket(seqnum); + if(r == notFound()){ + dout_con<<"Not found"<= BASE_HEADER_SIZE+3); + u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); + assert(type == TYPE_RELIABLE); + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); + + // Find the right place for the packet and insert it there + + // If list is empty, just add it + if(m_list.empty()) + { + m_list.push_back(p); + // Done. + return; + } + // Otherwise find the right place + core::list::Iterator i; + i = m_list.begin(); + // Find the first packet in the list which has a higher seqnum + for(; i != m_list.end(); i++){ + u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + if(s == seqnum){ + throw AlreadyExistsException("Same seqnum in list"); + } + if(seqnum_higher(s, seqnum)){ + break; + } + } + // If we're at the end of the list, add the packet to the + // end of the list + if(i == m_list.end()) + { + m_list.push_back(p); + // Done. + return; + } + // Insert before i + m_list.insert_before(i, p); +} + +void ReliablePacketBuffer::incrementTimeouts(float dtime) +{ + core::list::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++){ + i->time += dtime; + i->totaltime += dtime; + } +} + +void ReliablePacketBuffer::resetTimedOuts(float timeout) +{ + core::list::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++){ + if(i->time >= timeout) + i->time = 0.0; + } +} + +bool ReliablePacketBuffer::anyTotaltimeReached(float timeout) +{ + core::list::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++){ + if(i->totaltime >= timeout) + return true; + } + return false; +} + +core::list ReliablePacketBuffer::getTimedOuts(float timeout) +{ + core::list timed_outs; + core::list::Iterator i; + i = m_list.begin(); + for(; i != m_list.end(); i++) + { + if(i->time >= timeout) + timed_outs.push_back(*i); + } + return timed_outs; +} + +/* + IncomingSplitBuffer +*/ + +IncomingSplitBuffer::~IncomingSplitBuffer() +{ + core::map::Iterator i; + i = m_buf.getIterator(); + for(; i.atEnd() == false; i++) + { + delete i.getNode()->getValue(); + } +} +/* + This will throw a GotSplitPacketException when a full + split packet is constructed. +*/ +void IncomingSplitBuffer::insert(BufferedPacket &p, bool reliable) +{ + u32 headersize = BASE_HEADER_SIZE + 7; + assert(p.data.getSize() >= headersize); + u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); + assert(type == TYPE_SPLIT); + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); + u16 chunk_count = readU16(&p.data[BASE_HEADER_SIZE+3]); + u16 chunk_num = readU16(&p.data[BASE_HEADER_SIZE+5]); + + // Add if doesn't exist + if(m_buf.find(seqnum) == NULL) + { + IncomingSplitPacket *sp = new IncomingSplitPacket(); + sp->chunk_count = chunk_count; + sp->reliable = reliable; + m_buf[seqnum] = sp; + } + + IncomingSplitPacket *sp = m_buf[seqnum]; + + // TODO: These errors should be thrown or something? Dunno. + if(chunk_count != sp->chunk_count) + derr_con<<"Connection: WARNING: chunk_count="<chunk_count="<chunk_count + <reliable) + derr_con<<"Connection: WARNING: reliable="<reliable="<reliable + <chunks.find(chunk_num) != NULL) + throw AlreadyExistsException("Chunk already in buffer"); + + // Cut chunk data out of packet + u32 chunkdatasize = p.data.getSize() - headersize; + SharedBuffer chunkdata(chunkdatasize); + memcpy(*chunkdata, &(p.data[headersize]), chunkdatasize); + + // Set chunk data in buffer + sp->chunks[chunk_num] = chunkdata; + + // If not all chunks are received, return + if(sp->allReceived() == false) + return; + + // Calculate total size + u32 totalsize = 0; + core::map >::Iterator i; + i = sp->chunks.getIterator(); + for(; i.atEnd() == false; i++) + { + totalsize += i.getNode()->getValue().getSize(); + } + + SharedBuffer fulldata(totalsize); + + // Copy chunks to data buffer + u32 start = 0; + for(u32 chunk_i=0; chunk_ichunk_count; + chunk_i++) + { + SharedBuffer buf = sp->chunks[chunk_i]; + u16 chunkdatasize = buf.getSize(); + memcpy(&fulldata[start], *buf, chunkdatasize); + start += chunkdatasize;; + } + + // Remove sp from buffer + m_buf.remove(seqnum); + delete sp; + + throw GotSplitPacketException(fulldata); +} +void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout) +{ + core::list remove_queue; + core::map::Iterator i; + i = m_buf.getIterator(); + for(; i.atEnd() == false; i++) + { + IncomingSplitPacket *p = i.getNode()->getValue(); + // Reliable ones are not removed by timeout + if(p->reliable == true) + continue; + p->time += dtime; + if(p->time >= timeout) + remove_queue.push_back(i.getNode()->getKey()); + } + core::list::Iterator j; + j = remove_queue.begin(); + for(; j != remove_queue.end(); j++) + { + dout_con<<"NOTE: Removing timed out unreliable split packet" + < 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::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + delete peer; + } +} + +void Connection::Serve(unsigned short port) +{ + m_socket.Bind(port); + m_peer_id = PEER_ID_SERVER; +} + +void Connection::Connect(Address address) +{ + core::map::Node *node = m_peers.find(PEER_ID_SERVER); + if(node != NULL){ + throw ConnectionException("Already connected to a server"); + } + + Peer *peer = new Peer(PEER_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 data(0); + Send(PEER_ID_SERVER, 0, data, true); + + //m_waiting_new_peer_id = true; +} + +bool Connection::Connected() +{ + if(m_peers.size() != 1) + return false; + + core::map::Node *node = m_peers.find(PEER_ID_SERVER); + if(node == NULL) + return false; + + if(m_peer_id == PEER_ID_NEW) + return false; + + return true; +} + +SharedBuffer Channel::ProcessPacket( + SharedBuffer packetdata, + Connection *con, + u16 peer_id, + u8 channelnum, + bool reliable) +{ + IndentationRaiser iraiser(&(con->m_indentation)); + + if(packetdata.getSize() < 1) + throw InvalidIncomingDataException("packetdata.getSize() < 1"); + + u8 type = readU8(&packetdata[0]); + + if(type == TYPE_CONTROL) + { + if(packetdata.getSize() < 2) + throw InvalidIncomingDataException("packetdata.getSize() < 2"); + + u8 controltype = readU8(&packetdata[1]); + + if(controltype == CONTROLTYPE_ACK) + { + if(packetdata.getSize() < 4) + throw InvalidIncomingDataException + ("packetdata.getSize() < 4 (ACK header size)"); + + u16 seqnum = readU16(&packetdata[2]); + con->PrintInfo(); + dout_con<<"Got CONTROLTYPE_ACK: channelnum=" + <<((int)channelnum&0xff)<<", peer_id="<PrintInfo(); + outgoing_reliables.print(); + dout_con<PrintInfo(derr_con); + derr_con<<"WARNING: ACKed packet not " + "in outgoing queue" + <PrintInfo(); + dout_con<<"Got new peer id: "<GetPeerID() != PEER_ID_NEW) + { + con->PrintInfo(derr_con); + derr_con<<"WARNING: Not changing" + " existing peer id."<SetPeerID(peer_id_new); + } + throw ProcessedSilentlyException("Got a SET_PEER_ID"); + } + else if(controltype == CONTROLTYPE_PING) + { + // Just ignore it, the incoming data already reset + // the timeout counter + con->PrintInfo(); + dout_con<<"PING"<PrintInfo(derr_con); + derr_con<<"INVALID TYPE_CONTROL: invalid controltype=" + <<((int)controltype&0xff)<PrintInfo(); + dout_con<<"RETURNING TYPE_ORIGINAL to user" + < payload(packetdata.getSize() - ORIGINAL_HEADER_SIZE); + memcpy(*payload, &packetdata[ORIGINAL_HEADER_SIZE], payload.getSize()); + return payload; + } + else if(type == TYPE_SPLIT) + { + // We have to create a packet again for buffering + // This isn't actually too bad an idea. + BufferedPacket packet = makePacket( + con->GetPeer(peer_id)->address, + packetdata, + con->GetProtocolID(), + peer_id, + channelnum); + try{ + // Buffer the packet + incoming_splits.insert(packet, reliable); + } + // This exception happens when all the pieces of a packet + // are collected. + catch(GotSplitPacketException &e) + { + con->PrintInfo(); + dout_con<<"RETURNING TYPE_SPLIT: Constructed full data, " + <<"size="<PrintInfo(); + dout_con<<"BUFFERING TYPE_SPLIT"<PrintInfo(); + if(is_future_packet) + dout_con<<"BUFFERING"; + else if(is_old_packet) + dout_con<<"OLD"; + else + dout_con<<"RECUR"; + dout_con<<" TYPE_RELIABLE seqnum="< reply(4); + writeU8(&reply[0], TYPE_CONTROL); + writeU8(&reply[1], CONTROLTYPE_ACK); + writeU16(&reply[2], seqnum); + con->SendAsPacket(peer_id, channelnum, reply, false); + + //if(seqnum_higher(seqnum, next_incoming_seqnum)) + if(is_future_packet) + { + /*con->PrintInfo(); + dout_con<<"Buffering reliable packet (seqnum=" + <GetPeer(peer_id)->address, + packetdata, + con->GetProtocolID(), + peer_id, + channelnum); + try{ + incoming_reliables.insert(packet); + + /*con->PrintInfo(); + dout_con<<"INCOMING: "; + incoming_reliables.print(); + dout_con< payload(packetdata.getSize() - RELIABLE_HEADER_SIZE); + memcpy(*payload, &packetdata[RELIABLE_HEADER_SIZE], payload.getSize()); + + return ProcessPacket(payload, con, peer_id, channelnum, true); + } + else + { + con->PrintInfo(derr_con); + derr_con<<"Got invalid type="<<((int)type&0xff)< Channel::CheckIncomingBuffers(Connection *con, + u16 &peer_id) +{ + u16 firstseqnum = 0; + // Clear old packets from start of buffer + try{ + for(;;){ + firstseqnum = incoming_reliables.getFirstSeqnum(); + if(seqnum_higher(next_incoming_seqnum, firstseqnum)) + incoming_reliables.popFirst(); + else + break; + } + // This happens if all packets are old + }catch(con::NotFoundException) + {} + + if(incoming_reliables.empty() == false) + { + if(firstseqnum == next_incoming_seqnum) + { + BufferedPacket p = incoming_reliables.popFirst(); + + peer_id = readPeerId(*p.data); + u8 channelnum = readChannel(*p.data); + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); + + con->PrintInfo(); + dout_con<<"UNBUFFERING TYPE_RELIABLE" + <<" seqnum="< Connection::GetFromBuffers(u16 &peer_id) +{ + core::map::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + for(u16 i=0; ichannels[i]; + try{ + SharedBuffer resultdata = channel->CheckIncomingBuffers + (this, peer_id); + + return resultdata; + } + catch(NoIncomingDataException &e) + { + } + catch(InvalidIncomingDataException &e) + { + } + catch(ProcessedSilentlyException &e) + { + } + } + } + throw NoIncomingDataException("No relevant data in buffers"); +} + +u32 Connection::Receive(u16 &peer_id, u8 *data, u32 datasize) +{ + /* + Receive a packet from the network + */ + + // TODO: We can not know how many layers of header there are. + // For now, just assume there are no other than the base headers. + u32 packet_maxsize = datasize + BASE_HEADER_SIZE; + Buffer packetdata(packet_maxsize); + + for(;;) + { + try + { + /* + Check if some buffer has relevant data + */ + try{ + SharedBuffer resultdata = GetFromBuffers(peer_id); + + if(datasize < resultdata.getSize()) + throw InvalidIncomingDataException + ("Buffer too small for received data"); + + memcpy(data, *resultdata, resultdata.getSize()); + return resultdata.getSize(); + } + catch(NoIncomingDataException &e) + { + } + + Address sender; + + s32 received_size = m_socket.Receive(sender, *packetdata, packet_maxsize); + + if(received_size < 0) + throw NoIncomingDataException("No incoming data"); + if(received_size < BASE_HEADER_SIZE) + throw InvalidIncomingDataException("No full header received"); + if(readU32(&packetdata[0]) != m_protocol_id) + throw InvalidIncomingDataException("Invalid protocol id"); + + peer_id = readPeerId(*packetdata); + u8 channelnum = readChannel(*packetdata); + if(channelnum > CHANNEL_COUNT-1){ + PrintInfo(derr_con); + derr_con<<"Receive(): Invalid channel "<::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + if(peer->has_sent_with_id) + continue; + if(peer->address == sender) + break; + } + + /* + If no peer was found with the same address and port, + we shall assume it is a new peer and create an entry. + */ + if(j.atEnd()) + { + // Pass on to adding the peer + } + // Else: A peer was found. + else + { + Peer *peer = j.getNode()->getValue(); + peer_id = peer->id; + PrintInfo(derr_con); + derr_con<<"WARNING: Assuming unknown peer to be " + <<"peer_id="<getValue(); + + // Validate peer address + if(peer->address != sender) + { + PrintInfo(derr_con); + derr_con<<"Peer "<timeout_counter = 0.0; + + Channel *channel = &(peer->channels[channelnum]); + + // Throw the received packet to channel->processPacket() + + // Make a new SharedBuffer from the data without the base headers + SharedBuffer strippeddata(received_size - BASE_HEADER_SIZE); + memcpy(*strippeddata, &packetdata[BASE_HEADER_SIZE], + strippeddata.getSize()); + + try{ + // Process it (the result is some data with no headers made by us) + SharedBuffer resultdata = channel->ProcessPacket + (strippeddata, this, peer_id, channelnum); + + PrintInfo(); + dout_con<<"ProcessPacket returned data of size " + < data, bool reliable) +{ + core::map::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + Send(peer->id, channelnum, data, reliable); + } +} + +void Connection::Send(u16 peer_id, u8 channelnum, + SharedBuffer data, bool reliable) +{ + assert(channelnum < CHANNEL_COUNT); + + Peer *peer = GetPeer(peer_id); + Channel *channel = &(peer->channels[channelnum]); + + u32 chunksize_max = m_max_packet_size - BASE_HEADER_SIZE; + if(reliable) + chunksize_max -= RELIABLE_HEADER_SIZE; + + core::list > originals; + originals = makeAutoSplitPacket(data, chunksize_max, + channel->next_outgoing_split_seqnum); + + core::list >::Iterator i; + i = originals.begin(); + for(; i != originals.end(); i++) + { + SharedBuffer original = *i; + + SendAsPacket(peer_id, channelnum, original, reliable); + } +} + +void Connection::SendAsPacket(u16 peer_id, u8 channelnum, + SharedBuffer data, bool reliable) +{ + Peer *peer = GetPeer(peer_id); + Channel *channel = &(peer->channels[channelnum]); + + if(reliable) + { + u16 seqnum = channel->next_outgoing_seqnum; + channel->next_outgoing_seqnum++; + + SharedBuffer reliable = makeReliablePacket(data, seqnum); + + // Add base headers and make a packet + BufferedPacket p = makePacket(peer->address, reliable, + m_protocol_id, m_peer_id, channelnum); + + try{ + // Buffer the packet + channel->outgoing_reliables.insert(p); + } + catch(AlreadyExistsException &e) + { + PrintInfo(derr_con); + derr_con<<"WARNING: Going to send a reliable packet " + "seqnum="<address, data, + m_protocol_id, m_peer_id, channelnum); + + // Send the packet + RawSend(p); + } +} + +void Connection::RawSend(const BufferedPacket &packet) +{ + m_socket.Send(packet.address, *packet.data, packet.data.getSize()); +} + +void Connection::RunTimeouts(float dtime) +{ + core::list timeouted_peers; + core::map::Iterator j; + j = m_peers.getIterator(); + for(; j.atEnd() == false; j++) + { + Peer *peer = j.getNode()->getValue(); + + /* + Check peer timeout + */ + peer->timeout_counter += dtime; + if(peer->timeout_counter > m_timeout) + { + PrintInfo(derr_con); + derr_con<<"RunTimeouts(): Peer "<id + <<" has timed out." + <<" (source=peer->timeout_counter)" + <id); + // Don't bother going through the buffers of this one + continue; + } + + float resend_timeout = peer->resend_timeout; + for(u16 i=0; i timed_outs; + core::list::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 "<id + <<" has timed out." + <<" (source=reliable packet totaltime)" + <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="<deletingPeer(m_peers[*i], true); + delete m_peers[*i]; + m_peers.remove(*i); + } +} + +Peer* Connection::GetPeer(u16 peer_id) +{ + core::map::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::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 Connection::GetPeers() +{ + core::list list; + core::map::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< +#include +#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 m_data; +public: + GotSplitPacketException(SharedBuffer data): + m_data(data) + {} + SharedBuffer getData() + { + return m_data; + } +}; + +inline u16 readPeerId(u8 *packetdata) +{ + return readU16(&packetdata[4]); +} +inline u8 readChannel(u8 *packetdata) +{ + return readU8(&packetdata[6]); +} + +#define SEQNUM_MAX 65535 +inline bool seqnum_higher(u16 higher, u16 lower) +{ + if(lower > higher && lower - higher > SEQNUM_MAX/2){ + return true; + } + return (higher > lower); +} + +struct BufferedPacket +{ + BufferedPacket(u8 *a_data, u32 a_size): + data(a_data, a_size), time(0.0), totaltime(0.0) + {} + BufferedPacket(u32 a_size): + data(a_size), time(0.0), totaltime(0.0) + {} + SharedBuffer data; // Data of the packet, including headers + float time; // Seconds from buffering the packet or re-sending + float totaltime; // Seconds from buffering the packet + Address address; // Sender or destination +}; + +// This adds the base headers to the data and makes a packet out of it +BufferedPacket makePacket(Address &address, u8 *data, u32 datasize, + u32 protocol_id, u16 sender_peer_id, u8 channel); +BufferedPacket makePacket(Address &address, SharedBuffer &data, + u32 protocol_id, u16 sender_peer_id, u8 channel); + +// Add the TYPE_ORIGINAL header to the data +SharedBuffer makeOriginalPacket( + SharedBuffer data); + +// Split data in chunks and add TYPE_SPLIT headers to them +core::list > makeSplitPacket( + SharedBuffer data, + u32 chunksize_max, + u16 seqnum); + +// Depending on size, make a TYPE_ORIGINAL or TYPE_SPLIT packet +// Increments split_seqnum if a split packet is made +core::list > makeAutoSplitPacket( + SharedBuffer data, + u32 chunksize_max, + u16 &split_seqnum); + +// Add the TYPE_RELIABLE header to the data +SharedBuffer makeReliablePacket( + SharedBuffer data, + u16 seqnum); + +struct IncomingSplitPacket +{ + IncomingSplitPacket() + { + time = 0.0; + reliable = false; + } + // Key is chunk number, value is data without headers + core::map > chunks; + u32 chunk_count; + float time; // Seconds from adding + bool reliable; // If true, isn't deleted on timeout + + bool allReceived() + { + return (chunks.size() == chunk_count); + } +}; + +/* +=== NOTES === + +A packet is sent through a channel to a peer with a basic header: +TODO: Should we have a receiver_peer_id also? + Header (7 bytes): + [0] u32 protocol_id + [4] u16 sender_peer_id + [6] u8 channel +sender_peer_id: + Unique to each peer. + value 0 is reserved for making new connections + value 1 is reserved for server +channel: + The lower the number, the higher the priority is. + Only channels 0, 1 and 2 exist. +*/ +#define BASE_HEADER_SIZE 7 +#define PEER_ID_NEW 0 +#define PEER_ID_SERVER 1 +#define CHANNEL_COUNT 3 +/* +Packet types: + +CONTROL: This is a packet used by the protocol. +- When this is processed, nothing is handed to the user. + Header (2 byte): + [0] u8 type + [1] u8 controltype +controltype and data description: + CONTROLTYPE_ACK + [2] u16 seqnum + CONTROLTYPE_SET_PEER_ID + [2] u16 peer_id_new + CONTROLTYPE_PING + - This can be sent in a reliable packet to get a reply +*/ +#define TYPE_CONTROL 0 +#define CONTROLTYPE_ACK 0 +#define CONTROLTYPE_SET_PEER_ID 1 +#define CONTROLTYPE_PING 2 +/* +ORIGINAL: This is a plain packet with no control and no error +checking at all. +- When this is processed, it is directly handed to the user. + Header (1 byte): + [0] u8 type +*/ +#define TYPE_ORIGINAL 1 +#define ORIGINAL_HEADER_SIZE 1 +/* +SPLIT: These are sequences of packets forming one bigger piece of +data. +- When processed and all the packet_nums 0...packet_count-1 are + present (this should be buffered), the resulting data shall be + directly handed to the user. +- If the data fails to come up in a reasonable time, the buffer shall + be silently discarded. +- These can be sent as-is or atop of a RELIABLE packet stream. + Header (7 bytes): + [0] u8 type + [1] u16 seqnum + [3] u16 chunk_count + [5] u16 chunk_num +*/ +#define TYPE_SPLIT 2 +/* +RELIABLE: Delivery of all RELIABLE packets shall be forced by ACKs, +and they shall be delivered in the same order as sent. This is done +with a buffer in the receiving and transmitting end. +- When this is processed, the contents of each packet is recursively + processed as packets. + Header (3 bytes): + [0] u8 type + [1] u16 seqnum + +*/ +#define TYPE_RELIABLE 3 +#define RELIABLE_HEADER_SIZE 3 +//#define SEQNUM_INITIAL 0x10 +#define SEQNUM_INITIAL 65500 + +/* + A buffer which stores reliable packets and sorts them internally + for fast access to the smallest one. +*/ + +typedef core::list::Iterator RPBSearchResult; + +class ReliablePacketBuffer +{ +public: + + void print(); + bool empty(); + u32 size(); + RPBSearchResult findPacket(u16 seqnum); + RPBSearchResult notFound(); + u16 getFirstSeqnum(); + BufferedPacket popFirst(); + BufferedPacket popSeqnum(u16 seqnum); + void insert(BufferedPacket &p); + void incrementTimeouts(float dtime); + void resetTimedOuts(float timeout); + bool anyTotaltimeReached(float timeout); + core::list getTimedOuts(float timeout); + +private: + core::list m_list; +}; + +/* + A buffer for reconstructing split packets +*/ + +class IncomingSplitBuffer +{ +public: + ~IncomingSplitBuffer(); + /* + This will throw a GotSplitPacketException when a full + split packet is constructed. + */ + void insert(BufferedPacket &p, bool reliable); + + void removeUnreliableTimedOuts(float dtime, float timeout); + +private: + // Key is seqnum + core::map m_buf; +}; + +class Connection; + +struct Channel +{ + Channel(); + ~Channel(); + /* + Processes a packet with the basic header stripped out. + Parameters: + packetdata: Data in packet (with no base headers) + con: The connection to which the channel is associated + (used for sending back stuff (ACKs)) + peer_id: peer id of the sender of the packet in question + channelnum: channel on which the packet was sent + reliable: true if recursing into a reliable packet + */ + SharedBuffer ProcessPacket( + SharedBuffer packetdata, + Connection *con, + u16 peer_id, + u8 channelnum, + bool reliable=false); + + // Returns next data from a buffer if possible + // throws a NoIncomingDataException if no data is available + // If found, sets peer_id + SharedBuffer CheckIncomingBuffers(Connection *con, + u16 &peer_id); + + u16 next_outgoing_seqnum; + u16 next_incoming_seqnum; + u16 next_outgoing_split_seqnum; + + // This is for buffering the incoming packets that are coming in + // the wrong order + ReliablePacketBuffer incoming_reliables; + // This is for buffering the sent packets so that the sender can + // re-send them if no ACK is received + ReliablePacketBuffer outgoing_reliables; + + IncomingSplitBuffer incoming_splits; +}; + +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 GetFromBuffers(u16 &peer_id); + + // The peer_id of sender is stored in peer_id + // Return value: I guess this always throws an exception or + // actually gets data + u32 Receive(u16 &peer_id, u8 *data, u32 datasize); + + // These will automatically package the data as an original or split + void SendToAll(u8 channelnum, SharedBuffer data, bool reliable); + void Send(u16 peer_id, u8 channelnum, SharedBuffer data, bool reliable); + // Send data as a packet; it will be wrapped in base header and + // optionally to a reliable packet. + void SendAsPacket(u16 peer_id, u8 channelnum, + SharedBuffer data, bool reliable); + // Sends a raw packet + void RawSend(const BufferedPacket &packet); + + void RunTimeouts(float dtime); + // Can throw a PeerNotFoundException + Peer* GetPeer(u16 peer_id); + // returns NULL if failed + Peer* GetPeerNoEx(u16 peer_id); + core::list 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 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 00000000..e91ded0e --- /dev/null +++ b/src/constants.h @@ -0,0 +1,72 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#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 00000000..24894138 --- /dev/null +++ b/src/debug.cpp @@ -0,0 +1,189 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#include "debug.h" +#include +#include + +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #define sleep_ms(x) Sleep(x) +#else + #include + #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 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::Iterator + i = g_debug_stacks.getIterator(); + i.atEnd() == false; i++) + { + DebugStack *stack = i.getNode()->getValue(); + + for(int i=0; iprint(g_debugstreams[i], true); + } + } +} + +DebugStacker::DebugStacker(const char *text) +{ + threadid_t threadid = get_current_thread_id(); + + JMutexAutoLock lock(g_debug_stacks_mutex); + + core::map::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 00000000..014456c0 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,176 @@ +/* +(c) 2010 Perttu Ahola +*/ + +/* + Debug stack and assertion +*/ + +#ifndef DEBUG_HEADER +#define DEBUG_HEADER + +#include +#include +#include +#include +#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 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 00000000..412bb33e --- /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::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::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::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 "<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::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::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::Iterator i = m_players.begin(); + i != m_players.end(); i++) + { + Player *player = *i; + if(player->peer_id == peer_id) + return player; + } + return NULL; +} + +core::list Environment::getPlayers() +{ + return m_players; +} + +void Environment::printPlayers(std::ostream &o) +{ + o<<"Players in environment:"<::Iterator i = m_players.begin(); + i != m_players.end(); i++) + { + Player *player = *i; + o<<"Player peer_id="<peer_id< +#include "common_irrlicht.h" +#include "player.h" +#include "map.h" +#include + +class Environment +{ +public: + // Environment will delete the map passed to the constructor + Environment(Map *map, std::ostream &dout); + ~Environment(); + /* + This can do anything to the environment, such as removing + timed-out players. + 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 getPlayers(); + void printPlayers(std::ostream &o); +private: + Map *m_map; + core::list m_players; + // Debug output goes here + std::ostream &m_dout; +}; + +#endif + diff --git a/src/exceptions.h b/src/exceptions.h new file mode 100644 index 00000000..cbe13f1f --- /dev/null +++ b/src/exceptions.h @@ -0,0 +1,116 @@ +#ifndef EXCEPTIONS_HEADER +#define EXCEPTIONS_HEADER + +#include + +class BaseException : public std::exception +{ +public: + BaseException(const char *s) + { + m_s = s; + } + virtual const char * what() const throw() + { + return m_s; + } + const char *m_s; +}; + +class AsyncQueuedException : public BaseException +{ +public: + AsyncQueuedException(const char *s): + BaseException(s) + {} +}; + +class NotImplementedException : public BaseException +{ +public: + NotImplementedException(const char *s): + BaseException(s) + {} +}; + +class AlreadyExistsException : public BaseException +{ +public: + AlreadyExistsException(const char *s): + BaseException(s) + {} +}; + +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 00000000..00124704 --- /dev/null +++ b/src/filesys.cpp @@ -0,0 +1,205 @@ +#include "filesys.h" +#include + +namespace fs +{ + +#ifdef _WIN32 + +#define _WIN32_WINNT 0x0501 +#include +#include +#include +#include +#include +#include + +#define BUFSIZE MAX_PATH + +std::vector GetDirListing(std::string pathstring) +{ + std::vector 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 +#include +#include +#include + +std::vector GetDirListing(std::string pathstring) +{ + std::vector listing; + + DIR *dp; + struct dirent *dirp; + if((dp = opendir(pathstring.c_str())) == NULL) { + //std::cout<<"Error("<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 GetDirListing(std::string pathstring) +{ + std::vector 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"< +#include +#include "exceptions.h" + +namespace fs +{ + +struct DirListNode +{ + std::string name; + bool dir; +}; + +std::vector 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 00000000..a4027ee7 --- /dev/null +++ b/src/heightmap.cpp @@ -0,0 +1,872 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#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 &next_squares) +{ + /*dstream<<"makeDiamond(): center=" + <<"("<getGroundHeight(npos, false); + //dstream<<"h="< GROUNDHEIGHT_VALID_MINVALUE) + continue; + setGroundHeight(dirs[i] * a, corners[i]); + } + + /*dstream<<"corners filled:"< 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::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::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::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((" + <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()"<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="< 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::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 hmdata(heightmap_size); + + core::map::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 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="<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 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 00000000..a5da092a --- /dev/null +++ b/src/heightmap.h @@ -0,0 +1,556 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef HEIGHTMAP_HEADER +#define HEIGHTMAP_HEADER + +#include +#include +#include + +#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<= 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((" + <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 &next_squares); + + void makeSquare( + v2s16 center, + s16 a, + f32 randmax, + core::map &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()"< 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 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::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 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 00000000..4fe21b30 --- /dev/null +++ b/src/inventory.cpp @@ -0,0 +1,320 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#include "inventory.h" +#include "serialization.h" +#include "utility.h" +#include "debug.h" +#include +#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=\""<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; iserialize(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; iclone(); + } + } + + return *this; +} + +u32 Inventory::getSize() +{ + return m_items.size(); +} + +u32 Inventory::getUsedSlots() +{ + u32 num = 0; + for(u32 i=0; 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; igetName()) + 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; iserialize(o); + o<<"\n"; + } + } +} + +//END diff --git a/src/inventory.h b/src/inventory.h new file mode 100644 index 00000000..8ea8bd65 --- /dev/null +++ b/src/inventory.h @@ -0,0 +1,195 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef INVENTORY_HEADER +#define INVENTORY_HEADER + +#include +#include +#include +#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< 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< m_items; + u32 m_size; +}; + +#endif + diff --git a/src/light.cpp b/src/light.cpp new file mode 100644 index 00000000..95bb37a8 --- /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 00000000..b76ac3a5 --- /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 00000000..a5fb6b31 --- /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 00000000..971e0eac --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,2339 @@ +/* +(c) 2010 Perttu Ahola + +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/ + +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 + #define sleep_ms(x) Sleep(x) +#else + #include + #define sleep_ms(x) usleep(x*1000) +#endif + +#include +#include +#include +#include +#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 + +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: \""< 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: \"" + < 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<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 (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 m_texts; + core::array 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<= 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< yes"< no"< "< list = server.getPlayerInfo(); + core::list::Iterator i; + static u32 sum_old = 0; + u32 sum = PIChecksum(list); + if(sum != sum_old) + { + std::cout<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< hosting"< "< \""< res_count || r0 == 0) + r0 = 2; + + { + u16 i = r0-1; + std::cout<<"-> "; + std::cout<<(i+1)<<": "<(screenW, screenH), + 16, fullscreen, false, false, &receiver); + // With vsync + /*device = createDevice(driverType, + core::dimension2d(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 center(screenW/2, screenH/2); + core::dimension2d textd = font->getDimension(text); + std::cout<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< 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<step(0.1); + } + sleep_ms(100); + } + } + catch(con::PeerNotFoundException &e) + { + std::cout<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(5, 5, 5+600, 5+textsize.Y), + false, false); + // Second line of debug text + gui::IGUIStaticText *guitext2 = guienv->addStaticText( + L"", + core::rect(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(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: " + <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 > 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="<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"<getMousePos().X - 320; + s32 dy = g_input->getMousePos().Y - 240; + //std::cout<<"window active, pos difference "< 89.5) camera_pitch = 89.5; + } + g_input->setMousePos(320, 240); + } + else{ + device->getCursorControl()->setVisible(true); + + //std::cout<<"window inactive"<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 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"< 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<getBlock()->getPos(), + selected_object->getId(), g_selected_item); + } + else if(g_input->getRightClicked()) + { + std::cout<getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN) + { + dstream<<"Sign object right-clicked"<addStaticText(L"", + core::rect(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 nodefacebox; + f32 mindistance = BS * 1001; + + v3s16 pos_i = floatToInt(player_position); + + /*std::cout<<"pos_i=("<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< 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< facebox(corners[0],corners[1]); + core::aabbox3d facebox(corners[0]); + facebox.addInternalPoint(corners[1]); + + if(facebox.intersectsWithLine(shootline)){ + nodefound = true; + nodepos = np; + neighbourpos = np + directions[i]; + mindistance = distance; + nodefacebox = facebox; + } + } + } + }}} + + if(nodefound) + { + //std::cout<setText(positiontext);*/ + } + + hilightboxes.push_back(nodefacebox); + + if(g_input->getLeftClicked()) + { + //std::cout<getRightClicked()) + { + //std::cout<setText(L""); + } + + } // selected_object == NULL + + g_input->resetLeftClicked(); + g_input->resetRightClicked(); + + /* + Calculate stuff for drawing + */ + + v2u32 screensize = driver->getScreenSize(); + core::vector2d 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"<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: "<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<drawAll()"<drawAll(); + scenetime = timer.stop(true); + } + + { + //TimeTaker timer9("auxiliary drawings", device); + // 0ms + + driver->draw2DLine(displaycenter - core::vector2d(10,0), + displaycenter + core::vector2d(10,0), + video::SColor(255,255,255,255)); + driver->draw2DLine(displaycenter - core::vector2d(0,10), + displaycenter + core::vector2d(0,10), + video::SColor(255,255,255,255)); + + //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 >::Iterator i=hilightboxes.begin(); + i != hilightboxes.end(); i++) + { + /*std::cout<<"hilightbox min=" + <<"("<MinEdge.X<<","<MinEdge.Y<<","<MinEdge.Z<<")" + <<" max=" + <<"("<MaxEdge.X<<","<MaxEdge.Y<<","<MaxEdge.Z<<")" + <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< +*/ + +#ifndef MAIN_HEADER +#define MAIN_HEADER + +#include +extern std::string getTimestamp(); +#define DTIME (getTimestamp()+": ") + +#include + +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 + +// 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 00000000..c69c3f24 --- /dev/null +++ b/src/map.cpp @@ -0,0 +1,2854 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#include "map.h" +//#include "player.h" +#include "main.h" +#include "jmutexautolock.h" +#include "client.h" +#include "filesys.h" +#include "utility.h" + +#ifdef _WIN32 + #include + #define sleep_ms(x) Sleep(x) +#else + #include + #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::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::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::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" + <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<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 light_sources; + core::map 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 & light_sources, + core::map & modified_blocks) +{ + v3s16 dirs[6] = { + v3s16(0,0,1), // back + v3s16(0,1,0), // top + v3s16(1,0,0), // right + v3s16(0,0,-1), // front + v3s16(0,-1,0), // bottom + v3s16(-1,0,0), // left + }; + + /* + 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 & from_nodes, + core::map & light_sources, + core::map & modified_blocks) +{ + v3s16 dirs[6] = { + v3s16(0,0,1), // back + v3s16(0,1,0), // top + v3s16(1,0,0), // right + v3s16(0,0,-1), // front + v3s16(0,-1,0), // bottom + v3s16(-1,0,0), // left + }; + + if(from_nodes.size() == 0) + return; + + u32 blockchangecount = 0; + + core::map unlighted_nodes; + core::map::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"< 0) + unspreadLight(unlighted_nodes, light_sources, modified_blocks); +} + +/* + A single-node wrapper of the above +*/ +void Map::unLightNeighbors(v3s16 pos, u8 lightwas, + core::map & light_sources, + core::map & modified_blocks) +{ + core::map 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 & from_nodes, + core::map & 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 lighted_nodes; + core::map::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=("<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 " + < 0) + spreadLight(lighted_nodes, modified_blocks); +} + +/* + A single-node source variation of the above. +*/ +void Map::lightNeighbors(v3s16 pos, + core::map & modified_blocks) +{ + core::map 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 & 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 & a_blocks, + core::map & modified_blocks) +{ + /*m_dout<::Iterator i = a_blocks.begin(); + for(; i != a_blocks.end(); i++) + { + MapBlock *block = *i;*/ + + core::map light_sources; + + core::map unlight_from; + + core::map::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; zgetNode(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" + <propagateSunlight(light_sources); + + // If bottom is valid, we're done. + if(bottom_valid) + break; + + /*dstream<<"Bottom for sunlight-propagated block (" + < &modified_blocks)*/ +void Map::addNodeAndUpdate(v3s16 p, MapNode n, + core::map &modified_blocks) +{ + /*PrintInfo(m_dout); + m_dout< light_sources; + core::map 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< &modified_blocks) +{ + /*PrintInfo(m_dout); + m_dout< light_sources; + core::map 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< ybottom="<= ybottom; y--) + { + v3s16 p2(p.X, y, p.Z); + /*m_dout<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::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 &list, bool only_blocks) +{ + core::list::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 *deleted_blocks) +{ + JMutexAutoLock lock(m_sector_mutex); + + core::list sector_deletion_queue; + core::map::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 blocks; + sector->getBlocks(blocks); + for(core::list::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< 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 ("<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=("<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 *objects = new core::map; + 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="<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="< 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; igetGroundHeight(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; igetGroundHeight(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 &changed_blocks, + core::map &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(): " + <<"("<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; z0setYaw(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 changed_blocks_sector; + + /* + Check if any sector's objects can be placed now. + If so, place them. + */ + core::map *objects = sector->getObjects(); + core::list objects_to_remove; + for(core::map::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" + <::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 blocks; + sector->getBlocks(blocks); + core::list::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< list = fs::GetDirListing(m_savedir+"/sectors/"); + + dstream<::iterator i; + for(i=list.begin(); i!=list.end(); i++) + { + if(counter > printed_counter + 10) + { + dstream<dir == false) + continue; + try{ + sector = loadSectorMeta(i->name); + } + catch(InvalidFilenameException &e) + { + // This catches unknown crap in directory + } + + if(ENABLE_BLOCK_LOADING) + { + std::vector list2 = fs::GetDirListing + (m_savedir+"/sectors/"+i->name); + std::vector::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< hmdata = m_heightmap->serialize(version); + /* + [0] u8 serialization version + [1] X master heightmap + */ + u32 fullsize = 1 + hmdata.getSize(); + SharedBuffer 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 list2 = fs::GetDirListing + (m_savedir+"/sectors/"+sectorsubdir); + std::vector::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 " + <<"("<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 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 ("< blocks_changed; + blocks_changed.insert(block->getPos(), block); + core::map modified_blocks; + updateLighting(blocks_changed, modified_blocks); + + // Close input file + is.close(); + + // Save modified blocks + core::map::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(0,0,0, + map->getW()*BS, map->getH()*BS, map->getD()*BS);*/ + /*m_box = core::aabbox3d(0,0,0, + map->getSizeNodes().X * BS, + map->getSizeNodes().Y * BS, + map->getSizeNodes().Z * BS);*/ + m_box = core::aabbox3d(-BS*1000000,-BS*1000000,-BS*1000000, + BS*1000000,BS*1000000,BS*1000000); + + 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::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<getMeshBufferCount(); + + for(u32 i=0; igetMeshBuffer(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::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." + <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; imesh->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="<::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" + <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 + 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="<getReferenceCount() + <getMeshBuffer + (g_materials[MATERIAL_GRASS]); + if(buf != NULL) + dstream<<"grass buf refcount="<getReferenceCount() + <drop(); + } + else + { + dstream<<"WARNING: There was no old master heightmap mesh"< +*/ + +#ifndef MAP_HEADER +#define MAP_HEADER + +#include +#include +#include +#include + +#ifdef _WIN32 + #include + #define sleep_s(x) Sleep((x*1000)) +#else + #include + #define sleep_s(x) sleep(x) +#endif + +#include "common_irrlicht.h" +#include "heightmap.h" +#include "loadstatus.h" +#include "mapnode.h" +#include "mapblock.h" +#include "mapsector.h" +#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 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 getNodeBox(v3s16 p) + { + return core::aabbox3d( + (float)p.X * BS - 0.5*BS, + (float)p.Y * BS - 0.5*BS, + (float)p.Z * BS - 0.5*BS, + (float)p.X * BS + 0.5*BS, + (float)p.Y * BS + 0.5*BS, + (float)p.Z * BS + 0.5*BS + ); + } + + //bool sectorExists(v2s16 p); + MapSector * getSectorNoGenerate(v2s16 p2d); + /* + 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 & from_nodes, + core::map & light_sources, + core::map & modified_blocks); + + void unLightNeighbors(v3s16 pos, u8 lightwas, + core::map & light_sources, + core::map & modified_blocks); + + void spreadLight(core::map & from_nodes, + core::map & modified_blocks); + + void lightNeighbors(v3s16 pos, + core::map & modified_blocks); + + v3s16 getBrightestNeighbour(v3s16 p); + + s16 propagateSunlight(v3s16 start, + core::map & modified_blocks); + + void updateLighting(core::map & a_blocks, + core::map & modified_blocks); + + /* + These handle lighting but not faces. + */ + void addNodeAndUpdate(v3s16 p, MapNode n, + core::map &modified_blocks); + void removeNodeAndUpdate(v3s16 p, + core::map &modified_blocks); + + /* + Updates the faces of the given block and blocks on the + leading edge. + */ + void updateMeshes(v3s16 blockpos); + + //core::aabbox3d 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 &list, bool only_blocks); + + // Returns count of deleted sectors + u32 deleteUnusedSectors(float timeout, bool only_blocks=false, + core::list *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 &changed_blocks, + core::map &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& 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 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 00000000..25561008 --- /dev/null +++ b/src/mapblock.cpp @@ -0,0 +1,698 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#include "mapblock.h" +#include "map.h" +// For g_materials +#include "main.h" +#include "light.h" +#include + + +/* + 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 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(0,1)); + f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c, + core::vector2d(abs_scale,1)); + f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c, + core::vector2d(abs_scale,0)); + f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c, + core::vector2d(0,0));*/ + f->vertices[0] = video::S3DVertex(vertex_pos[0], zerovector, c, + core::vector2d(0,1)); + f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c, + core::vector2d(abs_scale,1)); + f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c, + core::vector2d(abs_scale,0)); + f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c, + core::vector2d(0,0)); + + f->material = material; + + 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 &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 *fastfaces_new = new core::list; + + /* + 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; ygetSize() > 0) + { + mesh_new = new scene::SMesh(); + scene::IMeshBuffer *buf = NULL; + + core::list::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 + 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 "<getSize()<<" faces " + <<"and uses "<getMeshBufferCount() + <<" materials"<::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; igetMeshBuffer(i); + }*/ + // Drop the mesh + mesh_old->drop(); + //delete mesh_old; + } + + mesh_mutex.Unlock(); + + //std::cout<<"added "< & 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 dest(buflen); + + dest[0] = is_underground; + for(u32 i=0; i materialdata(nodecount); + for(u32 i=0; i paramdata(nodecount); + for(u32 i=0; i 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 +*/ + +#ifndef MAPBLOCK_HEADER +#define MAPBLOCK_HEADER + +#include +#include +#include +#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 getBox() + { + return core::aabbox3d(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= 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 &dest); + + void updateMesh(); + + bool propagateSunlight(core::map & 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 &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 00000000..985a01dc --- /dev/null +++ b/src/mapblockobject.cpp @@ -0,0 +1,641 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#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=("< 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 objectbox( + m_collision_box->MinEdge + position, + m_collision_box->MaxEdge + position + ); + + core::aabbox3d 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 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::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 ids_to_delete; + for(core::map::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::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="<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::Iterator + i = ids_to_delete.getIterator(); + i.atEnd() == false; i++) + { + s16 id = i.getNode()->getKey(); + + /*dstream<<"MapBlockObjectList deleting object" + " id="<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"<m_id == -1) + { + object->m_id = getFreeId(); + } + + if(m_objects.find(object->m_id) != NULL) + { + dstream<<"MapBlockObjectList::add(): " + "object with same id already exists"<m_block = m_block; + + /*v3f p = object->m_pos; + dstream<<"MapBlockObjectList::add(): " + <<"m_block->getPos()=(" + <getPos().X<<"," + <getPos().Y<<"," + <getPos().Z<<")" + <<" inserting object with id="<m_id + <<" pos=" + <<"("<m_id, object); +} + +void MapBlockObjectList::clear() +{ + JMutexAutoLock lock(m_mutex); + + for(core::map::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::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::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 ids_to_delete; + + for(core::map::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::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::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"<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" + <<"("<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"< &dest) +{ + for(core::map::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 00000000..1939cc89 --- /dev/null +++ b/src/mapblockobject.h @@ -0,0 +1,892 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef MAPBLOCKOBJECT_HEADER +#define MAPBLOCKOBJECT_HEADER + +#include "common_irrlicht.h" +#include +#include +#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 shootline) + { + if(m_selection_box == NULL) + return false; + + core::aabbox3d offsetted_box( + m_selection_box->MinEdge + m_pos, + m_selection_box->MaxEdge + m_pos + ); + + return offsetted_box.intersectsWithLine(shootline); + } + + core::aabbox3d getSelectionBoxOnMap() + { + v3f absolute_pos = getAbsolutePos(); + + core::aabbox3d 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 * m_collision_box; + + // Shall be left NULL if can't be selected + core::aabbox3d * 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"<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"<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 + (-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"<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"<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 + (-BS*0.3,0,-BS*0.3, BS*0.3,BS*0.5,BS*0.3); + m_selection_box = new core::aabbox3d + (-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 + (-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; igetVideoDriver(); + + 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 &dest); + +private: + JMutex m_mutex; + // Key is id + core::map m_objects; + MapBlock *m_block; +}; + + +#endif + diff --git a/src/mapnode.h b/src/mapnode.h new file mode 100644 index 00000000..68e66916 --- /dev/null +++ b/src/mapnode.h @@ -0,0 +1,280 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef MAPNODE_HEADER +#define MAPNODE_HEADER + +#include +#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 00000000..8dd1c8c9 --- /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::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::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 &dest) +{ + JMutexAutoLock lock(m_mutex); + + core::list ref_list; + + core::map::Iterator bi; + + bi = m_blocks.getIterator(); + for(; bi.atEnd() == false; bi++) + { + MapBlock *b = bi.getNode()->getValue(); + 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; igetGroundHeight(p_in_hm); + + /*if(h < GROUNDHEIGHT_VALID_MINVALUE) + { + std::cout<<"Sector heightmap ("<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 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::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 data(hm_size); + + u16 hm_count = m_hm_split * m_hm_split; + + // Write heightmaps + for(u16 i=0; iserialize(*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::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 & 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 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 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 *objects = new core::map; + + if(version >= 5) + { + u8 b[2]; + is.read((char*)b, 2); + u16 object_count = readU16(b); + + for(u16 i=0; iinsert(p, d); + } + } + + /* + Get or create sector + */ + + ServerMapSector *sector = NULL; + + core::map::Node *n = sectors.find(p2d); + + if(n != NULL) + { + dstream<<"deSerializing existent sectors not supported " + "at the moment, because code hasn't been tested." + <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; im_heightmaps[i]; + sector->m_heightmaps[i] = hms[i]; + if(oldhm != NULL) + delete oldhm; + } + + // Set (or change) objects + core::map *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 00000000..196a129c --- /dev/null +++ b/src/mapsector.h @@ -0,0 +1,318 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef MAPSECTOR_HEADER +#define MAPSECTOR_HEADER + +#include +#include "common_irrlicht.h" +#include "mapblock.h" +#include "heightmap.h" +#include "exceptions.h" + +/* + This is an Y-wise stack of MapBlocks. +*/ + +#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 &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 *blocks) + { + core::map 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::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 &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 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; yprint(); + } + } + + void setObjects(core::map *objects) + { + m_objects = objects; + differs_from_disk = true; + } + + core::map * 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 & 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 *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 00000000..080de606 --- /dev/null +++ b/src/player.cpp @@ -0,0 +1,358 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#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(""); +} + +Player::~Player() +{ +} + +void Player::move(f32 dtime, Map &map) +{ + v3f position = getPosition(); + v3f oldpos = position; + v3s16 oldpos_i = floatToInt(oldpos); + + /*std::cout<<"oldpos_i=("< playerbox( + position.X - PLAYER_RADIUS, + position.Y - 0.0, + position.Z - PLAYER_RADIUS, + position.X + PLAYER_RADIUS, + position.Y + PLAYER_HEIGHT, + position.Z + PLAYER_RADIUS + ); + core::aabbox3d playerbox_old( + oldpos.X - PLAYER_RADIUS, + oldpos.Y - 0.0, + oldpos.Z - PLAYER_RADIUS, + oldpos.X + PLAYER_RADIUS, + oldpos.Y + PLAYER_HEIGHT, + oldpos.Z + PLAYER_RADIUS + ); + + //hilightboxes.push_back(playerbox); + + touching_ground = false; + + /*std::cout<<"Checking collisions for (" + < (" + < nodebox = Map::getNodeBox( + v3s16(x,y,z)); + + // See if the player is touching ground + if( + 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(-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 00000000..e692f55b --- /dev/null +++ b/src/player.h @@ -0,0 +1,210 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#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& 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 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 00000000..3213ec9c --- /dev/null +++ b/src/porting.h @@ -0,0 +1,15 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#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 00000000..72f67559 --- /dev/null +++ b/src/serialization.cpp @@ -0,0 +1,77 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#include "serialization.h" +#include "utility.h" + +void compress(SharedBuffer 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 +*/ + +#ifndef SERIALIZATION_HEADER +#define SERIALIZATION_HEADER + +#include "common_irrlicht.h" +#include "exceptions.h" +#include +#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 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 00000000..a5f55ab5 --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,2115 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#include "server.h" +#include "utility.h" +#include +#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()"<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<m_emerge_queue.pop(); + if(qptr == NULL) + break; + + SharedPtr q(qptr); + + v3s16 &p = q->pos; + + //derr_server<<"EmergeThread::Thread(): running"<::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=" + <<"("<isDummy()) + { + //dstream<<"EmergeThread: Got a dummy block"< 0) + { + dout_server<::Iterator i = changed_blocks.getIterator(); + i.atEnd() == false; i++) + { + MapBlock *block = i.getNode()->getValue(); + v3s16 p = block->getPos(); + dout_server<<"("<::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::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<= MAX_SIMULTANEOUS_BLOCK_SENDS) + { + //dstream<<"Not sending any blocks, Queue full."<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 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 "< list; + getFacePositions(list, d); + + core::list::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. " + < 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 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="<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 &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." + < players = server->m_env.getPlayers(); + + // Write player count + u16 playercount = players.size(); + writeU16(buf, playercount); + os.write((char*)buf, 2); + + core::list::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 blocks; + + for(s16 d = 0; d <= d_max; d++) + { + core::list list; + getFacePositions(list, d); + + core::list::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 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::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 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"< 15) + { + dstream<<"RemoteClient::SentBlock(): " + <<"m_blocks_sending.size()=" + < &blocks) +{ + JMutexAutoLock sendinglock(m_blocks_sending_mutex); + JMutexAutoLock sentlock(m_blocks_sent_mutex); + + m_nearest_unsent_d = 0; + + for(core::map::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 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 remove_queue; + for(core::map::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::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)< 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 "<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<id<serialization_version = deployed; + getClient(peer->id)->pending_serialization_version = deployed; + + if(deployed == SER_FMT_VER_INVALID) + { + derr_server<= 20+3) + { + data[20+3-1] = 0; + player->updateName((const char*)&data[3]); + } + + // Now answer with a TOCLIENT_INIT + + SharedBuffer 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<id<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<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; iSetBlockNotSent(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"<getObject(id); + + if(obj == NULL) + { + derr_server<<"PICK_OBJECT object not found"<inventory.getUsedSlots() == player->inventory.getSize()) + { + dout_server<<"Player inventory has no free space"<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 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" + <id)->m_time_from_building.set(0.0); + + // Create packet + u32 replysize = 8; + SharedBuffer 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 modified_blocks; + m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks); + } + catch(InvalidPositionException &e) + { + derr_server<<"Server: Ignoring ADDNODE: Node not found" + <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 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=\"" + <getInventoryString() + <<"\""<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=" + <<"("<createObject + (pos, player->getYaw(), player->getPitch()); + + if(obj == NULL) + derr_server<<"WARNING: oitem created NULL object" + <addObject(obj); + + //dout_server<<"Placed object"<inventory.deleteItem(item_i); + // Send inventory + SendInventory(peer_id); + } + } + + } // button == 1 + /* + Catch invalid buttons + */ + else + { + derr_server<<"WARNING: Server: Invalid button " + <getObject(id); + if(obj == NULL) + { + derr_server<<"Error while setting sign text: " + "object not found"<getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN) + { + derr_server<<"Error while setting sign text: " + "object is not a sign"<setText(text); + + obj->getBlock()->setChangedFlag(); + } + else + { + derr_server<<"WARNING: Server::ProcessData(): Ignoring " + "unknown command "<::Iterator i = ps.begin(); + core::list 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::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 data((u8*)os.str().c_str(), os.str().size()); + + /*dstream<<"Server::SendSectorMeta(): sending packet" + " with "< Server::getPlayerInfo() +{ + DSTACK(__FUNCTION_NAME); + JMutexAutoLock envlock(m_env_mutex); + JMutexAutoLock conlock(m_con_mutex); + + core::list list; + + core::list players = m_env.getPlayers(); + + core::list::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=" + <id<::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; iinventory.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=" + <id<<", timeout="<::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 stepped_blocks; + + for(core::map::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 players = m_env.getPlayers(); + + u32 player_count = players.getSize(); + u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count; + + SharedBuffer data(datasize); + writeU16(&data[0], TOCLIENT_PLAYERINFO); + + u32 start = 2; + core::list::Iterator i; + for(i = players.begin(); + i != players.end(); i++) + { + Player *player = *i; + + /*dstream<<"Server sending player info for player with " + "peer_id="<peer_id<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 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"<::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 "<peer_id<peer_id; + client->SendBlocks(this, dtime); + } + + //dstream<<"Server::SendBlocks(): END"<::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 00000000..5c856333 --- /dev/null +++ b/src/server.h @@ -0,0 +1,388 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef SERVER_HEADER +#define SERVER_HEADER + +#include "connection.h" +#include "environment.h" +#include "common_irrlicht.h" +#include + +#ifdef _WIN32 + #include + #define sleep_ms(x) Sleep(x) +#else + #include + #define sleep_ms(x) usleep(x*1000) +#endif + +struct QueuedBlockEmerge +{ + v3s16 pos; + // key = peer_id, value = flags + core::map peer_ids; +}; + +/* + This is a thread-safe class. +*/ +class BlockEmergeQueue +{ +public: + BlockEmergeQueue() + { + m_mutex.Init(); + } + + ~BlockEmergeQueue() + { + JMutexAutoLock lock(m_mutex); + + core::list::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::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::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 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 &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 &stepped_blocks + ); + + void GotBlock(v3s16 p); + + void SentBlock(v3s16 p); + + void SetBlockNotSent(v3s16 p); + void SetBlocksNotSent(core::map &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 "< +#include +#include +#include + +// 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)<<":" + < "; + destination.print(); + dstream<<", size="<20) + dstream<<"..."; + if(dumping_packet) + dstream<<" (DUMPED BY INTERNET_SIMULATOR)"; + dstream<20) + dstream<<"..."; + dstream< + #include + #include + #pragma comment(lib, "wsock32.lib") +typedef SOCKET socket_t; +typedef int socklen_t; +#else + #include + #include + #include + #include + #include +typedef int socket_t; +#endif + +#include +#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 00000000..dbbec11a --- /dev/null +++ b/src/strfnd.h @@ -0,0 +1,96 @@ +#ifndef STRFND_HEADER +#define STRFND_HEADER + +#include + +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.size()"<=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 00000000..8cdd8442 --- /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 + +#ifdef _WIN32 + #include + #define sleep_ms(x) Sleep(x) +#else + #include + #define sleep_ms(x) usleep(x*1000) +#endif + +/* + Asserts that the exception occurs +*/ +#define EXCEPTION_CHECK(EType, code)\ +{\ + bool exception_thrown = false;\ + try{ code; }\ + catch(EType &e) { exception_thrown = true; }\ + assert(exception_thrown);\ +} + +struct TestUtilities +{ + void Run() + { + /*dstream<<"wrapDegrees(100.0) = "< 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()="< "; + for(u32 i=0; i validity_exceptions; + + TC() + { + position_valid = true; + } + + virtual bool isValidPosition(v3s16 p) + { + //return position_valid ^ (p==position_valid_exception); + bool exception = false; + for(core::list::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 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 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 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:"<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)"<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 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)< p2 = con::makeReliablePacket(data1, seqnum); + + /*dstream<<"p2.getSize()="< data = SharedBufferFromString("Hello World!"); + + dstream<<"** running client.Send()"< data1 = SharedBufferFromString("hello1"); + SharedBuffer data2 = SharedBufferFromString("Hello2"); + + Address client_address = + server.GetPeer(peer_id_client)->address; + + dstream<<"*** Sending packets in wrong order (2,1,2)" + <channels[chn]; + u16 sn = ch->next_outgoing_seqnum; + ch->next_outgoing_seqnum = sn+1; + server.Send(peer_id_client, chn, data2, true); + ch->next_outgoing_seqnum = sn; + server.Send(peer_id_client, chn, data1, true); + ch->next_outgoing_seqnum = sn+1; + server.Send(peer_id_client, chn, data2, true); + + sleep_ms(50); + + dstream<<"*** Receiving the packets"< 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<20) + dstream<<"..."; + dstream< +*/ + +#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 00000000..4178e9d9 --- /dev/null +++ b/src/utility.h @@ -0,0 +1,607 @@ +/* +(c) 2010 Perttu Ahola +*/ + +#ifndef UTILITY_HEADER +#define UTILITY_HEADER + +#include "common_irrlicht.h" +#include "debug.h" +#include "strfnd.h" +#include +#include + +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 +class SharedPtr +{ +public: + SharedPtr(T *t=NULL) + { + refcount = new int; + *refcount = 1; + ptr = t; + } + SharedPtr(SharedPtr &t) + { + //*this = t; + drop(); + refcount = t.refcount; + (*refcount)++; + ptr = t.ptr; + } + ~SharedPtr() + { + drop(); + } + SharedPtr & operator=(T *t) + { + drop(); + refcount = new int; + *refcount = 1; + ptr = t; + return *this; + } + SharedPtr & operator=(SharedPtr &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 +class Buffer +{ +public: + Buffer(unsigned int size) + { + m_size = size; + data = new T[size]; + } + Buffer(const Buffer &buffer) + { + m_size = buffer.m_size; + data = new T[buffer.m_size]; + memcpy(data, buffer.data, buffer.m_size); + } + Buffer(T *t, unsigned int size) + { + m_size = size; + data = new T[size]; + memcpy(data, t, size); + } + ~Buffer() + { + delete[] data; + } + T & operator[](unsigned int i) const + { + return data[i]; + } + T * operator*() const + { + return data; + } + unsigned int getSize() const + { + return m_size; + } +private: + T *data; + unsigned int m_size; +}; + +template +class SharedBuffer +{ +public: + SharedBuffer(unsigned int size) + { + m_size = size; + data = new T[size]; + refcount = new unsigned int; + (*refcount) = 1; + } + SharedBuffer(const SharedBuffer &buffer) + { + //std::cout<<"SharedBuffer(const SharedBuffer &buffer)"< &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 SharedBufferFromString(const char *string) +{ + SharedBuffer b((u8*)string, strlen(string)+1); + return b; +} + +template +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< &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=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 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 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= '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 +