1
0

Merge branch 'master' into portals

Conflicts:
	src/ClientHandle.cpp
	src/Entities/Player.cpp
	src/Entities/Player.h
	src/Protocol/Protocol125.cpp
	src/Protocol/Protocol17x.cpp
This commit is contained in:
Tiger Wang 2014-06-10 20:51:22 +01:00
commit bfa8aaf41b
72 changed files with 2885 additions and 310 deletions

View File

@ -1969,7 +1969,7 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage);
BroadcastChatSuccess = { Params = "Message", Return = "", Notes = "Prepends Green [INFO] / colours entire text (depending on ShouldUseChatPrefixes()) and broadcasts message. For success messages." },
BroadcastChatWarning = { Params = "Message", Return = "", Notes = "Prepends Rose [WARN] / colours entire text (depending on ShouldUseChatPrefixes()) and broadcasts message. For concerning events, such as plugin reload etc." },
CreateAndInitializeWorld = { Params = "WorldName", Return = "{{cWorld|cWorld}}", Notes = "Creates a new world and initializes it. If there is a world whith the same name it returns nil." },
FindAndDoWithPlayer = { Params = "PlayerName, CallbackFunction", Return = "", Notes = "Calls the given callback function for the given player." },
FindAndDoWithPlayer = { Params = "PlayerName, CallbackFunction", Return = "", Notes = "Calls the given callback function for all players with names partially (or fully) matching the name string provided." },
ForEachPlayer = { Params = "CallbackFunction", Return = "", Notes = "Calls the given callback function for each player. The callback function has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|cPlayer}})</pre>" },
ForEachWorld = { Params = "CallbackFunction", Return = "", Notes = "Calls the given callback function for each world. The callback function has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cWorld|cWorld}})</pre>" },
GetCraftingRecipes = { Params = "", Return = "{{cCraftingRecipe|cCraftingRecipe}}", Notes = "Returns the CraftingRecipes object" },

View File

@ -61,18 +61,30 @@
#--------------------------
# Fuels
! 263:1 = 1600 # 1 Coal -> 80 sec
! 263:1:1 = 1600 # 1 Charcoal -> 80 sec
! 42:126:1 = 150 # 1 Halfslab -> 7.5 seconds
! 5:1 = 300 # 1 Planks -> 15 sec
! 280:1 = 100 # 1 Stick -> 5 sec
! 85:1 = 300 # 1 Fence -> 15 sec
! 53:1 = 300 # 1 Wooden Stairs -> 15 sec
! 58:1 = 300 # 1 Crafting Table -> 15 sec
! 47:1 = 300 # 1 Bookshelf -> 15 sec
! 54:1 = 300 # 1 Chest -> 15 sec
! 84:1 = 300 # 1 Jukebox -> 15 sec
! 327:1 = 200000 # 1 Lava Bucket -> 1000 sec
! 17:1 = 300 # 1 Wood -> 15 sec
! 6:1 = 100 # 1 Sapling -> 5 sec
! 173:1 = 7400 # 1 Coal Block -> 370 sec, based on https://github.com/minetest/common/commit/e0f5a6fd6936052756e27a05a2bfdd6aa86b38e1 which is a clone of MC
! 263:1 = 1600 # 1 Coal -> 80 sec
! 263:1:1 = 1600 # 1 Charcoal -> 80 sec
! 126:1 = 15 # 1 Halfslab -> 7.5 sec
! 5:1 = 300 # 1 Planks -> 15 sec
! 280:1 = 100 # 1 Stick -> 5 sec
! 85:1 = 300 # 1 Fence -> 15 sec
! 53:1 = 300 # 1 Wooden Stairs -> 15 sec
! 58:1 = 300 # 1 Crafting Table -> 15 sec
! 47:1 = 300 # 1 Bookshelf -> 15 sec
! 54:1 = 300 # 1 Chest -> 15 sec
! 84:1 = 300 # 1 Jukebox -> 15 sec
! 327:1 = 20000 # 1 Lava Bucket -> 1000 sec
! 17:1 = 300 # 1 Wood -> 15 sec
! 6:1 = 100 # 1 Sapling -> 5 sec
! 173:1 = 16000 # 1 Coal Block -> 800 sec
! 369:1 = 2400 # 1 Blaze Rod -> 120 sec
! 25:1 = 300 # 1 Note Block -> 15 sec
! 151:1 = 300 # 1 Daylight Sensor -> 15 sec
! 107:1 = 300 # 1 Fence Gate -> 15 sec
! 167:1 = 300 # 1 Trapdoor -> 15 sec
! 146:1 = 300 # 1 Trapped Chest -> 15 sec
! 72:1 = 300 # 1 Pressure Plate -> 15 sec
! 270:1 = 200 # 1 Wooden Pickaxe -> 10 sec
! 271:1 = 200 # 1 Wooden Axe -> 10 sec
! 269:1 = 200 # 1 Wooden Shovel -> 10 sec
! 290:1 = 200 # 1 Wooden Hoe -> 10 sec
! 268:1 = 200 # 1 Wooden Sword -> 10 sec

View File

@ -240,6 +240,7 @@ template <typename Type> class cItemCallback
public:
/// Called for each item in the internal list; return true to stop the loop, or false to continue enumerating
virtual bool Item(Type * a_Type) = 0;
virtual ~cItemCallback() {};
} ;

View File

