summaryrefslogtreecommitdiffstats
path: root/src/Entities/ArrowEntity.cpp
blob: 5f8988b61b657318d7baae04fe21b26b055dbe0c (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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#include "Globals.h"  // NOTE: MSVC stupidness requires this to be the same across all modules

#include "Player.h"
#include "ArrowEntity.h"
#include "../Chunk.h"
#include "../Blocks/BlockButton.h"





cArrowEntity::cArrowEntity(cEntity * a_Creator, Vector3d a_Pos, Vector3d a_Speed) :
	Super(pkArrow, a_Creator, a_Pos, a_Speed, 0.5f, 0.5f),
	m_PickupState(psNoPickup),
	m_DamageCoeff(2),
	m_IsCritical(false),
	m_Timer(0),
	m_bIsCollected(false)
{
	SetMass(0.1);
	SetGravity(-20.0f);

	FLOGD(
		"Created arrow {0} with speed {1:.02f} and rot {{{2:.02f}, {3:.02f}}}",
		m_UniqueID,
		GetSpeed(),
		GetYaw(),
		GetPitch()
	);
}





cArrowEntity::cArrowEntity(cPlayer & a_Player, double a_Force) :
	cArrowEntity(&a_Player, a_Player.GetThrowStartPos(), a_Player.GetThrowSpeed(a_Force * 1.5 * 20))
{
	m_IsCritical = a_Force >= 1;
	m_PickupState = a_Player.IsGameModeCreative() ? psInCreative : psInSurvivalOrCreative;
}





bool cArrowEntity::CanPickup(const cPlayer & a_Player) const
{
	switch (m_PickupState)
	{
		case psNoPickup:             return false;
		case psInSurvivalOrCreative: return (a_Player.IsGameModeSurvival() || a_Player.IsGameModeCreative());
		case psInCreative:           return a_Player.IsGameModeCreative();
	}
	UNREACHABLE("Unsupported arrow pickup state");
}





void cArrowEntity::OnHitSolidBlock(Vector3d a_HitPos, eBlockFace a_HitFace)
{
	// Save the direction we're going in before Super resets it
	auto Speed = GetSpeed();
	Speed.Normalize();

	/*
	The line tracer returns the arrow hit position located on the face of a block;
	if the arrow hit a block at -5, 95, -5 then a_HitPos would start off as -4, 95.5, -4.1,
	i.e. it collided with the X face.

	First we subtract a bit of speed vector so it doesn't appear black clientside.

	Then we add a bit of speed vector to make a_HitPos -4.0001, 95.5, -4.1
	and floor to get exactly -5, 95, -5 which is stored in m_HitBlockPos.
	*/

	// Shift the arrow's position slightly back so that less than 50% of the hitbox
	// is in the block. Otherwise the arrow can appear black
	Super::OnHitSolidBlock(a_HitPos - (Speed / 100), a_HitFace);

	// Nudge into the block a tiny bit according to its direction of travel
	// Floor to give the coordinates of the block it crashed into
	m_HitBlockPos = (a_HitPos + (Speed / 100000)).Floor();

	// Broadcast arrow hit sound
	m_World->BroadcastSoundEffect(
		"entity.arrow.hit",
		m_HitBlockPos,
		0.5f,
		static_cast<float>(0.75 + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)
	);

	// Trigger any buttons that were hit
	// Wooden buttons will be depressed by the arrow
	cBlockButtonHandler::OnArrowHit(*m_World, m_HitBlockPos, a_HitFace);

	if ((m_World->GetBlock(m_HitBlockPos) == E_BLOCK_TNT) && IsOnFire())
	{
		m_World->SetBlock(m_HitBlockPos, E_BLOCK_AIR, 0);
		m_World->SpawnPrimedTNT(m_HitBlockPos);
	}
}





void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, Vector3d a_HitPos)
{
	Super::OnHitEntity(a_EntityHit, a_HitPos);

	int Damage = static_cast<int>(GetSpeed().Length() / 20 * m_DamageCoeff + 0.5);
	if (m_IsCritical)
	{
		Damage += m_World->GetTickRandomNumber(Damage / 2 + 2);
	}

	unsigned int PowerLevel = m_CreatorData.m_Enchantments.GetLevel(cEnchantments::enchPower);
	if (PowerLevel > 0)
	{
		int ExtraDamage = static_cast<int>(ceil(0.25 * (PowerLevel + 1)));
		Damage += ExtraDamage;
	}

	double Knockback = 10;

	unsigned int PunchLevel = m_CreatorData.m_Enchantments.GetLevel(cEnchantments::enchPunch);
	unsigned int PunchLevelMultiplier = 8;

	Knockback += PunchLevelMultiplier * PunchLevel;
	a_EntityHit.TakeDamage(dtRangedAttack, GetCreatorUniqueID(), Damage, Knockback);

	if (IsOnFire() && !a_EntityHit.IsInWater())
	{
		a_EntityHit.StartBurning(100);
	}

	// Broadcast successful hit sound
	GetWorld()->BroadcastSoundEffect(
		"entity.arrow.hit",
		GetPosition(),
		0.5,
		static_cast<float>(0.75 + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)
	);

	Destroy();
}





void cArrowEntity::CollectedBy(cPlayer & a_Dest)
{
	if (m_IsInGround && !m_bIsCollected && CanPickup(a_Dest))
	{
		// Do not add the arrow to the inventory when the player is in creative:
		if (!a_Dest.IsGameModeCreative())
		{
			int NumAdded = a_Dest.GetInventory().AddItem(cItem(E_ITEM_ARROW));
			if (NumAdded == 0)
			{
				// No space in the inventory
				return;
			}
		}

		GetWorld()->BroadcastCollectEntity(*this, a_Dest, 1);
		GetWorld()->BroadcastSoundEffect(
			"entity.item.pickup",
			GetPosition(),
			0.3f,
			(1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)
		);
		m_bIsCollected = true;
	}
}





void cArrowEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	Super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}
	m_Timer += a_Dt;

	if (m_bIsCollected)
	{
		if (m_Timer > std::chrono::milliseconds(500))
		{
			Destroy();
			return;
		}
	}
	else if (m_Timer > std::chrono::minutes(5))
	{
		Destroy();
		return;
	}

	if (m_IsInGround)
	{
		if (m_World->GetBlock(m_HitBlockPos) == E_BLOCK_AIR)  // Block attached to was destroyed?
		{
			m_IsInGround = false;  // Yes, begin simulating physics again
		}
	}
	else if (IsInWater())  // Arrow in water?
	{
		ApplyFriction(m_Speed, ARROW_WATER_FRICTION, static_cast<float>(a_Dt.count()));  // Yes, slow down arrow
	}
}





bool cArrowEntity::DoesPreventBlockPlacement(void) const
{
	return false;
}