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