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