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