@ -19,6 +19,8 @@ with specific implementation notes regarding MCServer.</p>
<li><a href="#heightgen">Terrain height</a></li>
<li><a href="#compositiongen">Terrain composition</a></li>
<li><a href="#finishgen">Finishers</a></li>
<li><a href="#makefaster">Making it all faster</a></li>
<li><a href="#GPU">Executing on a GPU</a></li>
</ul>
</p>
@ -304,16 +306,224 @@ using the same approach as in MultiStepMap - by using a thresholded 2D Perlin no
<hr />
<a name="heightgen"><h2>Terrain height</h2></a>
<p>As with biomes, the easiest way to generate terrain height is not generating at all - assigning a constant
height value to all columns. This is again useful either for internal tests, and for worlds like MineCraft's
Flat world.</p>
<p>For a somewhat more realistic landscape, we will employ the good old 2D Perlin noise. We can use it
directly as a heightmap - each value we get from the noise is stretched into the desired range (usually from
40 to 120 blocks for regular MineCraft worlds) and used as the height value. However, this doesn't play too
well with the biomes we've just generated. If the biome says "ocean" and the Perlin noise says "mountain",
the end result will be unpleasant.</p>
<p>So we want a height generator that is biome-aware. The easiest way of doing this is to have a separate
generator for each biome. Simply use the biome map to select which generator to use, then ask the appropriate
generator for the height value. Again, this doesn't work too well - imagine an ExtremeHills biome right next
to an Ocean biome. If no extra care is taken, the border between these two will be a high wall. The following
image shows a 2D representation (for simplification purposes) of the problem:</p>
<img src="img/biomeheights.jpg" />
<p>This requires some further processing. What we need is for the terrain height to be dependent not only on
the immediate biome for that column, but also on the close surroundings of the column. This is exactly the
kind of task that averaging is designed for. If we take the area of 9x9 biomes centered around the queried
column, generate height for each of the biomes therein, sum them up and divide by 81 (the number of biomes
summed), we will be effectively making a 9-long running average over the terrain, and all the borders will
suddenly become smooth. The following image shows the situation from the previous paragraph after applying
the averaging process: </p>
<img src="img/biomeheightsavg.jpg" />
<p>The approach used in MCServer's Biomal generator is based on this idea, with two slight modifications.
Instead of using a separate generator for each biome, one generator is used with a different set of input
parameters for each biomes. These input parameters modify the overall amplitude and frequency of the Perlin
noise that the generator produces, thus modifying the final terrain with regards to biomes. Additionally, the
averaging process is weighted - columns closer to the queried column get a more powerful weight in the sum
than the columns further away. The following image shows the output of MCServer's Biomal terrain height
generator (each block type represents a different biome - ocean in the front (stone), plains and ice plains
behind it (lapis, whitewool), extreme hills back right (soulsand), desert hills back left (mossy
cobble)):</p>
<a name="biomalheights"><img src="img/biomalheights.jpg" /></a>
<p>One key observation about this whole approach is that in order for it to work, the biomes must be
available for columns outside the currently generated chunk, otherwise the columns at the chunk's edge would
not be able to properly average their height. This requirement can be fulfilled only by biome generators that
adhere to the second <a href="#expectedproperties">Expected property</a> - that re-generating will produce
the same data. If the biome generator returned different data for the same chunk each time it was invoked, it
would become impossible to apply the averaging.</p>
<p>(TODO: height with variations (N/A in MCS yet)</p>
<hr />
<a name="compositiongen"><h2>Terrain composition</h2></a>
<p>As with the other generators, the composition generator category has its easy and debugging items, too.
There's the "special" composition of "all the blocks are the same type", which fills the entire column, from
the bottom to the height, with a single blocktype. This generator is useful when testing the generators in
the other categories, to speed up the generation by leaving out unnecessary calculations. Another special
compositor is a similar one, that fills all blocks with the same type, but the type varies for each biome.
This way it's easy to see the generated biomes and possibly the heights for those biomes, as shown in the
previous section on the <a href="#biomalheights">height averaging screenshot</a>.</p>
<p>For a natural look, we need to put together a more complicated algorithm. The standard set forth in
MineCraft is that the top of the world is covered in grass, then there are a few blocks of dirt and finally
stone. This basic layout is then varied for different biomes - deserts have sand and sandstone instead of the
grass and dirt layer. Mushroom biomes have mycelium in place of the grass. This per-biome dependency is
trivial to implement - when compositing, simply use the appropriate layout for the column's biome.</p>
<p>The next change concerns oceans. The generated heightmap doesn't include any waterlevel indication
whatsoever. So it's up to the terrain compositor to actually decide where to place water. We do this by
configuration - simply have a value in the config file specifying the sealevel height. The compositor then
has to add water above any column which has a height lower than that. Additionally, the water needs to
override per-biome layout selection - we don't want grass blocks to generate under water when the terrain
height in the plains biome drops below the sealevel accidentally.</p>
<p>The final feature in the compositor is the decision between multiple composition layouts within a single
biome. A megataiga biome contains patches of non-grass dirt and podzol blocks, and the ocean floor can be
made of dirt, gravel, sand or clay. A simple 2D Perlin noise can be used to select the layout to use for a
specific column - simply threshold the noise's value by as many thresholds as there are layout variations,
and use the layout corresponding to the threshold:</p>
<img src="img/perlincompositor1.jpg" />
<img src="img/perlincompositor2.jpg" />
<img src="img/perlincompositor3.jpg" />
<h3>Nether composition</h3>
<p>So far we've been discussing only the Overworld generator. But MineCraft contains more than that. The
Nether has a completely different look and feel, and quite different processes are required to generate that.
Recall that MineCraft's Nether is 128 blocks high, with bedrock both at the top and the bottom. Between these
two, the terrain looks more like a cavern than a surface. Not surprisingly, the Nether doesn't need a
complicated height generator, it can use the flat height. However, the terrain composition must take an
altogether different approach.</p>
<p>The very first idea is to use the Perlin noise, but generate it in 3D, rather than 2D. Then, for each
block, evaluate the noise value, if below 0, make it air, if not, make it netherrack.
<p>To make it so that the bedrock at the top and at the bottom is never revealed, we can add a value
increasing the more the Y coord gets towards the bottom or the top. This way the thresholding then guarantees
that there will be no air anywhere near the bedrock.</p>
<p>(TODO)</p>
<hr />
<a name="finishgen"><h2>Finishers</h2></a>
<p>Finishers are a vast category of various additions to the terrain generator. They range from very easy
ones, such as generating snow on top of the terrain in cold biomes, through medium ones, such as growing
patches of flowers, complicated ones, such as placing trees and generating caves, all the way to very
complicated ones such as villages and nether fortresses. There is no formal distinction between all these
"categories", the only thing they have in common is that they take a chunk of blocks and modify it in some
way.</p>
<h3>Snow</h3>
<p>Snow is probably the easiest of the finishers. It generates a block of snow on top of each block that is
on top of the terrain and is not marked as non-snowable. It checks the chunk's heightmap to determine the top
block, then checks whether the block supports snow on its top. Rails, levers and tall grass don't support
snow, for example.</p>
<h3>Ice</h3>
<p>Another example of an easy finisher. This scans through the world and turn each water block on the surface
into an ice block if the biome is cold. This means that any water block that is under any kind of other
block, such as under a tree's leaves, will still stay water. Thus an additional improvement could be made by
scanning down from the surface block through blocks that we deem as non-surface, such as leaves, torches,
ladders, fences etc. Note that MCServer currently implements only the easy solution.</p>
<h3>Bottom lava</h3>
<p>Most worlds in MineCraft have lava lakes at their bottom. Generating these is pretty straightforward: Use
the user-configured depth and replace all the air blocks below this depth with lava blocks. Note however,
that this makes this generator dependent on the order in which the finishers are applied. If the mineshafts
generate before bottom lava, the mineshafts that are below the lava level will get filled with lava. On the
other hand, if bottom lava is generated before the mineshafts, it is possible for a mineshaft to "drill
through" a lake of lava. MCServer doesn't try to solve this and instead lets the admin choose whichever they
prefer.</p>
<h3>Specific foliage</h3>
<p>There are generators for specific kinds of foliage. The dead bushes in the desert biome and lilypads in
the swamp biome both share the same generating pattern. They are both specific to a single biome and they
both require a specific block underneath them in order to generate. Their implementation is simple: pick
several random columns in the chunk. If the column is of the correct biome and has the correct top block,
add the foliage block on top.</p>
<p>In order to generate the same set of coordinates when the chunk is re-generated, we use the Perlin noise's
basis functions (the ones providing the random values for Perlin cell vertices). These basically work as a
hash function for the coorinates - the same input coordinates generate the same output value. We use the
chunk's coordinates as two of the coords, and the iteration number as the third coordinate, to generate a
random number. We then check the biome and the top block at those coordinates, if they allow, we generate the
foliage block on top.</p>
<p>Another example of specific foliage is the tall grass in the plains biome. There are quite a lot of these
tall grass blocks, it would be inefficient to generate them using the random-coords approach described above.
Instead, we will use a 2D Perlin noise again, with a threshold defining where to put the grass and where
not.</p>
<h3>Small foliage</h3>
<p>For the flowers, grass, mushrooms in caves etc. we want to use a slightly different algorithm. These
foliage blocks are customarily generated in small "clumps" - there are several blocks of the same type near
together. To generate these, we first select random coords, using the coord hash functions, for a center of a
clump. Then we select the type of block to generate. Finally, we loop over adding a random (coord hash)
number to the clump center coords to get the block where to generate the foliage block:</p>
<img src="img/smallfoliageclumps.jpg" />
<p>In order to make the clump more "round" and "centered", we want the offsets to be closer to the clump
center more often. This is done using a thing called Gaussian function distribution. Instead of having each
random number generate with the same probability, we want higher probability for the numbers around zero,
like this:</p>
<img src="img/gaussprobability.jpg" />
<p>Instead of doing complicated calculations to match this shape exactly, we will use a much easier shape.
By adding together two random numbers in the same range, we get the probability distribution that has a
"roof" shape, enough for our needs:</p>
<img src="img/roofprobability.jpg" />
<p>(For the curious, there is a proof that adding together infinitely many uniform-distributed random numbers
produces random numbers with the Gaussian distribution.)</p>
<p>This scheme can be used to produce clumps of flowers, when we select the 2D coords of the clump center on
the top surface of the terrain. We simply generate the 2D coords of the foliage blocks and use the terrain
height to find the third coord. If we want to generate clumps of mushrooms in the caves, however, we need to
generate the clump center coords in 3D and either use 3 offsets for the mushrooms, or use 2 offsets plus
searching for the closest opening Y-wise in the terrain.</p>
<p>Note that the clumps generated by this scheme may overlap several chunks. Therefore it's crucial to
actually check the surrounding chunks if their clumps overlap into the currently generated chunk, and apply
those as well, otherwise there will be visible cuts in the foliage along the chunks borders.</p>
<h3>Springs</h3>
<p>Water and lava springs are essential for making the underground quite a lot more interesting. They are
rather easy to generate, but a bit more difficult to get right. Generating simply means that a few random
locations (obtained by our familiar coord hashing) are checked and if the block type in there is stone. Then
we see all the horizontal neighbors of the block, plus the block underneath. If all of them except one are
stone, and the one left is air, our block is suitable for turning into a spring. If there were more air
neighbors, the spring would look somewhat unnatural; if there were no air neighbors, the spring won't flow
anywhere, so it would be rather useless.</p>
<p>The difficult part about springs is the amount of them to generate. There should be a few springs on the
surface, perhaps a bit more in the mountaineous biomes. There should be quite a few more springs underground,
but there should definitely be more water springs than lava springs in the upper levels of the terrain, while
there should be more lava springs and almost no water springs near the bottom. To accomodate this, the
MCServer team has made a tool that scanned through MineCraft's terrain and counted the amount of both types
of springs in relation to their height. Two curves have been found for the distribution of each type of the
spring:</p>
<img src="http://mc-server.xoft.cz/img/vanilla_springs_huge.png" />
<p>MCServer uses an approximation of the above curves to choose the height at which to generate the
spring.</p>
<!--
<h3>Caves</h3>
<p>Caves are definitely one of the main things people notice about MineCraft terrain. There are quite a lot
of different algorithms available to generate terrain with caves.
-->
<hr />
<a name="makefaster"><h2>Making it all faster</h2></a>
<p>(TODO)</p>
<a name="GPU"><h2>Executing on a GPU</h2></a>
<p>Much of the terain generation consists of doing the same thing for every single column or block in a chunk. This
sort of computation is much faster on a GPU as GPUs are massively parallel. High end GPUs can execute up to 30,000
threads simultaneously, which would allow them to generate every block in half a chunk in parallel or every column
in over 100 chunks in parallel. A naive comparison suggests that a 800MHz GPU with 15,000 threads can execute parallel
code 250 times faster than a 3GHz CPU with 128 bit SIMD. Obviously we want to harness that power.</p>
</body>
</html>

BIN
docs/img/biomalheights.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/img/biomeheights.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,93 @@
-- Allow debugging by ZBS, if run under the IDE:
local mobdebugfound, mobdebug = pcall(require, "mobdebug")
if mobdebugfound then mobdebug.start() end
-- The list of valid arguments that the ToLua scripts can process:
local KnownArgs = {
['v'] = true,
['h'] = true,
['p'] = true,
['P'] = true,
['o'] = true,
['n'] = true,
['H'] = true,
['S'] = true,
['1'] = true,
['L'] = true,
['D'] = true,
['W'] = true,
['C'] = true,
['E'] = true,
['t'] = true,
['q'] = true,
}
-- The flags table used by ToLua scripts, to be filled from the cmdline params:
flags = {}
-- Te extra parameters used by ToLua scripts:
_extra_parameters = {}
-- ToLua version required by the scripts:
TOLUA_VERSION = "tolua++-1.0.92"
-- Lua version used by ToLua, required by the scripts:
TOLUA_LUA_VERSION = "Lua 5.1"
-- Process the cmdline params into the flags table:
local args = arg or {}
local argc = #args
local i = 1
while (i <= argc) do
local argv = args[i]
if (argv:sub(1, 1) == "-") then
if (KnownArgs[argv:sub(2)]) then
print("Setting flag \"" .. argv:sub(2) .. "\" to \"" .. args[i + 1] .. "\".")
flags[argv:sub(2)] = args[i + 1]
i = i + 1
else
print("Unknown option (" .. i .. "): " .. argv)
print("Aborting.")
os.exit(1)
end
else
print("Setting flag \"f\" to \"" .. argv .. "\".")
flags['f'] = argv
break
end
i = i + 1
end
-- Get the path where the scripts are located:
path = args[0] or ""
local index = path:find("/[^/]*$")
if (index == nil) then
index = path:find("\\[^\\]*$")
end
if (index ~= nil) then
path = path:sub(1, index)
end
print("path is set to \"" .. path .. "\".")
-- Call the ToLua processor:
dofile(path .. "all.lua")

View File

@ -383,7 +383,7 @@ end
-- called to output an error message
function output_error_hook(...)
return string.format(...)
return string.format(...) -- Note that this line must not end in the triple-dot-parenthesis due to pre-parsing
end
-- custom pushers

View File

@ -18,17 +18,16 @@ local function pp_dofile(path)
local ret = file:read("*a")
file:close()
ret = string.gsub(ret, "%.%.%.%s*%)", "...) local arg = {n=select('#', ...), ...};")
ret = string.gsub(ret, "%.%.%.%s*%)$", "...) local arg = {n=select('#', ...), ...};")
loaded = true
return ret
end
end
local f = load(getfile, path)
local f, err = load(getfile, path)
if not f then
error("error loading file "..path)
error("error loading file " .. path .. ": " .. err)
end
return f()
end

View File

@ -524,7 +524,7 @@ function Declaration (s,kind,is_parameter)
end
-- check the form: mod type* name
local s1 = gsub(s,"(%b\[\])",function (n) return gsub(n,'%*','\1') end)
local s1 = gsub(s,"(%b%[%])",function (n) return gsub(n,'%*','\1') end)
t = split_c_tokens(s1,'%*')
if t.n == 2 then
t[2] = gsub(t[2],'\1','%*') -- restore * in dimension expression

View File

@ -132,7 +132,7 @@ function classFeature:cfuncname (n)
if not fname or fname == '' then
fname = self.name
end
n = string.gsub(n..'_'.. (fname), "[<>:, \.%*&]", "_")
n = string.gsub(n..'_'.. (fname), "[<>:, %.%*&]", "_")
return n
end

View File

@ -0,0 +1,27 @@
:: AllToLua_Lua.bat
:: This scripts updates the automatically-generates Lua bindings in Bindings.cpp / Bindings.h
:: When called without any parameters, it will pause for a keypress at the end
:: Call with any parameter to disable the wait (for buildserver use)
:: This script assumes "lua" executable to be in PATH, it uses a pure-lua implementation of the ToLua processor
@echo off
:: Regenerate the files:
echo Regenerating LUA bindings . . .
lua ..\..\lib\tolua++\src\bin\lua\_driver.lua -L virtual_method_hooks.lua -o Bindings.cpp -H Bindings.h AllToLua.pkg
: Wait for keypress, if no param given:
echo.
if %ALLTOLUA_WAIT%N == N pause

View File

@ -309,6 +309,14 @@ void cBlockArea::Clear(void)
void cBlockArea::Create(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes)
{
if ((a_SizeX < 0) || (a_SizeY < 0) || (a_SizeZ < 0))
{
LOGWARNING("Creating a cBlockArea with a negative size! Call to Create ignored. (%d, %d, %d)",
a_SizeX, a_SizeY, a_SizeZ
);
return;
}
Clear();
int BlockCount = a_SizeX * a_SizeY * a_SizeZ;
if ((a_DataTypes & baTypes) != 0)

View File

@ -790,6 +790,7 @@ enum eDimension
dimNether = -1,
dimOverworld = 0,
dimEnd = 1,
dimNotSet = 255, // For things that need an "indeterminate" state, such as cProtocol's LastSentDimension
} ;

View File

@ -62,7 +62,7 @@ void cBlockDoorHandler::OnCancelRightClick(cChunkInterface & a_ChunkInterface, c
a_WorldInterface.SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, a_Player);
NIBBLETYPE Meta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ);
if (Meta & 8)
if (Meta & 0x8)
{
// Current block is top of the door
a_WorldInterface.SendBlockTo(a_BlockX, a_BlockY - 1, a_BlockZ, a_Player);

View File

@ -20,12 +20,12 @@ public:
virtual void OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override;
virtual void OnCancelRightClick(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) override;
virtual const char * GetStepSound(void) override;
virtual NIBBLETYPE MetaRotateCCW(NIBBLETYPE a_Meta) override;
virtual NIBBLETYPE MetaRotateCW(NIBBLETYPE a_Meta) override;
virtual NIBBLETYPE MetaMirrorXY(NIBBLETYPE a_Meta) override;
virtual NIBBLETYPE MetaMirrorYZ(NIBBLETYPE a_Meta) override;
virtual bool GetPlacementBlockTypeMeta(
cChunkInterface & a_ChunkInterface, cPlayer * a_Player,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
@ -52,7 +52,7 @@ public:
return true;
}
virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override
{
a_Pickups.push_back(cItem((m_BlockType == E_BLOCK_WOODEN_DOOR) ? E_ITEM_WOODEN_DOOR : E_ITEM_IRON_DOOR, 1, 0));
@ -77,8 +77,8 @@ public:
{
return ((a_RelY > 0) && (a_Chunk.GetBlock(a_RelX, a_RelY - 1, a_RelZ) != E_BLOCK_AIR));
}
bool CanReplaceBlock(BLOCKTYPE a_BlockType)
{
switch (a_BlockType)
@ -99,7 +99,7 @@ public:
}
/// Converts the player's yaw to placed door's blockmeta
/** Converts the player's yaw to placed door's blockmeta */
inline static NIBBLETYPE PlayerYawToMetaData(double a_Yaw)
{
ASSERT((a_Yaw >= -180) && (a_Yaw < 180));
@ -111,67 +111,109 @@ public:
}
if ((a_Yaw >= 0) && (a_Yaw < 90))
{
return 0x0;
return 0x00;
}
else if ((a_Yaw >= 180) && (a_Yaw < 270))
{
return 0x2;
return 0x02;
}
else if ((a_Yaw >= 90) && (a_Yaw < 180))
{
return 0x1;
return 0x01;
}
else
{
return 0x3;
return 0x03;
}
}
/// Returns true if the specified blocktype is any kind of door
/** Returns true if the specified blocktype is any kind of door */
inline static bool IsDoor(BLOCKTYPE a_Block)
{
return (a_Block == E_BLOCK_WOODEN_DOOR) || (a_Block == E_BLOCK_IRON_DOOR);
}
/// Returns the metadata for the opposite door state (open vs closed)
static NIBBLETYPE ChangeStateMetaData(NIBBLETYPE a_MetaData)
static NIBBLETYPE IsOpen(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
return a_MetaData ^ 4;
NIBBLETYPE Meta = GetCompleteDoorMeta(a_ChunkInterface, a_BlockX, a_BlockY, a_BlockZ);
return ((Meta & 0x04) != 0);
}
/// Changes the door at the specified coords from open to close or vice versa
static void ChangeDoor(cChunkInterface & a_ChunkInterface, int a_X, int a_Y, int a_Z)
/** Returns the complete meta composed from the both parts of the door as (TopMeta << 4) | BottomMeta
The coords may point to either part of the door.
The returned value has bit 3 (0x08) set iff the coords point to the top part of the door.
Fails gracefully for (invalid) doors on the world's top and bottom. */
static NIBBLETYPE GetCompleteDoorMeta(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
NIBBLETYPE OldMetaData = a_ChunkInterface.GetBlockMeta(a_X, a_Y, a_Z);
NIBBLETYPE Meta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ);
a_ChunkInterface.SetBlockMeta(a_X, a_Y, a_Z, ChangeStateMetaData(OldMetaData));
if (OldMetaData & 8)
if ((Meta & 0x08) != 0)
{
// Current block is top of the door
BLOCKTYPE BottomBlock = a_ChunkInterface.GetBlock(a_X, a_Y - 1, a_Z);
NIBBLETYPE BottomMeta = a_ChunkInterface.GetBlockMeta(a_X, a_Y - 1, a_Z);
if (IsDoor(BottomBlock) && !(BottomMeta & 8))
// The coords are pointing at the top part of the door
if (a_BlockX > 0)
{
a_ChunkInterface.SetBlockMeta(a_X, a_Y - 1, a_Z, ChangeStateMetaData(BottomMeta));
NIBBLETYPE DownMeta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY - 1, a_BlockZ);
return (DownMeta & 0x07) | 0x08 | (Meta << 4);
}
// This is the top part of the door at the bottommost layer of the world, there's no bottom:
return 0x08 | (Meta << 4);
}
else
{
// Current block is bottom of the door
BLOCKTYPE TopBlock = a_ChunkInterface.GetBlock(a_X, a_Y + 1, a_Z);
NIBBLETYPE TopMeta = a_ChunkInterface.GetBlockMeta(a_X, a_Y + 1, a_Z);
if (IsDoor(TopBlock) && (TopMeta & 8))
// The coords are pointing at the bottom part of the door
if (a_BlockY < cChunkDef::Height - 1)
{
a_ChunkInterface.SetBlockMeta(a_X, a_Y + 1, a_Z, ChangeStateMetaData(TopMeta));
NIBBLETYPE UpMeta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY + 1, a_BlockZ);
return Meta | (UpMeta << 4);
}
// This is the bottom part of the door at the topmost layer of the world, there's no top:
return Meta;
}
}
/** Sets the door to the specified state. If the door is already in that state, does nothing. */
static void SetOpen(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ, bool a_Open)
{
BLOCKTYPE Block = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY, a_BlockZ);
if (!IsDoor(Block))
{
return;
}
NIBBLETYPE Meta = GetCompleteDoorMeta(a_ChunkInterface, a_BlockX, a_BlockY, a_BlockZ);
bool IsOpened = ((Meta & 0x04) != 0);
if (IsOpened == a_Open)
{
return;
}
// Change the door
NIBBLETYPE NewMeta = (Meta & 0x07) ^ 0x04; // Flip the "IsOpen" bit (0x04)
if ((Meta & 0x08) == 0)
{
// The block is the bottom part of the door
a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, NewMeta);
}
else
{
// The block is the top part of the door, set the meta to the corresponding top part
if (a_BlockY > 0)
{
a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY - 1, a_BlockZ, NewMeta);
}
}
}
/** Changes the door at the specified coords from open to close or vice versa */
static void ChangeDoor(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
SetOpen(a_ChunkInterface, a_BlockX, a_BlockY, a_BlockZ, !IsOpen(a_ChunkInterface, a_BlockX, a_BlockY, a_BlockZ));
}
} ;

