void cEntity::SetSwimState(cChunk & a_Chunk) { int RelY = (int)floor(GetPosY() + 0.1); if ((RelY < 0) || (RelY >= cChunkDef::Height - 1)) { m_IsSwimming = false; m_IsSubmerged = false; return; } BLOCKTYPE BlockIn; int RelX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width; // Check if the player is swimming: if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn)) { // This sometimes happens on Linux machines // Ref.: http://forum.mc-server.org/showthread.php?tid=1244 LOGD("SetSwimState failure: RelX = %d, RelZ = %d, Pos = %.02f, %.02f}", RelX, RelY, GetPosX(), GetPosZ() ); m_IsSwimming = false; m_IsSubmerged = false; return; } 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(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { m_PathfinderActivated = false; // Disable Pathfinding until it's fixed. TODO // 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 = FloorC(Pos.y); if ((RelY < 0) || (RelY >= cChunkDef::Height)) { return; } int RelX = FloorC(Pos.x) - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = FloorC(Pos.z) - a_Chunk.GetPosZ() * cChunkDef::Width; BLOCKTYPE BlockType; if (a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockType) && !IsBlockWater(BlockType) && !IsOnFire()) { // Burn for 10 ticks, then decide again StartBurning(10); } super::Tick(a_Dt, a_Chunk); }
void cFireworkEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) { int RelX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width; int PosY = POSY_TOINT; if ((PosY < 0) || (PosY >= cChunkDef::Height)) { goto setspeed; } if (m_IsInGround) { if (a_Chunk.GetBlock(RelX, POSY_TOINT + 1, RelZ) == E_BLOCK_AIR) { m_IsInGround = false; } else { return; } } else { if (a_Chunk.GetBlock(RelX, POSY_TOINT + 1, RelZ) != E_BLOCK_AIR) { OnHitSolidBlock(GetPosition(), BLOCK_FACE_YM); return; } } setspeed: AddSpeedY(1); AddPosition(GetSpeed() * (a_Dt / 1000)); }
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 if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn)) { // This sometimes happens on Linux machines // Ref.: http://forum.mc-server.org/showthread.php?tid=1244 LOGD("SetSwimState failure: RelX = %d, RelZ = %d, LastPos = {%.02f, %.02f}, Pos = %.02f, %.02f}", RelX, RelY, m_LastPosX, m_LastPosZ, GetPosX(), GetPosZ() ); m_IsSwimming = false; m_IsSubmerged = false; return; } m_IsSwimming = IsBlockWater(BlockIn); // Check if the player is submerged: VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY + 1, RelZ, BlockIn)); m_IsSubmerged = IsBlockWater(BlockIn); }
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 } } }
void cArrowEntity::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); m_Timer += a_Dt; if (m_bIsCollected) { if (m_Timer > 500.f) // 0.5 seconds { Destroy(); return; } } else if (m_Timer > 1000 * 60 * 5) // 5 minutes { Destroy(); return; } if (m_IsInGround) { // When an arrow hits, the client doesn't think its in the ground and keeps on moving, IF BroadcastMovementUpdate() and TeleportEntity was called during flight, AT ALL // Fix is to simply not sync with the client and send a teleport to confirm pos after arrow has stabilised (around 1 sec after landing) // We can afford to do this because xoft's algorithm for trajectory is near perfect, so things are pretty close anyway without sync // Besides, this seems to be what the vanilla server does, note how arrows teleport half a second after they hit to the server position if (m_HitGroundTimer != -1) // Sent a teleport already, don't do again { if (m_HitGroundTimer > 1000.f) // Send after a second, could be less, but just in case { m_World->BroadcastTeleportEntity(*this); m_HitGroundTimer = -1; } 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 == NULL) { // 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 } } }
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); } }
/// Moves items out from this hopper into the destination. Returns true if the contents have changed. bool cHopperEntity::MoveItemsOut(cChunk & a_Chunk, Int64 a_CurrentTick) { if (a_CurrentTick - m_LastMoveItemsOutTick < TICKS_PER_TRANSFER) { // Too early after the previous transfer return false; } int bx, by, bz; NIBBLETYPE Meta = a_Chunk.GetMeta(m_RelX, m_PosY, m_RelZ); if (!GetOutputBlockPos(Meta, bx, by, bz)) { // Not attached to another container return false; } if (by < 0) { // Cannot output below the zero-th block level return false; } // Convert coords to relative: int rx = bx - a_Chunk.GetPosX() * cChunkDef::Width; int rz = bz - a_Chunk.GetPosZ() * cChunkDef::Width; cChunk * DestChunk = a_Chunk.GetRelNeighborChunkAdjustCoords(rx, rz); if (DestChunk == NULL) { // The destination chunk has been unloaded, don't tick return false; } // Call proper moving function, based on the blocktype present at the coords: bool res = false; switch (DestChunk->GetBlock(rx, by, rz)) { case E_BLOCK_CHEST: res = MoveItemsToChest(*DestChunk, bx, by, bz); break; case E_BLOCK_FURNACE: res = MoveItemsToFurnace(*DestChunk, bx, by, bz, Meta); break; case E_BLOCK_DISPENSER: case E_BLOCK_DROPPER: res = MoveItemsToGrid(((cDropSpenserEntity *)DestChunk->GetBlockEntity(bx, by, bz))->GetContents()); break; case E_BLOCK_HOPPER: res = MoveItemsToGrid(((cHopperEntity *) DestChunk->GetBlockEntity(bx, by, bz))->GetContents()); break; case E_BLOCK_LIT_FURNACE: res = MoveItemsToFurnace(*DestChunk, bx, by, bz, Meta); break; } // If the item has been moved, reset the last tick: if (res) { m_LastMoveItemsOutTick = a_CurrentTick; } return res; }
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 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); }
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 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(); }
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(); } }
void cMinecart::HandleRailPhysics(float a_Dt, cChunk & a_Chunk) { super::HandlePhysics(a_Dt, a_Chunk); // Main physics handling /* NOTE: Please bear in mind that taking away from negatives make them even more negative, adding to negatives make them positive, etc. */ // Get block meta below the cart int RelPosX = (int)floor(GetPosX()) - a_Chunk.GetPosX() * cChunkDef::Width; int RelPosZ = (int)floor(GetPosZ()) - a_Chunk.GetPosZ() * cChunkDef::Width; NIBBLETYPE BelowMeta = a_Chunk.GetMeta(RelPosX, (int)floor(GetPosY() - 1), RelPosZ); double SpeedX = GetSpeedX(), SpeedY = GetSpeedY(), SpeedZ = GetSpeedZ(); // Get current speed switch (BelowMeta) { case E_META_RAIL_ZM_ZP: // NORTHSOUTH { SetRotation(270); SpeedY = 0; // Don't move vertically as on ground SpeedX = 0; // Correct diagonal movement from curved rails if (SpeedZ != 0) // Don't do anything if cart is stationary { if (SpeedZ > 0) { // Going SOUTH, slow down SpeedZ = SpeedZ - 0.1; } else { // Going NORTH, slow down SpeedZ = SpeedZ + 0.1; } } break; } case E_META_RAIL_XM_XP: // EASTWEST { SetRotation(180); SpeedY = 0; SpeedZ = 0; if (SpeedX != 0) { if (SpeedX > 0) { SpeedX = SpeedX - 0.1; } else { SpeedX = SpeedX + 0.1; } } break; } case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH { SetRotation(270); SetPosY(floor(GetPosY()) + 0.2); // It seems it doesn't work without levitation :/ SpeedX = 0; if (SpeedZ >= 0) { // SpeedZ POSITIVE, going SOUTH if (SpeedZ <= MAX_SPEED) // Speed limit { SpeedZ = SpeedZ + 0.5; // Speed up SpeedY = (0 - SpeedZ); // Downward movement is negative (0 minus positive numbers is negative) } else { SpeedZ = MAX_SPEED; // Enforce speed limit SpeedY = (0 - SpeedZ); } } else { // SpeedZ NEGATIVE, going NORTH SpeedZ = SpeedZ + 0.4; // Slow down SpeedY = (0 - SpeedZ); // Upward movement is positive (0 minus negative number is positive number) } break; } case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH { SetRotation(270); SetPosY(floor(GetPosY()) + 0.2); SpeedX = 0; if (SpeedZ > 0) { // SpeedZ POSITIVE, going SOUTH SpeedZ = SpeedZ - 0.4; // Slow down SpeedY = SpeedZ; // Upward movement positive } else { if (SpeedZ >= MAX_SPEED_NEGATIVE) // Speed limit { // SpeedZ NEGATIVE, going NORTH SpeedZ = SpeedZ - 0.5; // Speed up SpeedY = SpeedZ; // Downward movement negative } else { SpeedZ = MAX_SPEED_NEGATIVE; // Enforce speed limit SpeedY = SpeedZ; } } break; } case E_META_RAIL_ASCEND_XM: // ASCEND EAST { SetRotation(180); SetPosY(floor(GetPosY()) + 0.2); SpeedZ = 0; if (SpeedX >= 0) { if (SpeedX <= MAX_SPEED) { SpeedX = SpeedX + 0.5; SpeedY = (0 - SpeedX); } else { SpeedX = MAX_SPEED; SpeedY = (0 - SpeedX); } } else { SpeedX = SpeedX + 0.4; SpeedY = (0 - SpeedX); } break; } case E_META_RAIL_ASCEND_XP: // ASCEND WEST { SetRotation(180); SetPosY(floor(GetPosY()) + 0.2); SpeedZ = 0; if (SpeedX > 0) { SpeedX = SpeedX - 0.4; SpeedY = SpeedX; } else { if (SpeedX >= MAX_SPEED_NEGATIVE) { SpeedX = SpeedX - 0.5; SpeedY = SpeedX; } else { SpeedX = MAX_SPEED_NEGATIVE; SpeedY = SpeedX; } } break; } case E_META_RAIL_CURVED_ZM_XM: // Ends pointing NORTH and WEST { SetRotation(315); // Set correct rotation server side SetPosY(floor(GetPosY()) + 0.2); // Levitate dat cart if (SpeedZ > 0) // Cart moving south { SpeedX = (0 - SpeedZ); // Diagonally move southwest (which will make cart hit a southwest rail) } else if (SpeedX > 0) // Cart moving east { SpeedZ = (0 - SpeedX); // Diagonally move northeast } break; } case E_META_RAIL_CURVED_ZM_XP: // Curved NORTH EAST { SetRotation(225); SetPosY(floor(GetPosY()) + 0.2); if (SpeedZ > 0) { SpeedX = SpeedZ; } else if (SpeedX < 0) { SpeedZ = SpeedX; } break; } case E_META_RAIL_CURVED_ZP_XM: // Curved SOUTH WEST { SetRotation(135); SetPosY(floor(GetPosY()) + 0.2); if (SpeedZ < 0) { SpeedX = SpeedZ; } else if (SpeedX > 0) { SpeedZ = SpeedX; } break; } case E_META_RAIL_CURVED_ZP_XP: // Curved SOUTH EAST { SetRotation(45); SetPosY(floor(GetPosY()) + 0.2); if (SpeedZ < 0) { SpeedX = (0 - SpeedZ); } else if (SpeedX < 0) { SpeedZ = (0 - SpeedX); } break; } default: { ASSERT(!"Unhandled rail meta!"); // Dun dun DUN! break; } } // Set speed to speed variables SetSpeedX(SpeedX); SetSpeedY(SpeedY); SetSpeedZ(SpeedZ); // Broadcast position to client BroadcastMovementUpdate(); }
void cEntity::TickBurning(cChunk & a_Chunk) { // Remember the current burning state: bool HasBeenBurning = (m_TicksLeftBurning > 0); if (m_World->IsWeatherWet()) { if (POSY_TOINT > m_World->GetHeight(POSX_TOINT, POSZ_TOINT)) { m_TicksLeftBurning = 0; } } // Do the burning damage: if (m_TicksLeftBurning > 0) { m_TicksSinceLastBurnDamage++; if (m_TicksSinceLastBurnDamage >= BURN_TICKS_PER_DAMAGE) { if (!m_IsFireproof) { TakeDamage(dtOnFire, NULL, BURN_DAMAGE, 0); } m_TicksSinceLastBurnDamage = 0; } m_TicksLeftBurning--; } // Update the burning times, based on surroundings: int MinRelX = (int)floor(GetPosX() - m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width; int MaxRelX = (int)floor(GetPosX() + m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width; int MinRelZ = (int)floor(GetPosZ() - m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width; int MaxRelZ = (int)floor(GetPosZ() + m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width; int MinY = std::max(0, std::min(cChunkDef::Height - 1, POSY_TOINT)); int MaxY = std::max(0, std::min(cChunkDef::Height - 1, (int)ceil (GetPosY() + m_Height))); bool HasWater = false; bool HasLava = false; bool HasFire = false; for (int x = MinRelX; x <= MaxRelX; x++) { for (int z = MinRelZ; z <= MaxRelZ; z++) { int RelX = x; int RelZ = z; for (int y = MinY; y <= MaxY; y++) { BLOCKTYPE Block; a_Chunk.UnboundedRelGetBlockType(RelX, y, RelZ, Block); switch (Block) { case E_BLOCK_FIRE: { HasFire = true; break; } case E_BLOCK_LAVA: case E_BLOCK_STATIONARY_LAVA: { HasLava = true; break; } case E_BLOCK_STATIONARY_WATER: case E_BLOCK_WATER: { HasWater = true; break; } } // switch (BlockType) } // for y } // for z } // for x if (HasWater) { // Extinguish the fire m_TicksLeftBurning = 0; } if (HasLava) { // Burn: m_TicksLeftBurning = BURN_TICKS; // Periodically damage: m_TicksSinceLastLavaDamage++; if (m_TicksSinceLastLavaDamage >= LAVA_TICKS_PER_DAMAGE) { if (!m_IsFireproof) { TakeDamage(dtLavaContact, NULL, LAVA_DAMAGE, 0); } m_TicksSinceLastLavaDamage = 0; } } else { m_TicksSinceLastLavaDamage = 0; } if (HasFire) { // Burn: m_TicksLeftBurning = BURN_TICKS; // Periodically damage: m_TicksSinceLastFireDamage++; if (m_TicksSinceLastFireDamage >= FIRE_TICKS_PER_DAMAGE) { if (!m_IsFireproof) { TakeDamage(dtFireContact, NULL, FIRE_DAMAGE, 0); } m_TicksSinceLastFireDamage = 0; } } else { m_TicksSinceLastFireDamage = 0; } // If just started / finished burning, notify descendants: if ((m_TicksLeftBurning > 0) && !HasBeenBurning) { OnStartedBurning(); } else if ((m_TicksLeftBurning <= 0) && HasBeenBurning) { OnFinishedBurning(); } }
/// Moves items out from this hopper into the destination. Returns true if the contents have changed. bool cHopperEntity::MoveItemsOut(cChunk & a_Chunk, Int64 a_CurrentTick) { if (a_CurrentTick - m_LastMoveItemsOutTick < TICKS_PER_TRANSFER) { // Too early after the previous transfer return false; } // Get the coords of the block where to output items: int OutX, OutY, OutZ; NIBBLETYPE Meta = a_Chunk.GetMeta(m_RelX, m_PosY, m_RelZ); if (!GetOutputBlockPos(Meta, OutX, OutY, OutZ)) { // Not attached to another container return false; } if (OutY < 0) { // Cannot output below the zero-th block level return false; } // Convert coords to relative: int OutRelX = OutX - a_Chunk.GetPosX() * cChunkDef::Width; int OutRelZ = OutZ - a_Chunk.GetPosZ() * cChunkDef::Width; cChunk * DestChunk = a_Chunk.GetRelNeighborChunkAdjustCoords(OutRelX, OutRelZ); if (DestChunk == NULL) { // The destination chunk has been unloaded, don't tick return false; } // Call proper moving function, based on the blocktype present at the coords: bool res = false; switch (DestChunk->GetBlock(OutRelX, OutY, OutRelZ)) { case E_BLOCK_TRAPPED_CHEST: case E_BLOCK_CHEST: { // Chests have special handling because of double-chests res = MoveItemsToChest(*DestChunk, OutX, OutY, OutZ); break; } case E_BLOCK_LIT_FURNACE: case E_BLOCK_FURNACE: { // Furnaces have special handling because of the direction-to-slot relation res = MoveItemsToFurnace(*DestChunk, OutX, OutY, OutZ, Meta); break; } case E_BLOCK_DISPENSER: case E_BLOCK_DROPPER: case E_BLOCK_HOPPER: { cBlockEntityWithItems * BlockEntity = (cBlockEntityWithItems *)DestChunk->GetBlockEntity(OutX, OutY, OutZ); if (BlockEntity == NULL) { LOGWARNING("%s: A block entity was not found where expected at {%d, %d, %d}", __FUNCTION__, OutX, OutY, OutZ); return false; } res = MoveItemsToGrid(*BlockEntity); break; } } // If the item has been moved, reset the last tick: if (res) { m_LastMoveItemsOutTick = a_CurrentTick; } return res; }
/// 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: cChestEntity * ConnectedChest = (cChestEntity *)a_Chunk.GetBlockEntity(a_BlockX, a_BlockY, a_BlockZ); if (ConnectedChest == NULL) { LOGWARNING("%s: A chest entity was not found where expected, at {%d, %d, %d}", __FUNCTION__, a_BlockX, a_BlockY, a_BlockZ); return false; } if (MoveItemsToGrid(*ConnectedChest)) { // Chest block directly connected was not full return true; } // Check if the chest is a double-chest (chest block directly connected was full), if so, try to move into the other half: static const struct { int x, z; } Coords [] = { {1, 0}, {-1, 0}, {0, 1}, {0, -1}, } ; int RelX = a_BlockX - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = a_BlockZ - a_Chunk.GetPosZ() * cChunkDef::Width; for (size_t i = 0; i < ARRAYCOUNT(Coords); i++) { int x = RelX + Coords[i].x; int z = RelZ + Coords[i].z; cChunk * Neighbor = a_Chunk.GetRelNeighborChunkAdjustCoords(x, z); if (Neighbor == NULL) { continue; } BLOCKTYPE Block = Neighbor->GetBlock(x, a_BlockY, z); if (Block != ConnectedChest->GetBlockType()) { // Not the same kind of chest continue; } cChestEntity * Chest = (cChestEntity *)Neighbor->GetBlockEntity(a_BlockX + Coords[i].x, a_BlockY, a_BlockZ + Coords[i].z); if (Chest == NULL) { LOGWARNING("%s: A chest entity was not found where expected, at {%d, %d, %d} (%d, %d)", __FUNCTION__, a_BlockX + Coords[i].x, a_BlockY, a_BlockZ + Coords[i].z, x, z); continue; } if (MoveItemsToGrid(*Chest)) { return true; } return false; } // The chest was single and nothing could be moved return false; }