Exemple #1
0
void cChicken::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (IsBaby())
	{
		return;  // Babies don't lay eggs
	}

	if (
		((m_EggDropTimer == 6000) && GetRandomProvider().RandBool()) ||
		m_EggDropTimer == 12000
	)
	{
		cItems Drops;
		m_EggDropTimer = 0;
		Drops.push_back(cItem(E_ITEM_EGG, 1));
		m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ(), 10);
	}
	else
	{
		m_EggDropTimer++;
	}
}
Exemple #2
0
void cPawn::TargetingMe(cMonster * a_Monster)
{
	ASSERT(IsTicking());
	ASSERT(m_TargetingMe.size() < 10000);
	ASSERT(a_Monster->GetTarget() == this);
	m_TargetingMe.push_back(a_Monster);
}
Exemple #3
0
void cMinecartWithFurnace::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (m_IsFueled)
	{
		m_FueledTimeLeft--;
		if (m_FueledTimeLeft < 0)
		{
			m_IsFueled = false;
			m_World->BroadcastEntityMetadata(*this);
			return;
		}

		if (GetSpeed().Length() > 6)
		{
			return;
		}
		AddSpeed(GetSpeed() / 4);
	}
}
Exemple #4
0
void cWither::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (m_WitherInvulnerableTicks > 0)
	{
		unsigned int NewTicks = m_WitherInvulnerableTicks - 1;

		if (NewTicks == 0)
		{
			m_World->DoExplosionAt(7.0, GetPosX(), GetPosY(), GetPosZ(), false, esWitherBirth, this);
		}

		m_WitherInvulnerableTicks = NewTicks;

		if ((NewTicks % 10) == 0)
		{
			Heal(10);
		}
	}

	m_World->BroadcastEntityMetadata(*this);
}
Exemple #5
0
void cCreeper::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (((GetTarget() == nullptr) || !TargetIsInRange()) && !m_BurnedWithFlintAndSteel)
	{
		if (m_bIsBlowing)
		{
			m_ExplodingTimer = 0;
			m_bIsBlowing = false;
			m_World->BroadcastEntityMetadata(*this);
		}
	}
	else
	{
		if (m_bIsBlowing)
		{
			m_ExplodingTimer += 1;
		}

		if ((m_ExplodingTimer == 30) && (GetHealth() > 0.0))  // only explode when not already dead
		{
			m_World->DoExplosionAt((m_bIsCharged ? 5 : 3), GetPosX(), GetPosY(), GetPosZ(), false, esMonster, this);
			Destroy();  // Just in case we aren't killed by the explosion
		}
	}
}
Exemple #6
0
void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (!IsTame() && !IsBaby())
	{
		if (m_CheckPlayerTickCount == 23)
		{
			m_World->DoWithNearestPlayer(GetPosition(), 10, [&](cPlayer & a_Player) -> bool
			{
				cItems Items;
				GetBreedingItems(Items);
				if (Items.ContainsType(a_Player.GetEquippedItem().m_ItemType))
				{
					if (!IsBegging())
					{
						SetIsBegging(true);
						m_World->BroadcastEntityMetadata(*this);
					}

					MoveToPosition(a_Player.GetPosition());
				}
				else
				{
					if (IsBegging())
					{
						SetIsBegging(false);
						m_World->BroadcastEntityMetadata(*this);
					}
				}

				return true;
			}, true);
			m_CheckPlayerTickCount = 0;
		}
		else
		{
			m_CheckPlayerTickCount++;
		}
	}

	if (IsTame() && !IsSitting())
	{
		TickFollowPlayer();
	}
	else if (IsSitting())
	{
		StopMovingToPosition();
	}

	m_World->BroadcastEntityMetadata(*this);
}
void cProjectileEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
    super::Tick(a_Dt, a_Chunk);
    if (!IsTicking())
    {
        // The base class tick destroyed us
        return;
    }
    BroadcastMovementUpdate();
}
Exemple #8
0
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_HasTeleported)  // Sent a teleport already, don't do again
		{
			if (m_HitGroundTimer > std::chrono::milliseconds(500))
			{
				m_World->BroadcastTeleportEntity(*this);
				m_HasTeleported = true;
			}
			else
			{
				m_HitGroundTimer += a_Dt;
			}
		}

		int RelPosX = m_HitBlockPos.x - a_Chunk.GetPosX() * cChunkDef::Width;
		int RelPosZ = m_HitBlockPos.z - a_Chunk.GetPosZ() * cChunkDef::Width;
		cChunk * Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelPosX, RelPosZ);

		if (Chunk == nullptr)
		{
			// Inside an unloaded chunk, abort
			return;
		}

		if (Chunk->GetBlock(RelPosX, m_HitBlockPos.y, RelPosZ) == E_BLOCK_AIR)  // Block attached to was destroyed?
		{
			m_IsInGround = false;  // Yes, begin simulating physics again
		}
	}
}
Exemple #9
0
void cPawn::NoLongerTargetingMe(cMonster * a_Monster)
{
	ASSERT(IsTicking());  // Our destroy override is supposed to clear all targets before we're destroyed.
	for (auto i = m_TargetingMe.begin(); i != m_TargetingMe.end(); ++i)
	{
		cMonster * Monster = *i;
		if (Monster == a_Monster)
		{
			ASSERT(Monster->GetTarget() != this);  // The monster is notifying us it is no longer targeting us, assert if that's a lie
			m_TargetingMe.erase(i);
			return;
		}
	}
	ASSERT(false);  // If this happens, something is wrong. Perhaps the monster never called TargetingMe() or called NoLongerTargetingMe() twice.
}
Exemple #10
0
void cTNTEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}
	BroadcastMovementUpdate();

	m_FuseTicks -= 1;
	if (m_FuseTicks <= 0)
	{
		Explode();
	}
}
Exemple #11
0
void cPig::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	// If the attachee player is holding a carrot-on-stick, let them drive this pig:
	if (m_bIsSaddled && (m_Attachee != nullptr))
	{
		if (m_Attachee->IsPlayer() && (m_Attachee->GetEquippedWeapon().m_ItemType == E_ITEM_CARROT_ON_STICK))
		{
			MoveToPosition((m_Attachee->GetPosition()) + (m_Attachee->GetLookVector()*10));
		}
	}
}
Exemple #12
0
void cSnowGolem::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}
	if (IsBiomeNoDownfall(m_World->GetBiomeAt(POSX_TOINT, POSZ_TOINT)))
	{
		TakeDamage(*this);
	}
	else
	{
		BLOCKTYPE BlockBelow = m_World->GetBlock(POSX_TOINT, POSY_TOINT - 1, POSZ_TOINT);
		BLOCKTYPE Block = m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT);
		if ((Block == E_BLOCK_AIR) && cBlockInfo::IsSolid(BlockBelow))
		{
			m_World->SetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT, E_BLOCK_SNOW, 0);
		}
	}
}
void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (m_EMState == CHASING)
	{
		CheckEventLostPlayer();
	}
	else
	{
		CheckEventSeePlayer(a_Chunk);
	}

	auto target = GetTarget();
	if (target == nullptr)
	{
		return;
	}

	// TODO: Currently all mobs see through lava, but only Nether-native mobs should be able to.
	Vector3d MyHeadPosition = GetPosition() + Vector3d(0, GetHeight(), 0);
	Vector3d TargetPosition = target->GetPosition() + Vector3d(0, target->GetHeight(), 0);
	if (
		TargetIsInRange() &&
		cLineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetPosition, cLineBlockTracer::losAirWaterLava) &&
		(GetHealth() > 0.0)
	)
	{
		// Attack if reached destination, target isn't null, and have a clear line of sight to target (so won't attack through walls)
		Attack(a_Dt);
	}
}
Exemple #14
0
/** Sets the target. */
void cMonster::SetTarget (cPawn * a_NewTarget)
{
	ASSERT((a_NewTarget == nullptr) || (IsTicking()));
	if (m_Target == a_NewTarget)
	{
		return;
	}
	cPawn * OldTarget = m_Target;
	m_Target = a_NewTarget;

	if (OldTarget != nullptr)
	{
		// Notify the old target that we are no longer targeting it.
		OldTarget->NoLongerTargetingMe(this);
	}

	if (a_NewTarget != nullptr)
	{
		ASSERT(a_NewTarget->IsTicking());
		// Notify the new target that we are now targeting it.
		m_Target->TargetingMe(this);
	}

}
Exemple #15
0
void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	if (!IsAngry())
	{
		cMonster::Tick(a_Dt, a_Chunk);
		if (m_NotificationCooldown > 0)
		{
			m_NotificationCooldown -= 1;
		}
	}
	else
	{
		super::Tick(a_Dt, a_Chunk);
	}

	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (GetTarget() == nullptr)
	{
		m_World->DoWithNearestPlayer(GetPosition(), static_cast<float>(m_SightDistance), [&](cPlayer & a_Player) -> bool
		{
			switch (a_Player.GetEquippedItem().m_ItemType)
			{
				case E_ITEM_BONE:
				case E_ITEM_RAW_BEEF:
				case E_ITEM_STEAK:
				case E_ITEM_RAW_CHICKEN:
				case E_ITEM_COOKED_CHICKEN:
				case E_ITEM_ROTTEN_FLESH:
				case E_ITEM_RAW_PORKCHOP:
				case E_ITEM_COOKED_PORKCHOP:
				{
					if (!IsBegging())
					{
						SetIsBegging(true);
						m_World->BroadcastEntityMetadata(*this);
					}

					m_FinalDestination = a_Player.GetPosition();  // So that we will look at a player holding food

					// Don't move to the player if the wolf is sitting.
					if (!IsSitting())
					{
						MoveToPosition(a_Player.GetPosition());
					}

					break;
				}
				default:
				{
					if (IsBegging())
					{
						SetIsBegging(false);
						m_World->BroadcastEntityMetadata(*this);
					}
				}
			}

			return true;
		});
	}
	else
	{
		if (IsSitting())
		{
			SetTarget(nullptr);
		}
		else
		{
			MoveToPosition(GetTarget()->GetPosition());
			if (TargetIsInRange())
			{
				Attack(a_Dt);
			}
		}
	}

	if (IsTame() && !IsSitting())
	{
		TickFollowPlayer();
	}
	else if (IsSitting())
	{
		StopMovingToPosition();
	}
}
Exemple #16
0
void cPawn::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	std::vector<cEntityEffect *> EffectsToTick;

	// Iterate through this entity's applied effects
	for (tEffectMap::iterator iter = m_EntityEffects.begin(); iter != m_EntityEffects.end();)
	{
		// Copies values to prevent pesky wrong accesses and erasures
		cEntityEffect::eType EffectType = iter->first;
		cEntityEffect * Effect = iter->second.get();

		// Iterates (must be called before any possible erasure)
		++iter;

		// Remove effect if duration has elapsed
		if (Effect->GetDuration() - Effect->GetTicks() <= 0)
		{
			RemoveEntityEffect(EffectType);
		}
		// Call OnTick later to make sure the iterator won't be invalid
		else
		{
			EffectsToTick.push_back(Effect);
		}

		// TODO: Check for discrepancies between client and server effect values
	}


	for (auto * Effect : EffectsToTick)
	{
		Effect->OnTick(*this);
	}

	// Spectators cannot push entities around
	if ((!IsPlayer()) || (!static_cast<cPlayer *>(this)->IsGameModeSpectator()))
	{
		m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), [=](cEntity & a_Entity)
			{
				if (a_Entity.GetUniqueID() == GetUniqueID())
				{
					return false;
				}

				// we only push other mobs, boats and minecarts
				if ((a_Entity.GetEntityType() != etMonster) && (a_Entity.GetEntityType() != etMinecart) && (a_Entity.GetEntityType() != etBoat))
				{
					return false;
				}

				// do not push a boat / minecart you're sitting in
				if (IsAttachedTo(&a_Entity))
				{
					return false;
				}

				Vector3d v3Delta = a_Entity.GetPosition() - GetPosition();
				v3Delta.y = 0.0;  // we only push sideways
				v3Delta *= 1.0 / (v3Delta.Length() + 0.01);  // we push harder if we're close
				// QUESTION: is there an additional multiplier for this? current shoving seems a bit weak

				a_Entity.AddSpeed(v3Delta);
				return false;
			}
		);
	}

	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}
	HandleFalling();
}
void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (m_EMState == ESCAPING)
	{
		CheckEventLostPlayer();
	}

	// if we have a partner, mate
	if (m_LovePartner != nullptr)
	{

		if (m_MatingTimer > 0)
		{
			// If we should still mate, keep bumping into them until baby is made
			Vector3d Pos = m_LovePartner->GetPosition();
			MoveToPosition(Pos);
		}
		else
		{
			// Mating finished. Spawn baby
			Vector3f Pos = (GetPosition() + m_LovePartner->GetPosition()) * 0.5;
			UInt32 BabyID = m_World->SpawnMob(Pos.x, Pos.y, Pos.z, GetMobType(), true);

			cPassiveMonster * Baby = nullptr;

			m_World->DoWithEntityByID(BabyID, [&](cEntity & a_Entity)
				{
					Baby = static_cast<cPassiveMonster *>(&a_Entity);
					return true;
				}
			);

			if (Baby != nullptr)
			{
				Baby->InheritFromParents(this, m_LovePartner);
			}

			m_World->SpawnExperienceOrb(Pos.x, Pos.y, Pos.z, GetRandomProvider().RandInt(1, 6));

			m_LovePartner->ResetLoveMode();
			ResetLoveMode();
		}
	}
	else
	{
		// We have no partner, so we just chase the player if they have our breeding item
		cItems FollowedItems;
		GetFollowedItems(FollowedItems);
		if (FollowedItems.Size() > 0)
		{
			cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance));
			if (a_Closest_Player != nullptr)
			{
				cItem EquippedItem = a_Closest_Player->GetEquippedItem();
				if (FollowedItems.ContainsType(EquippedItem))
				{
					Vector3d PlayerPos = a_Closest_Player->GetPosition();
					MoveToPosition(PlayerPos);
				}
			}
		}
	}

	// If we are in love mode but we have no partner, search for a partner neabry
	if (m_LoveTimer > 0)
	{
		if (m_LovePartner == nullptr)
		{
			m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), 8, 8), [=](cEntity & a_Entity)
				{
					// If the entity is not a monster, don't breed with it
					// Also, do not self-breed
					if ((a_Entity.GetEntityType() != etMonster) || (&a_Entity == this))
					{
						return false;
					}

					auto & Me = static_cast<cPassiveMonster&>(*this);
					auto & PotentialPartner = static_cast<cPassiveMonster&>(a_Entity);

					// If the potential partner is not of the same species, don't breed with it
					if (PotentialPartner.GetMobType() != Me.GetMobType())
					{
						return false;
					}

					// If the potential partner is not in love
					// Or they already have a mate, do not breed with them
					if ((!PotentialPartner.IsInLove()) || (PotentialPartner.GetPartner() != nullptr))
					{
						return false;
					}

					// All conditions met, let's breed!
					PotentialPartner.EngageLoveMode(&Me);
					Me.EngageLoveMode(&PotentialPartner);
					return true;
				}
			);
		}

		m_LoveTimer--;
	}
	if (m_MatingTimer > 0)
	{
		m_MatingTimer--;
	}
	if (m_LoveCooldown > 0)
	{
		m_LoveCooldown--;
	}
}
Exemple #18
0
void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}
	GET_AND_VERIFY_CURRENT_CHUNK(Chunk, POSX_TOINT, POSZ_TOINT);

	ASSERT((GetTarget() == nullptr) || (GetTarget()->IsPawn() && (GetTarget()->GetWorld() == GetWorld())));
	if (m_AttackCoolDownTicksLeft > 0)
	{
		m_AttackCoolDownTicksLeft -= 1;
	}

	if (m_Health <= 0)
	{
		// The mob is dead, but we're still animating the "puff" they leave when they die
		m_DestroyTimer += a_Dt;
		if (m_DestroyTimer > std::chrono::seconds(1))
		{
			Destroy(true);
		}
		return;
	}

	if (m_TicksSinceLastDamaged < 100)
	{
		++m_TicksSinceLastDamaged;
	}
	if ((GetTarget() != nullptr))
	{
		ASSERT(GetTarget()->IsTicking());

		if (GetTarget()->IsPlayer())
		{
			if (static_cast<cPlayer *>(GetTarget())->IsGameModeCreative())
			{
				SetTarget(nullptr);
				m_EMState = IDLE;
			}
		}
	}

	// Process the undead burning in daylight.
	HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk));

	bool a_IsFollowingPath = false;
	if (m_PathfinderActivated)
	{
		if (ReachedFinalDestination())
		{
			StopMovingToPosition();  // Simply sets m_PathfinderActivated to false.
		}
		else
		{
			// Note that m_NextWayPointPosition is actually returned by GetNextWayPoint)
			switch (m_PathFinder.GetNextWayPoint(*Chunk, GetPosition(), &m_FinalDestination, &m_NextWayPointPosition, m_EMState == IDLE ? true : false))
			{
				case ePathFinderStatus::PATH_FOUND:
				{
					/* If I burn in daylight, and I won't burn where I'm standing, and I'll burn in my next position, and at least one of those is true:
					1. I am idle
					2. I was not hurt by a player recently.
					Then STOP. */
					if (
						m_BurnsInDaylight && ((m_TicksSinceLastDamaged >= 100) || (m_EMState == IDLE)) &&
						WouldBurnAt(m_NextWayPointPosition, *Chunk) &&
						!WouldBurnAt(GetPosition(), *Chunk)
					)
					{
						// If we burn in daylight, and we would burn at the next step, and we won't burn where we are right now, and we weren't provoked recently:
						StopMovingToPosition();
					}
					else
					{
						a_IsFollowingPath = true;  // Used for proper body / head orientation only.
						MoveToWayPoint(*Chunk);
					}
					break;
				}
				case ePathFinderStatus::PATH_NOT_FOUND:
				{
					StopMovingToPosition();
					break;
				}
				default:
				{

				}
			}
		}
	}

	SetPitchAndYawFromDestination(a_IsFollowingPath);

	switch (m_EMState)
	{
		case IDLE:
		{
			// If enemy passive we ignore checks for player visibility.
			InStateIdle(a_Dt, a_Chunk);
			break;
		}
		case CHASING:
		{
			// If we do not see a player anymore skip chasing action.
			InStateChasing(a_Dt, a_Chunk);
			break;
		}
		case ESCAPING:
		{
			InStateEscaping(a_Dt, a_Chunk);
			break;
		}
		case ATTACKING: break;
	}  // switch (m_EMState)

	BroadcastMovementUpdate();

	if (m_AgingTimer > 0)
	{
		m_AgingTimer--;
		if ((m_AgingTimer <= 0) && IsBaby())
		{
			SetAge(1);
			m_World->BroadcastEntityMetadata(*this);
		}
	}
}
Exemple #19
0
void cHorse::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	if (!m_bIsMouthOpen)
	{
		if (m_World->GetTickRandomNumber(50) == 25)
		{
			m_bIsMouthOpen = true;
		}
	}
	else
	{
		if (m_World->GetTickRandomNumber(10) == 5)
		{
			m_bIsMouthOpen = false;
		}
	}

	if ((m_Attachee != nullptr) && (!m_bIsTame))
	{
		if (m_TameAttemptTimes < m_TimesToTame)
		{
			if (m_World->GetTickRandomNumber(50) == 25)
			{
				m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, FloorC(GetPosX()), FloorC(GetPosY()), FloorC(GetPosZ()), int(SmokeDirection::SOUTH_EAST));
				m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, FloorC(GetPosX()), FloorC(GetPosY()), FloorC(GetPosZ()), int(SmokeDirection::SOUTH_WEST));
				m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, FloorC(GetPosX()), FloorC(GetPosY()), FloorC(GetPosZ()), int(SmokeDirection::NORTH_EAST));
				m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, FloorC(GetPosX()), FloorC(GetPosY()), FloorC(GetPosZ()), int(SmokeDirection::NORTH_WEST));

				m_Attachee->Detach();
				m_bIsRearing = true;
			}
		}
		else
		{
			// TODO: emit hearts here
			m_bIsTame = true;
		}
	}

	if (m_bIsRearing)
	{
		if (m_RearTickCount == 20)
		{
			m_bIsRearing = false;
			m_RearTickCount = 0;
		}
		else
		{
			m_RearTickCount++;
		}
	}

	m_World->BroadcastEntityMetadata(*this);
}
Exemple #20
0
void cMinecart::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	ASSERT(IsTicking());

	int PosY = POSY_TOINT;
	if ((PosY <= 0) || (PosY >= cChunkDef::Height))
	{
		// Outside the world, just process normal falling physics
		super::HandlePhysics(a_Dt, a_Chunk);
		BroadcastMovementUpdate();
		return;
	}

	int RelPosX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width;
	int RelPosZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width;
	cChunk * Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelPosX, RelPosZ);
	if (Chunk == nullptr)
	{
		// Inside an unloaded chunk, bail out all processing
		return;
	}

	BLOCKTYPE InsideType;
	NIBBLETYPE InsideMeta;
	Chunk->GetBlockTypeMeta(RelPosX, PosY, RelPosZ, InsideType, InsideMeta);

	if (!IsBlockRail(InsideType))
	{
		// When a descending minecart hits a flat rail, it goes through the ground; check for this
		Chunk->GetBlockTypeMeta(RelPosX, PosY + 1, RelPosZ, InsideType, InsideMeta);
		if (IsBlockRail(InsideType))
		{
			// Push cart upwards
			AddPosY(1);
		}
	}

	bool WasDetectorRail = false;
	if (IsBlockRail(InsideType))
	{
		if (InsideType == E_BLOCK_RAIL)
		{
			SnapToRail(InsideMeta);
		}
		else
		{
			SnapToRail(InsideMeta & 0x07);
		}

		switch (InsideType)
		{
			case E_BLOCK_RAIL: HandleRailPhysics(InsideMeta, a_Dt); break;
			case E_BLOCK_ACTIVATOR_RAIL: break;
			case E_BLOCK_POWERED_RAIL: HandlePoweredRailPhysics(InsideMeta); break;
			case E_BLOCK_DETECTOR_RAIL:
			{
				HandleDetectorRailPhysics(InsideMeta, a_Dt);
				WasDetectorRail = true;
				break;
			}
			default: VERIFY(!"Unhandled rail type despite checking if block was rail!"); break;
		}

		AddPosition(GetSpeed() * (static_cast<double>(a_Dt.count()) / 1000));  // Commit changes; as we use our own engine when on rails, this needs to be done, whereas it is normally in Entity.cpp
	}
	else
	{
		// Not on rail, default physics
		SetPosY(floor(GetPosY()) + 0.35);  // HandlePhysics overrides this if minecart can fall, else, it is to stop ground clipping minecart bottom when off-rail
		super::HandlePhysics(a_Dt, *Chunk);
	}

	if (m_bIsOnDetectorRail && !Vector3i(POSX_TOINT, POSY_TOINT, POSZ_TOINT).Equals(m_DetectorRailPosition))
	{
		m_World->SetBlock(m_DetectorRailPosition.x, m_DetectorRailPosition.y, m_DetectorRailPosition.z, E_BLOCK_DETECTOR_RAIL, m_World->GetBlockMeta(m_DetectorRailPosition) & 0x07);
		m_bIsOnDetectorRail = false;
	}
	else if (WasDetectorRail)
	{
		m_bIsOnDetectorRail = true;
		m_DetectorRailPosition = Vector3i(POSX_TOINT, POSY_TOINT, POSZ_TOINT);
	}

	// Broadcast positioning changes to client
	BroadcastMovementUpdate();
}
void cProjectileEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
    if (m_IsInGround)
    {
        // Already-grounded projectiles don't move at all
        return;
    }

    auto DtSec = std::chrono::duration_cast<std::chrono::duration<double>>(a_Dt);

    const Vector3d DeltaSpeed = GetSpeed() * DtSec.count();
    const Vector3d Pos = GetPosition();
    const Vector3d NextPos = Pos + DeltaSpeed;

    // Test for entity collisions:
    cProjectileEntityCollisionCallback EntityCollisionCallback(this, Pos, NextPos);
    a_Chunk.ForEachEntity(EntityCollisionCallback);
    if (EntityCollisionCallback.HasHit())
    {
        // An entity was hit:
        Vector3d HitPos = Pos + (NextPos - Pos) * EntityCollisionCallback.GetMinCoeff();

        // DEBUG:
        LOGD("Projectile %d has hit an entity %d (%s) at {%.02f, %.02f, %.02f} (coeff %.03f)",
             m_UniqueID,
             EntityCollisionCallback.GetHitEntity()->GetUniqueID(),
             EntityCollisionCallback.GetHitEntity()->GetClass(),
             HitPos.x, HitPos.y, HitPos.z,
             EntityCollisionCallback.GetMinCoeff()
            );

        OnHitEntity(*(EntityCollisionCallback.GetHitEntity()), HitPos);
        if (!IsTicking())
        {
            return;  // We were destroyed by an override of OnHitEntity
        }
    }
    // TODO: Test the entities in the neighboring chunks, too

    // Trace the tick's worth of movement as a line:
    cProjectileTracerCallback TracerCallback(this);
    if (!cLineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos))
    {
        // Something has been hit, abort all other processing
        return;
    }
    // The tracer also checks the blocks for slowdown blocks - water and lava - and stores it for later in its SlowdownCoeff

    // Update the position:
    SetPosition(NextPos);

    // Add slowdown and gravity effect to the speed:
    Vector3d NewSpeed(GetSpeed());
    NewSpeed.y += m_Gravity * DtSec.count();
    NewSpeed -= NewSpeed * (m_AirDrag * 20.0f) * DtSec.count();
    SetSpeed(NewSpeed);
    SetYawFromSpeed();
    SetPitchFromSpeed();

    /*
    LOGD("Projectile %d: pos {%.02f, %.02f, %.02f}, speed {%.02f, %.02f, %.02f}, rot {%.02f, %.02f}",
    	m_UniqueID,
    	GetPosX(), GetPosY(), GetPosZ(),
    	GetSpeedX(), GetSpeedY(), GetSpeedZ(),
    	GetYaw(), GetPitch()
    );
    */
}
Exemple #22
0
void cHorse::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	super::Tick(a_Dt, a_Chunk);
	if (!IsTicking())
	{
		// The base class tick destroyed us
		return;
	}

	auto & Random = GetRandomProvider();

	if (!m_bIsMouthOpen)
	{
		if (Random.RandBool(0.02))
		{
			m_bIsMouthOpen = true;
		}
	}
	else
	{
		if (Random.RandBool(0.10))
		{
			m_bIsMouthOpen = false;
		}
	}

	if ((m_Attachee != nullptr) && (!m_bIsTame))
	{
		if (m_TameAttemptTimes < m_TimesToTame)
		{
			if (Random.RandBool(0.02))
			{
				m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, GetPosition().Floor(), int(SmokeDirection::SOUTH_EAST));
				m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, GetPosition().Floor(), int(SmokeDirection::SOUTH_WEST));
				m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, GetPosition().Floor(), int(SmokeDirection::NORTH_EAST));
				m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, GetPosition().Floor(), int(SmokeDirection::NORTH_WEST));

				m_World->BroadcastSoundEffect("entity.horse.angry", GetPosition(), 1.0f, 1.0f);
				m_Attachee->Detach();
				m_bIsRearing = true;
			}
		}
		else
		{
			m_World->BroadcastParticleEffect("heart", static_cast<Vector3f>(GetPosition()), Vector3f{}, 0, 5);
			m_bIsTame = true;
		}
	}

	if (m_bIsRearing)
	{
		if (m_RearTickCount == 20)
		{
			m_bIsRearing = false;
			m_RearTickCount = 0;
		}
		else
		{
			m_RearTickCount++;
		}
	}

	m_World->BroadcastEntityMetadata(*this);
}