001package net.minecraft.client.multiplayer; 002 003import cpw.mods.fml.relauncher.Side; 004import cpw.mods.fml.relauncher.SideOnly; 005import java.util.HashSet; 006import java.util.Iterator; 007import java.util.Random; 008import java.util.Set; 009import net.minecraft.block.Block; 010import net.minecraft.client.Minecraft; 011import net.minecraft.client.particle.EntityFireworkStarterFX; 012import net.minecraft.crash.CrashReport; 013import net.minecraft.crash.CrashReportCategory; 014import net.minecraft.entity.Entity; 015import net.minecraft.entity.item.EntityMinecart; 016import net.minecraft.entity.item.SoundUpdaterMinecart; 017import net.minecraft.nbt.NBTTagCompound; 018import net.minecraft.network.packet.Packet255KickDisconnect; 019import net.minecraft.profiler.Profiler; 020import net.minecraft.server.gui.IUpdatePlayerListBox; 021import net.minecraft.util.IntHashMap; 022import net.minecraft.world.ChunkCoordIntPair; 023import net.minecraft.world.World; 024import net.minecraft.world.WorldProvider; 025import net.minecraft.world.WorldSettings; 026import net.minecraft.world.chunk.Chunk; 027import net.minecraft.world.chunk.IChunkProvider; 028import net.minecraft.world.storage.SaveHandlerMP; 029 030import net.minecraftforge.common.MinecraftForge; 031import net.minecraftforge.event.world.WorldEvent; 032 033@SideOnly(Side.CLIENT) 034public 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 * Schedule the entity for removal during the next tick. Marks the entity dead in anticipation. 190 */ 191 public void removeEntity(Entity par1Entity) 192 { 193 super.removeEntity(par1Entity); 194 this.entityList.remove(par1Entity); 195 } 196 197 /** 198 * Start the skin for this entity downloading, if necessary, and increment its reference counter 199 */ 200 protected void obtainEntitySkin(Entity par1Entity) 201 { 202 super.obtainEntitySkin(par1Entity); 203 204 if (this.entitySpawnQueue.contains(par1Entity)) 205 { 206 this.entitySpawnQueue.remove(par1Entity); 207 } 208 } 209 210 /** 211 * Decrement the reference counter for this entity's skin image data 212 */ 213 public void releaseEntitySkin(Entity par1Entity) 214 { 215 super.releaseEntitySkin(par1Entity); 216 217 if (this.entityList.contains(par1Entity)) 218 { 219 if (par1Entity.isEntityAlive()) 220 { 221 this.entitySpawnQueue.add(par1Entity); 222 } 223 else 224 { 225 this.entityList.remove(par1Entity); 226 } 227 } 228 } 229 230 /** 231 * Add an ID to Entity mapping to entityHashSet 232 */ 233 public void addEntityToWorld(int par1, Entity par2Entity) 234 { 235 Entity var3 = this.getEntityByID(par1); 236 237 if (var3 != null) 238 { 239 this.removeEntity(var3); 240 } 241 242 this.entityList.add(par2Entity); 243 par2Entity.entityId = par1; 244 245 if (!this.spawnEntityInWorld(par2Entity)) 246 { 247 this.entitySpawnQueue.add(par2Entity); 248 } 249 250 this.entityHashSet.addKey(par1, par2Entity); 251 } 252 253 /** 254 * Returns the Entity with the given ID, or null if it doesn't exist in this World. 255 */ 256 public Entity getEntityByID(int par1) 257 { 258 return (Entity)(par1 == this.mc.thePlayer.entityId ? this.mc.thePlayer : (Entity)this.entityHashSet.lookup(par1)); 259 } 260 261 public Entity removeEntityFromWorld(int par1) 262 { 263 Entity var2 = (Entity)this.entityHashSet.removeObject(par1); 264 265 if (var2 != null) 266 { 267 this.entityList.remove(var2); 268 this.removeEntity(var2); 269 } 270 271 return var2; 272 } 273 274 public boolean setBlockAndMetadataAndInvalidate(int par1, int par2, int par3, int par4, int par5) 275 { 276 this.invalidateBlockReceiveRegion(par1, par2, par3, par1, par2, par3); 277 return super.setBlockAndMetadataWithNotify(par1, par2, par3, par4, par5); 278 } 279 280 /** 281 * If on MP, sends a quitting packet. 282 */ 283 public void sendQuittingDisconnectingPacket() 284 { 285 this.sendQueue.quitWithPacket(new Packet255KickDisconnect("Quitting")); 286 } 287 288 public IUpdatePlayerListBox func_82735_a(EntityMinecart par1EntityMinecart) 289 { 290 return new SoundUpdaterMinecart(this.mc.sndManager, par1EntityMinecart, this.mc.thePlayer); 291 } 292 293 /** 294 * Updates all weather states. 295 */ 296 protected void updateWeather() 297 { 298 super.updateWeather(); 299 } 300 301 @Override 302 public void updateWeatherBody() 303 { 304 if (!this.provider.hasNoSky) 305 { 306 this.prevRainingStrength = this.rainingStrength; 307 308 if (this.worldInfo.isRaining()) 309 { 310 this.rainingStrength = (float)((double)this.rainingStrength + 0.01D); 311 } 312 else 313 { 314 this.rainingStrength = (float)((double)this.rainingStrength - 0.01D); 315 } 316 317 if (this.rainingStrength < 0.0F) 318 { 319 this.rainingStrength = 0.0F; 320 } 321 322 if (this.rainingStrength > 1.0F) 323 { 324 this.rainingStrength = 1.0F; 325 } 326 327 this.prevThunderingStrength = this.thunderingStrength; 328 329 if (this.worldInfo.isThundering()) 330 { 331 this.thunderingStrength = (float)((double)this.thunderingStrength + 0.01D); 332 } 333 else 334 { 335 this.thunderingStrength = (float)((double)this.thunderingStrength - 0.01D); 336 } 337 338 if (this.thunderingStrength < 0.0F) 339 { 340 this.thunderingStrength = 0.0F; 341 } 342 343 if (this.thunderingStrength > 1.0F) 344 { 345 this.thunderingStrength = 1.0F; 346 } 347 } 348 } 349 350 public void func_73029_E(int par1, int par2, int par3) 351 { 352 byte var4 = 16; 353 Random var5 = new Random(); 354 355 for (int var6 = 0; var6 < 1000; ++var6) 356 { 357 int var7 = par1 + this.rand.nextInt(var4) - this.rand.nextInt(var4); 358 int var8 = par2 + this.rand.nextInt(var4) - this.rand.nextInt(var4); 359 int var9 = par3 + this.rand.nextInt(var4) - this.rand.nextInt(var4); 360 int var10 = this.getBlockId(var7, var8, var9); 361 362 if (var10 == 0 && this.rand.nextInt(8) > var8 && this.provider.getWorldHasVoidParticles()) 363 { 364 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); 365 } 366 else if (var10 > 0) 367 { 368 Block.blocksList[var10].randomDisplayTick(this, var7, var8, var9, var5); 369 } 370 } 371 } 372 373 /** 374 * also releases skins. 375 */ 376 public void removeAllEntities() 377 { 378 this.loadedEntityList.removeAll(this.unloadedEntityList); 379 int var1; 380 Entity var2; 381 int var3; 382 int var4; 383 384 for (var1 = 0; var1 < this.unloadedEntityList.size(); ++var1) 385 { 386 var2 = (Entity)this.unloadedEntityList.get(var1); 387 var3 = var2.chunkCoordX; 388 var4 = var2.chunkCoordZ; 389 390 if (var2.addedToChunk && this.chunkExists(var3, var4)) 391 { 392 this.getChunkFromChunkCoords(var3, var4).removeEntity(var2); 393 } 394 } 395 396 for (var1 = 0; var1 < this.unloadedEntityList.size(); ++var1) 397 { 398 this.releaseEntitySkin((Entity)this.unloadedEntityList.get(var1)); 399 } 400 401 this.unloadedEntityList.clear(); 402 403 for (var1 = 0; var1 < this.loadedEntityList.size(); ++var1) 404 { 405 var2 = (Entity)this.loadedEntityList.get(var1); 406 407 if (var2.ridingEntity != null) 408 { 409 if (!var2.ridingEntity.isDead && var2.ridingEntity.riddenByEntity == var2) 410 { 411 continue; 412 } 413 414 var2.ridingEntity.riddenByEntity = null; 415 var2.ridingEntity = null; 416 } 417 418 if (var2.isDead) 419 { 420 var3 = var2.chunkCoordX; 421 var4 = var2.chunkCoordZ; 422 423 if (var2.addedToChunk && this.chunkExists(var3, var4)) 424 { 425 this.getChunkFromChunkCoords(var3, var4).removeEntity(var2); 426 } 427 428 this.loadedEntityList.remove(var1--); 429 this.releaseEntitySkin(var2); 430 } 431 } 432 } 433 434 /** 435 * Adds some basic stats of the world to the given crash report. 436 */ 437 public CrashReportCategory addWorldInfoToCrashReport(CrashReport par1CrashReport) 438 { 439 CrashReportCategory var2 = super.addWorldInfoToCrashReport(par1CrashReport); 440 var2.addCrashSectionCallable("Forced entities", new CallableMPL1(this)); 441 var2.addCrashSectionCallable("Retry entities", new CallableMPL2(this)); 442 return var2; 443 } 444 445 /** 446 * par8 is loudness, all pars passed to minecraftInstance.sndManager.playSound 447 */ 448 public void playSound(double par1, double par3, double par5, String par7Str, float par8, float par9, boolean par10) 449 { 450 float var11 = 16.0F; 451 452 if (par8 > 1.0F) 453 { 454 var11 *= par8; 455 } 456 457 double var12 = this.mc.renderViewEntity.getDistanceSq(par1, par3, par5); 458 459 if (var12 < (double)(var11 * var11)) 460 { 461 if (par10 && var12 > 100.0D) 462 { 463 double var14 = Math.sqrt(var12) / 40.0D; 464 this.mc.sndManager.func_92070_a(par7Str, (float)par1, (float)par3, (float)par5, par8, par9, (int)Math.round(var14 * 20.0D)); 465 } 466 else 467 { 468 this.mc.sndManager.playSound(par7Str, (float)par1, (float)par3, (float)par5, par8, par9); 469 } 470 } 471 } 472 473 public void func_92088_a(double par1, double par3, double par5, double par7, double par9, double par11, NBTTagCompound par13NBTTagCompound) 474 { 475 this.mc.effectRenderer.addEffect(new EntityFireworkStarterFX(this, par1, par3, par5, par7, par9, par11, this.mc.effectRenderer, par13NBTTagCompound)); 476 } 477 478 static Set getEntityList(WorldClient par0WorldClient) 479 { 480 return par0WorldClient.entityList; 481 } 482 483 static Set getEntitySpawnQueue(WorldClient par0WorldClient) 484 { 485 return par0WorldClient.entitySpawnQueue; 486 } 487}