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    }