001package net.minecraft.client.multiplayer;
002
003import cpw.mods.fml.relauncher.Side;
004import cpw.mods.fml.relauncher.SideOnly;
005import java.util.HashSet;
006import java.util.Iterator;
007import java.util.Random;
008import java.util.Set;
009import net.minecraft.block.Block;
010import net.minecraft.client.Minecraft;
011import net.minecraft.client.particle.EntityFireworkStarterFX;
012import net.minecraft.crash.CrashReport;
013import net.minecraft.crash.CrashReportCategory;
014import net.minecraft.entity.Entity;
015import net.minecraft.entity.item.EntityMinecart;
016import net.minecraft.entity.item.SoundUpdaterMinecart;
017import net.minecraft.nbt.NBTTagCompound;
018import net.minecraft.network.packet.Packet255KickDisconnect;
019import net.minecraft.profiler.Profiler;
020import net.minecraft.server.gui.IUpdatePlayerListBox;
021import net.minecraft.util.IntHashMap;
022import net.minecraft.world.ChunkCoordIntPair;
023import net.minecraft.world.World;
024import net.minecraft.world.WorldProvider;
025import net.minecraft.world.WorldSettings;
026import net.minecraft.world.chunk.Chunk;
027import net.minecraft.world.chunk.IChunkProvider;
028import net.minecraft.world.storage.SaveHandlerMP;
029
030import net.minecraftforge.common.MinecraftForge;
031import net.minecraftforge.event.world.WorldEvent;
032
033@SideOnly(Side.CLIENT)
034public 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     * Schedule the entity for removal during the next tick. Marks the entity dead in anticipation.
190     */
191    public void removeEntity(Entity par1Entity)
192    {
193        super.removeEntity(par1Entity);
194        this.entityList.remove(par1Entity);
195    }
196
197    /**
198     * Start the skin for this entity downloading, if necessary, and increment its reference counter
199     */
200    protected void obtainEntitySkin(Entity par1Entity)
201    {
202        super.obtainEntitySkin(par1Entity);
203
204        if (this.entitySpawnQueue.contains(par1Entity))
205        {
206            this.entitySpawnQueue.remove(par1Entity);
207        }
208    }
209
210    /**
211     * Decrement the reference counter for this entity's skin image data
212     */
213    public void releaseEntitySkin(Entity par1Entity)
214    {
215        super.releaseEntitySkin(par1Entity);
216
217        if (this.entityList.contains(par1Entity))
218        {
219            if (par1Entity.isEntityAlive())
220            {
221                this.entitySpawnQueue.add(par1Entity);
222            }
223            else
224            {
225                this.entityList.remove(par1Entity);
226            }
227        }
228    }
229
230    /**
231     * Add an ID to Entity mapping to entityHashSet
232     */
233    public void addEntityToWorld(int par1, Entity par2Entity)
234    {
235        Entity var3 = this.getEntityByID(par1);
236
237        if (var3 != null)
238        {
239            this.removeEntity(var3);
240        }
241
242        this.entityList.add(par2Entity);
243        par2Entity.entityId = par1;
244
245        if (!this.spawnEntityInWorld(par2Entity))
246        {
247            this.entitySpawnQueue.add(par2Entity);
248        }
249
250        this.entityHashSet.addKey(par1, par2Entity);
251    }
252
253    /**
254     * Returns the Entity with the given ID, or null if it doesn't exist in this World.
255     */
256    public Entity getEntityByID(int par1)
257    {
258        return (Entity)(par1 == this.mc.thePlayer.entityId ? this.mc.thePlayer : (Entity)this.entityHashSet.lookup(par1));
259    }
260
261    public Entity removeEntityFromWorld(int par1)
262    {
263        Entity var2 = (Entity)this.entityHashSet.removeObject(par1);
264
265        if (var2 != null)
266        {
267            this.entityList.remove(var2);
268            this.removeEntity(var2);
269        }
270
271        return var2;
272    }
273
274    public boolean setBlockAndMetadataAndInvalidate(int par1, int par2, int par3, int par4, int par5)
275    {
276        this.invalidateBlockReceiveRegion(par1, par2, par3, par1, par2, par3);
277        return super.setBlockAndMetadataWithNotify(par1, par2, par3, par4, par5);
278    }
279
280    /**
281     * If on MP, sends a quitting packet.
282     */
283    public void sendQuittingDisconnectingPacket()
284    {
285        this.sendQueue.quitWithPacket(new Packet255KickDisconnect("Quitting"));
286    }
287
288    public IUpdatePlayerListBox func_82735_a(EntityMinecart par1EntityMinecart)
289    {
290        return new SoundUpdaterMinecart(this.mc.sndManager, par1EntityMinecart, this.mc.thePlayer);
291    }
292
293    /**
294     * Updates all weather states.
295     */
296    protected void updateWeather()
297    {
298        super.updateWeather();
299    }
300
301    @Override
302    public void updateWeatherBody()
303    {
304        if (!this.provider.hasNoSky)
305        {
306            this.prevRainingStrength = this.rainingStrength;
307
308            if (this.worldInfo.isRaining())
309            {
310                this.rainingStrength = (float)((double)this.rainingStrength + 0.01D);
311            }
312            else
313            {
314                this.rainingStrength = (float)((double)this.rainingStrength - 0.01D);
315            }
316
317            if (this.rainingStrength < 0.0F)
318            {
319                this.rainingStrength = 0.0F;
320            }
321
322            if (this.rainingStrength > 1.0F)
323            {
324                this.rainingStrength = 1.0F;
325            }
326
327            this.prevThunderingStrength = this.thunderingStrength;
328
329            if (this.worldInfo.isThundering())
330            {
331                this.thunderingStrength = (float)((double)this.thunderingStrength + 0.01D);
332            }
333            else
334            {
335                this.thunderingStrength = (float)((double)this.thunderingStrength - 0.01D);
336            }
337
338            if (this.thunderingStrength < 0.0F)
339            {
340                this.thunderingStrength = 0.0F;
341            }
342
343            if (this.thunderingStrength > 1.0F)
344            {
345                this.thunderingStrength = 1.0F;
346            }
347        }
348    }
349
350    public void func_73029_E(int par1, int par2, int par3)
351    {
352        byte var4 = 16;
353        Random var5 = new Random();
354
355        for (int var6 = 0; var6 < 1000; ++var6)
356        {
357            int var7 = par1 + this.rand.nextInt(var4) - this.rand.nextInt(var4);
358            int var8 = par2 + this.rand.nextInt(var4) - this.rand.nextInt(var4);
359            int var9 = par3 + this.rand.nextInt(var4) - this.rand.nextInt(var4);
360            int var10 = this.getBlockId(var7, var8, var9);
361
362            if (var10 == 0 && this.rand.nextInt(8) > var8 && this.provider.getWorldHasVoidParticles())
363            {
364                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);
365            }
366            else if (var10 > 0)
367            {
368                Block.blocksList[var10].randomDisplayTick(this, var7, var8, var9, var5);
369            }
370        }
371    }
372
373    /**
374     * also releases skins.
375     */
376    public void removeAllEntities()
377    {
378        this.loadedEntityList.removeAll(this.unloadedEntityList);
379        int var1;
380        Entity var2;
381        int var3;
382        int var4;
383
384        for (var1 = 0; var1 < this.unloadedEntityList.size(); ++var1)
385        {
386            var2 = (Entity)this.unloadedEntityList.get(var1);
387            var3 = var2.chunkCoordX;
388            var4 = var2.chunkCoordZ;
389
390            if (var2.addedToChunk && this.chunkExists(var3, var4))
391            {
392                this.getChunkFromChunkCoords(var3, var4).removeEntity(var2);
393            }
394        }
395
396        for (var1 = 0; var1 < this.unloadedEntityList.size(); ++var1)
397        {
398            this.releaseEntitySkin((Entity)this.unloadedEntityList.get(var1));
399        }
400
401        this.unloadedEntityList.clear();
402
403        for (var1 = 0; var1 < this.loadedEntityList.size(); ++var1)
404        {
405            var2 = (Entity)this.loadedEntityList.get(var1);
406
407            if (var2.ridingEntity != null)
408            {
409                if (!var2.ridingEntity.isDead && var2.ridingEntity.riddenByEntity == var2)
410                {
411                    continue;
412                }
413
414                var2.ridingEntity.riddenByEntity = null;
415                var2.ridingEntity = null;
416            }
417
418            if (var2.isDead)
419            {
420                var3 = var2.chunkCoordX;
421                var4 = var2.chunkCoordZ;
422
423                if (var2.addedToChunk && this.chunkExists(var3, var4))
424                {
425                    this.getChunkFromChunkCoords(var3, var4).removeEntity(var2);
426                }
427
428                this.loadedEntityList.remove(var1--);
429                this.releaseEntitySkin(var2);
430            }
431        }
432    }
433
434    /**
435     * Adds some basic stats of the world to the given crash report.
436     */
437    public CrashReportCategory addWorldInfoToCrashReport(CrashReport par1CrashReport)
438    {
439        CrashReportCategory var2 = super.addWorldInfoToCrashReport(par1CrashReport);
440        var2.addCrashSectionCallable("Forced entities", new CallableMPL1(this));
441        var2.addCrashSectionCallable("Retry entities", new CallableMPL2(this));
442        return var2;
443    }
444
445    /**
446     * par8 is loudness, all pars passed to minecraftInstance.sndManager.playSound
447     */
448    public void playSound(double par1, double par3, double par5, String par7Str, float par8, float par9, boolean par10)
449    {
450        float var11 = 16.0F;
451
452        if (par8 > 1.0F)
453        {
454            var11 *= par8;
455        }
456
457        double var12 = this.mc.renderViewEntity.getDistanceSq(par1, par3, par5);
458
459        if (var12 < (double)(var11 * var11))
460        {
461            if (par10 && var12 > 100.0D)
462            {
463                double var14 = Math.sqrt(var12) / 40.0D;
464                this.mc.sndManager.func_92070_a(par7Str, (float)par1, (float)par3, (float)par5, par8, par9, (int)Math.round(var14 * 20.0D));
465            }
466            else
467            {
468                this.mc.sndManager.playSound(par7Str, (float)par1, (float)par3, (float)par5, par8, par9);
469            }
470        }
471    }
472
473    public void func_92088_a(double par1, double par3, double par5, double par7, double par9, double par11, NBTTagCompound par13NBTTagCompound)
474    {
475        this.mc.effectRenderer.addEffect(new EntityFireworkStarterFX(this, par1, par3, par5, par7, par9, par11, this.mc.effectRenderer, par13NBTTagCompound));
476    }
477
478    static Set getEntityList(WorldClient par0WorldClient)
479    {
480        return par0WorldClient.entityList;
481    }
482
483    static Set getEntitySpawnQueue(WorldClient par0WorldClient)
484    {
485        return par0WorldClient.entitySpawnQueue;
486    }
487}