    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}