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 }