summaryrefslogtreecommitdiffstats
path: root/src/Items/ItemLilypad.h
blob: b676b1ae6d0e6e30ec78bc74c6ff592b142402a6 (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

#pragma once

#include "ItemHandler.h"
#include "../Entities/Player.h"
#include "../LineBlockTracer.h"





class cItemLilypadHandler final : public cItemHandler
{
	using Super = cItemHandler;

  public:
	constexpr cItemLilypadHandler(int a_ItemType) :
		Super(a_ItemType)
	{
	}





	virtual bool IsPlaceable(void) const override
	{
		return false;  // Set as not placeable so OnItemUse is called
	}





	virtual bool OnItemUse(
		cWorld * a_World,
		cPlayer * a_Player,
		cBlockPluginInterface & a_PluginInterface,
		const cItem & a_HeldItem,
		const Vector3i a_ClickedBlockPos,
		eBlockFace a_ClickedBlockFace
	) const override
	{
		// The client sends BLOCK_FACE_NONE when it determines it should do a tracing-based placement.
		// Otherwise, a normal block face is sent.

		if (a_ClickedBlockFace != BLOCK_FACE_NONE)
		{
			// The position the client wants the lilypad placed.
			const auto PlacePos = AddFaceDirection(a_ClickedBlockPos, a_ClickedBlockFace);

			// Lilypad should not replace non air and non water blocks:
			if (const auto BlockToReplace = a_World->GetBlock(PlacePos); (BlockToReplace != E_BLOCK_AIR) &&
				(BlockToReplace != E_BLOCK_WATER) && (BlockToReplace != E_BLOCK_STATIONARY_WATER))
			{
				return false;
			}

			const auto Below = PlacePos.addedY(-1);
			if (Below.y < 0)
			{
				return false;
			}

			// Lilypad should be placed only if there is a water block below:
			if (const auto BlockBelow = a_World->GetBlock(Below);
				(BlockBelow != E_BLOCK_WATER) && (BlockBelow != E_BLOCK_STATIONARY_WATER))
			{
				return false;
			}

			a_World->SetBlock(PlacePos, E_BLOCK_LILY_PAD, 0);
			if (!a_Player->IsGameModeCreative())
			{
				a_Player->GetInventory().RemoveOneEquippedItem();
			}

			return true;
		}

		class cCallbacks : public cBlockTracer::cCallbacks
		{
		  public:
			virtual bool OnNextBlock(
				Vector3i a_CBBlockPos,
				BLOCKTYPE a_CBBlockType,
				NIBBLETYPE a_CBBlockMeta,
				eBlockFace a_CBEntryFace
			) override
			{
				if (!IsBlockWater(a_CBBlockType) || (a_CBBlockMeta != 0)  // The hit block should be a source
				)
				{
					// TODO: Vanilla stops the trace. However, we need to continue the trace, to work around our lack of
					// block bounding box support which would otherwise mean we misbehave when clicking through the
					// voxel a (e.g.) button occupies. Now, however, we misbehave when clicking on a block near water...
					// Nonetheless, the former would cause ghost blocks, so continue for now.

					// Ignore and continue trace:
					return false;
				}

				Position = AddFaceDirection(a_CBBlockPos, BLOCK_FACE_YP);  // Always place pad at top of water block
				return true;
			}

			Vector3i Position;

		} Callbacks;

		const auto EyePosition = a_Player->GetEyePosition();
		const auto End = EyePosition + a_Player->GetLookVector() * 5;
		if (cLineBlockTracer::Trace(*a_Player->GetWorld(), Callbacks, EyePosition, End))
		{
			// The line traced to completion; no suitable water was found:
			return false;
		}

		const auto BlockToReplace = a_World->GetBlock(Callbacks.Position);
		if (BlockToReplace != E_BLOCK_AIR)
		{
			// Lilypad should not replace non air blocks:
			return false;
		}

		a_World->SetBlock(Callbacks.Position, E_BLOCK_LILY_PAD, 0);
		if (!a_Player->IsGameModeCreative())
		{
			a_Player->GetInventory().RemoveOneEquippedItem();
		}

		return true;
	}
};