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    }