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