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