View File

@ -1518,6 +1518,7 @@ void cChunk::FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockT
}
MarkDirty();
m_IsRedstoneDirty = true;
m_ChunkData.SetBlock(a_RelX, a_RelY, a_RelZ, a_BlockType);

View File

@ -332,6 +332,7 @@ public:
if (hasChanged)
{
MarkDirty();
m_IsRedstoneDirty = true;
m_PendingSendBlocks.push_back(sSetBlock(m_PosX, m_PosZ, a_RelX, a_RelY, a_RelZ, GetBlock(a_RelX, a_RelY, a_RelZ), a_Meta));
}
@ -378,10 +379,13 @@ public:
cSandSimulatorChunkData & GetSandSimulatorData (void) { return m_SandSimulatorData; }
cRedstoneSimulatorChunkData * GetRedstoneSimulatorData(void) { return &m_RedstoneSimulatorData; }
cRedstoneSimulatorChunkData * GetRedstoneSimulatorQueuedData(void) { return &m_RedstoneSimulatorQueuedData; }
cIncrementalRedstoneSimulator::PoweredBlocksList * GetRedstoneSimulatorPoweredBlocksList(void) { return &m_RedstoneSimulatorPoweredBlocksList; }
cIncrementalRedstoneSimulator::LinkedBlocksList * GetRedstoneSimulatorLinkedBlocksList(void) { return &m_RedstoneSimulatorLinkedBlocksList; };
cIncrementalRedstoneSimulator::SimulatedPlayerToggleableList * GetRedstoneSimulatorSimulatedPlayerToggleableList(void) { return &m_RedstoneSimulatorSimulatedPlayerToggleableList; };
cIncrementalRedstoneSimulator::RepeatersDelayList * GetRedstoneSimulatorRepeatersDelayList(void) { return &m_RedstoneSimulatorRepeatersDelayList; };
bool IsRedstoneDirty(void) const { return m_IsRedstoneDirty; }
void SetIsRedstoneDirty(bool a_Flag) { m_IsRedstoneDirty = a_Flag; }
cBlockEntity * GetBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ);
cBlockEntity * GetBlockEntity(const Vector3i & a_BlockPos) { return GetBlockEntity(a_BlockPos.x, a_BlockPos.y, a_BlockPos.z); }
@ -450,13 +454,16 @@ private:
cSandSimulatorChunkData m_SandSimulatorData;
cRedstoneSimulatorChunkData m_RedstoneSimulatorData;
cRedstoneSimulatorChunkData m_RedstoneSimulatorQueuedData;
cIncrementalRedstoneSimulator::PoweredBlocksList m_RedstoneSimulatorPoweredBlocksList;
cIncrementalRedstoneSimulator::LinkedBlocksList m_RedstoneSimulatorLinkedBlocksList;
cIncrementalRedstoneSimulator::SimulatedPlayerToggleableList m_RedstoneSimulatorSimulatedPlayerToggleableList;
cIncrementalRedstoneSimulator::RepeatersDelayList m_RedstoneSimulatorRepeatersDelayList;
/** Indicates if simulate-once blocks should be updated by the redstone simulator */
bool m_IsRedstoneDirty;
// pick up a random block of this chunk
// Pick up a random block of this chunk
void getRandomBlockCoords(int& a_X, int& a_Y, int& a_Z);
void getThreeRandomNumber(int& a_X, int& a_Y, int& a_Z,int a_MaxX, int a_MaxY, int a_MaxZ);

View File

@ -258,7 +258,7 @@ bool cChunkData::SetMeta(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_Nibble
(m_Sections[Section]->m_BlockMetas[Index / 2] & (0xf0 >> ((Index & 1) * 4))) | // The untouched nibble
((a_Nibble & 0x0f) << ((Index & 1) * 4)) // The nibble being set
);
return oldval == a_Nibble;
return oldval != a_Nibble;
}

View File

@ -1665,6 +1665,30 @@ void cChunkMap::AddEntity(cEntity * a_Entity)
void cChunkMap::AddEntityIfNotPresent(cEntity * a_Entity)
{
cCSLock Lock(m_CSLayers);
cChunkPtr Chunk = GetChunkNoGen(a_Entity->GetChunkX(), ZERO_CHUNK_Y, a_Entity->GetChunkZ());
if (
(Chunk == NULL) || // Chunk not present at all
(!Chunk->IsValid() && !a_Entity->IsPlayer()) // Chunk present, but no valid data; players need to spawn in such chunks (#953)
)
{
LOGWARNING("Entity at %p (%s, ID %d) spawning in a non-existent chunk, the entity is lost.",
a_Entity, a_Entity->GetClass(), a_Entity->GetUniqueID()
);
return;
}
if (!Chunk->HasEntity(a_Entity->GetUniqueID()))
{
Chunk->AddEntity(a_Entity);
}
}
bool cChunkMap::HasEntity(int a_UniqueID)
{
cCSLock Lock(m_CSLayers);

View File

@ -199,6 +199,10 @@ public:
/** Adds the entity to its appropriate chunk, takes ownership of the entity pointer */
void AddEntity(cEntity * a_Entity);
/** Adds the entity to its appropriate chunk, if the entity is not already added.
Takes ownership of the entity pointer */
void AddEntityIfNotPresent(cEntity * a_Entity);
/** Returns true if the entity with specified ID is present in the chunks */
bool HasEntity(int a_EntityID);

View File

@ -326,7 +326,7 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID)
// Send experience
m_Player->SendExperience();
m_Player->Initialize(World);
m_Player->Initialize(*World);
m_State = csAuthenticated;
// Query player team
@ -355,7 +355,7 @@ void cClientHandle::StreamChunks(void)
}
ASSERT(m_Player != NULL);
int ChunkPosX = FAST_FLOOR_DIV((int)m_Player->GetPosX(), cChunkDef::Width);
int ChunkPosZ = FAST_FLOOR_DIV((int)m_Player->GetPosZ(), cChunkDef::Width);
if ((ChunkPosX == m_LastStreamedChunkX) && (ChunkPosZ == m_LastStreamedChunkZ))
@ -1088,18 +1088,25 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e
cWorld * World = m_Player->GetWorld();
if (
(Diff(m_Player->GetPosX(), (double)a_BlockX) > 6) ||
(Diff(m_Player->GetPosY(), (double)a_BlockY) > 6) ||
(Diff(m_Player->GetPosZ(), (double)a_BlockZ) > 6)
(a_BlockFace != BLOCK_FACE_NONE) && // The client is interacting with a specific block
(
(Diff(m_Player->GetPosX(), (double)a_BlockX) > 6) || // The block is too far away
(Diff(m_Player->GetPosY(), (double)a_BlockY) > 6) ||
(Diff(m_Player->GetPosZ(), (double)a_BlockZ) > 6)
)
)
{
if (a_BlockFace != BLOCK_FACE_NONE)
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
if (a_BlockY < cChunkDef::Height - 1)
{
AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, m_Player); // 2 block high things
m_Player->GetInventory().SendEquippedSlot();
}
if (a_BlockY > 0)
{
World->SendBlockTo(a_BlockX, a_BlockY - 1, a_BlockZ, m_Player); // 2 block high things
}
m_Player->GetInventory().SendEquippedSlot();
return;
}
@ -1745,18 +1752,8 @@ void cClientHandle::SendData(const char * a_Data, size_t a_Size)
void cClientHandle::MoveToWorld(cWorld & a_World, bool a_SendRespawnPacket)
void cClientHandle::RemoveFromWorld(void)
{
UNUSED(a_World);
ASSERT(m_Player != NULL);
if (a_SendRespawnPacket)
{
SendRespawn(a_World);
}
cWorld * World = m_Player->GetWorld();
// Remove all associated chunks:
cChunkCoordsList Chunks;
{
@ -1766,7 +1763,6 @@ void cClientHandle::MoveToWorld(cWorld & a_World, bool a_SendRespawnPacket)
}
for (cChunkCoordsList::iterator itr = Chunks.begin(), end = Chunks.end(); itr != end; ++itr)
{
World->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this);
m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ);
} // for itr - Chunks[]

View File

@ -250,8 +250,9 @@ public:
void SendData(const char * a_Data, size_t a_Size);
/** Called when the player moves into a different world; queues sreaming the new chunks */
void MoveToWorld(cWorld & a_World, bool a_SendRespawnPacket);
/** Called when the player moves into a different world.
Sends an UnloadChunk packet for each loaded chunk and resets the streamed chunks. */
void RemoveFromWorld(void);
/** Called when the player will enchant a Item */
void HandleEnchantItem(Byte & WindowID, Byte & Enchantment);

View File

@ -130,9 +130,9 @@ const char * cEntity::GetParentClass(void) const
bool cEntity::Initialize(cWorld * a_World)
bool cEntity::Initialize(cWorld & a_World)
{
if (cPluginManager::Get()->CallHookSpawningEntity(*a_World, *this))
if (cPluginManager::Get()->CallHookSpawningEntity(a_World, *this))
{
return false;
}
@ -145,13 +145,13 @@ bool cEntity::Initialize(cWorld * a_World)
*/
m_IsInitialized = true;
m_World = a_World;
m_World = &a_World;
m_World->AddEntity(this);
cPluginManager::Get()->CallHookSpawnedEntity(*a_World, *this);
cPluginManager::Get()->CallHookSpawnedEntity(a_World, *this);
// Spawn the entity on the clients:
a_World->BroadcastSpawnEntity(*this);
a_World.BroadcastSpawnEntity(*this);
return true;
}

View File

@ -146,8 +146,9 @@ public:
cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height);
virtual ~cEntity();
/// Spawns the entity in the world; returns true if spawned, false if not (plugin disallowed)
virtual bool Initialize(cWorld * a_World);
/** Spawns the entity in the world; returns true if spawned, false if not (plugin disallowed).
Adds the entity to the world. */
virtual bool Initialize(cWorld & a_World);
// tolua_begin
@ -430,6 +431,9 @@ public:
UNUSED(a_Killer);
}
/** Sets the internal world pointer to a new cWorld, doesn't update anything else. */
void SetWorld(cWorld * a_World) { m_World = a_World; }
protected:
static cCriticalSection m_CSCount;
static int m_EntityCount;
@ -494,8 +498,6 @@ protected:
virtual void Destroyed(void) {} // Called after the entity has been destroyed
void SetWorld(cWorld * a_World) { m_World = a_World; }
/** Called in each tick to handle air-related processing i.e. drowning */
virtual void HandleAir();

View File

@ -55,6 +55,7 @@ void cItemFrame::KilledBy(cEntity * a_Killer)
{
if (m_Item.IsEmpty())
{
SetHealth(0);
super::KilledBy(a_Killer);
Destroy();
return;
@ -69,8 +70,9 @@ void cItemFrame::KilledBy(cEntity * a_Killer)
}
SetHealth(GetMaxHealth());
m_Item.Clear();
m_Item.Empty();
m_Rotation = 0;
SetInvulnerableTicks(0);
GetWorld()->BroadcastEntityMetadata(*this);
}

View File

