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