summaryrefslogtreecommitdiffstats
path: root/src/Blocks/BlockGrass.h
blob: 09af250c36f428812d6b054662bf138aefbe1f37 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170

#pragma once

#include "BlockHandler.h"
#include "../FastRandom.h"
#include "../Root.h"
#include "../Bindings/PluginManager.h"





class cBlockGrassHandler final :
	public cBlockHandler
{
public:

	using cBlockHandler::cBlockHandler;

private:

	enum class Survivability
	{
		// Light level so good that the grass can try to spread to neighbours
		CanSpread,

		// Stay put, light is enough to live on but not propagate
		DoNothing,

		// Insufficient light, death is upon us
		DieInDarkness
	};

	virtual cItems ConvertToPickups(const NIBBLETYPE a_BlockMeta, const cItem * const a_Tool) const override
	{
		if (!ToolHasSilkTouch(a_Tool))
		{
			return cItem(E_BLOCK_DIRT, 1, 0);
		}
		return cItem(E_BLOCK_GRASS, 1, 0);
	}





	virtual void OnUpdate(
		cChunkInterface & a_ChunkInterface,
		cWorldInterface & a_WorldInterface,
		cBlockPluginInterface & a_PluginInterface,
		cChunk & a_Chunk,
		const Vector3i a_RelPos
	) const override
	{
		if (!a_Chunk.IsLightValid())
		{
			a_Chunk.GetWorld()->QueueLightChunk(a_Chunk.GetPosX(), a_Chunk.GetPosZ());
			return;
		}

		switch (cBlockGrassHandler::DetermineSurvivability(a_Chunk, a_RelPos))
		{
			case Survivability::CanSpread: break;
			case Survivability::DoNothing: return;
			case Survivability::DieInDarkness:
			{
				a_Chunk.FastSetBlock(a_RelPos, E_BLOCK_DIRT, E_META_DIRT_NORMAL);
				return;
			}
		}

		// Grass spreads to adjacent dirt blocks:
		for (unsigned i = 0; i < 2; i++)  // Pick two blocks to grow to
		{
			auto & Random = GetRandomProvider();
			int OfsX = Random.RandInt(-1, 1);
			int OfsY = Random.RandInt(-3, 1);
			int OfsZ = Random.RandInt(-1, 1);

			cBlockGrassHandler::TrySpreadTo(a_Chunk, a_RelPos + Vector3i(OfsX, OfsY, OfsZ));
		}  // for i - repeat twice
	}





	virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) const override
	{
		UNUSED(a_Meta);
		return 1;
	}

private:

	/** Check if conditions are favourable to a grass block at the given position.
	If they are not, the grass dies and is turned to dirt.
	Returns whether conditions are so good that the grass can try to spread to neighbours. */
	static Survivability DetermineSurvivability(cChunk & a_Chunk, const Vector3i a_RelPos)
	{
		const auto AbovePos = a_RelPos.addedY(1);
		if (!cChunkDef::IsValidHeight(AbovePos.y))
		{
			return Survivability::CanSpread;
		}

		// Grass turns back to dirt when the block above it is not transparent or water.
		// It does not turn to dirt when a snow layer is above.
		const auto Above = a_Chunk.GetBlock(AbovePos);
		if (
			(Above != E_BLOCK_SNOW) &&
			(!cBlockInfo::IsTransparent(Above) || IsBlockWater(Above)))
		{
			return Survivability::DieInDarkness;
		}

		// Make sure that there is enough light at the source block to spread
		const auto Light = std::max(a_Chunk.GetBlockLight(AbovePos), a_Chunk.GetSkyLightAltered(AbovePos));
		return (Light >= 9) ? Survivability::CanSpread : Survivability::DoNothing;
	}





	/** Attempt to spread grass to a block at the given position. */
	static void TrySpreadTo(cChunk & a_Chunk, Vector3i a_RelPos)
	{
		if (!cChunkDef::IsValidHeight(a_RelPos.y))
		{
			// Y Coord out of range
			return;
		}

		auto Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(a_RelPos);
		if ((Chunk == nullptr) || !Chunk->IsValid())
		{
			// Unloaded chunk
			return;
		}

		BLOCKTYPE  DestBlock;
		NIBBLETYPE DestMeta;
		Chunk->GetBlockTypeMeta(a_RelPos, DestBlock, DestMeta);

		if ((DestBlock != E_BLOCK_DIRT) || (DestMeta != E_META_DIRT_NORMAL))
		{
			// Not a regular dirt block
			return;
		}

		const auto AbovePos = a_RelPos.addedY(1);
		const auto Above = Chunk->GetBlock(AbovePos);
		const auto Light = std::max(Chunk->GetBlockLight(AbovePos), Chunk->GetSkyLightAltered(AbovePos));

		if (
			(Light > 4) &&
			cBlockInfo::IsTransparent(Above) &&
			!IsBlockLava(Above) &&
			!IsBlockWaterOrIce(Above)
		)
		{
			const auto AbsPos = Chunk->RelativeToAbsolute(a_RelPos);
			if (!cRoot::Get()->GetPluginManager()->CallHookBlockSpread(*Chunk->GetWorld(), AbsPos, ssGrassSpread))
			{
				Chunk->FastSetBlock(a_RelPos, E_BLOCK_GRASS, 0);
			}
		}
	}
} ;