@ -934,6 +934,8 @@ void cPlayer::Killed(cEntity * a_Victim)
void cPlayer::Respawn(void)
{
ASSERT(m_World != NULL);
m_Health = GetMaxHealth();
SetInvulnerableTicks(20);
@ -1590,24 +1592,22 @@ bool cPlayer::MoveToWorld(const AString & a_WorldName, cWorld * a_World)
return false;
}
eDimension OldDimension = m_World->GetDimension();
// Send the respawn packet:
if (m_ClientHandle != NULL)
{
m_ClientHandle->SendRespawn(*World);
}
// Remove all links to the old world
m_World->RemovePlayer(this);
m_ClientHandle->RemoveFromAllChunks();
// If the dimension is different, we can send the respawn packet
// http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02
bool SendRespawn = OldDimension != World->GetDimension();
m_ClientHandle->MoveToWorld(*World, SendRespawn);
// Add player to all the necessary parts of the new world
SetWorld(World);
m_ClientHandle->StreamChunks();
World->AddEntity(this);
// Queue adding player to the new world, including all the necessary adjustments to the object
World->AddPlayer(this);
if (SendRespawn)
if (GetWorld()->GetDimension() != World->GetDimension())
{
GetClientHandle()->SendPlayerMoveLook();
GetClientHandle()->SendHealth();

View File

@ -328,6 +328,8 @@ public:
void SetVisible( bool a_bVisible ); // tolua_export
bool IsVisible(void) const { return m_bVisible; } // tolua_export
/** Moves the player to the specified world.
Returns true if successful, false on failure (world not found). */
virtual bool MoveToWorld(const AString & a_WorldName, cWorld * a_World = NULL) override; // tolua_export
/** Saves all player data, such as inventory, to JSON */

View File

@ -561,10 +561,16 @@ void cCompoGenNether::ComposeTerrain(cChunkDesc & a_ChunkDesc)
// Interpolate the lowest floor:
for (int z = 0; z <= 16 / INTERPOL_Z; z++) for (int x = 0; x <= 16 / INTERPOL_X; x++)
{
FloorLo[INTERPOL_X * x + 17 * INTERPOL_Z * z] =
//*
FloorLo[INTERPOL_X * x + 17 * INTERPOL_Z * z] =
m_Noise1.IntNoise3DInt(BaseX + INTERPOL_X * x, 0, BaseZ + INTERPOL_Z * z) *
m_Noise2.IntNoise3DInt(BaseX + INTERPOL_X * x, 0, BaseZ + INTERPOL_Z * z) /
256;
//*/
/*
FloorLo[INTERPOL_X * x + 17 * INTERPOL_Z * z] =
m_Noise1.IntNoise3DInt(BaseX + INTERPOL_X * x, 0, BaseZ + INTERPOL_Z * z) / 256;
//*/
} // for x, z - FloorLo[]
LinearUpscale2DArrayInPlace<17, 17, INTERPOL_X, INTERPOL_Z>(FloorLo);
@ -574,10 +580,16 @@ void cCompoGenNether::ComposeTerrain(cChunkDesc & a_ChunkDesc)
// First update the high floor:
for (int z = 0; z <= 16 / INTERPOL_Z; z++) for (int x = 0; x <= 16 / INTERPOL_X; x++)
{
//*
FloorHi[INTERPOL_X * x + 17 * INTERPOL_Z * z] =
m_Noise1.IntNoise3DInt(BaseX + INTERPOL_X * x, Segment + SEGMENT_HEIGHT, BaseZ + INTERPOL_Z * z) *
m_Noise2.IntNoise3DInt(BaseX + INTERPOL_Z * x, Segment + SEGMENT_HEIGHT, BaseZ + INTERPOL_Z * z) /
256;
//*/
/*
FloorHi[INTERPOL_X * x + 17 * INTERPOL_Z * z] =
m_Noise1.IntNoise3DInt(BaseX + INTERPOL_X * x, Segment + SEGMENT_HEIGHT, BaseZ + INTERPOL_Z * z) / 256;
//*/
} // for x, z - FloorLo[]
LinearUpscale2DArrayInPlace<17, 17, INTERPOL_X, INTERPOL_Z>(FloorHi);

View File

@ -24,6 +24,7 @@
#include "NetherFortGen.h"
#include "Noise3DGenerator.h"
#include "POCPieceGenerator.h"
#include "RainbowRoadsGen.h"
#include "Ravines.h"
#include "UnderwaterBaseGen.h"
#include "VillageGen.h"
@ -377,6 +378,13 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile)
{
m_FinishGens.push_back(new cFinishGenPreSimulator);
}
else if (NoCaseCompare(*itr, "RainbowRoads") == 0)
{
int GridSize = a_IniFile.GetValueSetI("Generator", "RainbowRoadsGridSize", 512);
int MaxDepth = a_IniFile.GetValueSetI("Generator", "RainbowRoadsMaxDepth", 30);
int MaxSize = a_IniFile.GetValueSetI("Generator", "RainbowRoadsMaxSize", 260);
m_FinishGens.push_back(new cRainbowRoadsGen(Seed, GridSize, MaxDepth, MaxSize));
}
else if (NoCaseCompare(*itr, "Ravines") == 0)
{
m_FinishGens.push_back(new cStructGenRavines(Seed, 128));
@ -395,9 +403,9 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile)
}
else if (NoCaseCompare(*itr, "UnderwaterBases") == 0)
{
int GridSize = a_IniFile.GetValueSetI("Generator", "UnderwaterBaseGridSize", 1024);
int MaxDepth = a_IniFile.GetValueSetI("Generator", "UnderwaterBaseMaxDepth", 7);
int MaxSize = a_IniFile.GetValueSetI("Generator", "UnderwaterBaseMaxSize", 128);
int GridSize = a_IniFile.GetValueSetI("Generator", "UnderwaterBaseGridSize", 1024);
int MaxDepth = a_IniFile.GetValueSetI("Generator", "UnderwaterBaseMaxDepth", 7);
int MaxSize = a_IniFile.GetValueSetI("Generator", "UnderwaterBaseMaxSize", 128);
m_FinishGens.push_back(new cUnderwaterBaseGen(Seed, GridSize, MaxDepth, MaxSize, *m_BiomeGen));
}
else if (NoCaseCompare(*itr, "Villages") == 0)

View File

