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 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 = 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 = 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); } } }
bool cFloodyFluidSimulator::HardenBlock(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta) { // Only lava blocks can harden if (!IsBlockLava(a_BlockType)) { return false; } bool ShouldHarden = false; BLOCKTYPE BlockType; NIBBLETYPE BlockMeta; static const Vector3i Coords[] = { Vector3i( 1, 0, 0), Vector3i(-1, 0, 0), Vector3i( 0, 0, 1), Vector3i( 0, 0, -1), }; for (size_t i = 0; i < ARRAYCOUNT(Coords); i++) { if (!a_Chunk->UnboundedRelGetBlock(a_RelX + Coords[i].x, a_RelY, a_RelZ + Coords[i].z, BlockType, BlockMeta)) { continue; } if (IsBlockWater(BlockType)) { ShouldHarden = true; } } // for i - Coords[] if (ShouldHarden) { if (a_Meta == 0) { // Source lava block a_Chunk->SetBlock(a_RelX, a_RelY, a_RelZ, E_BLOCK_OBSIDIAN, 0); return true; } // Ignore last lava level else if (a_Meta <= 4) { a_Chunk->SetBlock(a_RelX, a_RelY, a_RelZ, E_BLOCK_COBBLESTONE, 0); return true; } } return false; }
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 cPawn::HandleFalling(void) { /* Not pretty looking, and is more suited to wherever server-sided collision detection is implemented. The following condition sets on-ground-ness if The player isn't swimming or flying (client hardcoded conditions) and they're on a block (Y is exact) - ensure any they could be standing on (including on the edges) is solid or they're on a slab (Y significand is 0.5) - ditto with slab check they're on a snow layer (Y divisible by 0.125) - ditto with snow layer check */ static const auto HalfWidth = GetWidth() / 2; static const auto EPS = 0.0001; /* Since swimming is decided in a tick and is asynchronous to this, we have to check for dampeners ourselves. The behaviour as of 1.8.9 is the following: - Landing in water alleviates all fall damage - Passing through any liquid (water + lava) and cobwebs "slows" the player down, i.e. resets the fall distance to that block, but only after checking for fall damage (this means that plummeting into lava will still kill the player via fall damage, although cobwebs will slow players down enough to have multiple updates that keep them alive) - Slime blocks reverse falling velocity, unless it's a crouching player, in which case they act as standard blocks. They also reset the topmost point of the damage calculation with each bounce, so if the block is removed while the player is bouncing or crouches after a bounce, the last bounce's zenith is considered as fall damage. With this in mind, we first check the block at the player's feet, then the one below that (because fences), and decide which behaviour we want to go with. */ BLOCKTYPE BlockAtFoot = (cChunkDef::IsValidHeight(POSY_TOINT)) ? GetWorld()->GetBlock(POS_TOINT) : E_BLOCK_AIR; /* We initialize these with what the foot is really IN, because for sampling we will move down with the epsilon above */ bool IsFootInWater = IsBlockWater(BlockAtFoot); bool IsFootInLiquid = IsFootInWater || IsBlockLava(BlockAtFoot) || (BlockAtFoot == E_BLOCK_COBWEB); // okay so cobweb is not _technically_ a liquid... bool IsFootOnSlimeBlock = false; /* The "cross" we sample around to account for the player width/girth */ static const struct { int x, z; } CrossSampleCoords[] = { { 0, 0 }, { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 }, }; /* The blocks we're interested in relative to the player to account for larger than 1 blocks. This can be extended to do additional checks in case there are blocks that are represented as one block in memory but have a hitbox larger than 1 (like fences) */ static const struct { int x, y, z; } BlockSampleOffsets[] = { { 0, 0, 0 }, // TODO: something went wrong here (offset 0?) { 0, -1, 0 }, // Potentially causes mis-detection (IsFootInWater) when player stands on block diagonal to water (i.e. on side of pool) }; /* Here's the rough outline of how this mechanism works: We take the player's pointlike position (sole of feet), and expand it into a crosslike shape. If any of the five points hit a block, we consider the player to be "on" (or "in") the ground. */ bool OnGround = false; for (size_t i = 0; i < ARRAYCOUNT(CrossSampleCoords); i++) { /* We calculate from the player's position, one of the cross-offsets above, and we move it down slightly so it's beyond inaccuracy. The added advantage of this method is that if the player is simply standing on the floor, the point will move into the next block, and the floor() will retrieve that instead of air. */ Vector3d CrossTestPosition = GetPosition() + Vector3d(CrossSampleCoords[i].x * HalfWidth, -EPS, CrossSampleCoords[i].z * HalfWidth); /* We go through the blocks that we consider "relevant" */ for (size_t j = 0; j < ARRAYCOUNT(BlockSampleOffsets); j++) { Vector3i BlockTestPosition = CrossTestPosition.Floor() + Vector3i(BlockSampleOffsets[j].x, BlockSampleOffsets[j].y, BlockSampleOffsets[j].z); if (!cChunkDef::IsValidHeight(BlockTestPosition.y)) { continue; } BLOCKTYPE Block = GetWorld()->GetBlock(BlockTestPosition); NIBBLETYPE BlockMeta = GetWorld()->GetBlockMeta(BlockTestPosition); /* we do the cross-shaped sampling to check for water / liquids, but only on our level because water blocks are never bigger than unit voxels */ if (j == 0) { IsFootInWater |= IsBlockWater(Block); IsFootInLiquid |= IsFootInWater || IsBlockLava(Block) || (Block == E_BLOCK_COBWEB); // okay so cobweb is not _technically_ a liquid... IsFootOnSlimeBlock |= (Block == E_BLOCK_SLIME_BLOCK); } /* If the block is solid, and the blockhandler confirms the block to be inside, we're officially on the ground. */ if ((cBlockInfo::IsSolid(Block)) && (cBlockInfo::GetHandler(Block)->IsInsideBlock(CrossTestPosition - BlockTestPosition, Block, BlockMeta))) { OnGround = true; } } } /* So here's the use of the rules above: */ /* 1. Falling in water absorbs all fall damage */ bool FallDamageAbsorbed = IsFootInWater; /* 2. Falling in liquid (lava, water, cobweb) or hitting a slime block resets the "fall zenith". Note: Even though the pawn bounces back with no damage after hitting the slime block, the "fall zenith" will continue to increase back up when flying upwards - which is good */ bool ShouldBounceOnSlime = true; if (IsPlayer()) { auto Player = static_cast<cPlayer *>(this); /* 3. If the player is flying or climbing, absorb fall damage */ FallDamageAbsorbed |= Player->IsFlying() || Player->IsClimbing(); /* 4. If the player is about to bounce on a slime block and is not crouching, absorb all fall damage */ ShouldBounceOnSlime = !Player->IsCrouched(); FallDamageAbsorbed |= (IsFootOnSlimeBlock && ShouldBounceOnSlime); } else { /* 5. Bouncing on a slime block absorbs all fall damage */ FallDamageAbsorbed |= IsFootOnSlimeBlock; } /* If the player is not crouching or is not a player, shoot them back up. NOTE: this will only work in some cases; should be done in HandlePhysics() */ if (IsFootOnSlimeBlock && ShouldBounceOnSlime) { // TODO: doesn't work too well, causes dissatisfactory experience for players on slime blocks - SetSpeedY(-GetSpeedY()); } // TODO: put player speed into GetSpeedY, and use that. // If flying, climbing, swimming, or going up... if (FallDamageAbsorbed || ((GetPosition() - m_LastPosition).y > 0)) { // ...update the ground height to have the highest position of the player (i.e. jumping up adds to the eventual fall damage) m_LastGroundHeight = GetPosY(); } if (OnGround) { auto Damage = static_cast<int>(m_LastGroundHeight - GetPosY() - 3.0); if ((Damage > 0) && !FallDamageAbsorbed) { TakeDamage(dtFalling, nullptr, Damage, Damage, 0); // Fall particles GetWorld()->BroadcastParticleEffect( "blockdust", GetPosition(), { 0, 0, 0 }, (Damage - 1.f) * ((0.3f - 0.1f) / (15.f - 1.f)) + 0.1f, // Map damage (1 - 15) to particle speed (0.1 - 0.3) static_cast<int>((Damage - 1.f) * ((50.f - 20.f) / (15.f - 1.f)) + 20.f), // Map damage (1 - 15) to particle quantity (20 - 50) { { GetWorld()->GetBlock(POS_TOINT - Vector3i(0, 1, 0)), 0 } } ); } m_bTouchGround = true; m_LastGroundHeight = GetPosY(); } else { m_bTouchGround = false; } /* Note: it is currently possible to fall through lava and still die from fall damage because of the client skipping an update about the lava block. This can only be resolved by somehow integrating these above checks into the tracer in HandlePhysics. */ }
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 cFloodyFluidSimulator::SimulateBlock(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ) { FLOG("Simulating block {%d, %d, %d}: block %d, meta %d", a_Chunk->GetPosX() * cChunkDef::Width + a_RelX, a_RelY, a_Chunk->GetPosZ() * cChunkDef::Width + a_RelZ, a_Chunk->GetBlock(a_RelX, a_RelY, a_RelZ), a_Chunk->GetMeta(a_RelX, a_RelY, a_RelZ) ); BLOCKTYPE MyBlock; NIBBLETYPE MyMeta; a_Chunk->GetBlockTypeMeta(a_RelX, a_RelY, a_RelZ, MyBlock, MyMeta); if (!IsAnyFluidBlock(MyBlock)) { // Can happen - if a block is scheduled for simulating and gets replaced in the meantime. FLOG(" BadBlockType exit"); return; } // When in contact with water, lava should harden if (HardenBlock(a_Chunk, a_RelX, a_RelY, a_RelZ, MyBlock, MyMeta)) { // Block was changed, bail out return; } if (MyMeta != 0) { // Source blocks aren't checked for tributaries, others are. if (CheckTributaries(a_Chunk, a_RelX, a_RelY, a_RelZ, MyMeta)) { // Has no tributary, has been decreased (in CheckTributaries()), // no more processing needed (neighbors have been scheduled by the decrease) FLOG(" CheckTributaries exit"); return; } } // New meta for the spreading to neighbors: // If this is a source block or was falling, the new meta is just the falloff // Otherwise it is the current meta plus falloff (may be larger than max height, will be checked later) NIBBLETYPE NewMeta = ((MyMeta == 0) || ((MyMeta & 0x08) != 0)) ? m_Falloff : (MyMeta + m_Falloff); bool SpreadFurther = true; if (a_RelY > 0) { BLOCKTYPE Below = a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ); if (IsPassableForFluid(Below) || IsBlockLava(Below) || IsBlockWater(Below)) { // Spread only down, possibly washing away what's there or turning lava to stone / cobble / obsidian: SpreadToNeighbor(a_Chunk, a_RelX, a_RelY - 1, a_RelZ, 8); // Source blocks spread both downwards and sideways if (MyMeta != 0) { SpreadFurther = false; } } // If source creation is on, check for it here: else if ( (m_NumNeighborsForSource > 0) && // Source creation is on (MyMeta == m_Falloff) && // Only exactly one block away from a source (fast bail-out) !IsPassableForFluid(Below) && // Only exactly 1 block deep CheckNeighborsForSource(a_Chunk, a_RelX, a_RelY, a_RelZ) // Did we create a source? ) { // We created a source, no more spreading is to be done now // Also has been re-scheduled for ticking in the next wave, so no marking is needed return; } } if (SpreadFurther && (NewMeta < 8)) { // Spread to the neighbors: SpreadXZ(a_Chunk, a_RelX, a_RelY, a_RelZ, NewMeta); } // Mark as processed: a_Chunk->FastSetBlock(a_RelX, a_RelY, a_RelZ, m_StationaryFluidBlock, MyMeta); }
void cFloodyFluidSimulator::SpreadToNeighbor(cChunk * a_NearChunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_NewMeta) { ASSERT(a_NewMeta <= 8); // Invalid meta values ASSERT(a_NewMeta > 0); // Source blocks aren't spread a_NearChunk = a_NearChunk->GetRelNeighborChunkAdjustCoords(a_RelX, a_RelZ); if ((a_NearChunk == NULL) || (!a_NearChunk->IsValid())) { // Chunk not available return; } const int BlockX = a_NearChunk->GetPosX() * cChunkDef::Width + a_RelX; const int BlockZ = a_NearChunk->GetPosZ() * cChunkDef::Width + a_RelZ; BLOCKTYPE BlockType; NIBBLETYPE BlockMeta; a_NearChunk->GetBlockTypeMeta(a_RelX, a_RelY, a_RelZ, BlockType, BlockMeta); if (IsAllowedBlock(BlockType)) { if ((BlockMeta == a_NewMeta) || IsHigherMeta(BlockMeta, a_NewMeta)) { // Don't spread there, there's already a higher or same level there return; } } // Check water - lava interaction: if (m_FluidBlock == E_BLOCK_LAVA) { if (IsBlockWater(BlockType)) { // Lava flowing into water, change to stone / cobblestone based on direction: BLOCKTYPE NewBlock = (a_NewMeta == 8) ? E_BLOCK_STONE : E_BLOCK_COBBLESTONE; FLOG(" Lava flowing into water, turning water at rel {%d, %d, %d} into stone", a_RelX, a_RelY, a_RelZ, ItemTypeToString(NewBlock).c_str() ); a_NearChunk->SetBlock(a_RelX, a_RelY, a_RelZ, NewBlock, 0); a_NearChunk->BroadcastSoundEffect("random.fizz", BlockX * 8, a_RelY * 8, BlockZ * 8, 0.5f, 1.5f); return; } } else if (m_FluidBlock == E_BLOCK_WATER) { if (IsBlockLava(BlockType)) { // Water flowing into lava, change to cobblestone / obsidian based on dest block: BLOCKTYPE NewBlock = (BlockMeta == 0) ? E_BLOCK_OBSIDIAN : E_BLOCK_COBBLESTONE; FLOG(" Water flowing into lava, turning lava at rel {%d, %d, %d} into %s", a_RelX, a_RelY, a_RelZ, ItemTypeToString(NewBlock).c_str() ); a_NearChunk->SetBlock(a_RelX, a_RelY, a_RelZ, NewBlock, 0); a_NearChunk->BroadcastSoundEffect("random.fizz", BlockX * 8, a_RelY * 8, BlockZ * 8, 0.5f, 1.5f); return; } } else { ASSERT(!"Unknown fluid!"); } if (!IsPassableForFluid(BlockType)) { // Can't spread there return; } // Wash away the block there, if possible: if (CanWashAway(BlockType)) { cBlockHandler * Handler = BlockHandler(BlockType); if (Handler->DoesDropOnUnsuitable()) { cChunkInterface ChunkInterface(m_World.GetChunkMap()); cBlockInServerPluginInterface PluginInterface(m_World); Handler->DropBlock( ChunkInterface, m_World, PluginInterface, NULL, BlockX, a_RelY, BlockZ ); } } // if (CanWashAway) // Spread: FLOG(" Spreading to {%d, %d, %d} with meta %d", BlockX, a_RelY, BlockZ, a_NewMeta); a_NearChunk->SetBlock(a_RelX, a_RelY, a_RelZ, m_FluidBlock, a_NewMeta); m_World.GetSimulatorManager()->WakeUp(BlockX, a_RelY, BlockZ, a_NearChunk); HardenBlock(a_NearChunk, a_RelX, a_RelY, a_RelZ, m_FluidBlock, a_NewMeta); }
void cMonster::TickPathFinding() { const int PosX = POSX_TOINT; const int PosY = POSY_TOINT; const int PosZ = POSZ_TOINT; m_FinalDestination.y = (double)FindFirstNonAirBlockPosition(m_FinalDestination.x, m_FinalDestination.z); std::vector<Vector3d> m_PotentialCoordinates; m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ)); static const struct // Define which directions to try to move to { int x, z; } gCrossCoords[] = { { 1, 0}, {-1, 0}, { 0, 1}, { 0,-1}, } ; if ((PosY - 1 < 0) || (PosY + 2 > cChunkDef::Height) /* PosY + 1 will never be true if PosY + 2 is not */) { // Too low/high, can't really do anything FinishPathFinding(); return; } for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) { if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ))) { continue; } BLOCKTYPE BlockAtY = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ); BLOCKTYPE BlockAtYP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 1, gCrossCoords[i].z + PosZ); BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ); int LowestY = FindFirstNonAirBlockPosition(gCrossCoords[i].x + PosX, gCrossCoords[i].z + PosZ); BLOCKTYPE BlockAtLowestY = m_World->GetBlock(gCrossCoords[i].x + PosX, LowestY, gCrossCoords[i].z + PosZ); if ( (!cBlockInfo::IsSolid(BlockAtY)) && (!cBlockInfo::IsSolid(BlockAtYP)) && (!IsBlockLava(BlockAtLowestY)) && (BlockAtLowestY != E_BLOCK_CACTUS) && (PosY - LowestY < FALL_DAMAGE_HEIGHT) ) { m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ)); } else if ( (cBlockInfo::IsSolid(BlockAtY)) && (BlockAtY != E_BLOCK_CACTUS) && (!cBlockInfo::IsSolid(BlockAtYP)) && (!cBlockInfo::IsSolid(BlockAtYPP)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE) ) { m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ)); } } if (!m_PotentialCoordinates.empty()) { Vector3f ShortestCoords = m_PotentialCoordinates.front(); for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr) { Vector3f Distance = m_FinalDestination - ShortestCoords; Vector3f Distance2 = m_FinalDestination - *itr; if (Distance.SqrLength() > Distance2.SqrLength()) { ShortestCoords = *itr; } } m_Destination = ShortestCoords; m_Destination.z += 0.5f; m_Destination.x += 0.5f; } else { FinishPathFinding(); } }
void cMonster::TickPathFinding() { int PosX = (int)floor(GetPosX()); int PosY = (int)floor(GetPosY()); int PosZ = (int)floor(GetPosZ()); m_FinalDestination.y = (double)FindFirstNonAirBlockPosition(m_FinalDestination.x, m_FinalDestination.z); std::vector<Vector3d> m_PotentialCoordinates; m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ)); static const struct // Define which directions to try to move to { int x, z; } gCrossCoords[] = { { 1, 0}, {-1, 0}, { 0, 1}, { 0,-1}, } ; for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) { if ((gCrossCoords[i].x + PosX == PosX) && (gCrossCoords[i].z + PosZ == PosZ)) { continue; } if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ))) { continue; } BLOCKTYPE BlockAtY = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ); BLOCKTYPE BlockAtYP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 1, gCrossCoords[i].z + PosZ); BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ); BLOCKTYPE BlockAtYM = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY - 1, gCrossCoords[i].z + PosZ); if ((!g_BlockIsSolid[BlockAtY]) && (!g_BlockIsSolid[BlockAtYP]) && (!IsBlockLava(BlockAtYM)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE)) { m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ)); } else if ((g_BlockIsSolid[BlockAtY]) && (!g_BlockIsSolid[BlockAtYP]) && (!g_BlockIsSolid[BlockAtYPP]) && (!IsBlockLava(BlockAtYM)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE)) { m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ)); } } if (!m_PotentialCoordinates.empty()) { Vector3f ShortestCoords = m_PotentialCoordinates.front(); for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr) { Vector3f Distance = m_FinalDestination - ShortestCoords; Vector3f Distance2 = m_FinalDestination - *itr; if (Distance.SqrLength() > Distance2.SqrLength()) { ShortestCoords = *itr; } } m_Destination = ShortestCoords; m_Destination.z += 0.5f; m_Destination.x += 0.5f; } else { FinishPathFinding(); } }