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 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); }
/** Draws the road into the chunk. The heightmap is not queried from the heightgen, but is given via parameter, so that it may be queried just once for all roads in a chunk. */ void DrawRoad(cChunkDesc & a_Chunk, cPlacedPiece & a_Road, cChunkDef::HeightMap & a_HeightMap) { cCuboid RoadCoords = a_Road.GetHitBox(); RoadCoords.Sort(); int MinX = std::max(RoadCoords.p1.x - a_Chunk.GetChunkX() * cChunkDef::Width, 0); int MaxX = std::min(RoadCoords.p2.x - a_Chunk.GetChunkX() * cChunkDef::Width, cChunkDef::Width - 1); int MinZ = std::max(RoadCoords.p1.z - a_Chunk.GetChunkZ() * cChunkDef::Width, 0); int MaxZ = std::min(RoadCoords.p2.z - a_Chunk.GetChunkZ() * cChunkDef::Width, cChunkDef::Width - 1); auto WaterRoadBlockType = m_Prefabs.GetVillageWaterRoadBlockType(); auto WaterRoadBlockMeta = m_Prefabs.GetVillageWaterRoadBlockMeta(); auto RoadBlockType = m_Prefabs.GetVillageRoadBlockType(); auto RoadBlockMeta = m_Prefabs.GetVillageRoadBlockMeta(); for (int z = MinZ; z <= MaxZ; z++) { for (int x = MinX; x <= MaxX; x++) { if (IsBlockWater(a_Chunk.GetBlockType(x, cChunkDef::GetHeight(a_HeightMap, x, z), z))) { a_Chunk.SetBlockTypeMeta(x, cChunkDef::GetHeight(a_HeightMap, x, z), z, WaterRoadBlockType, WaterRoadBlockMeta); } else { a_Chunk.SetBlockTypeMeta(x, cChunkDef::GetHeight(a_HeightMap, x, z), z, RoadBlockType, RoadBlockMeta); } } } }
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 cFinishGenIce::GenFinish(cChunkDesc & a_ChunkDesc) { // Turn surface water into ice in icy biomes for (int z = 0; z < cChunkDef::Width; z++) { for (int x = 0; x < cChunkDef::Width; x++) { int Height = a_ChunkDesc.GetHeight(x, z); if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height) { // Height isn't high enough for snow to start forming. continue; } if (!IsBlockWater(a_ChunkDesc.GetBlockType(x, Height, z))) { // The block isn't a water block. continue; } if (a_ChunkDesc.GetBlockMeta(x, Height, z) != 0) { // The water block isn't a source block. continue; } a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE); } // for x } // for z }
bool cMobSpawner::CheckPackCenter(BLOCKTYPE a_BlockType) { // Packs of non-water mobs can only be centered on an air block // Packs of water mobs can only be centered on a water block if (m_MonsterFamily == cMonster::mfWater) { return IsBlockWater(a_BlockType); } else { return a_BlockType == E_BLOCK_AIR; } }
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); }
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 cPath::BuildPath() { cPathCell * CurrentCell = GetCell(m_Destination); while (CurrentCell->m_Parent != nullptr) { // Waypoints are cylinders that start at some particular x, y, z and have infinite height. // Submerging water waypoints allows swimming mobs to be able to touch them. if (IsBlockWater(GetCell(CurrentCell->m_Location + Vector3i(0, -1, 0))->m_BlockType)) { CurrentCell->m_Location.y -= 30; } m_PathPoints.push_back(CurrentCell->m_Location); // Populate the cPath with points. All midpoints are added. Destination is added. Source is excluded. CurrentCell = CurrentCell->m_Parent; } }
void cBoat::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); BroadcastMovementUpdate(); SetSpeed(GetSpeed() * 0.97); // Slowly decrease the speed if ((POSY_TOINT < 0) || (POSY_TOINT >= cChunkDef::Height)) { return; } if (IsBlockWater(m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT))) { if (GetSpeedY() < 2) { AddSpeedY(0.2); } } }
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); }
bool cMobSpawner::CanSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, eMonsterType a_MobType, EMCSBiome a_Biome) { if (a_Chunk == nullptr) { return false; } cFastRandom Random; BLOCKTYPE TargetBlock = a_Chunk->GetBlock(a_RelX, a_RelY, a_RelZ); cPlayer * a_Closest_Player = a_Chunk->GetWorld()->FindClosestPlayer(a_Chunk->PositionToWorldPosition(a_RelX, a_RelY, a_RelZ), 24); if (a_Closest_Player != nullptr) // Too close to a player, bail out { return false; } if ((a_RelY >= cChunkDef::Height - 1) || (a_RelY <= 0)) { return false; } NIBBLETYPE BlockLight = a_Chunk->GetBlockLight(a_RelX, a_RelY, a_RelZ); NIBBLETYPE SkyLight = a_Chunk->GetSkyLight(a_RelX, a_RelY, a_RelZ); BLOCKTYPE BlockAbove = a_Chunk->GetBlock(a_RelX, a_RelY + 1, a_RelZ); BLOCKTYPE BlockBelow = a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ); SkyLight = a_Chunk->GetTimeAlteredLight(SkyLight); switch (a_MobType) { case mtGuardian: { return IsBlockWater(TargetBlock) && (a_RelY >= 45) && (a_RelY <= 62); } case mtSquid: { return IsBlockWater(TargetBlock) && (a_RelY >= 45) && (a_RelY <= 62); } case mtBat: { return (a_RelY <= 63) && (BlockLight <= 4) && (SkyLight <= 4) && (TargetBlock == E_BLOCK_AIR) && !cBlockInfo::IsTransparent(BlockAbove); } case mtChicken: case mtCow: case mtPig: case mtHorse: case mtRabbit: case mtSheep: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!cBlockInfo::IsTransparent(BlockBelow)) && (BlockBelow == E_BLOCK_GRASS) && (SkyLight >= 9) ); } case mtOcelot: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && ( (BlockBelow == E_BLOCK_GRASS) || (BlockBelow == E_BLOCK_LEAVES) || (BlockBelow == E_BLOCK_NEW_LEAVES) ) && (a_RelY >= 62) && (Random.NextInt(3) != 0) ); } case mtEnderman: { if (a_RelY < 250) { BLOCKTYPE BlockTop = a_Chunk->GetBlock(a_RelX, a_RelY + 2, a_RelZ); if (BlockTop == E_BLOCK_AIR) { BlockTop = a_Chunk->GetBlock(a_RelX, a_RelY + 3, a_RelZ); return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (BlockTop == E_BLOCK_AIR) && (!cBlockInfo::IsTransparent(BlockBelow)) && (SkyLight <= 7) && (BlockLight <= 7) ); } } break; } case mtSpider: { bool CanSpawn = true; bool HasFloor = false; for (int x = 0; x < 2; ++x) { for (int z = 0; z < 2; ++z) { CanSpawn = a_Chunk->UnboundedRelGetBlockType(a_RelX + x, a_RelY, a_RelZ + z, TargetBlock); CanSpawn = CanSpawn && (TargetBlock == E_BLOCK_AIR); if (!CanSpawn) { return false; } HasFloor = ( HasFloor || ( a_Chunk->UnboundedRelGetBlockType(a_RelX + x, a_RelY - 1, a_RelZ + z, TargetBlock) && !cBlockInfo::IsTransparent(TargetBlock) ) ); } } return CanSpawn && HasFloor && (SkyLight <= 7) && (BlockLight <= 7); } case mtCaveSpider: { return ( (TargetBlock == E_BLOCK_AIR) && (!cBlockInfo::IsTransparent(BlockBelow)) && (SkyLight <= 7) && (BlockLight <= 7) && (Random.NextInt(2) == 0) ); } case mtCreeper: case mtSkeleton: case mtZombie: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!cBlockInfo::IsTransparent(BlockBelow)) && (SkyLight <= 7) && (BlockLight <= 7) && (Random.NextInt(2) == 0) ); } case mtMagmaCube: case mtSlime: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!cBlockInfo::IsTransparent(BlockBelow)) && ( (a_RelY <= 40) || (a_Biome == biSwampland) ) ); } case mtGhast: case mtZombiePigman: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!cBlockInfo::IsTransparent(BlockBelow)) && (Random.NextInt(20) == 0) ); } case mtWolf: { return ( (TargetBlock == E_BLOCK_GRASS) && (BlockAbove == E_BLOCK_AIR) && ( (a_Biome == biTaiga) || (a_Biome == biTaigaHills) || (a_Biome == biForest) || (a_Biome == biForestHills) || (a_Biome == biColdTaiga) || (a_Biome == biColdTaigaHills) || (a_Biome == biTaigaM) || (a_Biome == biMegaTaiga) || (a_Biome == biMegaTaigaHills) ) ); } case mtMooshroom: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (BlockBelow == E_BLOCK_MYCELIUM) && ( (a_Biome == biMushroomShore) || (a_Biome == biMushroomIsland) ) ); } default: { LOGD("MG TODO: Write spawning rule for mob type %d", a_MobType); return false; } } return false; }
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 cFloater::Tick(float a_Dt, cChunk & a_Chunk) { HandlePhysics(a_Dt, a_Chunk); if (IsBlockWater(m_World->GetBlock((int) GetPosX(), (int) GetPosY(), (int) GetPosZ())) && m_World->GetBlockMeta((int) GetPosX(), (int) GetPosY(), (int) GetPosZ()) == 0) { if ((!m_CanPickupItem) && (m_AttachedMobID == -1)) // Check if you can't already pickup a fish and if the floater isn't attached to a mob. { if (m_CountDownTime <= 0) { m_World->BroadcastSoundEffect("random.splash", GetPosX(), GetPosY(), GetPosZ(), 1, 1); SetPosY(GetPosY() - 1); m_CanPickupItem = true; m_PickupCountDown = 20; m_CountDownTime = 100 + m_World->GetTickRandomNumber(800); LOGD("Floater %i can be picked up", GetUniqueID()); } else if (m_CountDownTime == 20) // Calculate the position where the particles should spawn and start producing them. { LOGD("Started producing particles for floater %i", GetUniqueID()); m_ParticlePos.Set(GetPosX() + (-4 + m_World->GetTickRandomNumber(8)), GetPosY(), GetPosZ() + (-4 + m_World->GetTickRandomNumber(8))); m_World->BroadcastParticleEffect("splash", (float) m_ParticlePos.x, (float) m_ParticlePos.y, (float) m_ParticlePos.z, 0, 0, 0, 0, 15); } else if (m_CountDownTime < 20) { m_ParticlePos = (m_ParticlePos + (GetPosition() - m_ParticlePos) / 6); m_World->BroadcastParticleEffect("splash", (float) m_ParticlePos.x, (float) m_ParticlePos.y, (float) m_ParticlePos.z, 0, 0, 0, 0, 15); } m_CountDownTime--; if (m_World->GetHeight((int) GetPosX(), (int) GetPosZ()) == (int) GetPosY()) { if (m_World->IsWeatherWet() && m_World->GetTickRandomNumber(3) == 0) // 25% chance of an extra countdown when being rained on. { m_CountDownTime--; } } else // if the floater is underground it has a 50% chance of not decreasing the countdown. { if (m_World->GetTickRandomNumber(1) == 0) { m_CountDownTime++; } } } SetSpeedY(0.7); } if (CanPickup()) // Make sure the floater "loses its fish" { m_PickupCountDown--; if (m_PickupCountDown == 0) { m_CanPickupItem = false; LOGD("The fish is gone. Floater %i can not pick an item up.", GetUniqueID()); } } if ((GetSpeed().Length() > 4) && (m_AttachedMobID == -1)) { cFloaterEntityCollisionCallback Callback(this, GetPosition(), GetPosition() + GetSpeed() / 20); a_Chunk.ForEachEntity(Callback); if (Callback.HasHit()) { AttachTo(Callback.GetHitEntity()); Callback.GetHitEntity()->TakeDamage(*this); // TODO: the player attacked the mob not the floater. m_AttachedMobID = Callback.GetHitEntity()->GetUniqueID(); } } cFloaterCheckEntityExist EntityCallback; m_World->DoWithEntityByID(m_PlayerID, EntityCallback); if (!EntityCallback.DoesExist()) // The owner doesn't exist anymore. Destroy the floater entity. { Destroy(true); } if (m_AttachedMobID != -1) { m_World->DoWithEntityByID(m_AttachedMobID, EntityCallback); // The mob the floater was attached to doesn't exist anymore. if (!EntityCallback.DoesExist()) { m_AttachedMobID = -1; } } SetSpeedX(GetSpeedX() * 0.95); SetSpeedZ(GetSpeedZ() * 0.95); BroadcastMovementUpdate(); }
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); }
bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk) { // If the Y coord is out of range, return the most logical result without considering anything else: int RelY = FloorC(a_Location.y); if (RelY < 0) { // Never burn under the world return false; } if (RelY >= cChunkDef::Height) { // Always burn above the world return true; } if (RelY <= 0) { // The mob is about to die, no point in burning return false; } PREPARE_REL_AND_CHUNK(a_Location, a_Chunk); if (!RelSuccess) { return false; } if ( (Chunk->GetBlock(Rel.x, Rel.y, Rel.z) != E_BLOCK_SOULSAND) && // Not on soulsand (GetWorld()->GetTimeOfDay() < 12000 + 1000) && // Daytime GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining ) { int MobHeight = FloorC(a_Location.y + GetHeight()) - 1; // The block Y coord of the mob's head if (MobHeight >= cChunkDef::Height) { return true; } // Start with the highest block and scan down to the mob's head. // If a non transparent is found, return false (do not burn). Otherwise return true. // Note that this loop is not a performance concern as transparent blocks are rare and the loop almost always bailes out // instantly.(An exception is e.g. standing under a long column of glass). int CurrentBlock = Chunk->GetHeight(Rel.x, Rel.z); while (CurrentBlock >= MobHeight) { BLOCKTYPE Block = Chunk->GetBlock(Rel.x, CurrentBlock, Rel.z); if ( // Do not burn if a block above us meets one of the following conditions: (!cBlockInfo::IsTransparent(Block)) || (Block == E_BLOCK_LEAVES) || (Block == E_BLOCK_NEW_LEAVES) || (IsBlockWater(Block)) ) { return false; } --CurrentBlock; } return true; } return false; }
void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) { // TODO Add collision detection with entities. a_Dt /= 1000; // Convert from msec to sec Vector3d NextPos = Vector3d(GetPosX(),GetPosY(),GetPosZ()); Vector3d NextSpeed = Vector3d(GetSpeedX(),GetSpeedY(),GetSpeedZ()); int BlockX = (int) floor(NextPos.x); int BlockY = (int) floor(NextPos.y); int BlockZ = (int) floor(NextPos.z); if ((BlockY >= cChunkDef::Height) || (BlockY < 0)) { // Outside of the world cChunk * NextChunk = a_Chunk.GetNeighborChunk(BlockX, BlockZ); // See if we can commit our changes. If not, we will discard them. if (NextChunk != NULL) { SetSpeed(NextSpeed); NextPos += (NextSpeed * a_Dt); SetPosition(NextPos); } return; } // Make sure we got the correct chunk and a valid one. No one ever knows... cChunk * NextChunk = a_Chunk.GetNeighborChunk(BlockX, BlockZ); if (NextChunk != NULL) { int RelBlockX = BlockX - (NextChunk->GetPosX() * cChunkDef::Width); int RelBlockZ = BlockZ - (NextChunk->GetPosZ() * cChunkDef::Width); BLOCKTYPE BlockIn = NextChunk->GetBlock( RelBlockX, BlockY, RelBlockZ ); BLOCKTYPE BlockBelow = (BlockY > 0) ? NextChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR; if (!g_BlockIsSolid[BlockIn]) // Making sure we are not inside a solid block { if (m_bOnGround) // check if it's still on the ground { if (!g_BlockIsSolid[BlockBelow]) // Check if block below is air or water. { m_bOnGround = false; } } } else { // Push out entity. BLOCKTYPE GotBlock; static const struct { int x, y, z; } gCrossCoords[] = { { 1, 0, 0}, {-1, 0, 0}, { 0, 0, 1}, { 0, 0, -1}, } ; bool IsNoAirSurrounding = true; for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++) { if (!NextChunk->UnboundedRelGetBlockType(RelBlockX + gCrossCoords[i].x, BlockY, RelBlockZ + gCrossCoords[i].z, GotBlock)) { // The pickup is too close to an unloaded chunk, bail out of any physics handling return; } if (!g_BlockIsSolid[GotBlock]) { NextPos.x += gCrossCoords[i].x; NextPos.z += gCrossCoords[i].z; IsNoAirSurrounding = false; break; } } // for i - gCrossCoords[] if (IsNoAirSurrounding) { NextPos.y += 0.5; } m_bOnGround = true; LOGD("Entity #%d (%s) is inside a block at {%d, %d, %d}", m_UniqueID, GetClass(), BlockX, BlockY, BlockZ ); } if (!m_bOnGround) { float fallspeed; if (IsBlockWater(BlockIn)) { fallspeed = m_Gravity * a_Dt / 3; // Fall 3x slower in water. } else if (IsBlockRail(BlockBelow) && IsMinecart()) // Rails aren't solid, except for Minecarts { fallspeed = 0; m_bOnGround = true; } else if (BlockIn == E_BLOCK_COBWEB) { NextSpeed.y *= 0.05; // Reduce overall falling speed fallspeed = 0; // No falling. } else { // Normal gravity fallspeed = m_Gravity * a_Dt; } NextSpeed.y += fallspeed; } else { if (IsMinecart()) { if (!IsBlockRail(BlockBelow)) { // Friction if minecart is off track, otherwise, Minecart.cpp handles this if (NextSpeed.SqrLength() > 0.0004f) { NextSpeed.x *= 0.7f / (1 + a_Dt); if (fabs(NextSpeed.x) < 0.05) { NextSpeed.x = 0; } NextSpeed.z *= 0.7f / (1 + a_Dt); if (fabs(NextSpeed.z) < 0.05) { NextSpeed.z = 0; } } } } else { // Friction for non-minecarts if (NextSpeed.SqrLength() > 0.0004f) { NextSpeed.x *= 0.7f / (1 + a_Dt); if (fabs(NextSpeed.x) < 0.05) { NextSpeed.x = 0; } NextSpeed.z *= 0.7f / (1 + a_Dt); if (fabs(NextSpeed.z) < 0.05) { NextSpeed.z = 0; } } } } // Adjust X and Z speed for COBWEB temporary. This speed modification should be handled inside block handlers since we // might have different speed modifiers according to terrain. if (BlockIn == E_BLOCK_COBWEB) { NextSpeed.x *= 0.25; NextSpeed.z *= 0.25; } //Get water direction Direction WaterDir = m_World->GetWaterSimulator()->GetFlowingDirection(BlockX, BlockY, BlockZ); m_WaterSpeed *= 0.9f; //Reduce speed each tick switch(WaterDir) { case X_PLUS: m_WaterSpeed.x = 0.2f; m_bOnGround = false; break; case X_MINUS: m_WaterSpeed.x = -0.2f; m_bOnGround = false; break; case Z_PLUS: m_WaterSpeed.z = 0.2f; m_bOnGround = false; break; case Z_MINUS: m_WaterSpeed.z = -0.2f; m_bOnGround = false; break; default: break; } if (fabs(m_WaterSpeed.x) < 0.05) { m_WaterSpeed.x = 0; } if (fabs(m_WaterSpeed.z) < 0.05) { m_WaterSpeed.z = 0; } NextSpeed += m_WaterSpeed; if( NextSpeed.SqrLength() > 0.f ) { cTracer Tracer( GetWorld() ); int Ret = Tracer.Trace( NextPos, NextSpeed, 2 ); if( Ret ) // Oh noez! we hit something { // Set to hit position if( (Tracer.RealHit - NextPos).SqrLength() <= ( NextSpeed * a_Dt ).SqrLength() ) { if( Ret == 1 ) { if( Tracer.HitNormal.x != 0.f ) NextSpeed.x = 0.f; if( Tracer.HitNormal.y != 0.f ) NextSpeed.y = 0.f; if( Tracer.HitNormal.z != 0.f ) NextSpeed.z = 0.f; if( Tracer.HitNormal.y > 0 ) // means on ground { m_bOnGround = true; } } NextPos.Set(Tracer.RealHit.x,Tracer.RealHit.y,Tracer.RealHit.z); NextPos.x += Tracer.HitNormal.x * 0.3f; NextPos.y += Tracer.HitNormal.y * 0.05f; // Any larger produces entity vibration-upon-the-spot NextPos.z += Tracer.HitNormal.z * 0.3f; } else { NextPos += (NextSpeed * a_Dt); } } else { // We didn't hit anything, so move =] NextPos += (NextSpeed * a_Dt); } } BlockX = (int) floor(NextPos.x); BlockZ = (int) floor(NextPos.z); NextChunk = NextChunk->GetNeighborChunk(BlockX,BlockZ); // See if we can commit our changes. If not, we will discard them. if (NextChunk != NULL) { if (NextPos.x != GetPosX()) SetPosX(NextPos.x); if (NextPos.y != GetPosY()) SetPosY(NextPos.y); if (NextPos.z != GetPosZ()) SetPosZ(NextPos.z); if (NextSpeed.x != GetSpeedX()) SetSpeedX(NextSpeed.x); if (NextSpeed.y != GetSpeedY()) SetSpeedY(NextSpeed.y); if (NextSpeed.z != GetSpeedZ()) SetSpeedZ(NextSpeed.z); } } }
bool cMap::UpdatePixel(unsigned int a_X, unsigned int a_Z) { int BlockX = m_CenterX + static_cast<int>((a_X - m_Width / 2) * GetPixelWidth()); int BlockZ = m_CenterZ + static_cast<int>((a_Z - m_Height / 2) * GetPixelWidth()); int ChunkX, ChunkZ; cChunkDef::BlockToChunk(BlockX, BlockZ, ChunkX, ChunkZ); int RelX = BlockX - (ChunkX * cChunkDef::Width); int RelZ = BlockZ - (ChunkZ * cChunkDef::Width); ASSERT(m_World != nullptr); ColorID PixelData; m_World->DoWithChunk(ChunkX, ChunkZ, [&](cChunk & a_Chunk) { if (!a_Chunk.IsValid()) { return false; } if (GetDimension() == dimNether) { // TODO 2014-02-22 xdot: Nether maps return false; } static const std::array<unsigned char, 4> BrightnessID = { { 3, 0, 1, 2 } }; // Darkest to lightest BLOCKTYPE TargetBlock; NIBBLETYPE TargetMeta; auto Height = a_Chunk.GetHeight(RelX, RelZ); auto ChunkHeight = cChunkDef::Height; a_Chunk.GetBlockTypeMeta(RelX, Height, RelZ, TargetBlock, TargetMeta); auto ColourID = BlockHandler(TargetBlock)->GetMapBaseColourID(TargetMeta); if (IsBlockWater(TargetBlock)) { ChunkHeight /= 4; while (((--Height) != -1) && IsBlockWater(a_Chunk.GetBlock(RelX, Height, RelZ))) { continue; } } else if (ColourID == 0) { while (((--Height) != -1) && ((ColourID = BlockHandler(a_Chunk.GetBlock(RelX, Height, RelZ))->GetMapBaseColourID(a_Chunk.GetMeta(RelX, Height, RelZ))) == 0)) { continue; } } // Multiply base color ID by 4 and add brightness ID const int BrightnessIDSize = static_cast<int>(BrightnessID.size()); PixelData = ColourID * 4 + BrightnessID[static_cast<size_t>(Clamp<int>((BrightnessIDSize * Height) / ChunkHeight, 0, BrightnessIDSize - 1))]; return false; } ); SetPixel(a_X, a_Z, PixelData); return true; }
bool cTracer::Trace( const Vector3f & a_Start, const Vector3f & a_Direction, int a_Distance, bool a_LineOfSight) { if ((a_Start.y < 0) || (a_Start.y >= cChunkDef::Height)) { LOGD("%s: Start Y is outside the world (%.2f), not tracing.", __FUNCTION__, a_Start.y); return false; } SetValues(a_Start, a_Direction); Vector3f End = a_Start + (dir * (float)a_Distance); if (End.y < 0) { float dist = -a_Start.y / dir.y; End = a_Start + (dir * dist); } // end voxel coordinates end1.x = (int)floorf(End.x); end1.y = (int)floorf(End.y); end1.z = (int)floorf(End.z); // check if first is occupied if (pos.Equals(end1)) { return false; } bool reachedX = false, reachedY = false, reachedZ = false; int Iterations = 0; while (Iterations < a_Distance) { Iterations++; if ((tMax.x < tMax.y) && (tMax.x < tMax.z)) { tMax.x += tDelta.x; pos.x += step.x; } else if (tMax.y < tMax.z) { tMax.y += tDelta.y; pos.y += step.y; } else { tMax.z += tDelta.z; pos.z += step.z; } if (step.x > 0.0f) { if (pos.x >= end1.x) { reachedX = true; } } else if (pos.x <= end1.x) { reachedX = true; } if (step.y > 0.0f) { if (pos.y >= end1.y) { reachedY = true; } } else if (pos.y <= end1.y) { reachedY = true; } if (step.z > 0.0f) { if (pos.z >= end1.z) { reachedZ = true; } } else if (pos.z <= end1.z) { reachedZ = true; } if (reachedX && reachedY && reachedZ) { return false; } if ((pos.y < 0) || (pos.y >= cChunkDef::Height)) { return false; } BLOCKTYPE BlockID = m_World->GetBlock(pos.x, pos.y, pos.z); // Block is counted as a collision if we are not doing a line of sight and it is solid, // or if the block is not air and not water. That way mobs can still see underwater. if ((!a_LineOfSight && cBlockInfo::IsSolid(BlockID)) || (a_LineOfSight && (BlockID != E_BLOCK_AIR) && !IsBlockWater(BlockID))) { BlockHitPosition = pos; int Normal = GetHitNormal(a_Start, End, pos); if (Normal > 0) { HitNormal = m_NormalTable[Normal-1]; } return true; } } return false; }
void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) { // TODO Add collision detection with entities. a_Dt /= 1000; Vector3d NextPos = Vector3d(GetPosX(),GetPosY(),GetPosZ()); Vector3d NextSpeed = Vector3d(GetSpeedX(),GetSpeedY(),GetSpeedZ()); int BlockX = (int) floor(NextPos.x); int BlockY = (int) floor(NextPos.y); int BlockZ = (int) floor(NextPos.z); if ((BlockY >= cChunkDef::Height) || (BlockY < 0)) { // Outside of the world // TODO: Current speed should still be added to the entity position // Otherwise TNT explosions in the void will still effect the bottommost layers of the world return; } // Make sure we got the correct chunk and a valid one. No one ever knows... cChunk * NextChunk = a_Chunk.GetNeighborChunk(BlockX,BlockZ); if (NextChunk != NULL) { int RelBlockX = BlockX - (NextChunk->GetPosX() * cChunkDef::Width); int RelBlockZ = BlockZ - (NextChunk->GetPosZ() * cChunkDef::Width); BLOCKTYPE BlockIn = NextChunk->GetBlock( RelBlockX, BlockY, RelBlockZ ); if (!g_BlockIsSolid[BlockIn]) // Making sure we are not inside a solid block { if (m_bOnGround) // check if it's still on the ground { BLOCKTYPE BlockBelow = NextChunk->GetBlock( RelBlockX, BlockY - 1, RelBlockZ ); if (!g_BlockIsSolid[BlockBelow]) // Check if block below is air or water. { m_bOnGround = false; } } } else { //Push out entity. m_bOnGround = true; NextPos.y += 0.2; LOGD("Entity #%d (%s) is inside a block at {%d,%d,%d}", m_UniqueID, GetClass(), BlockX, BlockY, BlockZ); } if (!m_bOnGround) { float fallspeed; if (IsBlockWater(BlockIn)) { fallspeed = -3.0f * a_Dt; //Fall slower in water. } else if (BlockIn == E_BLOCK_COBWEB) { NextSpeed.y *= 0.05; //Reduce overall falling speed fallspeed = 0; //No falling. } else { //Normal gravity fallspeed = m_Gravity * a_Dt; } NextSpeed.y += fallspeed; } else { //Friction if (NextSpeed.SqrLength() > 0.0004f) { NextSpeed.x *= 0.7f/(1+a_Dt); if ( fabs(NextSpeed.x) < 0.05 ) NextSpeed.x = 0; NextSpeed.z *= 0.7f/(1+a_Dt); if ( fabs(NextSpeed.z) < 0.05 ) NextSpeed.z = 0; } } //Adjust X and Z speed for COBWEB temporary. This speed modification should be handled inside block handlers since we //might have different speed modifiers according to terrain. if (BlockIn == E_BLOCK_COBWEB) { NextSpeed.x *= 0.25; NextSpeed.z *= 0.25; } //Get water direction Direction WaterDir = m_World->GetWaterSimulator()->GetFlowingDirection(BlockX, BlockY, BlockZ); m_WaterSpeed *= 0.9f; //Reduce speed each tick switch(WaterDir) { case X_PLUS: m_WaterSpeed.x = 1.f; m_bOnGround = false; break; case X_MINUS: m_WaterSpeed.x = -1.f; m_bOnGround = false; break; case Z_PLUS: m_WaterSpeed.z = 1.f; m_bOnGround = false; break; case Z_MINUS: m_WaterSpeed.z = -1.f; m_bOnGround = false; break; default: break; } if (fabs(m_WaterSpeed.x) < 0.05) { m_WaterSpeed.x = 0; } if (fabs(m_WaterSpeed.z) < 0.05) { m_WaterSpeed.z = 0; } NextSpeed += m_WaterSpeed; if( NextSpeed.SqrLength() > 0.f ) { cTracer Tracer( GetWorld() ); int Ret = Tracer.Trace( NextPos, NextSpeed, 2 ); if( Ret ) // Oh noez! we hit something { // Set to hit position if( (Tracer.RealHit - NextPos).SqrLength() <= ( NextSpeed * a_Dt ).SqrLength() ) { if( Ret == 1 ) { if( Tracer.HitNormal.x != 0.f ) NextSpeed.x = 0.f; if( Tracer.HitNormal.y != 0.f ) NextSpeed.y = 0.f; if( Tracer.HitNormal.z != 0.f ) NextSpeed.z = 0.f; if( Tracer.HitNormal.y > 0 ) // means on ground { m_bOnGround = true; } } NextPos.Set(Tracer.RealHit.x,Tracer.RealHit.y,Tracer.RealHit.z); NextPos.x += Tracer.HitNormal.x * 0.5f; NextPos.z += Tracer.HitNormal.z * 0.5f; } else NextPos += (NextSpeed * a_Dt); } else { // We didn't hit anything, so move =] NextPos += (NextSpeed * a_Dt); } } BlockX = (int) floor(NextPos.x); BlockZ = (int) floor(NextPos.z); NextChunk = NextChunk->GetNeighborChunk(BlockX,BlockZ); // See if we can commit our changes. If not, we will discard them. if (NextChunk != NULL) { if (NextPos.x != GetPosX()) SetPosX(NextPos.x); if (NextPos.y != GetPosY()) SetPosY(NextPos.y); if (NextPos.z != GetPosZ()) SetPosZ(NextPos.z); if (NextSpeed.x != GetSpeedX()) SetSpeedX(NextSpeed.x); if (NextSpeed.y != GetSpeedY()) SetSpeedY(NextSpeed.y); if (NextSpeed.z != GetSpeedZ()) SetSpeedZ(NextSpeed.z); } } }
void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) { int BlockX = POSX_TOINT; int BlockY = POSY_TOINT; int BlockZ = POSZ_TOINT; // Position changed -> super::HandlePhysics() called GET_AND_VERIFY_CURRENT_CHUNK(NextChunk, BlockX, BlockZ) // TODO Add collision detection with entities. a_Dt /= 1000; // Convert from msec to sec Vector3d NextPos = Vector3d(GetPosX(), GetPosY(), GetPosZ()); Vector3d NextSpeed = Vector3d(GetSpeedX(), GetSpeedY(), GetSpeedZ()); if ((BlockY >= cChunkDef::Height) || (BlockY < 0)) { // Outside of the world AddSpeedY(m_Gravity * a_Dt); AddPosition(GetSpeed() * a_Dt); return; } int RelBlockX = BlockX - (NextChunk->GetPosX() * cChunkDef::Width); int RelBlockZ = BlockZ - (NextChunk->GetPosZ() * cChunkDef::Width); BLOCKTYPE BlockIn = NextChunk->GetBlock( RelBlockX, BlockY, RelBlockZ ); BLOCKTYPE BlockBelow = (BlockY > 0) ? NextChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR; if (!cBlockInfo::IsSolid(BlockIn)) // Making sure we are not inside a solid block { if (m_bOnGround) // check if it's still on the ground { if (!cBlockInfo::IsSolid(BlockBelow)) // Check if block below is air or water. { m_bOnGround = false; } } } else { // Push out entity. BLOCKTYPE GotBlock; static const struct { int x, y, z; } gCrossCoords[] = { { 1, 0, 0}, {-1, 0, 0}, { 0, 0, 1}, { 0, 0, -1}, } ; bool IsNoAirSurrounding = true; for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) { if (!NextChunk->UnboundedRelGetBlockType(RelBlockX + gCrossCoords[i].x, BlockY, RelBlockZ + gCrossCoords[i].z, GotBlock)) { // The pickup is too close to an unloaded chunk, bail out of any physics handling return; } if (!cBlockInfo::IsSolid(GotBlock)) { NextPos.x += gCrossCoords[i].x; NextPos.z += gCrossCoords[i].z; IsNoAirSurrounding = false; break; } } // for i - gCrossCoords[] if (IsNoAirSurrounding) { NextPos.y += 0.5; } m_bOnGround = true; /* // DEBUG: LOGD("Entity #%d (%s) is inside a block at {%d, %d, %d}", m_UniqueID, GetClass(), BlockX, BlockY, BlockZ ); */ } if (!m_bOnGround) { float fallspeed; if (IsBlockWater(BlockIn)) { fallspeed = m_Gravity * a_Dt / 3; // Fall 3x slower in water. } else if (BlockIn == E_BLOCK_COBWEB) { NextSpeed.y *= 0.05; // Reduce overall falling speed fallspeed = 0; // No falling. } else { // Normal gravity fallspeed = m_Gravity * a_Dt; } NextSpeed.y += fallspeed; } else { // Friction if (NextSpeed.SqrLength() > 0.0004f) { NextSpeed.x *= 0.7f / (1 + a_Dt); if (fabs(NextSpeed.x) < 0.05) { NextSpeed.x = 0; } NextSpeed.z *= 0.7f / (1 + a_Dt); if (fabs(NextSpeed.z) < 0.05) { NextSpeed.z = 0; } } } // Adjust X and Z speed for COBWEB temporary. This speed modification should be handled inside block handlers since we // might have different speed modifiers according to terrain. if (BlockIn == E_BLOCK_COBWEB) { NextSpeed.x *= 0.25; NextSpeed.z *= 0.25; } //Get water direction Direction WaterDir = m_World->GetWaterSimulator()->GetFlowingDirection(BlockX, BlockY, BlockZ); m_WaterSpeed *= 0.9f; //Reduce speed each tick switch(WaterDir) { case X_PLUS: m_WaterSpeed.x = 0.2f; m_bOnGround = false; break; case X_MINUS: m_WaterSpeed.x = -0.2f; m_bOnGround = false; break; case Z_PLUS: m_WaterSpeed.z = 0.2f; m_bOnGround = false; break; case Z_MINUS: m_WaterSpeed.z = -0.2f; m_bOnGround = false; break; default: break; } if (fabs(m_WaterSpeed.x) < 0.05) { m_WaterSpeed.x = 0; } if (fabs(m_WaterSpeed.z) < 0.05) { m_WaterSpeed.z = 0; } NextSpeed += m_WaterSpeed; if (NextSpeed.SqrLength() > 0.f) { cTracer Tracer(GetWorld()); // Distance traced is an integer, so we round up from the distance we should go (Speed * Delta), else we will encounter collision detection failurse int DistanceToTrace = (int)(ceil((NextSpeed * a_Dt).SqrLength()) * 2); bool HasHit = Tracer.Trace(NextPos, NextSpeed, DistanceToTrace); if (HasHit) { // Oh noez! We hit something: verify that the (hit position - current) was smaller or equal to the (position that we should travel without obstacles - current) // This is because previously, we traced with a length that was rounded up (due to integer limitations), and in the case that something was hit, we don't want to overshoot our projected movement if ((Tracer.RealHit - NextPos).SqrLength() <= (NextSpeed * a_Dt).SqrLength()) { // Block hit was within our projected path // Begin by stopping movement in the direction that we hit something. The Normal is the line perpendicular to a 2D face and in this case, stores what block face was hit through either -1 or 1. // For example: HitNormal.y = -1 : BLOCK_FACE_YM; HitNormal.y = 1 : BLOCK_FACE_YP if (Tracer.HitNormal.x != 0.f) NextSpeed.x = 0.f; if (Tracer.HitNormal.y != 0.f) NextSpeed.y = 0.f; if (Tracer.HitNormal.z != 0.f) NextSpeed.z = 0.f; if (Tracer.HitNormal.y == 1) // Hit BLOCK_FACE_YP, we are on the ground { m_bOnGround = true; } // Now, set our position to the hit block (i.e. move part way along our intended trajectory) NextPos.Set(Tracer.RealHit.x, Tracer.RealHit.y, Tracer.RealHit.z); NextPos.x += Tracer.HitNormal.x * 0.1; NextPos.y += Tracer.HitNormal.y * 0.05; NextPos.z += Tracer.HitNormal.z * 0.1; } else { // We have hit a block but overshot our intended trajectory, move normally, safe in the warm cocoon of knowledge that we won't appear to teleport forwards on clients, // and that this piece of software will come to be hailed as the epitome of performance and functionality in C++, never before seen, and of such a like that will never // be henceforth seen again in the time of programmers and man alike // </&sensationalist> NextPos += (NextSpeed * a_Dt); } } else { // We didn't hit anything, so move =] NextPos += (NextSpeed * a_Dt); } } SetPosition(NextPos); SetSpeed(NextSpeed); }
int cTracer::GetHitNormal(const Vector3f & start, const Vector3f & end, const Vector3i & a_BlockPos) { Vector3i SmallBlockPos = a_BlockPos; char BlockID = m_World->GetBlock( a_BlockPos.x, a_BlockPos.y, a_BlockPos.z); if (BlockID == E_BLOCK_AIR || IsBlockWater(BlockID)) { return 0; } Vector3f BlockPos; BlockPos = Vector3f(SmallBlockPos); Vector3f Look = (end - start); Look.Normalize(); float dot = Look.Dot( Vector3f(-1, 0, 0)); // first face normal is x -1 if (dot < 0) { int Lines = LinesCross( start.x, start.y, end.x, end.y, BlockPos.x, BlockPos.y, BlockPos.x, BlockPos.y + 1); if (Lines == 1) { Lines = LinesCross( start.x, start.z, end.x, end.z, BlockPos.x, BlockPos.z, BlockPos.x, BlockPos.z + 1); if (Lines == 1) { intersect3D_SegmentPlane( start, end, BlockPos, Vector3f(-1, 0, 0)); return 1; } } } dot = Look.Dot( Vector3f(0, 0, -1)); // second face normal is z -1 if (dot < 0) { int Lines = LinesCross( start.z, start.y, end.z, end.y, BlockPos.z, BlockPos.y, BlockPos.z, BlockPos.y + 1); if (Lines == 1) { Lines = LinesCross( start.z, start.x, end.z, end.x, BlockPos.z, BlockPos.x, BlockPos.z, BlockPos.x + 1); if (Lines == 1) { intersect3D_SegmentPlane( start, end, BlockPos, Vector3f(0, 0, -1)); return 2; } } } dot = Look.Dot( Vector3f(1, 0, 0)); // third face normal is x 1 if (dot < 0) { int Lines = LinesCross( start.x, start.y, end.x, end.y, BlockPos.x + 1, BlockPos.y, BlockPos.x + 1, BlockPos.y + 1); if (Lines == 1) { Lines = LinesCross( start.x, start.z, end.x, end.z, BlockPos.x + 1, BlockPos.z, BlockPos.x + 1, BlockPos.z + 1); if (Lines == 1) { intersect3D_SegmentPlane( start, end, BlockPos + Vector3f(1, 0, 0), Vector3f(1, 0, 0)); return 3; } } } dot = Look.Dot( Vector3f(0, 0, 1)); // fourth face normal is z 1 if (dot < 0) { int Lines = LinesCross( start.z, start.y, end.z, end.y, BlockPos.z + 1, BlockPos.y, BlockPos.z + 1, BlockPos.y + 1); if (Lines == 1) { Lines = LinesCross( start.z, start.x, end.z, end.x, BlockPos.z + 1, BlockPos.x, BlockPos.z + 1, BlockPos.x + 1); if (Lines == 1) { intersect3D_SegmentPlane( start, end, BlockPos + Vector3f(0, 0, 1), Vector3f(0, 0, 1)); return 4; } } } dot = Look.Dot( Vector3f(0, 1, 0)); // fifth face normal is y 1 if (dot < 0) { int Lines = LinesCross( start.y, start.x, end.y, end.x, BlockPos.y + 1, BlockPos.x, BlockPos.y + 1, BlockPos.x + 1); if (Lines == 1) { Lines = LinesCross( start.y, start.z, end.y, end.z, BlockPos.y + 1, BlockPos.z, BlockPos.y + 1, BlockPos.z + 1); if (Lines == 1) { intersect3D_SegmentPlane( start, end, BlockPos + Vector3f(0, 1, 0), Vector3f(0, 1, 0)); return 5; } } } dot = Look.Dot( Vector3f(0, -1, 0)); // sixth face normal is y -1 if (dot < 0) { int Lines = LinesCross( start.y, start.x, end.y, end.x, BlockPos.y, BlockPos.x, BlockPos.y, BlockPos.x + 1); if (Lines == 1) { Lines = LinesCross( start.y, start.z, end.y, end.z, BlockPos.y, BlockPos.z, BlockPos.y, BlockPos.z + 1); if (Lines == 1) { intersect3D_SegmentPlane( start, end, BlockPos, Vector3f(0, -1, 0)); return 6; } } } return 0; }
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. */ }
bool cMobSpawner::CanSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, cMonster::eType a_MobType, EMCSBiome a_Biome) { BLOCKTYPE TargetBlock = E_BLOCK_AIR; if (m_AllowedTypes.find(a_MobType) != m_AllowedTypes.end() && a_Chunk->UnboundedRelGetBlockType(a_RelX, a_RelY, a_RelZ, TargetBlock)) { NIBBLETYPE BlockLight = a_Chunk->GetBlockLight(a_RelX, a_RelY, a_RelZ); NIBBLETYPE SkyLight = a_Chunk->GetSkyLight(a_RelX, a_RelY, a_RelZ); BLOCKTYPE BlockAbove = a_Chunk->GetBlock(a_RelX, a_RelY + 1, a_RelZ); BLOCKTYPE BlockBelow = a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ); SkyLight = a_Chunk->GetTimeAlteredLight(SkyLight); switch(a_MobType) { case cMonster::mtSquid: { return IsBlockWater(TargetBlock) && (a_RelY >= 45) && (a_RelY <= 62); } case cMonster::mtBat: { return (a_RelY <= 63) && (BlockLight <= 4) && (SkyLight <= 4) && (TargetBlock == E_BLOCK_AIR) && (!g_BlockTransparent[BlockAbove]); } case cMonster::mtChicken: case cMonster::mtCow: case cMonster::mtPig: case cMonster::mtHorse: case cMonster::mtSheep: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && (BlockBelow == E_BLOCK_GRASS) && (SkyLight >= 9) ); } case cMonster::mtOcelot: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && ( (BlockBelow == E_BLOCK_GRASS) || (BlockBelow == E_BLOCK_LEAVES) ) && (a_RelY >= 62) && (m_Random.NextInt(3, a_Biome) != 0) ); } case cMonster::mtEnderman: { if (a_RelY < 250) { BLOCKTYPE BlockTop = a_Chunk->GetBlock(a_RelX, a_RelY + 2, a_RelZ); if (BlockTop == E_BLOCK_AIR) { BlockTop = a_Chunk->GetBlock(a_RelX, a_RelY + 3, a_RelZ); return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (BlockTop == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && (SkyLight <= 7) && (BlockLight <= 7) ); } } break; } case cMonster::mtSpider: { bool CanSpawn = true; bool HaveFloor = false; for (int x = 0; x < 2; ++x) { for(int z = 0; z < 2; ++z) { CanSpawn = a_Chunk->UnboundedRelGetBlockType(a_RelX + x, a_RelY, a_RelZ + z, TargetBlock); CanSpawn = CanSpawn && (TargetBlock == E_BLOCK_AIR); if (!CanSpawn) { return false; } HaveFloor = ( HaveFloor || ( a_Chunk->UnboundedRelGetBlockType(a_RelX + x, a_RelY - 1, a_RelZ + z, TargetBlock) && !g_BlockTransparent[TargetBlock] ) ); } } return CanSpawn && HaveFloor && (SkyLight <= 7) && (BlockLight <= 7); } case cMonster::mtCreeper: case cMonster::mtSkeleton: case cMonster::mtZombie: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && (SkyLight <= 7) && (BlockLight <= 7) && (m_Random.NextInt(2, a_Biome) == 0) ); } case cMonster::mtSlime: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && ( (a_RelY <= 40) || (a_Biome == biSwampland) ) ); } case cMonster::mtGhast: case cMonster::mtZombiePigman: { return ( (TargetBlock == E_BLOCK_AIR) && (BlockAbove == E_BLOCK_AIR) && (!g_BlockTransparent[BlockBelow]) && (m_Random.NextInt(20, a_Biome) == 0) ); } case cMonster::mtWolf: { return ( (TargetBlock == E_BLOCK_GRASS) && (BlockAbove == E_BLOCK_AIR) && ( (a_Biome == biTaiga) || (a_Biome == biTaigaHills) || (a_Biome == biForest) || (a_Biome == biForestHills) || (a_Biome == biColdTaiga) || (a_Biome == biColdTaigaHills) || (a_Biome == biTaigaM) || (a_Biome == biMegaTaiga) || (a_Biome == biMegaTaigaHills) ) ); } default: { LOGD("MG TODO: Write spawning rule for mob type %d", a_MobType); return false; } } } return false; }