@ -47,6 +47,10 @@ cTerrainHeightGen * cTerrainHeightGen::CreateHeightGen(cIniFile &a_IniFile, cBio
{
res = new cEndGen(a_Seed);
}
else if (NoCaseCompare(HeightGenName, "Mountains") == 0)
{
res = new cHeiGenMountains(a_Seed);
}
else if (NoCaseCompare(HeightGenName, "Noise3D") == 0)
{
res = new cNoise3DComposable(a_Seed);
@ -300,6 +304,68 @@ void cHeiGenClassic::InitializeHeightGen(cIniFile & a_IniFile)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cHeiGenMountains:
cHeiGenMountains::cHeiGenMountains(int a_Seed) :
m_Seed(a_Seed),
m_Noise(a_Seed)
{
}
void cHeiGenMountains::GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap)
{
NOISE_DATATYPE StartX = (NOISE_DATATYPE)(a_ChunkX * cChunkDef::Width);
NOISE_DATATYPE EndX = (NOISE_DATATYPE)(a_ChunkX * cChunkDef::Width + cChunkDef::Width - 1);
NOISE_DATATYPE StartZ = (NOISE_DATATYPE)(a_ChunkZ * cChunkDef::Width);
NOISE_DATATYPE EndZ = (NOISE_DATATYPE)(a_ChunkZ * cChunkDef::Width + cChunkDef::Width - 1);
NOISE_DATATYPE Workspace[16 * 16];
NOISE_DATATYPE Noise[16 * 16];
NOISE_DATATYPE PerlinNoise[16 * 16];
m_Noise.Generate2D(Noise, 16, 16, StartX, EndX, StartZ, EndZ, Workspace);
m_Perlin.Generate2D(PerlinNoise, 16, 16, StartX, EndX, StartZ, EndZ, Workspace);
for (int z = 0; z < cChunkDef::Width; z++)
{
int IdxZ = z * cChunkDef::Width;
for (int x = 0; x < cChunkDef::Width; x++)
{
int idx = IdxZ + x;
int hei = 100 - (int)((Noise[idx] + PerlinNoise[idx]) * 15);
if (hei < 10)
{
hei = 10;
}
if (hei > 250)
{
hei = 250;
}
cChunkDef::SetHeight(a_HeightMap, x , z, hei);
} // for x
} // for z
}
void cHeiGenMountains::InitializeHeightGen(cIniFile & a_IniFile)
{
// TODO: Read the params from an INI file
m_Noise.AddOctave(0.1f, 0.1f);
m_Noise.AddOctave(0.05f, 0.5f);
m_Noise.AddOctave(0.02f, 1.5f);
m_Perlin.AddOctave(0.01f, 1.5f);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cHeiGenBiomal:

View File

@ -106,6 +106,27 @@ protected:
class cHeiGenMountains :
public cTerrainHeightGen
{
public:
cHeiGenMountains(int a_Seed);
protected:
int m_Seed;
cRidgedMultiNoise m_Noise;
cPerlinNoise m_Perlin;
// cTerrainHeightGen overrides:
virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override;
virtual void InitializeHeightGen(cIniFile & a_IniFile) override;
} ;
class cHeiGenBiomal :
public cTerrainHeightGen
{

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
// RainbowRoadPrefabs.h
// Declares the prefabs in the group RainbowRoad
#include "../Prefab.h"
extern const cPrefab::sDef g_RainbowRoadPrefabs[];
extern const cPrefab::sDef g_RainbowRoadStartingPrefabs[];
extern const size_t g_RainbowRoadPrefabsCount;
extern const size_t g_RainbowRoadStartingPrefabsCount;

View File

@ -0,0 +1,115 @@
// RainbowRoadsGen.cpp
// Implements the cRainbowRoadsGen class representing the rainbow road generator
#include "Globals.h"
#include "RainbowRoadsGen.h"
#include "Prefabs/RainbowRoadPrefabs.h"
#include "PieceGenerator.h"
static cPrefabPiecePool g_RainbowRoads(g_RainbowRoadPrefabs, g_RainbowRoadPrefabsCount, g_RainbowRoadStartingPrefabs, g_RainbowRoadStartingPrefabsCount);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cRainbowRoadsGen::cRainbowRoads:
class cRainbowRoadsGen::cRainbowRoads :
public cGridStructGen::cStructure
{
typedef cGridStructGen::cStructure super;
public:
cRainbowRoads(
int a_Seed,
int a_OriginX, int a_OriginZ,
int a_MaxDepth,
int a_MaxSize
) :
super(a_OriginX, a_OriginZ),
m_Seed(a_Seed),
m_Noise(a_Seed),
m_MaxSize(a_MaxSize),
m_Borders(a_OriginX - a_MaxSize, 0, a_OriginZ - a_MaxSize, a_OriginX + a_MaxSize, 255, a_OriginZ + a_MaxSize)
{
// Generate the pieces for this base:
cBFSPieceGenerator pg(g_RainbowRoads, a_Seed);
pg.PlacePieces(a_OriginX, 190, a_OriginZ, a_MaxDepth, m_Pieces);
if (m_Pieces.empty())
{
return;
}
}
~cRainbowRoads()
{
cPieceGenerator::FreePieces(m_Pieces);
}
protected:
/** Seed for the random functions */
int m_Seed;
/** The noise used as a pseudo-random generator */
cNoise m_Noise;
/** Maximum size, in X/Z blocks, of the village (radius from the origin) */
int m_MaxSize;
/** Borders of the vilalge - no item may reach out of this cuboid. */
cCuboid m_Borders;
/** The village pieces, placed by the generator. */
cPlacedPieces m_Pieces;
// cGridStructGen::cStructure overrides:
virtual void DrawIntoChunk(cChunkDesc & a_Chunk) override
{
for (cPlacedPieces::iterator itr = m_Pieces.begin(), end = m_Pieces.end(); itr != end; ++itr)
{
cPrefab & Prefab = (cPrefab &)((*itr)->GetPiece());
Prefab.Draw(a_Chunk, *itr);
} // for itr - m_PlacedPieces[]
}
} ;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cRainbowRoadsGen:
cRainbowRoadsGen::cRainbowRoadsGen(int a_Seed, int a_GridSize, int a_MaxDepth, int a_MaxSize) :
super(a_Seed, a_GridSize, a_GridSize, a_MaxSize, a_MaxSize, 100),
m_Noise(a_Seed + 9000),
m_MaxDepth(a_MaxDepth),
m_MaxSize(a_MaxSize)
{
}
cGridStructGen::cStructurePtr cRainbowRoadsGen::CreateStructure(int a_OriginX, int a_OriginZ)
{
// Create a base based on the chosen prefabs:
return cStructurePtr(new cRainbowRoads(m_Seed, a_OriginX, a_OriginZ, m_MaxDepth, m_MaxSize));
}

View File

@ -0,0 +1,47 @@
// RainbowRoadsGen.h
// Declares the cRainbowRoadsGen class representing the underwater base generator
#pragma once
#include "GridStructGen.h"
#include "PrefabPiecePool.h"
class cRainbowRoadsGen :
public cGridStructGen
{
typedef cGridStructGen super;
public:
cRainbowRoadsGen(int a_Seed, int a_GridSize, int a_MaxDepth, int a_MaxSize);
protected:
class cRainbowRoads; // fwd: RainbowRoadsGen.cpp
/** The noise used for generating random numbers */
cNoise m_Noise;
/** Maximum depth of the generator tree*/
int m_MaxDepth;
/** Maximum size, in X/Z blocks, of the base (radius from the origin) */
int m_MaxSize;
// cGridStructGen overrides:
virtual cStructurePtr CreateStructure(int a_OriginX, int a_OriginZ) override;
} ;

View File

@ -75,7 +75,7 @@ public:
double z = Callbacks.m_Pos.z;
cBoat * Boat = new cBoat(x + 0.5, y + 1, z + 0.5);
Boat->Initialize(a_World);
Boat->Initialize(*a_World);
return true;
}

View File

@ -66,7 +66,7 @@ public:
{
return;
}
if (!Arrow->Initialize(a_Player->GetWorld()))
if (!Arrow->Initialize(*a_Player->GetWorld()))
{
delete Arrow;
return;

View File

@ -231,7 +231,7 @@ public:
else
{
cFloater * Floater = new cFloater(a_Player->GetPosX(), a_Player->GetStance(), a_Player->GetPosZ(), a_Player->GetLookVector() * 15, a_Player->GetUniqueID(), 100 + a_World->GetTickRandomNumber(800) - (a_Player->GetEquippedItem().m_Enchantments.GetLevel(cEnchantments::enchLure) * 100));
Floater->Initialize(a_World);
Floater->Initialize(*a_World);
a_Player->SetIsFishing(true, Floater->GetUniqueID());
}
return true;

View File

@ -34,7 +34,7 @@ public:
if (Block == E_BLOCK_AIR)
{
cItemFrame * ItemFrame = new cItemFrame(a_Dir, a_BlockX, a_BlockY, a_BlockZ);
if (!ItemFrame->Initialize(a_World))
if (!ItemFrame->Initialize(*a_World))
{
delete ItemFrame;
return false;

View File

@ -70,7 +70,7 @@ public:
return false;
}
} // switch (m_ItemType)
Minecart->Initialize(a_World);
Minecart->Initialize(*a_World);
if (!a_Player->IsGameModeCreative())
{

View File

@ -79,7 +79,7 @@ public:
};
cPainting * Painting = new cPainting(gPaintingTitlesList[a_World->GetTickRandomNumber(ARRAYCOUNT(gPaintingTitlesList) - 1)].Title, Dir, a_BlockX, a_BlockY, a_BlockZ);
Painting->Initialize(a_World);
Painting->Initialize(*a_World);
if (!a_Player->IsGameModeCreative())
{

View File

@ -7,8 +7,7 @@
cBat::cBat(void) :
// TODO: The size is only a guesstimate, measure in vanilla and fix the size values here
super("Bat", mtBat, "mob.bat.hurt", "mob.bat.death", 0.7, 0.7)
super("Bat", mtBat, "mob.bat.hurt", "mob.bat.death", 0.5, 0.9)
{
}

View File

@ -9,8 +9,7 @@
cBlaze::cBlaze(void) :
// TODO: The size is only a guesstimate, measure in vanilla and fix the size values here
super("Blaze", mtBlaze, "mob.blaze.hit", "mob.blaze.death", 0.7, 1.8)
super("Blaze", mtBlaze, "mob.blaze.hit", "mob.blaze.death", 0.6, 1.8)
{
}
@ -45,7 +44,7 @@ void cBlaze::Attack(float a_Dt)
{
return;
}
if (!FireCharge->Initialize(m_World))
if (!FireCharge->Initialize(*m_World))
{
delete FireCharge;
return;

View File

@ -20,7 +20,6 @@ void cCaveSpider::Tick(float a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
// TODO: Check vanilla if cavespiders really get passive during the day / in daylight
m_EMPersonality = (GetWorld()->GetTimeOfDay() < (12000 + 1000)) ? PASSIVE : AGGRESSIVE;
}

View File

@ -46,7 +46,7 @@ void cGhast::Attack(float a_Dt)
{
return;
}
if (!GhastBall->Initialize(m_World))
if (!GhastBall->Initialize(*m_World))
{
delete GhastBall;
return;

View File

@ -81,7 +81,7 @@ void cSkeleton::Attack(float a_Dt)
{
return;
}
if (!Arrow->Initialize(m_World))
if (!Arrow->Initialize(*m_World))
{
delete Arrow;
return;

View File

@ -30,7 +30,7 @@ bool cWither::IsArmored(void) const
bool cWither::Initialize(cWorld * a_World)
bool cWither::Initialize(cWorld & a_World)
{
// Set health before BroadcastSpawnEntity()
SetHealth(GetMaxHealth() / 3);

View File

@ -25,7 +25,7 @@ public:
bool IsArmored(void) const;
// cEntity overrides
virtual bool Initialize(cWorld * a_World) override;
virtual bool Initialize(cWorld & a_World) override;
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override;
virtual void Tick(float a_Dt, cChunk & a_Chunk) override;

View File

@ -854,7 +854,7 @@ void cPerlinNoise::Generate2D(
NOISE_DATATYPE Amplitude = FirstOctave.m_Amplitude;
for (int i = 0; i < ArrayCount; i++)
{
a_Array[i] *= Amplitude;
a_Array[i] = a_Workspace[i] * Amplitude;
}
// Add each octave:
@ -949,3 +949,171 @@ void cPerlinNoise::Generate3D(
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cRidgedMultiNoise:
cRidgedMultiNoise::cRidgedMultiNoise(void) :
m_Seed(0)
{
}
cRidgedMultiNoise::cRidgedMultiNoise(int a_Seed) :
m_Seed(a_Seed)
{
}
void cRidgedMultiNoise::SetSeed(int a_Seed)
{
m_Seed = a_Seed;
}
void cRidgedMultiNoise::AddOctave(float a_Frequency, float a_Amplitude)
{
m_Octaves.push_back(cOctave(m_Seed * ((int)m_Octaves.size() + 4) * 4 + 1024, a_Frequency, a_Amplitude));
}
void cRidgedMultiNoise::Generate2D(
NOISE_DATATYPE * a_Array, ///< Array to generate into [x + a_SizeX * y]
int a_SizeX, int a_SizeY, ///< Count of the array, in each direction
NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array in the X direction
NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY, ///< Noise-space coords of the array in the Y direction
NOISE_DATATYPE * a_Workspace ///< Workspace that this function can use and trash
) const
{
if (m_Octaves.empty())
{
// No work to be done
ASSERT(!"RidgedMulti: No octaves to generate!");
return;
}
bool ShouldFreeWorkspace = (a_Workspace == NULL);
int ArrayCount = a_SizeX * a_SizeY;
if (ShouldFreeWorkspace)
{
a_Workspace = new NOISE_DATATYPE[ArrayCount];
}
// Generate the first octave directly into array:
const cOctave & FirstOctave = m_Octaves.front();
FirstOctave.m_Noise.Generate2D(
a_Workspace, a_SizeX, a_SizeY,
a_StartX * FirstOctave.m_Frequency, a_EndX * FirstOctave.m_Frequency,
a_StartY * FirstOctave.m_Frequency, a_EndY * FirstOctave.m_Frequency
);
NOISE_DATATYPE Amplitude = FirstOctave.m_Amplitude;
for (int i = 0; i < ArrayCount; i++)
{
a_Array[i] = fabs(a_Workspace[i] * Amplitude);
}
// Add each octave:
for (cOctaves::const_iterator itr = m_Octaves.begin() + 1, end = m_Octaves.end(); itr != end; ++itr)
{
// Generate cubic noise for the octave:
itr->m_Noise.Generate2D(
a_Workspace, a_SizeX, a_SizeY,
a_StartX * itr->m_Frequency, a_EndX * itr->m_Frequency,
a_StartY * itr->m_Frequency, a_EndY * itr->m_Frequency
);
// Add the cubic noise into the output:
NOISE_DATATYPE Amplitude = itr->m_Amplitude;
for (int i = 0; i < ArrayCount; i++)
{
a_Array[i] += fabs(a_Workspace[i] * Amplitude);
}
}
if (ShouldFreeWorkspace)
{
delete[] a_Workspace;
}
}
void cRidgedMultiNoise::Generate3D(
NOISE_DATATYPE * a_Array, ///< Array to generate into [x + a_SizeX * y + a_SizeX * a_SizeY * z]
int a_SizeX, int a_SizeY, int a_SizeZ, ///< Count of the array, in each direction
NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array in the X direction
NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY, ///< Noise-space coords of the array in the Y direction
NOISE_DATATYPE a_StartZ, NOISE_DATATYPE a_EndZ, ///< Noise-space coords of the array in the Z direction
NOISE_DATATYPE * a_Workspace ///< Workspace that this function can use and trash
) const
{
if (m_Octaves.empty())
{
// No work to be done
ASSERT(!"RidgedMulti: No octaves to generate!");
return;
}
bool ShouldFreeWorkspace = (a_Workspace == NULL);
int ArrayCount = a_SizeX * a_SizeY * a_SizeZ;
if (ShouldFreeWorkspace)
{
a_Workspace = new NOISE_DATATYPE[ArrayCount];
}
// Generate the first octave directly into array:
const cOctave & FirstOctave = m_Octaves.front();
FirstOctave.m_Noise.Generate3D(
a_Workspace, a_SizeX, a_SizeY, a_SizeZ,
a_StartX * FirstOctave.m_Frequency, a_EndX * FirstOctave.m_Frequency,
a_StartY * FirstOctave.m_Frequency, a_EndY * FirstOctave.m_Frequency,
a_StartZ * FirstOctave.m_Frequency, a_EndZ * FirstOctave.m_Frequency
);
NOISE_DATATYPE Amplitude = FirstOctave.m_Amplitude;
for (int i = 0; i < ArrayCount; i++)
{
a_Array[i] = a_Workspace[i] * Amplitude;
}
// Add each octave:
for (cOctaves::const_iterator itr = m_Octaves.begin() + 1, end = m_Octaves.end(); itr != end; ++itr)
{
// Generate cubic noise for the octave:
itr->m_Noise.Generate3D(
a_Workspace, a_SizeX, a_SizeY, a_SizeZ,
a_StartX * itr->m_Frequency, a_EndX * itr->m_Frequency,
a_StartY * itr->m_Frequency, a_EndY * itr->m_Frequency,
a_StartZ * itr->m_Frequency, a_EndZ * itr->m_Frequency
);
// Add the cubic noise into the output:
NOISE_DATATYPE Amplitude = itr->m_Amplitude;
for (int i = 0; i < ArrayCount; i++)
{
a_Array[i] += a_Workspace[i] * Amplitude;
}
}
if (ShouldFreeWorkspace)
{
delete[] a_Workspace;
}
}

View File

@ -192,6 +192,70 @@ protected:
class cRidgedMultiNoise
{
public:
cRidgedMultiNoise(void);
cRidgedMultiNoise(int a_Seed);
void SetSeed(int a_Seed);
void AddOctave(NOISE_DATATYPE a_Frequency, NOISE_DATATYPE a_Amplitude);
void Generate1D(
NOISE_DATATYPE * a_Array, ///< Array to generate into
int a_SizeX, ///< Count of the array
NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array
NOISE_DATATYPE * a_Workspace = NULL ///< Workspace that this function can use and trash
) const;
void Generate2D(
NOISE_DATATYPE * a_Array, ///< Array to generate into [x + a_SizeX * y]
int a_SizeX, int a_SizeY, ///< Count of the array, in each direction
NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array in the X direction
NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY, ///< Noise-space coords of the array in the Y direction
NOISE_DATATYPE * a_Workspace = NULL ///< Workspace that this function can use and trash
) const;
void Generate3D(
NOISE_DATATYPE * a_Array, ///< Array to generate into [x + a_SizeX * y + a_SizeX * a_SizeY * z]
int a_SizeX, int a_SizeY, int a_SizeZ, ///< Count of the array, in each direction
NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array in the X direction
NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY, ///< Noise-space coords of the array in the Y direction
NOISE_DATATYPE a_StartZ, NOISE_DATATYPE a_EndZ, ///< Noise-space coords of the array in the Z direction
NOISE_DATATYPE * a_Workspace = NULL ///< Workspace that this function can use and trash
) const;
protected:
class cOctave
{
public:
cCubicNoise m_Noise;
NOISE_DATATYPE m_Frequency; // Coord multiplier
NOISE_DATATYPE m_Amplitude; // Value multiplier
cOctave(int a_Seed, NOISE_DATATYPE a_Frequency, NOISE_DATATYPE a_Amplitude) :
m_Noise(a_Seed),
m_Frequency(a_Frequency),
m_Amplitude(a_Amplitude)
{
}
} ;
typedef std::vector<cOctave> cOctaves;
int m_Seed;
cOctaves m_Octaves;
} ;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Inline function definitions:
// These need to be in the header, otherwise linker error occur in MSVC

View File

@ -60,6 +60,9 @@ static void SetThreadName(DWORD dwThreadID, const char * threadName)
cIsThread::cIsThread(const AString & iThreadName) :
m_ShouldTerminate(false),
m_ThreadName(iThreadName),
#ifdef _WIN32
m_ThreadID(0),
#endif
m_Handle(NULL_HANDLE)
{
}
@ -83,8 +86,8 @@ bool cIsThread::Start(void)
ASSERT(m_Handle == NULL_HANDLE); // Has already started one thread?
#ifdef _WIN32
// Create the thread suspended, so that the mHandle variable is valid in the thread procedure
DWORD ThreadID = 0;
m_Handle = CreateThread(NULL, 0, thrExecute, this, CREATE_SUSPENDED, &ThreadID);
m_ThreadID = 0;
m_Handle = CreateThread(NULL, 0, thrExecute, this, CREATE_SUSPENDED, &m_ThreadID);
if (m_Handle == NULL)
{
LOGERROR("ERROR: Could not create thread \"%s\", GLE = %d!", m_ThreadName.c_str(), GetLastError());
@ -96,7 +99,7 @@ bool cIsThread::Start(void)
// Thread naming is available only in MSVC
if (!m_ThreadName.empty())
{
SetThreadName(ThreadID, m_ThreadName.c_str());
SetThreadName(m_ThreadID, m_ThreadName.c_str());
}
#endif // _DEBUG and _MSC_VER
@ -177,3 +180,15 @@ unsigned long cIsThread::GetCurrentID(void)
bool cIsThread::IsCurrentThread(void) const
{
#ifdef _WIN32
return (GetCurrentThreadId() == m_ThreadID);
#else
return (m_Handle == pthread_self());
#endif
}

View File

@ -48,6 +48,9 @@ public:
/// Returns the OS-dependent thread ID for the caller's thread
static unsigned long GetCurrentID(void);
/** Returns true if the thread calling this function is the thread contained within this object. */
bool IsCurrentThread(void) const;
protected:
AString m_ThreadName;
@ -60,6 +63,7 @@ protected:
#ifdef _WIN32
DWORD m_ThreadID;
HANDLE m_Handle;
static DWORD __stdcall thrExecute(LPVOID a_Param)

View File

@ -133,7 +133,8 @@ typedef unsigned char Byte;
cProtocol125::cProtocol125(cClientHandle * a_Client) :
super(a_Client),
m_ReceivedData(32 KiB)
m_ReceivedData(32 KiB),
m_LastSentDimension(dimNotSet)
{
}
@ -591,6 +592,7 @@ void cProtocol125::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
WriteByte (0); // Unused
WriteByte (60); // Client list width or something
Flush();
m_LastSentDimension = a_World.GetDimension();
}
@ -834,6 +836,11 @@ void cProtocol125::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effect
void cProtocol125::SendRespawn(const cWorld & a_World)
{
cCSLock Lock(m_CSPacket);
if (m_LastSentDimension == a_World.GetDimension())
{
// Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do
return;
}
cPlayer * Player = m_Client->GetPlayer();
WriteByte (PACKET_RESPAWN);
WriteInt ((int)(a_World.GetDimension()));
@ -841,6 +848,8 @@ void cProtocol125::SendRespawn(const cWorld & a_World)
WriteChar ((char)Player->GetGameMode());
WriteShort (256); // Current world height
WriteString("default");
Flush();
m_LastSentDimension = a_World.GetDimension();
}

View File

@ -113,6 +113,10 @@ protected:
cByteBuffer m_ReceivedData; ///< Buffer for the received data
AString m_Username; ///< Stored in ParseHandshake(), compared to Login username
/** The dimension that was last sent to a player in a Respawn or Login packet.
Used to avoid Respawning into the same dimension, which confuses the client. */
eDimension m_LastSentDimension;
virtual void SendData(const char * a_Data, size_t a_Size) override;

View File

@ -253,7 +253,7 @@ void cProtocol132::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
WriteByte (0); // Unused, used to be world height
WriteByte (8); // Client list width or something
Flush();
m_LastSentDimension = a_World.GetDimension();
SendCompass(a_World);
}

View File

@ -92,7 +92,8 @@ cProtocol172::cProtocol172(cClientHandle * a_Client, const AString & a_ServerAdd
m_ReceivedData(32 KiB),
m_OutPacketBuffer(64 KiB),
m_OutPacketLenBuffer(20), // 20 bytes is more than enough for one VarInt
m_IsEncrypted(false)
m_IsEncrypted(false),
m_LastSentDimension(dimNotSet)
{
// Create the comm log file, if so requested:
if (g_ShouldLogCommIn || g_ShouldLogCommOut)
@ -656,6 +657,7 @@ void cProtocol172::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
Pkt.WriteByte(std::min(Server->GetMaxPlayers(), 60));
Pkt.WriteString("default"); // Level type - wtf?
}
m_LastSentDimension = a_World.GetDimension();
// Send the spawn position:
{
@ -986,12 +988,19 @@ void cProtocol172::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effect
void cProtocol172::SendRespawn(const cWorld & a_World)
{
if (m_LastSentDimension == a_World.GetDimension())
{
// Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do
return;
}
cPacketizer Pkt(*this, 0x07); // Respawn packet
cPlayer * Player = m_Client->GetPlayer();
Pkt.WriteInt((int)a_World.GetDimension());
Pkt.WriteByte(2); // TODO: Difficulty (set to Normal)
Pkt.WriteByte((Byte)Player->GetEffectiveGameMode());
Pkt.WriteString("default");
m_LastSentDimension = a_World.GetDimension();
}

View File

@ -244,6 +244,10 @@ protected:
/** The logfile where the comm is logged, when g_ShouldLogComm is true */
cFile m_CommLogFile;
/** The dimension that was last sent to a player in a Respawn or Login packet.
Used to avoid Respawning into the same dimension, which confuses the client. */
eDimension m_LastSentDimension;
/** Adds the received (unencrypted) data to m_ReceivedData, parses complete packets */
void AddReceivedData(const char * a_Data, size_t a_Size);

View File

@ -107,10 +107,16 @@ void cServer::cTickThread::Execute(void)
cServer::cServer(void) :
m_ListenThreadIPv4(*this, cSocket::IPv4, "Client IPv4"),
m_ListenThreadIPv6(*this, cSocket::IPv6, "Client IPv6"),
m_PlayerCount(0),
m_PlayerCountDiff(0),
m_ClientViewDistance(0),
m_bIsConnected(false),
m_bRestarting(false),
m_RCONServer(*this),
m_TickThread(*this)
m_MaxPlayers(0),
m_bIsHardcore(false),
m_TickThread(*this),
m_ShouldAuthenticate(false)
{
}

View File

@ -17,8 +17,14 @@
cIncrementalRedstoneSimulator::cIncrementalRedstoneSimulator(cWorld & a_World)
: super(a_World)
cIncrementalRedstoneSimulator::cIncrementalRedstoneSimulator(cWorld & a_World) :
super(a_World),
m_RedstoneSimulatorChunkData(),
m_PoweredBlocks(),
m_LinkedPoweredBlocks(),
m_SimulatedPlayerToggleableBlocks(),
m_RepeatersDelayList(),
m_Chunk()
{
}
@ -83,6 +89,7 @@ void cIncrementalRedstoneSimulator::RedstoneAddBlock(int a_BlockX, int a_BlockY,
{
LOGD("cIncrementalRedstoneSimulator: Erased block @ {%i, %i, %i} from powered blocks list as it no longer connected to a source", itr->a_BlockPos.x, itr->a_BlockPos.y, itr->a_BlockPos.z);
itr = PoweredBlocks->erase(itr);
a_Chunk->SetIsRedstoneDirty(true);
continue;
}
else if (
@ -97,6 +104,7 @@ void cIncrementalRedstoneSimulator::RedstoneAddBlock(int a_BlockX, int a_BlockY,
{
LOGD("cIncrementalRedstoneSimulator: Erased block @ {%i, %i, %i} from powered blocks list due to present/past metadata mismatch", itr->a_BlockPos.x, itr->a_BlockPos.y, itr->a_BlockPos.z);
itr = PoweredBlocks->erase(itr);
a_Chunk->SetIsRedstoneDirty(true);
continue;
}
++itr;
@ -112,6 +120,7 @@ void cIncrementalRedstoneSimulator::RedstoneAddBlock(int a_BlockX, int a_BlockY,
{
LOGD("cIncrementalRedstoneSimulator: Erased block @ {%i, %i, %i} from linked powered blocks list as it is no longer connected to a source", itr->a_BlockPos.x, itr->a_BlockPos.y, itr->a_BlockPos.z);
itr = LinkedPoweredBlocks->erase(itr);
a_Chunk->SetIsRedstoneDirty(true);
continue;
}
else if (
@ -125,6 +134,7 @@ void cIncrementalRedstoneSimulator::RedstoneAddBlock(int a_BlockX, int a_BlockY,
{
LOGD("cIncrementalRedstoneSimulator: Erased block @ {%i, %i, %i} from linked powered blocks list due to present/past metadata mismatch", itr->a_BlockPos.x, itr->a_BlockPos.y, itr->a_BlockPos.z);
itr = LinkedPoweredBlocks->erase(itr);
a_Chunk->SetIsRedstoneDirty(true);
continue;
}
}
@ -134,6 +144,7 @@ void cIncrementalRedstoneSimulator::RedstoneAddBlock(int a_BlockX, int a_BlockY,
{
LOGD("cIncrementalRedstoneSimulator: Erased block @ {%i, %i, %i} from linked powered blocks list as it is no longer powered through a valid middle block", itr->a_BlockPos.x, itr->a_BlockPos.y, itr->a_BlockPos.z);
itr = LinkedPoweredBlocks->erase(itr);
a_Chunk->SetIsRedstoneDirty(true);
continue;
}
}
@ -198,8 +209,16 @@ void cIncrementalRedstoneSimulator::RedstoneAddBlock(int a_BlockX, int a_BlockY,
{
return;
}
RedstoneSimulatorChunkData->push_back(cCoordWithBlockAndBool(RelX, a_BlockY, RelZ, Block, false));
for (cRedstoneSimulatorChunkData::iterator itr = a_Chunk->GetRedstoneSimulatorQueuedData()->begin(); itr != a_Chunk->GetRedstoneSimulatorQueuedData()->end(); ++itr)
{
if ((itr->x == RelX) && (itr->y == a_BlockY) && (itr->z == RelZ))
{
// Can't have duplicates in here either, in case something adds the block again before the structure can written to the main chunk data
return;
}
}
a_Chunk->GetRedstoneSimulatorQueuedData()->push_back(cCoordWithBlockAndBool(RelX, a_BlockY, RelZ, Block, false));
}
@ -208,22 +227,29 @@ void cIncrementalRedstoneSimulator::RedstoneAddBlock(int a_BlockX, int a_BlockY,
void cIncrementalRedstoneSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk)
{
// We still attempt to simulate all blocks in the chunk every tick, because of outside influence that needs to be taken into account
// For example, repeaters need to be ticked, pressure plates checked for entities, daylight sensor checked for light changes, etc.
// The easiest way to make this more efficient is probably just to reduce code within the handlers that put too much strain on server, like getting or setting blocks
// A marking dirty system might be a TODO for later on, perhaps
m_RedstoneSimulatorChunkData = a_Chunk->GetRedstoneSimulatorData();
if (m_RedstoneSimulatorChunkData->empty())
if (m_RedstoneSimulatorChunkData->empty() && a_Chunk->GetRedstoneSimulatorQueuedData()->empty())
{
return;
}
m_RedstoneSimulatorChunkData->insert(m_RedstoneSimulatorChunkData->end(), a_Chunk->GetRedstoneSimulatorQueuedData()->begin(), a_Chunk->GetRedstoneSimulatorQueuedData()->end());
a_Chunk->GetRedstoneSimulatorQueuedData()->clear();
m_PoweredBlocks = a_Chunk->GetRedstoneSimulatorPoweredBlocksList();
m_RepeatersDelayList = a_Chunk->GetRedstoneSimulatorRepeatersDelayList();
m_SimulatedPlayerToggleableBlocks = a_Chunk->GetRedstoneSimulatorSimulatedPlayerToggleableList();
m_LinkedPoweredBlocks = a_Chunk->GetRedstoneSimulatorLinkedBlocksList();
m_Chunk = a_Chunk;
bool ShouldUpdateSimulateOnceBlocks = false;
if (a_Chunk->IsRedstoneDirty())
{
// Simulate the majority of devices only if something (blockwise or power-wise) has changed
// Make sure to allow the chunk to resimulate after the initial run if there was a power change (ShouldUpdateSimulateOnceBlocks helps to do this)
a_Chunk->SetIsRedstoneDirty(false);
ShouldUpdateSimulateOnceBlocks = true;
}
for (cRedstoneSimulatorChunkData::iterator dataitr = m_RedstoneSimulatorChunkData->begin(); dataitr != m_RedstoneSimulatorChunkData->end();)
{
@ -235,63 +261,25 @@ void cIncrementalRedstoneSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int
switch (dataitr->Data)
{
case E_BLOCK_BLOCK_OF_REDSTONE: HandleRedstoneBlock(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_LEVER: HandleRedstoneLever(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_FENCE_GATE: HandleFenceGate(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_TNT: HandleTNT(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_TRAPDOOR: HandleTrapdoor(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_REDSTONE_WIRE: HandleRedstoneWire(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_NOTE_BLOCK: HandleNoteBlock(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_DAYLIGHT_SENSOR: HandleDaylightSensor(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_COMMAND_BLOCK: HandleCommandBlock(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_DAYLIGHT_SENSOR: HandleDaylightSensor(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_REDSTONE_TORCH_OFF:
case E_BLOCK_REDSTONE_TORCH_ON:
{
HandleRedstoneTorch(dataitr->x, dataitr->y, dataitr->z, dataitr->Data);
break;
}
case E_BLOCK_STONE_BUTTON:
case E_BLOCK_WOODEN_BUTTON:
{
HandleRedstoneButton(dataitr->x, dataitr->y, dataitr->z);
break;
}
case E_BLOCK_REDSTONE_REPEATER_OFF:
case E_BLOCK_REDSTONE_REPEATER_ON:
{
HandleRedstoneRepeater(dataitr->x, dataitr->y, dataitr->z, dataitr->Data);
break;
}
case E_BLOCK_PISTON:
case E_BLOCK_STICKY_PISTON:
{
HandlePiston(dataitr->x, dataitr->y, dataitr->z);
break;
}
case E_BLOCK_REDSTONE_LAMP_OFF:
case E_BLOCK_REDSTONE_LAMP_ON:
{
HandleRedstoneLamp(dataitr->x, dataitr->y, dataitr->z, dataitr->Data);
break;
}
case E_BLOCK_DISPENSER:
case E_BLOCK_DROPPER:
{
HandleDropSpenser(dataitr->x, dataitr->y, dataitr->z);
break;
}
case E_BLOCK_WOODEN_DOOR:
case E_BLOCK_IRON_DOOR:
{
HandleDoor(dataitr->x, dataitr->y, dataitr->z);
break;
}
case E_BLOCK_ACTIVATOR_RAIL:
case E_BLOCK_DETECTOR_RAIL:
case E_BLOCK_POWERED_RAIL:
{
HandleRail(dataitr->x, dataitr->y, dataitr->z, dataitr->Data);
bool FoundItem = false;
for (RepeatersDelayList::iterator repeateritr = m_RepeatersDelayList->begin(); repeateritr != m_RepeatersDelayList->end(); ++repeateritr)
{
if (repeateritr->a_RelBlockPos == Vector3i(dataitr->x, dataitr->y, dataitr->z))
{
HandleRedstoneRepeater(dataitr->x, dataitr->y, dataitr->z, dataitr->Data, repeateritr);
FoundItem = true;
break;
}
}
if (!FoundItem && ShouldUpdateSimulateOnceBlocks)
{
HandleRedstoneRepeater(dataitr->x, dataitr->y, dataitr->z, dataitr->Data, m_RepeatersDelayList->end());
}
break;
}
case E_BLOCK_WOODEN_PRESSURE_PLATE:
@ -302,7 +290,67 @@ void cIncrementalRedstoneSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int
HandlePressurePlate(dataitr->x, dataitr->y, dataitr->z, dataitr->Data);
break;
}
default: LOGD("Unhandled block (!) or unimplemented redstone block: %s", ItemToString(dataitr->Data).c_str()); break;
default: break;
}
if (ShouldUpdateSimulateOnceBlocks)
{
switch (dataitr->Data)
{
case E_BLOCK_REDSTONE_WIRE: HandleRedstoneWire(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_COMMAND_BLOCK: HandleCommandBlock(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_NOTE_BLOCK: HandleNoteBlock(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_BLOCK_OF_REDSTONE: HandleRedstoneBlock(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_LEVER: HandleRedstoneLever(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_FENCE_GATE: HandleFenceGate(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_TNT: HandleTNT(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_TRAPDOOR: HandleTrapdoor(dataitr->x, dataitr->y, dataitr->z); break;
case E_BLOCK_ACTIVATOR_RAIL:
case E_BLOCK_DETECTOR_RAIL:
case E_BLOCK_POWERED_RAIL:
{
HandleRail(dataitr->x, dataitr->y, dataitr->z, dataitr->Data);
break;
}
case E_BLOCK_WOODEN_DOOR:
case E_BLOCK_IRON_DOOR:
{
HandleDoor(dataitr->x, dataitr->y, dataitr->z);
break;
}
case E_BLOCK_REDSTONE_LAMP_OFF:
case E_BLOCK_REDSTONE_LAMP_ON:
{
HandleRedstoneLamp(dataitr->x, dataitr->y, dataitr->z, dataitr->Data);
break;
}
case E_BLOCK_DISPENSER:
case E_BLOCK_DROPPER:
{
HandleDropSpenser(dataitr->x, dataitr->y, dataitr->z);
break;
}
case E_BLOCK_PISTON:
case E_BLOCK_STICKY_PISTON:
{
HandlePiston(dataitr->x, dataitr->y, dataitr->z);
break;
}
case E_BLOCK_REDSTONE_TORCH_OFF:
case E_BLOCK_REDSTONE_TORCH_ON:
{
HandleRedstoneTorch(dataitr->x, dataitr->y, dataitr->z, dataitr->Data);
break;
}
case E_BLOCK_STONE_BUTTON:
case E_BLOCK_WOODEN_BUTTON:
{
HandleRedstoneButton(dataitr->x, dataitr->y, dataitr->z);
break;
}
default: break;
}
}
++dataitr;
}
@ -699,7 +747,7 @@ void cIncrementalRedstoneSimulator::HandleRedstoneWire(int a_RelBlockX, int a_Re
void cIncrementalRedstoneSimulator::HandleRedstoneRepeater(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, BLOCKTYPE a_MyState)
void cIncrementalRedstoneSimulator::HandleRedstoneRepeater(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, BLOCKTYPE a_MyState, RepeatersDelayList::iterator a_Itr)
{
/* Repeater Orientation Mini Guide:
===================================
@ -725,87 +773,99 @@ void cIncrementalRedstoneSimulator::HandleRedstoneRepeater(int a_RelBlockX, int
// Create a variable holding my meta to avoid multiple lookups.
NIBBLETYPE a_Meta = m_Chunk->GetMeta(a_RelBlockX, a_RelBlockY, a_RelBlockZ);
bool IsOn = (a_MyState == E_BLOCK_REDSTONE_REPEATER_ON);
bool WereItrsChanged = false;
if (!IsRepeaterLocked(a_RelBlockX, a_RelBlockY, a_RelBlockZ, a_Meta)) // If we're locked, change nothing. Otherwise:
{
bool IsSelfPowered = IsRepeaterPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, a_Meta);
if (IsSelfPowered && !IsOn) // Queue a power change if powered, but not on and not locked.
{
QueueRepeaterPowerChange(a_RelBlockX, a_RelBlockY, a_RelBlockZ, a_Meta, true);
WereItrsChanged = QueueRepeaterPowerChange(a_RelBlockX, a_RelBlockY, a_RelBlockZ, a_Meta, true);
}
else if (!IsSelfPowered && IsOn) // Queue a power change if unpowered, on, and not locked.
{
QueueRepeaterPowerChange(a_RelBlockX, a_RelBlockY, a_RelBlockZ, a_Meta, false);
}
}
for (RepeatersDelayList::iterator itr = m_RepeatersDelayList->begin(); itr != m_RepeatersDelayList->end(); ++itr)
{
if (!itr->a_RelBlockPos.Equals(Vector3i(a_RelBlockX, a_RelBlockY, a_RelBlockZ)))
{
continue;
}
if (itr->a_ElapsedTicks >= itr->a_DelayTicks) // Has the elapsed ticks reached the target ticks?
{
if (itr->ShouldPowerOn)
{
if (!IsOn)
{
m_Chunk->SetBlock(a_RelBlockX, a_RelBlockY, a_RelBlockZ, E_BLOCK_REDSTONE_REPEATER_ON, a_Meta); // For performance
}
switch (a_Meta & 0x3) // We only want the direction (bottom) bits
{
case 0x0:
{
SetBlockPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ - 1, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
SetDirectionLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, BLOCK_FACE_ZM);
break;
}
case 0x1:
{
SetBlockPowered(a_RelBlockX + 1, a_RelBlockY, a_RelBlockZ, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
SetDirectionLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, BLOCK_FACE_XP);
break;
}
case 0x2:
{
SetBlockPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ + 1, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
SetDirectionLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, BLOCK_FACE_ZP);
break;
}
case 0x3:
{
SetBlockPowered(a_RelBlockX - 1, a_RelBlockY, a_RelBlockZ, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
SetDirectionLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, BLOCK_FACE_XM);
break;
}
}
// Removal of the data entry will be handled in SimChunk - we still want to continue trying to power blocks, even if our delay time has reached
// Otherwise, the power state of blocks in front won't update after we have powered on
return;
}
else
{
if (IsOn)
{
m_Chunk->SetBlock(a_RelBlockX, a_RelBlockY, a_RelBlockZ, E_BLOCK_REDSTONE_REPEATER_OFF, a_Meta);
}
m_RepeatersDelayList->erase(itr); // We can remove off repeaters which don't need further updating
return;
}
WereItrsChanged = QueueRepeaterPowerChange(a_RelBlockX, a_RelBlockY, a_RelBlockZ, a_Meta, false);
}
else
{
// Apparently, incrementing ticks only works reliably here, and not in SimChunk;
// With a world with lots of redstone, the repeaters simply do not delay
// I am confounded to say why. Perhaps optimisation failure.
LOGD("Incremented a repeater @ {%i %i %i} | Elapsed ticks: %i | Target delay: %i", itr->a_RelBlockPos.x, itr->a_RelBlockPos.y, itr->a_RelBlockPos.z, itr->a_ElapsedTicks, itr->a_DelayTicks);
itr->a_ElapsedTicks++;
return;
}
}
else
{
return;
}
if (WereItrsChanged)
{
for (a_Itr = m_RepeatersDelayList->begin(); a_Itr != m_RepeatersDelayList->end(); ++a_Itr)
{
if (a_Itr->a_RelBlockPos == Vector3i(a_RelBlockX, a_RelBlockY, a_RelBlockZ))
{
break;
}
}
}
if (a_Itr->a_ElapsedTicks >= a_Itr->a_DelayTicks) // Has the elapsed ticks reached the target ticks?
{
if (a_Itr->ShouldPowerOn)
{
if (!IsOn)
{
m_Chunk->SetBlock(a_RelBlockX, a_RelBlockY, a_RelBlockZ, E_BLOCK_REDSTONE_REPEATER_ON, a_Meta); // For performance
}
switch (a_Meta & 0x3) // We only want the direction (bottom) bits
{
case 0x0:
{
SetBlockPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ - 1, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
SetDirectionLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, BLOCK_FACE_ZM);
break;
}
case 0x1:
{
SetBlockPowered(a_RelBlockX + 1, a_RelBlockY, a_RelBlockZ, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
SetDirectionLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, BLOCK_FACE_XP);
break;
}
case 0x2:
{
SetBlockPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ + 1, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
SetDirectionLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, BLOCK_FACE_ZP);
break;
}
case 0x3:
{
SetBlockPowered(a_RelBlockX - 1, a_RelBlockY, a_RelBlockZ, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
SetDirectionLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ, BLOCK_FACE_XM);
break;
}
}
// Removal of the data entry will be handled in SimChunk - we still want to continue trying to power blocks, even if our delay time has reached
// Otherwise, the power state of blocks in front won't update after we have powered on
return;
}
else
{
if (IsOn)
{
m_Chunk->SetBlock(a_RelBlockX, a_RelBlockY, a_RelBlockZ, E_BLOCK_REDSTONE_REPEATER_OFF, a_Meta);
}
m_RepeatersDelayList->erase(a_Itr); // We can remove off repeaters which don't need further updating
return;
}
}
else
{
// Apparently, incrementing ticks only works reliably here, and not in SimChunk;
// With a world with lots of redstone, the repeaters simply do not delay
// I am confounded to say why. Perhaps optimisation failure.
LOGD("Incremented a repeater @ {%i %i %i} | Elapsed ticks: %i | Target delay: %i", a_Itr->a_RelBlockPos.x, a_Itr->a_RelBlockPos.y, a_Itr->a_RelBlockPos.z, a_Itr->a_ElapsedTicks, a_Itr->a_DelayTicks);
a_Itr->a_ElapsedTicks++;
}
}
@ -905,8 +965,11 @@ void cIncrementalRedstoneSimulator::HandleDoor(int a_RelBlockX, int a_RelBlockY,
if (!AreCoordsSimulated(a_RelBlockX, a_RelBlockY, a_RelBlockZ, true))
{
cChunkInterface ChunkInterface(m_World.GetChunkMap());
cBlockDoorHandler::ChangeDoor(ChunkInterface, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
m_Chunk->BroadcastSoundParticleEffect(1003, BlockX, a_RelBlockY, BlockZ, 0);
if (!cBlockDoorHandler::IsOpen(ChunkInterface, BlockX, a_RelBlockY, BlockZ))
{
cBlockDoorHandler::SetOpen(ChunkInterface, BlockX, a_RelBlockY, BlockZ, true);
m_Chunk->BroadcastSoundParticleEffect(1003, BlockX, a_RelBlockY, BlockZ, 0);
}
SetPlayerToggleableBlockAsSimulated(a_RelBlockX, a_RelBlockY, a_RelBlockZ, true);
}
}
@ -915,8 +978,11 @@ void cIncrementalRedstoneSimulator::HandleDoor(int a_RelBlockX, int a_RelBlockY,
if (!AreCoordsSimulated(a_RelBlockX, a_RelBlockY, a_RelBlockZ, false))
{
cChunkInterface ChunkInterface(m_World.GetChunkMap());
cBlockDoorHandler::ChangeDoor(ChunkInterface, a_RelBlockX, a_RelBlockY, a_RelBlockZ);
m_Chunk->BroadcastSoundParticleEffect(1003, BlockX, a_RelBlockY, BlockZ, 0);
if (cBlockDoorHandler::IsOpen(ChunkInterface, BlockX, a_RelBlockY, BlockZ))
{
cBlockDoorHandler::SetOpen(ChunkInterface, BlockX, a_RelBlockY, BlockZ, false);
m_Chunk->BroadcastSoundParticleEffect(1003, BlockX, a_RelBlockY, BlockZ, 0);
}
SetPlayerToggleableBlockAsSimulated(a_RelBlockX, a_RelBlockY, a_RelBlockZ, false);
}
}
@ -1730,7 +1796,8 @@ void cIncrementalRedstoneSimulator::SetBlockPowered(int a_RelBlockX, int a_RelBl
return;
}
PoweredBlocksList * Powered = m_Chunk->GetNeighborChunk(BlockX, BlockZ)->GetRedstoneSimulatorPoweredBlocksList();
cChunk * Neighbour = m_Chunk->GetNeighborChunk(BlockX, BlockZ);
PoweredBlocksList * Powered = Neighbour->GetRedstoneSimulatorPoweredBlocksList();
for (PoweredBlocksList::iterator itr = Powered->begin(); itr != Powered->end(); ++itr) // Check powered list
{
if (
@ -1762,6 +1829,8 @@ void cIncrementalRedstoneSimulator::SetBlockPowered(int a_RelBlockX, int a_RelBl
RC.a_SourcePos = Vector3i(SourceX, a_RelSourceY, SourceZ);
RC.a_PowerLevel = a_PowerLevel;
Powered->push_back(RC);
Neighbour->SetIsRedstoneDirty(true);
m_Chunk->SetIsRedstoneDirty(true);
}
@ -1801,7 +1870,8 @@ void cIncrementalRedstoneSimulator::SetBlockLinkedPowered(
return;
}
LinkedBlocksList * Linked = m_Chunk->GetNeighborChunk(BlockX, BlockZ)->GetRedstoneSimulatorLinkedBlocksList();
cChunk * Neighbour = m_Chunk->GetNeighborChunk(BlockX, BlockZ);
LinkedBlocksList * Linked = Neighbour->GetRedstoneSimulatorLinkedBlocksList();
for (LinkedBlocksList::iterator itr = Linked->begin(); itr != Linked->end(); ++itr) // Check linked powered list
{
if (
@ -1822,6 +1892,8 @@ void cIncrementalRedstoneSimulator::SetBlockLinkedPowered(
RC.a_SourcePos = Vector3i(SourceX, a_RelSourceY, SourceZ);
RC.a_PowerLevel = a_PowerLevel;
Linked->push_back(RC);
Neighbour->SetIsRedstoneDirty(true);
m_Chunk->SetIsRedstoneDirty(true);
}
@ -1861,7 +1933,7 @@ void cIncrementalRedstoneSimulator::SetPlayerToggleableBlockAsSimulated(int a_Re
void cIncrementalRedstoneSimulator::QueueRepeaterPowerChange(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, NIBBLETYPE a_Meta, bool ShouldPowerOn)
bool cIncrementalRedstoneSimulator::QueueRepeaterPowerChange(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, NIBBLETYPE a_Meta, bool ShouldPowerOn)
{
for (RepeatersDelayList::iterator itr = m_RepeatersDelayList->begin(); itr != m_RepeatersDelayList->end(); ++itr)
{
@ -1869,14 +1941,14 @@ void cIncrementalRedstoneSimulator::QueueRepeaterPowerChange(int a_RelBlockX, in
{
if (ShouldPowerOn == itr->ShouldPowerOn) // We are queued already for the same thing, don't replace entry
{
return;
return false;
}
// Already in here (normal to allow repeater to continue on powering and updating blocks in front) - just update info and quit
itr->a_DelayTicks = (((a_Meta & 0xC) >> 0x2) + 1) * 2; // See below for description
itr->a_ElapsedTicks = 0;
itr->ShouldPowerOn = ShouldPowerOn;
return;
return false;
}
}
@ -1891,7 +1963,7 @@ void cIncrementalRedstoneSimulator::QueueRepeaterPowerChange(int a_RelBlockX, in
RC.a_ElapsedTicks = 0;
RC.ShouldPowerOn = ShouldPowerOn;
m_RepeatersDelayList->push_back(RC);
return;
return true;
}

View File

@ -108,7 +108,7 @@ private:
/** Handles redstone wire */
void HandleRedstoneWire(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ);
/** Handles repeaters */
void HandleRedstoneRepeater(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, BLOCKTYPE a_MyState);
void HandleRedstoneRepeater(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, BLOCKTYPE a_MyState, RepeatersDelayList::iterator a_Itr);
/* ====================== */
/* ====== DEVICES ====== */
@ -145,8 +145,8 @@ private:
void SetDirectionLinkedPowered(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, char a_Direction, unsigned char a_PowerLevel = MAX_POWER_LEVEL);
/** Marks all blocks immediately surrounding a coordinate as powered */
void SetAllDirsAsPowered(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, unsigned char a_PowerLevel = MAX_POWER_LEVEL);
/** Queues a repeater to be powered or unpowered */
void QueueRepeaterPowerChange(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, NIBBLETYPE a_Meta, bool ShouldPowerOn);
/** Queues a repeater to be powered or unpowered and returns if the m_RepeatersDelayList iterators were invalidated */
bool QueueRepeaterPowerChange(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ, NIBBLETYPE a_Meta, bool ShouldPowerOn);
/** Returns if a coordinate is powered or linked powered */
bool AreCoordsPowered(int a_RelBlockX, int a_RelBlockY, int a_RelBlockZ) { return AreCoordsDirectlyPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ) || AreCoordsLinkedPowered(a_RelBlockX, a_RelBlockY, a_RelBlockZ); }

View File

@ -60,7 +60,7 @@ void cSandSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChun
);
*/
cFallingBlock * FallingBlock = new cFallingBlock(Pos, BlockType, a_Chunk->GetMeta(itr->x, itr->y, itr->z));
FallingBlock->Initialize(&m_World);
FallingBlock->Initialize(m_World);
a_Chunk->SetBlock(itr->x, itr->y, itr->z, E_BLOCK_AIR, 0);
}
}

View File

@ -854,6 +854,7 @@ void cAnvilWindow::GetBlockPos(int & a_PosX, int & a_PosY, int & a_PosZ)
cEnchantingWindow::cEnchantingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) :
cWindow(wtEnchantment, "Enchant"),
m_SlotArea(),
m_BlockX(a_BlockX),
m_BlockY(a_BlockY),
m_BlockZ(a_BlockZ)

View File

@ -813,6 +813,20 @@ void cWorld::Tick(float a_Dt, int a_LastTickDurationMSec)
m_LastTimeUpdate = m_WorldAge;
}
// Add entities waiting in the queue to be added:
{
cCSLock Lock(m_CSEntitiesToAdd);
for (cEntityList::iterator itr = m_EntitiesToAdd.begin(), end = m_EntitiesToAdd.end(); itr != end; ++itr)
{
(*itr)->SetWorld(this);
m_ChunkMap->AddEntity(*itr);
}
m_EntitiesToAdd.clear();
}
// Add players waiting in the queue to be added:
AddQueuedPlayers();
m_ChunkMap->Tick(a_Dt);
TickClients(a_Dt);
@ -1701,7 +1715,7 @@ void cWorld::SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double
a_BlockX, a_BlockY, a_BlockZ,
*itr, IsPlayerCreated, SpeedX, SpeedY, SpeedZ
);
Pickup->Initialize(this);
Pickup->Initialize(*this);
}
}
@ -1722,7 +1736,7 @@ void cWorld::SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double
a_BlockX, a_BlockY, a_BlockZ,
*itr, IsPlayerCreated, (float)a_SpeedX, (float)a_SpeedY, (float)a_SpeedZ
);
Pickup->Initialize(this);
Pickup->Initialize(*this);
}
}
@ -1733,7 +1747,7 @@ void cWorld::SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double
int cWorld::SpawnFallingBlock(int a_X, int a_Y, int a_Z, BLOCKTYPE BlockType, NIBBLETYPE BlockMeta)
{
cFallingBlock * FallingBlock = new cFallingBlock(Vector3i(a_X, a_Y, a_Z), BlockType, BlockMeta);
FallingBlock->Initialize(this);
FallingBlock->Initialize(*this);
return FallingBlock->GetUniqueID();
}
@ -1749,7 +1763,7 @@ int cWorld::SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Reward)
}
cExpOrb * ExpOrb = new cExpOrb(a_X, a_Y, a_Z, a_Reward);
ExpOrb->Initialize(this);
ExpOrb->Initialize(*this);
return ExpOrb->GetUniqueID();
}
@ -1772,7 +1786,7 @@ int cWorld::SpawnMinecart(double a_X, double a_Y, double a_Z, int a_MinecartType
return -1;
}
} // switch (a_MinecartType)
Minecart->Initialize(this);
Minecart->Initialize(*this);
return Minecart->GetUniqueID();
}
@ -1783,7 +1797,7 @@ int cWorld::SpawnMinecart(double a_X, double a_Y, double a_Z, int a_MinecartType
void cWorld::SpawnPrimedTNT(double a_X, double a_Y, double a_Z, int a_FuseTicks, double a_InitialVelocityCoeff)
{
cTNTEntity * TNT = new cTNTEntity(a_X, a_Y, a_Z, a_FuseTicks);
TNT->Initialize(this);
TNT->Initialize(*this);
TNT->SetSpeed(
a_InitialVelocityCoeff * (GetTickRandomNumber(2) - 1), /** -1, 0, 1 */
a_InitialVelocityCoeff * 2,
@ -2299,7 +2313,7 @@ void cWorld::SetChunkData(
// Initialize the entities (outside the m_ChunkMap's CS, to fix FS #347):
for (cEntityList::iterator itr = a_Entities.begin(), end = a_Entities.end(); itr != end; ++itr)
{
(*itr)->Initialize(this);
(*itr)->Initialize(*this);
}
// If a client is requesting this chunk, send it to them:
@ -2392,23 +2406,8 @@ void cWorld::CollectPickupsByPlayer(cPlayer * a_Player)
void cWorld::AddPlayer(cPlayer * a_Player)
{
{
cCSLock Lock(m_CSPlayers);
ASSERT(std::find(m_Players.begin(), m_Players.end(), a_Player) == m_Players.end()); // Is it already in the list? HOW?
m_Players.remove(a_Player); // Make sure the player is registered only once
m_Players.push_back(a_Player);
}
// Add the player's client to the list of clients to be ticked:
if (a_Player->GetClientHandle() != NULL)
{
cCSLock Lock(m_CSClients);
m_ClientsToAdd.push_back(a_Player->GetClientHandle());
}
// The player has already been added to the chunkmap as the entity, do NOT add again!
cCSLock Lock(m_CSPlayersToAdd);
m_PlayersToAdd.push_back(a_Player);
}
@ -2417,17 +2416,26 @@ void cWorld::AddPlayer(cPlayer * a_Player)
void cWorld::RemovePlayer(cPlayer * a_Player)
{
m_ChunkMap->RemoveEntity(a_Player);
{
cCSLock Lock(m_CSPlayersToAdd);
m_PlayersToAdd.remove(a_Player);
}
{
cCSLock Lock(m_CSPlayers);
LOGD("Removing player \"%s\" from world \"%s\".", a_Player->GetName().c_str(), m_WorldName.c_str());
m_Players.remove(a_Player);
}
// Remove the player's client from the list of clients to be ticked:
if (a_Player->GetClientHandle() != NULL)
cClientHandle * Client = a_Player->GetClientHandle();
if (Client != NULL)
{
Client->RemoveFromWorld();
m_ChunkMap->RemoveClientFromChunks(Client);
cCSLock Lock(m_CSClients);
m_ClientsToRemove.push_back(a_Player->GetClientHandle());
m_ClientsToRemove.push_back(Client);
}
}
@ -2878,9 +2886,11 @@ void cWorld::ScheduleTask(int a_DelayTicks, cTask * a_Task)
void cWorld::AddEntity(cEntity * a_Entity)
{
m_ChunkMap->AddEntity(a_Entity);
cCSLock Lock(m_CSEntitiesToAdd);
m_EntitiesToAdd.push_back(a_Entity);
}
@ -2889,6 +2899,19 @@ void cWorld::AddEntity(cEntity * a_Entity)
bool cWorld::HasEntity(int a_UniqueID)
{
// Check if the entity is in the queue to be added to the world:
{
cCSLock Lock(m_CSEntitiesToAdd);
for (cEntityList::const_iterator itr = m_EntitiesToAdd.begin(), end = m_EntitiesToAdd.end(); itr != end; ++itr)
{
if ((*itr)->GetUniqueID() == a_UniqueID)
{
return true;
}
} // for itr - m_EntitiesToAdd[]
}
// Check if the entity is in the chunkmap:
return m_ChunkMap->HasEntity(a_UniqueID);
}
@ -3021,7 +3044,7 @@ int cWorld::SpawnMobFinalize(cMonster * a_Monster)
delete a_Monster;
return -1;
}
if (!a_Monster->Initialize(this))
if (!a_Monster->Initialize(*this))
{
delete a_Monster;
return -1;
@ -3043,7 +3066,7 @@ int cWorld::CreateProjectile(double a_PosX, double a_PosY, double a_PosZ, cProje
{
return -1;
}
if (!Projectile->Initialize(this))
if (!Projectile->Initialize(*this))
{
delete Projectile;
return -1;
@ -3173,6 +3196,60 @@ cFluidSimulator * cWorld::InitializeFluidSimulator(cIniFile & a_IniFile, const c
void cWorld::AddQueuedPlayers(void)
{
ASSERT(m_TickThread.IsCurrentThread());
// Grab the list of players to add, it has to be locked to access it:
cPlayerList PlayersToAdd;
{
cCSLock Lock(m_CSPlayersToAdd);
std::swap(PlayersToAdd, m_PlayersToAdd);
}
// Add all the players in the grabbed list:
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr)
{
ASSERT(std::find(m_Players.begin(), m_Players.end(), *itr) == m_Players.end()); // Is it already in the list? HOW?
m_Players.push_back(*itr);
(*itr)->SetWorld(this);
// Add to chunkmap, if not already there (Spawn vs MoveToWorld):
m_ChunkMap->AddEntityIfNotPresent(*itr);
} // for itr - PlayersToAdd[]
} // Lock(m_CSPlayers)
// Add all the players' clienthandles:
{
cCSLock Lock(m_CSClients);
for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr)
{
cClientHandle * Client = (*itr)->GetClientHandle();
if (Client != NULL)
{
m_Clients.push_back(Client);
}
} // for itr - PlayersToAdd[]
} // Lock(m_CSClients)
// Stream chunks to all eligible clients:
for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr)
{
cClientHandle * Client = (*itr)->GetClientHandle();
if (Client != NULL)
{
Client->StreamChunks();
}
} // for itr - PlayersToAdd[]
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cWorld::cTaskSaveAllChunks:
@ -3313,4 +3390,3 @@ void cWorld::cChunkGeneratorCallbacks::CallHookChunkGenerated (cChunkDesc & a_Ch

View File

@ -284,8 +284,15 @@ public:
void CollectPickupsByPlayer(cPlayer * a_Player);
void AddPlayer( cPlayer* a_Player );
void RemovePlayer( cPlayer* a_Player );
/** Adds the player to the world.
Uses a queue to store the player object until the Tick thread processes the addition event.
Also adds the player as an entity in the chunkmap, and the player's ClientHandle, if any, for ticking. */
void AddPlayer(cPlayer * a_Player);
/** Removes the player from the world.
Removes the player from the addition queue, too, if appropriate.
If the player has a ClientHandle, the ClientHandle is removed from all chunks in the world and will not be ticked by this world anymore. */
void RemovePlayer(cPlayer * a_Player);
/** Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true */
virtual bool ForEachPlayer(cPlayerListCallback & a_Callback) override; // >> EXPORTED IN MANUALBINDINGS <<
@ -301,7 +308,8 @@ public:
void SendPlayerList(cPlayer * a_DestPlayer); // Sends playerlist to the player
/** Adds the entity into its appropriate chunk; takes ownership of the entity ptr */
/** Adds the entity into its appropriate chunk; takes ownership of the entity ptr.
The entity is added lazily - this function only puts it in a queue that is then processed by the Tick thread. */
void AddEntity(cEntity * a_Entity);
bool HasEntity(int a_UniqueID);
@ -961,6 +969,18 @@ private:
/** Clients that are scheduled for adding, waiting for TickClients to add them */
cClientHandleList m_ClientsToAdd;
/** Guards m_EntitiesToAdd */
cCriticalSection m_CSEntitiesToAdd;
/** List of entities that are scheduled for adding, waiting for the Tick thread to add them. */
cEntityList m_EntitiesToAdd;
/** Guards m_PlayersToAdd */
cCriticalSection m_CSPlayersToAdd;
/** List of players that are scheduled for adding, waiting for the Tick thread to add them. */
cPlayerList m_PlayersToAdd;
cWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_OverworldName = "");
virtual ~cWorld();
@ -998,6 +1018,10 @@ private:
/** Creates a new redstone simulator.*/
cRedstoneSimulator * InitializeRedstoneSimulator(cIniFile & a_IniFile);
/** Adds the players queued in the m_PlayersToAdd queue into the m_Players list.
Assumes it is called from the Tick thread. */
void AddQueuedPlayers(void);
}; // tolua_export