001 package net.minecraft.client.multiplayer; 002 003 import cpw.mods.fml.common.Side; 004 import cpw.mods.fml.common.asm.SideOnly; 005 import java.util.HashSet; 006 import java.util.Iterator; 007 import java.util.Random; 008 import java.util.Set; 009 import net.minecraft.block.Block; 010 import net.minecraft.client.Minecraft; 011 import net.minecraft.crash.CrashReport; 012 import net.minecraft.crash.CrashReportCategory; 013 import net.minecraft.entity.Entity; 014 import net.minecraft.entity.item.EntityMinecart; 015 import net.minecraft.entity.item.SoundUpdaterMinecart; 016 import net.minecraft.network.packet.Packet255KickDisconnect; 017 import net.minecraft.profiler.Profiler; 018 import net.minecraft.server.gui.IUpdatePlayerListBox; 019 import net.minecraft.util.IntHashMap; 020 import net.minecraft.world.ChunkCoordIntPair; 021 import net.minecraft.world.World; 022 import net.minecraft.world.WorldProvider; 023 import net.minecraft.world.WorldSettings; 024 import net.minecraft.world.chunk.Chunk; 025 import net.minecraft.world.chunk.IChunkProvider; 026 import net.minecraft.world.storage.SaveHandlerMP; 027 028 import net.minecraftforge.common.MinecraftForge; 029 import net.minecraftforge.event.world.WorldEvent; 030 031 @SideOnly(Side.CLIENT) 032 public class WorldClient extends World 033 { 034 /** The packets that need to be sent to the server. */ 035 private NetClientHandler sendQueue; 036 037 /** The ChunkProviderClient instance */ 038 private ChunkProviderClient clientChunkProvider; 039 040 /** 041 * The hash set of entities handled by this client. Uses the entity's ID as the hash set's key. 042 */ 043 private IntHashMap entityHashSet = new IntHashMap(); 044 045 /** Contains all entities for this client, both spawned and non-spawned. */ 046 private Set entityList = new HashSet(); 047 048 /** 049 * Contains all entities for this client that were not spawned due to a non-present chunk. The game will attempt to 050 * spawn up to 10 pending entities with each subsequent tick until the spawn queue is empty. 051 */ 052 private Set entitySpawnQueue = new HashSet(); 053 private final Minecraft mc = Minecraft.getMinecraft(); 054 private final Set previousActiveChunkSet = new HashSet(); 055 056 public WorldClient(NetClientHandler par1NetClientHandler, WorldSettings par2WorldSettings, int par3, int par4, Profiler par5Profiler) 057 { 058 super(new SaveHandlerMP(), "MpServer", WorldProvider.getProviderForDimension(par3), par2WorldSettings, par5Profiler); 059 this.sendQueue = par1NetClientHandler; 060 this.difficultySetting = par4; 061 this.mapStorage = par1NetClientHandler.mapStorage; 062 this.isRemote = true; 063 finishSetup(); 064 this.setSpawnLocation(8, 64, 8); 065 MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(this)); 066 } 067 068 /** 069 * Runs a single tick for the world 070 */ 071 public void tick() 072 { 073 super.tick(); 074 this.func_82738_a(this.getTotalWorldTime() + 1L); 075 this.setWorldTime(this.getWorldTime() + 1L); 076 this.theProfiler.startSection("reEntryProcessing"); 077 078 for (int var1 = 0; var1 < 10 && !this.entitySpawnQueue.isEmpty(); ++var1) 079 { 080 Entity var2 = (Entity)this.entitySpawnQueue.iterator().next(); 081 this.entitySpawnQueue.remove(var2); 082 083 if (!this.loadedEntityList.contains(var2)) 084 { 085 this.spawnEntityInWorld(var2); 086 } 087 } 088 089 this.theProfiler.endStartSection("connection"); 090 this.sendQueue.processReadPackets(); 091 this.theProfiler.endStartSection("chunkCache"); 092 this.clientChunkProvider.unload100OldestChunks(); 093 this.theProfiler.endStartSection("tiles"); 094 this.tickBlocksAndAmbiance(); 095 this.theProfiler.endSection(); 096 } 097 098 /** 099 * Invalidates an AABB region of blocks from the receive queue, in the event that the block has been modified 100 * client-side in the intervening 80 receive ticks. 101 */ 102 public void invalidateBlockReceiveRegion(int par1, int par2, int par3, int par4, int par5, int par6) {} 103 104 /** 105 * Creates the chunk provider for this world. Called in the constructor. Retrieves provider from worldProvider? 106 */ 107 protected IChunkProvider createChunkProvider() 108 { 109 this.clientChunkProvider = new ChunkProviderClient(this); 110 return this.clientChunkProvider; 111 } 112 113 /** 114 * plays random cave ambient sounds and runs updateTick on random blocks within each chunk in the vacinity of a 115 * player 116 */ 117 protected void tickBlocksAndAmbiance() 118 { 119 super.tickBlocksAndAmbiance(); 120 this.previousActiveChunkSet.retainAll(this.activeChunkSet); 121 122 if (this.previousActiveChunkSet.size() == this.activeChunkSet.size()) 123 { 124 this.previousActiveChunkSet.clear(); 125 } 126 127 int var1 = 0; 128 Iterator var2 = this.activeChunkSet.iterator(); 129 130 while (var2.hasNext()) 131 { 132 ChunkCoordIntPair var3 = (ChunkCoordIntPair)var2.next(); 133 134 if (!this.previousActiveChunkSet.contains(var3)) 135 { 136 int var4 = var3.chunkXPos * 16; 137 int var5 = var3.chunkZPos * 16; 138 this.theProfiler.startSection("getChunk"); 139 Chunk var6 = this.getChunkFromChunkCoords(var3.chunkXPos, var3.chunkZPos); 140 this.moodSoundAndLightCheck(var4, var5, var6); 141 this.theProfiler.endSection(); 142 this.previousActiveChunkSet.add(var3); 143 ++var1; 144 145 if (var1 >= 10) 146 { 147 return; 148 } 149 } 150 } 151 } 152 153 public void doPreChunk(int par1, int par2, boolean par3) 154 { 155 if (par3) 156 { 157 this.clientChunkProvider.loadChunk(par1, par2); 158 } 159 else 160 { 161 this.clientChunkProvider.unloadChunk(par1, par2); 162 } 163 164 if (!par3) 165 { 166 this.markBlockRangeForRenderUpdate(par1 * 16, 0, par2 * 16, par1 * 16 + 15, 256, par2 * 16 + 15); 167 } 168 } 169 170 /** 171 * Called to place all entities as part of a world 172 */ 173 public boolean spawnEntityInWorld(Entity par1Entity) 174 { 175 boolean var2 = super.spawnEntityInWorld(par1Entity); 176 this.entityList.add(par1Entity); 177 178 if (!var2) 179 { 180 this.entitySpawnQueue.add(par1Entity); 181 } 182 183 return var2; 184 } 185 186 /** 187 * Dismounts the entity (and anything riding the entity), sets the dead flag, and removes the player entity from the 188 * player entity list. Called by the playerLoggedOut function. 189 */ 190 public void setEntityDead(Entity par1Entity) 191 { 192 super.setEntityDead(par1Entity); 193 this.entityList.remove(par1Entity); 194 } 195 196 /** 197 * Start the skin for this entity downloading, if necessary, and increment its reference counter 198 */ 199 protected void obtainEntitySkin(Entity par1Entity) 200 { 201 super.obtainEntitySkin(par1Entity); 202 203 if (this.entitySpawnQueue.contains(par1Entity)) 204 { 205 this.entitySpawnQueue.remove(par1Entity); 206 } 207 } 208 209 /** 210 * Decrement the reference counter for this entity's skin image data 211 */ 212 protected void releaseEntitySkin(Entity par1Entity) 213 { 214 super.releaseEntitySkin(par1Entity); 215 216 if (this.entityList.contains(par1Entity)) 217 { 218 if (par1Entity.isEntityAlive()) 219 { 220 this.entitySpawnQueue.add(par1Entity); 221 } 222 else 223 { 224 this.entityList.remove(par1Entity); 225 } 226 } 227 } 228 229 /** 230 * Add an ID to Entity mapping to entityHashSet 231 */ 232 public void addEntityToWorld(int par1, Entity par2Entity) 233 { 234 Entity var3 = this.getEntityByID(par1); 235 236 if (var3 != null) 237 { 238 this.setEntityDead(var3); 239 } 240 241 this.entityList.add(par2Entity); 242 par2Entity.entityId = par1; 243 244 if (!this.spawnEntityInWorld(par2Entity)) 245 { 246 this.entitySpawnQueue.add(par2Entity); 247 } 248 249 this.entityHashSet.addKey(par1, par2Entity); 250 } 251 252 /** 253 * Returns the Entity with the given ID, or null if it doesn't exist in this World. 254 */ 255 public Entity getEntityByID(int par1) 256 { 257 return (Entity)(par1 == this.mc.thePlayer.entityId ? this.mc.thePlayer : (Entity)this.entityHashSet.lookup(par1)); 258 } 259 260 public Entity removeEntityFromWorld(int par1) 261 { 262 Entity var2 = (Entity)this.entityHashSet.removeObject(par1); 263 264 if (var2 != null) 265 { 266 this.entityList.remove(var2); 267 this.setEntityDead(var2); 268 } 269 270 return var2; 271 } 272 273 public boolean setBlockAndMetadataAndInvalidate(int par1, int par2, int par3, int par4, int par5) 274 { 275 this.invalidateBlockReceiveRegion(par1, par2, par3, par1, par2, par3); 276 return super.setBlockAndMetadataWithNotify(par1, par2, par3, par4, par5); 277 } 278 279 /** 280 * If on MP, sends a quitting packet. 281 */ 282 public void sendQuittingDisconnectingPacket() 283 { 284 this.sendQueue.quitWithPacket(new Packet255KickDisconnect("Quitting")); 285 } 286 287 public IUpdatePlayerListBox func_82735_a(EntityMinecart par1EntityMinecart) 288 { 289 return new SoundUpdaterMinecart(this.mc.sndManager, par1EntityMinecart, this.mc.thePlayer); 290 } 291 292 /** 293 * Updates all weather states. 294 */ 295 protected void updateWeather() 296 { 297 super.updateWeather(); 298 } 299 300 @Override 301 public void updateWeatherBody() 302 { 303 if (!this.provider.hasNoSky) 304 { 305 if (this.lastLightningBolt > 0) 306 { 307 --this.lastLightningBolt; 308 } 309 310 this.prevRainingStrength = this.rainingStrength; 311 312 if (this.worldInfo.isRaining()) 313 { 314 this.rainingStrength = (float)((double)this.rainingStrength + 0.01D); 315 } 316 else 317 { 318 this.rainingStrength = (float)((double)this.rainingStrength - 0.01D); 319 } 320 321 if (this.rainingStrength < 0.0F) 322 { 323 this.rainingStrength = 0.0F; 324 } 325 326 if (this.rainingStrength > 1.0F) 327 { 328 this.rainingStrength = 1.0F; 329 } 330 331 this.prevThunderingStrength = this.thunderingStrength; 332 333 if (this.worldInfo.isThundering()) 334 { 335 this.thunderingStrength = (float)((double)this.thunderingStrength + 0.01D); 336 } 337 else 338 { 339 this.thunderingStrength = (float)((double)this.thunderingStrength - 0.01D); 340 } 341 342 if (this.thunderingStrength < 0.0F) 343 { 344 this.thunderingStrength = 0.0F; 345 } 346 347 if (this.thunderingStrength > 1.0F) 348 { 349 this.thunderingStrength = 1.0F; 350 } 351 } 352 } 353 354 public void func_73029_E(int par1, int par2, int par3) 355 { 356 byte var4 = 16; 357 Random var5 = new Random(); 358 359 for (int var6 = 0; var6 < 1000; ++var6) 360 { 361 int var7 = par1 + this.rand.nextInt(var4) - this.rand.nextInt(var4); 362 int var8 = par2 + this.rand.nextInt(var4) - this.rand.nextInt(var4); 363 int var9 = par3 + this.rand.nextInt(var4) - this.rand.nextInt(var4); 364 int var10 = this.getBlockId(var7, var8, var9); 365 366 if (var10 == 0 && this.rand.nextInt(8) > var8 && this.provider.getWorldHasVoidParticles()) 367 { 368 this.spawnParticle("depthsuspend", (double)((float)var7 + this.rand.nextFloat()), (double)((float)var8 + this.rand.nextFloat()), (double)((float)var9 + this.rand.nextFloat()), 0.0D, 0.0D, 0.0D); 369 } 370 else if (var10 > 0) 371 { 372 Block.blocksList[var10].randomDisplayTick(this, var7, var8, var9, var5); 373 } 374 } 375 } 376 377 /** 378 * also releases skins. 379 */ 380 public void removeAllEntities() 381 { 382 this.loadedEntityList.removeAll(this.unloadedEntityList); 383 int var1; 384 Entity var2; 385 int var3; 386 int var4; 387 388 for (var1 = 0; var1 < this.unloadedEntityList.size(); ++var1) 389 { 390 var2 = (Entity)this.unloadedEntityList.get(var1); 391 var3 = var2.chunkCoordX; 392 var4 = var2.chunkCoordZ; 393 394 if (var2.addedToChunk && this.chunkExists(var3, var4)) 395 { 396 this.getChunkFromChunkCoords(var3, var4).removeEntity(var2); 397 } 398 } 399 400 for (var1 = 0; var1 < this.unloadedEntityList.size(); ++var1) 401 { 402 this.releaseEntitySkin((Entity)this.unloadedEntityList.get(var1)); 403 } 404 405 this.unloadedEntityList.clear(); 406 407 for (var1 = 0; var1 < this.loadedEntityList.size(); ++var1) 408 { 409 var2 = (Entity)this.loadedEntityList.get(var1); 410 411 if (var2.ridingEntity != null) 412 { 413 if (!var2.ridingEntity.isDead && var2.ridingEntity.riddenByEntity == var2) 414 { 415 continue; 416 } 417 418 var2.ridingEntity.riddenByEntity = null; 419 var2.ridingEntity = null; 420 } 421 422 if (var2.isDead) 423 { 424 var3 = var2.chunkCoordX; 425 var4 = var2.chunkCoordZ; 426 427 if (var2.addedToChunk && this.chunkExists(var3, var4)) 428 { 429 this.getChunkFromChunkCoords(var3, var4).removeEntity(var2); 430 } 431 432 this.loadedEntityList.remove(var1--); 433 this.releaseEntitySkin(var2); 434 } 435 } 436 } 437 438 /** 439 * Adds some basic stats of the world to the given crash report. 440 */ 441 public CrashReportCategory addWorldInfoToCrashReport(CrashReport par1CrashReport) 442 { 443 CrashReportCategory var2 = super.addWorldInfoToCrashReport(par1CrashReport); 444 var2.addCrashSectionCallable("Forced entities", new CallableMPL1(this)); 445 var2.addCrashSectionCallable("Retry entities", new CallableMPL2(this)); 446 return var2; 447 } 448 449 /** 450 * par8 is loudness, all pars passed to minecraftInstance.sndManager.playSound 451 */ 452 public void playSound(double par1, double par3, double par5, String par7Str, float par8, float par9) 453 { 454 float var10 = 16.0F; 455 456 if (par8 > 1.0F) 457 { 458 var10 *= par8; 459 } 460 461 if (this.mc.renderViewEntity.getDistanceSq(par1, par3, par5) < (double)(var10 * var10)) 462 { 463 this.mc.sndManager.playSound(par7Str, (float)par1, (float)par3, (float)par5, par8, par9); 464 } 465 } 466 467 static Set getEntityList(WorldClient par0WorldClient) 468 { 469 return par0WorldClient.entityList; 470 } 471 472 static Set getEntitySpawnQueue(WorldClient par0WorldClient) 473 { 474 return par0WorldClient.entitySpawnQueue; 475 } 476 }