/// Moves items from a furnace above the hopper into this hopper. Returns true if contents have changed. bool cHopperEntity::MoveItemsFromFurnace(cChunk & a_Chunk) { cFurnaceEntity * Furnace = (cFurnaceEntity *)a_Chunk.GetBlockEntity(m_PosX, m_PosY + 1, m_PosZ); ASSERT(Furnace != NULL); // Try move from the output slot: if (MoveItemsFromSlot(*Furnace, cFurnaceEntity::fsOutput, true)) { cItem NewOutput(Furnace->GetOutputSlot()); Furnace->SetOutputSlot(NewOutput.AddCount(-1)); return true; } // No output moved, check if we can move an empty bucket out of the fuel slot: if (Furnace->GetFuelSlot().m_ItemType == E_ITEM_BUCKET) { if (MoveItemsFromSlot(*Furnace, cFurnaceEntity::fsFuel, true)) { Furnace->SetFuelSlot(cItem()); return true; } } // Nothing can be moved return false; }
bool cHopperEntity::MoveItemsFromFurnace(cChunk & a_Chunk) { cFurnaceEntity * Furnace = static_cast<cFurnaceEntity *>(a_Chunk.GetBlockEntity(m_PosX, m_PosY + 1, m_PosZ)); if (Furnace == nullptr) { LOGWARNING("%s: A furnace entity was not found where expected, at {%d, %d, %d}", __FUNCTION__, m_PosX, m_PosY + 1, m_PosZ); return false; } // Try move from the output slot: if (MoveItemsFromSlot(*Furnace, cFurnaceEntity::fsOutput)) { cItem NewOutput(Furnace->GetOutputSlot()); Furnace->SetOutputSlot(NewOutput.AddCount(-1)); return true; } // No output moved, check if we can move an empty bucket out of the fuel slot: if (Furnace->GetFuelSlot().m_ItemType == E_ITEM_BUCKET) { if (MoveItemsFromSlot(*Furnace, cFurnaceEntity::fsFuel)) { Furnace->SetFuelSlot(cItem()); return true; } } // Nothing can be moved return false; }
void cDropSpenserEntity::DropFromSlot(cChunk & a_Chunk, int a_SlotNum) { int DispX = m_PosX; int DispY = m_PosY; int DispZ = m_PosZ; NIBBLETYPE Meta = a_Chunk.GetMeta(m_RelX, m_PosY, m_RelZ); AddDropSpenserDir(DispX, DispY, DispZ, Meta); cItems Pickups; Pickups.push_back(m_Contents.RemoveOneItem(a_SlotNum)); const int PickupSpeed = m_World->GetTickRandomNumber(4) + 2; // At least 2, at most 6 int PickupSpeedX = 0, PickupSpeedY = 0, PickupSpeedZ = 0; switch (Meta) { case E_META_DROPSPENSER_FACING_YP: PickupSpeedY = PickupSpeed; break; case E_META_DROPSPENSER_FACING_YM: PickupSpeedY = -PickupSpeed; break; case E_META_DROPSPENSER_FACING_XM: PickupSpeedX = -PickupSpeed; break; case E_META_DROPSPENSER_FACING_XP: PickupSpeedX = PickupSpeed; break; case E_META_DROPSPENSER_FACING_ZM: PickupSpeedZ = -PickupSpeed; break; case E_META_DROPSPENSER_FACING_ZP: PickupSpeedZ = PickupSpeed; break; } double MicroX, MicroY, MicroZ; MicroX = DispX + 0.5; MicroY = DispY + 0.4; // Slightly less than half, to accomodate actual texture hole on DropSpenser MicroZ = DispZ + 0.5; m_World->SpawnItemPickups(Pickups, MicroX, MicroY, MicroZ, PickupSpeedX, PickupSpeedY, PickupSpeedZ); }
void cEntity::Tick(float a_Dt, cChunk & a_Chunk) { if (m_InvulnerableTicks > 0) { m_InvulnerableTicks--; } if (m_AttachedTo != NULL) { Vector3d DeltaPos = m_Pos - m_AttachedTo->GetPosition(); if (DeltaPos.Length() > 0.5) { SetPosition(m_AttachedTo->GetPosition()); if (IsPlayer()) { cPlayer * Player = (cPlayer *)this; Player->UpdateMovementStats(DeltaPos); } } } else { if (!a_Chunk.IsValid()) { return; } // Position changed -> super::Tick() called GET_AND_VERIFY_CURRENT_CHUNK(NextChunk, POSX_TOINT, POSZ_TOINT) TickBurning(*NextChunk); if (GetPosY() < VOID_BOUNDARY) { TickInVoid(*NextChunk); } else { m_TicksSinceLastVoidDamage = 0; } if (IsMob() || IsPlayer() || IsPickup() || IsExpOrb()) { DetectCacti(); } if (IsMob() || IsPlayer()) { // Set swimming state SetSwimState(*NextChunk); // Handle drowning HandleAir(); } // None of the above functions change position, we remain in the chunk of NextChunk HandlePhysics(a_Dt, *NextChunk); } }
bool cHopperEntity::Tick(float a_Dt, cChunk & a_Chunk) { Int64 CurrentTick = a_Chunk.GetWorld()->GetWorldAge(); bool res = false; res = MoveItemsIn (a_Chunk, CurrentTick) || res; res = MovePickupsIn(a_Chunk, CurrentTick) || res; res = MoveItemsOut (a_Chunk, CurrentTick) || res; return res; }
/// Moves items from a chest (dblchest) above the hopper into this hopper. Returns true if contents have changed. bool cHopperEntity::MoveItemsFromChest(cChunk & a_Chunk) { if (MoveItemsFromGrid(((cChestEntity *)a_Chunk.GetBlockEntity(m_PosX, m_PosY + 1, m_PosZ))->GetContents())) { // Moved the item from the chest directly above the hopper return true; } // Check if the chest is a double-chest, if so, try to move from there: static const struct { int x, z; } Coords [] = { {1, 0}, {-1, 0}, {0, 1}, {0, -1}, } ; for (int i = 0; i < ARRAYCOUNT(Coords); i++) { int x = m_RelX + Coords[i].x; int z = m_RelZ + Coords[i].z; cChunk * Neighbor = a_Chunk.GetRelNeighborChunkAdjustCoords(x, z); if ( (Neighbor == NULL) || (Neighbor->GetBlock(x, m_PosY + 1, z) != E_BLOCK_CHEST) ) { continue; } if (MoveItemsFromGrid(((cChestEntity *)Neighbor->GetBlockEntity(x, m_PosY, z))->GetContents())) { return true; } return false; } // The chest was single and nothing could be moved return false; }
/// Moves items to the chest at the specified coords. Returns true if contents have changed bool cHopperEntity::MoveItemsToChest(cChunk & a_Chunk, int a_BlockX, int a_BlockY, int a_BlockZ) { // Try the chest directly connected to the hopper: if (MoveItemsToGrid(((cChestEntity *)a_Chunk.GetBlockEntity(a_BlockX, a_BlockY, a_BlockZ))->GetContents())) { return true; } // Check if the chest is a double-chest, if so, try to move into the other half: static const struct { int x, z; } Coords [] = { {1, 0}, {-1, 0}, {0, 1}, {0, -1}, } ; for (int i = 0; i < ARRAYCOUNT(Coords); i++) { int x = m_RelX + Coords[i].x; int z = m_RelZ + Coords[i].z; cChunk * Neighbor = a_Chunk.GetRelNeighborChunkAdjustCoords(x, z); if ( (Neighbor == NULL) || (Neighbor->GetBlock(x, m_PosY + 1, z) != E_BLOCK_CHEST) ) { continue; } if (MoveItemsToGrid(((cChestEntity *)Neighbor->GetBlockEntity(a_BlockX, a_BlockY, a_BlockZ))->GetContents())) { return true; } return false; } // The chest was single and nothing could be moved return false; }
bool cHopperEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { UNUSED(a_Dt); Int64 CurrentTick = a_Chunk.GetWorld()->GetWorldAge(); bool res = false; res = MoveItemsIn (a_Chunk, CurrentTick) || res; res = MovePickupsIn(a_Chunk, CurrentTick) || res; res = MoveItemsOut (a_Chunk, CurrentTick) || res; return res; }
void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); BroadcastMovementUpdate(); // Notify clients of position m_Timer += a_Dt; if (!m_bCollected) { int BlockY = POSY_TOINT; int BlockX = POSX_TOINT; int BlockZ = POSZ_TOINT; if ((BlockY >= 0) && (BlockY < cChunkDef::Height)) // Don't do anything except for falling when outside the world { // Position might have changed due to physics. So we have to make sure we have the correct chunk. GET_AND_VERIFY_CURRENT_CHUNK(CurrentChunk, BlockX, BlockZ) int RelBlockX = BlockX - (CurrentChunk->GetPosX() * cChunkDef::Width); int RelBlockZ = BlockZ - (CurrentChunk->GetPosZ() * cChunkDef::Width); // If the pickup is on the bottommost block position, make it think the void is made of air: (#131) BLOCKTYPE BlockBelow = (BlockY > 0) ? CurrentChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR; BLOCKTYPE BlockIn = CurrentChunk->GetBlock(RelBlockX, BlockY, RelBlockZ); if ( IsBlockLava(BlockBelow) || (BlockBelow == E_BLOCK_FIRE) || IsBlockLava(BlockIn) || (BlockIn == E_BLOCK_FIRE) ) { m_bCollected = true; m_Timer = std::chrono::milliseconds(0); // We have to reset the timer. m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. if (m_Timer > std::chrono::milliseconds(500)) { Destroy(true); return; } } // Try to combine the pickup with adjacent same-item pickups: if (!IsDestroyed() && (m_Item.m_ItemCount < m_Item.GetMaxStackSize())) // Don't combine if already full { // By using a_Chunk's ForEachEntity() instead of cWorld's, pickups don't combine across chunk boundaries. // That is a small price to pay for not having to traverse the entire world for each entity. // The speedup in the tick thread is quite considerable. cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this); a_Chunk.ForEachEntity(PickupCombiningCallback); if (PickupCombiningCallback.FoundMatchingPickup()) { m_World->BroadcastEntityMetadata(*this); } } }
void cDropSpenserEntity::DropFromSlot(cChunk & a_Chunk, int a_SlotNum) { int DispX = m_PosX; int DispY = m_PosY; int DispZ = m_PosZ; NIBBLETYPE Meta = a_Chunk.GetMeta(m_RelX, m_PosY, m_RelZ); AddDropSpenserDir(DispX, DispY, DispZ, Meta); cItems Pickups; Pickups.push_back(m_Contents.RemoveOneItem(a_SlotNum)); m_World->SpawnItemPickups(Pickups, DispX, DispY, DispZ); }
void cEntity::Tick(float a_Dt, cChunk & a_Chunk) { if (m_AttachedTo != NULL) { if ((m_Pos - m_AttachedTo->GetPosition()).Length() > 0.5) { SetPosition(m_AttachedTo->GetPosition()); } } else { if (a_Chunk.IsValid()) { HandlePhysics(a_Dt, a_Chunk); } } if (a_Chunk.IsValid()) { TickBurning(a_Chunk); } }
void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) { int PosY = (int)floor(GetPosY()); if ((PosY <= 0) || (PosY >= cChunkDef::Height)) { // Outside the world, just process normal falling physics super::HandlePhysics(a_Dt, a_Chunk); BroadcastMovementUpdate(); return; } int RelPosX = (int)floor(GetPosX()) - a_Chunk.GetPosX() * cChunkDef::Width; int RelPosZ = (int)floor(GetPosZ()) - a_Chunk.GetPosZ() * cChunkDef::Width; cChunk * Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelPosX, RelPosZ); if (Chunk == NULL) { // Inside an unloaded chunk, bail out all processing return; } BLOCKTYPE BelowType = Chunk->GetBlock(RelPosX, PosY - 1, RelPosZ); BLOCKTYPE InsideType = Chunk->GetBlock(RelPosX, PosY, RelPosZ); if (IsBlockRail(BelowType)) { HandleRailPhysics(a_Dt, *Chunk); } else { if (IsBlockRail(InsideType)) { SetPosY(PosY + 1); HandleRailPhysics(a_Dt, *Chunk); } else { super::HandlePhysics(a_Dt, *Chunk); BroadcastMovementUpdate(); } } }
void cPlayer::SetSwimState(cChunk & a_Chunk) { int RelY = (int)floor(m_LastPosY + 0.1); if ((RelY < 0) || (RelY >= cChunkDef::Height - 1)) { m_IsSwimming = false; m_IsSubmerged = false; return; } BLOCKTYPE BlockIn; int RelX = (int)floor(m_LastPosX) - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = (int)floor(m_LastPosZ) - a_Chunk.GetPosZ() * cChunkDef::Width; // Check if the player is swimming: // Use Unbounded, because we're being called *after* processing super::Tick(), which could have changed our chunk VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn)); m_IsSwimming = IsBlockWater(BlockIn); // Check if the player is submerged: VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY + 1, RelZ, BlockIn)); m_IsSubmerged = IsBlockWater(BlockIn); }
void cSquid::Tick(float a_Dt, cChunk & a_Chunk) { // We must first process current location, and only then tick, otherwise we risk processing a location in a chunk // that is not where the entity currently resides (FS #411) Vector3d Pos = GetPosition(); // TODO: Not a real behavior, but cool :D int RelY = (int)floor(Pos.y); if ((RelY < 0) || (RelY >= cChunkDef::Height)) { return; } int RelX = (int)floor(Pos.x) - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = (int)floor(Pos.z) - a_Chunk.GetPosZ() * cChunkDef::Width; if (!IsBlockWater(a_Chunk.GetBlock(RelX, RelY, RelZ)) && !IsOnFire()) { // Burn for 10 ticks, then decide again StartBurning(10); } super::Tick(a_Dt, a_Chunk); }
/// Moves items to the furnace at the specified coords. Returns true if contents have changed bool cHopperEntity::MoveItemsToFurnace(cChunk & a_Chunk, int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_HopperMeta) { cFurnaceEntity * Furnace = (cFurnaceEntity *)a_Chunk.GetBlockEntity(a_BlockX, a_BlockY, a_BlockZ); if (a_HopperMeta == E_META_HOPPER_FACING_YM) { // Feed the input slot of the furnace return MoveItemsToSlot(*Furnace, cFurnaceEntity::fsInput); } else { // Feed the fuel slot of the furnace return MoveItemsToSlot(*Furnace, cFurnaceEntity::fsFuel); } }
void cMonster::InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { if (m_PathfinderActivated) { return; // Still getting there } m_IdleInterval += a_Dt; if (m_IdleInterval > std::chrono::seconds(1)) { // At this interval the results are predictable int rem = m_World->GetTickRandomNumber(6) + 1; m_IdleInterval -= std::chrono::seconds(1); // So nothing gets dropped when the server hangs for a few seconds Vector3d Dist; Dist.x = static_cast<double>(m_World->GetTickRandomNumber(10)) - 5.0; Dist.z = static_cast<double>(m_World->GetTickRandomNumber(10)) - 5.0; if ((Dist.SqrLength() > 2) && (rem >= 3)) { Vector3d Destination(GetPosX() + Dist.x, GetPosition().y, GetPosZ() + Dist.z); cChunk * Chunk = a_Chunk.GetNeighborChunk(static_cast<int>(Destination.x), static_cast<int>(Destination.z)); if ((Chunk == nullptr) || !Chunk->IsValid()) { return; } BLOCKTYPE BlockType; NIBBLETYPE BlockMeta; int RelX = static_cast<int>(Destination.x) - Chunk->GetPosX() * cChunkDef::Width; int RelZ = static_cast<int>(Destination.z) - Chunk->GetPosZ() * cChunkDef::Width; int YBelowUs = static_cast<int>(Destination.y) - 1; if (YBelowUs >= 0) { Chunk->GetBlockTypeMeta(RelX, YBelowUs, RelZ, BlockType, BlockMeta); if (BlockType != E_BLOCK_STATIONARY_WATER) // Idle mobs shouldn't enter water on purpose { MoveToPosition(Destination); } } } } }
void cDropSpenserEntity::DropSpense(cChunk & a_Chunk) { // Pick one of the occupied slots: int OccupiedSlots[9]; int SlotsCnt = 0; for (int i = m_Contents.GetNumSlots() - 1; i >= 0; i--) { if (!m_Contents.GetSlot(i).IsEmpty()) { OccupiedSlots[SlotsCnt] = i; SlotsCnt++; } } // for i - m_Contents[] if (SlotsCnt == 0) { // Nothing in the dropspenser, play the click sound m_World->BroadcastSoundEffect("random.click", m_PosX * 8, m_PosY * 8, m_PosZ * 8, 1.0f, 1.2f); return; } int RandomSlot = m_World->GetTickRandomNumber(SlotsCnt - 1); // DropSpense the item, using the specialized behavior in the subclasses: DropSpenseFromSlot(a_Chunk, OccupiedSlots[RandomSlot]); // Broadcast a smoke and click effects: NIBBLETYPE Meta = a_Chunk.GetMeta(m_RelX, m_PosY, m_RelZ); int SmokeDir = 0; switch (Meta) { case E_META_DROPSPENSER_FACING_XM: SmokeDir = 3; break; case E_META_DROPSPENSER_FACING_XP: SmokeDir = 5; break; case E_META_DROPSPENSER_FACING_ZM: SmokeDir = 1; break; case E_META_DROPSPENSER_FACING_ZP: SmokeDir = 7; break; } m_World->BroadcastSoundParticleEffect(2000, m_PosX * 8, m_PosY * 8, m_PosZ * 8, SmokeDir); m_World->BroadcastSoundEffect("random.click", m_PosX * 8, m_PosY * 8, m_PosZ * 8, 1.0f, 1.0f); // Update the UI window, if open: cWindow * Window = GetWindow(); if (Window != NULL) { Window->BroadcastWholeWindow(); } }
void cDropSpenserEntity::DropSpense(cChunk & a_Chunk) { // Pick one of the occupied slots: int OccupiedSlots[9]; int SlotsCnt = 0; for (int i = m_Contents.GetNumSlots() - 1; i >= 0; i--) { if (!m_Contents.GetSlot(i).IsEmpty()) { OccupiedSlots[SlotsCnt] = i; SlotsCnt++; } } // for i - m_Contents[] if (SlotsCnt == 0) { // Nothing in the dropspenser, play the click sound m_World->BroadcastSoundEffect("random.click", static_cast<double>(m_PosX), static_cast<double>(m_PosY), static_cast<double>(m_PosZ), 1.0f, 1.2f); return; } int RandomSlot = m_World->GetTickRandomNumber(SlotsCnt - 1); // DropSpense the item, using the specialized behavior in the subclasses: DropSpenseFromSlot(a_Chunk, OccupiedSlots[RandomSlot]); // Broadcast a smoke and click effects: NIBBLETYPE Meta = a_Chunk.GetMeta(m_RelX, m_PosY, m_RelZ); int SmokeDir = 0; switch (Meta) { case E_META_DROPSPENSER_FACING_YP: SmokeDir = 4; break; // YP & YM don't have associated smoke dirs, just do 4 (centre of block) case E_META_DROPSPENSER_FACING_YM: SmokeDir = 4; break; case E_META_DROPSPENSER_FACING_XM: SmokeDir = 3; break; case E_META_DROPSPENSER_FACING_XP: SmokeDir = 5; break; case E_META_DROPSPENSER_FACING_ZM: SmokeDir = 1; break; case E_META_DROPSPENSER_FACING_ZP: SmokeDir = 7; break; } m_World->BroadcastSoundParticleEffect(2000, m_PosX, m_PosY, m_PosZ, SmokeDir); m_World->BroadcastSoundEffect("random.click", static_cast<double>(m_PosX), static_cast<double>(m_PosY), static_cast<double>(m_PosZ), 1.0f, 1.0f); }
void cBlockHandler::Check(cChunkInterface & a_ChunkInterface, cBlockPluginInterface & a_PluginInterface, int a_RelX, int a_RelY, int a_RelZ, cChunk & a_Chunk) { if (!CanBeAt(a_ChunkInterface, a_RelX, a_RelY, a_RelZ, a_Chunk)) { if (DoesDropOnUnsuitable()) { int BlockX = a_RelX + a_Chunk.GetPosX() * cChunkDef::Width; int BlockZ = a_RelZ + a_Chunk.GetPosZ() * cChunkDef::Width; DropBlock(a_ChunkInterface, *a_Chunk.GetWorld(), a_PluginInterface, nullptr, BlockX, a_RelY, BlockZ); } a_Chunk.SetBlock(a_RelX, a_RelY, a_RelZ, E_BLOCK_AIR, 0); } else { // Wake up the simulators for this block: int BlockX = a_RelX + a_Chunk.GetPosX() * cChunkDef::Width; int BlockZ = a_RelZ + a_Chunk.GetPosZ() * cChunkDef::Width; a_Chunk.GetWorld()->GetSimulatorManager()->WakeUp(BlockX, a_RelY, BlockZ, &a_Chunk); } }
bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk) { cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(a_Location.x), FloorC(a_Location.z)); if ((Chunk == nullptr) || (!Chunk->IsValid())) { return false; } int RelX = FloorC(a_Location.x) - Chunk->GetPosX() * cChunkDef::Width; int RelY = FloorC(a_Location.y); int RelZ = FloorC(a_Location.z) - Chunk->GetPosZ() * cChunkDef::Width; if ( (Chunk->GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight (Chunk->GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand (GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining ) { return true; } return false; }
bool cFurnaceEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { UNUSED(a_Dt); if (m_FuelBurnTime <= 0) { // If a furnace is out of fuel, the progress bar reverses at twice the speed of cooking. m_TimeCooked = std::max((m_TimeCooked - 2), 0); // Reset progressbars, block type, and bail out m_BlockType = E_BLOCK_FURNACE; a_Chunk.FastSetBlock(GetRelX(), m_PosY, GetRelZ(), E_BLOCK_FURNACE, m_BlockMeta); UpdateProgressBars(); return false; } if (m_IsCooking) { m_TimeCooked++; if (m_TimeCooked >= m_NeedCookTime) { // Finished smelting one item FinishOne(); } } m_TimeBurned++; if (m_TimeBurned >= m_FuelBurnTime) { // The current fuel has been exhausted, use another one, if possible BurnNewFuel(); } UpdateProgressBars(); return true; }
void cPickup::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); BroadcastMovementUpdate(); //Notify clients of position m_Timer += a_Dt; if (!m_bCollected) { int BlockY = (int) floor(GetPosY()); if ((BlockY >= 0) && (BlockY < cChunkDef::Height)) // Don't do anything except for falling when outside the world { int BlockX = (int) floor(GetPosX()); int BlockZ = (int) floor(GetPosZ()); // Position might have changed due to physics. So we have to make sure we have the correct chunk. cChunk * CurrentChunk = a_Chunk.GetNeighborChunk(BlockX, BlockZ); if (CurrentChunk != NULL) // Make sure the chunk is loaded { int RelBlockX = BlockX - (CurrentChunk->GetPosX() * cChunkDef::Width); int RelBlockZ = BlockZ - (CurrentChunk->GetPosZ() * cChunkDef::Width); // If the pickup is on the bottommost block position, make it think the void is made of air: (#131) BLOCKTYPE BlockBelow = (BlockY > 0) ? CurrentChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR; BLOCKTYPE BlockIn = CurrentChunk->GetBlock(RelBlockX, BlockY, RelBlockZ); if ( IsBlockLava(BlockBelow) || (BlockBelow == E_BLOCK_FIRE) || IsBlockLava(BlockIn) || (BlockIn == E_BLOCK_FIRE) ) { m_bCollected = true; m_Timer = 0; // We have to reset the timer. m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. if (m_Timer > 500.f) { Destroy(true); return; } } if (!IsDestroyed()) // Don't try to combine if someone has tried to combine me { cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this); m_World->ForEachEntity(PickupCombiningCallback); // Not ForEachEntityInChunk, otherwise pickups don't combine across chunk boundaries if (PickupCombiningCallback.FoundMatchingPickup()) { m_World->BroadcastEntityMetadata(*this); } } } } } else { if (m_Timer > 500.f) // 0.5 second { Destroy(true); return; } } if (m_Timer > 1000 * 60 * 5) // 5 minutes { Destroy(true); return; } if (GetPosY() < -8) // Out of this world and no more visible! { Destroy(true); return; } }
void cDispenserEntity::DropSpenseFromSlot(cChunk & a_Chunk, int a_SlotNum) { int DispX = m_RelX; int DispY = m_PosY; int DispZ = m_RelZ; NIBBLETYPE Meta = a_Chunk.GetMeta(m_RelX, m_PosY, m_RelZ); AddDropSpenserDir(DispX, DispY, DispZ, Meta); cChunk * DispChunk = a_Chunk.GetRelNeighborChunkAdjustCoords(DispX, DispZ); if (DispChunk == NULL) { // Would dispense into / interact with a non-loaded chunk, ignore the tick return; } BLOCKTYPE DispBlock = DispChunk->GetBlock(DispX, DispY, DispZ); int BlockX = (DispX + DispChunk->GetPosX() * cChunkDef::Width); int BlockZ = (DispZ + DispChunk->GetPosZ() * cChunkDef::Width); // Dispense the item: switch (m_Contents.GetSlot(a_SlotNum).m_ItemType) { case E_ITEM_BUCKET: { LOGD("Dispensing empty bucket in slot %d; DispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(DispBlock).c_str(), DispBlock); switch (DispBlock) { case E_BLOCK_STATIONARY_WATER: case E_BLOCK_WATER: { if (ScoopUpLiquid(a_SlotNum, E_ITEM_WATER_BUCKET)) { DispChunk->SetBlock(DispX, DispY, DispZ, E_BLOCK_AIR, 0); } break; } case E_BLOCK_STATIONARY_LAVA: case E_BLOCK_LAVA: { if (ScoopUpLiquid(a_SlotNum, E_ITEM_LAVA_BUCKET)) { DispChunk->SetBlock(DispX, DispY, DispZ, E_BLOCK_AIR, 0); } break; } default: { DropFromSlot(a_Chunk, a_SlotNum); break; } } break; } // E_ITEM_BUCKET case E_ITEM_WATER_BUCKET: { LOGD("Dispensing water bucket in slot %d; DispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(DispBlock).c_str(), DispBlock); if (EmptyLiquidBucket(DispBlock, a_SlotNum)) { DispChunk->SetBlock(DispX, DispY, DispZ, E_BLOCK_WATER, 0); } else { DropFromSlot(a_Chunk, a_SlotNum); } break; } case E_ITEM_LAVA_BUCKET: { LOGD("Dispensing lava bucket in slot %d; DispBlock is \"%s\" (%d).", a_SlotNum, ItemTypeToString(DispBlock).c_str(), DispBlock); if (EmptyLiquidBucket(DispBlock, a_SlotNum)) { DispChunk->SetBlock(DispX, DispY, DispZ, E_BLOCK_LAVA, 0); } else { DropFromSlot(a_Chunk, a_SlotNum); } break; } case E_ITEM_SPAWN_EGG: { double MobX = 0.5 + (DispX + DispChunk->GetPosX() * cChunkDef::Width); double MobZ = 0.5 + (DispZ + DispChunk->GetPosZ() * cChunkDef::Width); if (m_World->SpawnMob(MobX, DispY, MobZ, (cMonster::eType)m_Contents.GetSlot(a_SlotNum).m_ItemDamage) >= 0) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } break; } case E_BLOCK_TNT: { // Spawn a primed TNT entity, if space allows: if (DispChunk->GetBlock(DispX, DispY, DispZ) == E_BLOCK_AIR) { double TNTX = 0.5 + (DispX + DispChunk->GetPosX() * cChunkDef::Width); double TNTZ = 0.5 + (DispZ + DispChunk->GetPosZ() * cChunkDef::Width); m_World->SpawnPrimedTNT(TNTX, DispY + 0.5, TNTZ, 80, 0); // 80 ticks fuse, no initial velocity m_Contents.ChangeSlotCount(a_SlotNum, -1); } break; } case E_ITEM_FLINT_AND_STEEL: { // Spawn fire if the block in front is air. if (DispChunk->GetBlock(DispX, DispY, DispZ) == E_BLOCK_AIR) { DispChunk->SetBlock(DispX, DispY, DispZ, E_BLOCK_FIRE, 0); bool ItemBroke = m_Contents.DamageItem(a_SlotNum, 1); if (ItemBroke) { m_Contents.ChangeSlotCount(a_SlotNum, -1); } } break; } case E_ITEM_FIRE_CHARGE: { SpawnProjectileFromDispenser(BlockX, DispY, BlockZ, cProjectileEntity::pkFireCharge, GetShootVector(Meta) * 20); m_Contents.ChangeSlotCount(a_SlotNum, -1); break; } case E_ITEM_ARROW: { SpawnProjectileFromDispenser(BlockX, DispY, BlockZ, cProjectileEntity::pkArrow, GetShootVector(Meta) * 20 + Vector3d(0, 1, 0)); m_Contents.ChangeSlotCount(a_SlotNum, -1); break; } case E_ITEM_SNOWBALL: { SpawnProjectileFromDispenser(BlockX, DispY, BlockZ, cProjectileEntity::pkSnowball, GetShootVector(Meta) * 20 + Vector3d(0, 1, 0)); m_Contents.ChangeSlotCount(a_SlotNum, -1); break; } case E_ITEM_EGG: { SpawnProjectileFromDispenser(BlockX, DispY, BlockZ, cProjectileEntity::pkEgg, GetShootVector(Meta) * 20 + Vector3d(0, 1, 0)); m_Contents.ChangeSlotCount(a_SlotNum, -1); break; } case E_ITEM_FIREWORK_ROCKET: { // TODO: Add the fireworks entity break; } default: { DropFromSlot(a_Chunk, a_SlotNum); break; } } // switch (ItemType) }
void cPickup::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); BroadcastMovementUpdate(); //Notify clients of position m_Timer += a_Dt; if (!m_bCollected) { int BlockY = (int) floor(GetPosY()); if (BlockY < cChunkDef::Height) // Don't do anything except for falling when above the world { int BlockX = (int) floor(GetPosX()); int BlockZ = (int) floor(GetPosZ()); //Position might have changed due to physics. So we have to make sure we have the correct chunk. cChunk * CurrentChunk = a_Chunk.GetNeighborChunk(BlockX, BlockZ); if (CurrentChunk != NULL) // Make sure the chunk is loaded { int RelBlockX = BlockX - (CurrentChunk->GetPosX() * cChunkDef::Width); int RelBlockZ = BlockZ - (CurrentChunk->GetPosZ() * cChunkDef::Width); BLOCKTYPE BlockBelow = CurrentChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ); BLOCKTYPE BlockIn = CurrentChunk->GetBlock(RelBlockX, BlockY, RelBlockZ); if ( IsBlockLava(BlockBelow) || (BlockBelow == E_BLOCK_FIRE) || IsBlockLava(BlockIn) || (BlockIn == E_BLOCK_FIRE) ) { m_bCollected = true; m_Timer = 0; // We have to reset the timer. m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. if (m_Timer > 500.f) { Destroy(true); return; } } } } } else { if (m_Timer > 500.f) // 0.5 second { Destroy(true); return; } } if (m_Timer > 1000 * 60 * 5) // 5 minutes { Destroy(true); return; } if (GetPosY() < -8) // Out of this world and no more visible! { Destroy(true); return; } }
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() ); */ }
void cFallingBlock::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { // GetWorld()->BroadcastTeleportEntity(*this); // Test position int BlockX = POSX_TOINT; int BlockY = (int)(GetPosY() - 0.5); int BlockZ = POSZ_TOINT; if (BlockY < 0) { // Fallen out of this world, just continue falling until out of sight, then destroy: if (BlockY < VOID_BOUNDARY) { Destroy(true); } return; } if (BlockY >= cChunkDef::Height) { // Above the world, just wait for it to fall back down return; } BLOCKTYPE BlockBelow = a_Chunk.GetBlock(BlockX - a_Chunk.GetPosX() * cChunkDef::Width, BlockY, BlockZ - a_Chunk.GetPosZ() * cChunkDef::Width); NIBBLETYPE BelowMeta = a_Chunk.GetMeta(BlockX - a_Chunk.GetPosX() * cChunkDef::Width, BlockY, BlockZ - a_Chunk.GetPosZ() * cChunkDef::Width); if (cSandSimulator::DoesBreakFallingThrough(BlockBelow, BelowMeta)) { // Fallen onto a block that breaks this into pickups (e. g. half-slab) // Must finish the fall with coords one below the block: cSandSimulator::FinishFalling(m_World, BlockX, BlockY, BlockZ, m_BlockType, m_BlockMeta); Destroy(true); return; } else if (!cSandSimulator::CanContinueFallThrough(BlockBelow)) { // Fallen onto a solid block /* LOGD( "Sand: Checked below at {%d, %d, %d} (rel {%d, %d, %d}), it's %s, finishing the fall.", BlockX, BlockY, BlockZ, BlockX - a_Chunk.GetPosX() * cChunkDef::Width, BlockY, BlockZ - a_Chunk.GetPosZ() * cChunkDef::Width, ItemTypeToString(BlockBelow).c_str() ); */ if (BlockY < cChunkDef::Height - 1) { cSandSimulator::FinishFalling(m_World, BlockX, BlockY + 1, BlockZ, m_BlockType, m_BlockMeta); } Destroy(true); return; } float MilliDt = a_Dt.count() * 0.001f; AddSpeedY(MilliDt * -9.8f); AddPosition(GetSpeed() * MilliDt); // If not static (one billionth precision) broadcast movement if ((fabs(GetSpeedX()) > std::numeric_limits<double>::epsilon()) || (fabs(GetSpeedZ()) > std::numeric_limits<double>::epsilon())) { BroadcastMovementUpdate(); } }
bool cMonster::EnsureProperDestination(cChunk & a_Chunk) { cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(m_FinalDestination.x), FloorC(m_FinalDestination.z)); BLOCKTYPE BlockType; NIBBLETYPE BlockMeta; if ((Chunk == nullptr) || !Chunk->IsValid()) { return false; } int RelX = FloorC(m_FinalDestination.x) - Chunk->GetPosX() * cChunkDef::Width; int RelZ = FloorC(m_FinalDestination.z) - Chunk->GetPosZ() * cChunkDef::Width; // If destination in the air, first try to go 1 block north, or east, or west. // This fixes the player leaning issue. // If that failed, we instead go down to the lowest air block. Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta); if (!cBlockInfo::IsSolid(BlockType)) { bool InTheAir = true; int x, z; for (z = -1; z <= 1; ++z) { for (x = -1; x <= 1; ++x) { if ((x==0) && (z==0)) { continue; } Chunk = a_Chunk.GetNeighborChunk(FloorC(m_FinalDestination.x+x), FloorC(m_FinalDestination.z+z)); if ((Chunk == nullptr) || !Chunk->IsValid()) { return false; } RelX = FloorC(m_FinalDestination.x+x) - Chunk->GetPosX() * cChunkDef::Width; RelZ = FloorC(m_FinalDestination.z+z) - Chunk->GetPosZ() * cChunkDef::Width; Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta); if (cBlockInfo::IsSolid(BlockType)) { m_FinalDestination.x += x; m_FinalDestination.z += z; InTheAir = false; goto breakBothLoops; } } } breakBothLoops: // Go down to the lowest air block. if (InTheAir) { while (m_FinalDestination.y > 0) { Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta); if (cBlockInfo::IsSolid(BlockType)) { break; } m_FinalDestination.y -= 1; } } } // If destination in water, go up to the highest water block. // If destination in solid, go up to first air block. bool InWater = false; while (m_FinalDestination.y < cChunkDef::Height) { Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y), RelZ, BlockType, BlockMeta); if (BlockType == E_BLOCK_STATIONARY_WATER) { InWater = true; } else if (cBlockInfo::IsSolid(BlockType)) { InWater = false; } else { break; } m_FinalDestination.y += 1; } if (InWater) { m_FinalDestination.y -= 1; } return true; }
void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) { if (m_ClientHandle != NULL) { if (m_ClientHandle->IsDestroyed()) { // This should not happen, because destroying a client will remove it from the world, but just in case m_ClientHandle = NULL; return; } if (!m_ClientHandle->IsPlaying()) { // We're not yet in the game, ignore everything return; } } m_Stats.AddValue(statMinutesPlayed, 1); if (!a_Chunk.IsValid()) { // This may happen if the cPlayer is created before the chunks have the chance of being loaded / generated (#83) return; } super::Tick(a_Dt, a_Chunk); // Handle charging the bow: if (m_IsChargingBow) { m_BowCharge += 1; } // Handle updating experience if (m_bDirtyExperience) { SendExperience(); } if (GetPosition() != m_LastPos) // Change in position from last tick? { // Apply food exhaustion from movement: ApplyFoodExhaustionFromMovement(); cRoot::Get()->GetPluginManager()->CallHookPlayerMoving(*this); m_ClientHandle->StreamChunks(); } BroadcastMovementUpdate(m_ClientHandle); if (m_Health > 0) // make sure player is alive { m_World->CollectPickupsByPlayer(this); if ((m_EatingFinishTick >= 0) && (m_EatingFinishTick <= m_World->GetWorldAge())) { FinishEating(); } HandleFood(); } if (m_IsFishing) { HandleFloater(); } // Update items (e.g. Maps) m_Inventory.UpdateItems(); // Send Player List (Once per m_LastPlayerListTime/1000 ms) cTimer t1; if (m_LastPlayerListTime + cPlayer::PLAYER_LIST_TIME_MS <= t1.GetNowTime()) { m_World->SendPlayerList(this); m_LastPlayerListTime = t1.GetNowTime(); } if (IsFlying()) { m_LastGroundHeight = (float)GetPosY(); } }
void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) { if (IsDestroyed()) // Mainly to stop detector rails triggering again after minecart is dead { return; } 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 == NULL) { // Inside an unloaded chunk, bail out all processing return; } BLOCKTYPE InsideType; NIBBLETYPE InsideMeta; Chunk->GetBlockTypeMeta(RelPosX, PosY, RelPosZ, InsideType, InsideMeta); if (!IsBlockRail(InsideType)) { Chunk->GetBlockTypeMeta(RelPosX, PosY + 1, RelPosZ, InsideType, InsideMeta); // When an descending minecart hits a flat rail, it goes through the ground; check for this if (IsBlockRail(InsideType)) AddPosY(1); // Push cart upwards } 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() * (a_Dt / 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(); }
bool cPathFinder::EnsureProperPoint(Vector3d & a_Vector, cChunk & a_Chunk) { cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(a_Vector.x), FloorC(a_Vector.z)); BLOCKTYPE BlockType; NIBBLETYPE BlockMeta; if ((Chunk == nullptr) || !Chunk->IsValid()) { return false; } int RelX = FloorC(a_Vector.x) - Chunk->GetPosX() * cChunkDef::Width; int RelZ = FloorC(a_Vector.z) - Chunk->GetPosZ() * cChunkDef::Width; // If destination in the air, first try to go 1 block north, or east, or west. // This fixes the player leaning issue. // If that failed, we instead go down to the lowest air block. Chunk->GetBlockTypeMeta(RelX, FloorC(a_Vector.y) - 1, RelZ, BlockType, BlockMeta); if (!(IsWaterOrSolid(BlockType))) { bool InTheAir = true; int x, z; for (z = -1; z <= 1; ++z) { for (x = -1; x <= 1; ++x) { if ((x == 0) && (z == 0)) { continue; } Chunk = a_Chunk.GetNeighborChunk(FloorC(a_Vector.x+x), FloorC(a_Vector.z+z)); if ((Chunk == nullptr) || !Chunk->IsValid()) { return false; } RelX = FloorC(a_Vector.x+x) - Chunk->GetPosX() * cChunkDef::Width; RelZ = FloorC(a_Vector.z+z) - Chunk->GetPosZ() * cChunkDef::Width; Chunk->GetBlockTypeMeta(RelX, FloorC(a_Vector.y) - 1, RelZ, BlockType, BlockMeta); if (IsWaterOrSolid((BlockType))) { a_Vector.x += x; a_Vector.z += z; InTheAir = false; goto breakBothLoops; } } } breakBothLoops: // Go down to the lowest air block. if (InTheAir) { while (a_Vector.y > 0) { Chunk->GetBlockTypeMeta(RelX, FloorC(a_Vector.y) - 1, RelZ, BlockType, BlockMeta); if (IsWaterOrSolid(BlockType)) { break; } a_Vector.y -= 1; } } } // If destination in water or solid, go up to the first air block. while (a_Vector.y < cChunkDef::Height) { Chunk->GetBlockTypeMeta(RelX, FloorC(a_Vector.y), RelZ, BlockType, BlockMeta); if (!IsWaterOrSolid(BlockType)) { break; } a_Vector.y += 1; } return true; }