    001package net.minecraft.world.chunk;
    002
    003import cpw.mods.fml.relauncher.Side;
    004import cpw.mods.fml.relauncher.SideOnly;
    005import java.util.ArrayList;
    006import java.util.Arrays;
    007import java.util.HashMap;
    008import java.util.Iterator;
    009import java.util.List;
    010import java.util.Map;
    011import java.util.Random;
    012import net.minecraft.block.Block;
    013import net.minecraft.block.ITileEntityProvider;
    014import net.minecraft.block.material.Material;
    015import net.minecraft.command.IEntitySelector;
    016import net.minecraft.entity.Entity;
    017import net.minecraft.tileentity.TileEntity;
    018import net.minecraft.util.AxisAlignedBB;
    019import net.minecraft.util.MathHelper;
    020import net.minecraft.world.ChunkCoordIntPair;
    021import net.minecraft.world.ChunkPosition;
    022import net.minecraft.world.EnumSkyBlock;
    023import net.minecraft.world.World;
    024import net.minecraft.world.biome.BiomeGenBase;
    025import net.minecraft.world.biome.WorldChunkManager;
    026import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
    027
    028import net.minecraftforge.common.MinecraftForge;
    029import net.minecraftforge.event.entity.EntityEvent;
    030import net.minecraftforge.event.world.ChunkEvent;
    031
    032public class Chunk
    033{
    034    /**
    035     * Determines if the chunk is lit or not at a light value greater than 0.
    036     */
    037    public static boolean isLit;
    038
    039    /**
    040     * Used to store block IDs, block MSBs, Sky-light maps, Block-light maps, and metadata. Each entry corresponds to a
    041     * logical segment of 16x16x16 blocks, stacked vertically.
    042     */
    043    private ExtendedBlockStorage[] storageArrays;
    044
    045    /**
    046     * Contains a 16x16 mapping on the X/Z plane of the biome ID to which each colum belongs.
    047     */
    048    private byte[] blockBiomeArray;
    049
    050    /**
    051     * A map, similar to heightMap, that tracks how far down precipitation can fall.
    052     */
    053    public int[] precipitationHeightMap;
    054
    055    /** Which columns need their skylightMaps updated. */
    056    public boolean[] updateSkylightColumns;
    057
    058    /** Whether or not this Chunk is currently loaded into the World */
    059    public boolean isChunkLoaded;
    060
    061    /** Reference to the World object. */
    062    public World worldObj;
    063    public int[] heightMap;
    064
    065    /** The x coordinate of the chunk. */
    066    public final int xPosition;
    067
    068    /** The z coordinate of the chunk. */
    069    public final int zPosition;
    070    private boolean isGapLightingUpdated;
    071
    072    /** A Map of ChunkPositions to TileEntities in this chunk */
    073    public Map chunkTileEntityMap;
    074
    075    /**
    076     * Array of Lists containing the entities in this Chunk. Each List represents a 16 block subchunk.
    077     */
    078    public List[] entityLists;
    079
    080    /** Boolean value indicating if the terrain is populated. */
    081    public boolean isTerrainPopulated;
    082
    083    /**
    084     * Set to true if the chunk has been modified and needs to be updated internally.
    085     */
    086    public boolean isModified;
    087
    088    /**
    089     * Whether this Chunk has any Entities and thus requires saving on every tick
    090     */
    091    public boolean hasEntities;
    092
    093    /** The time according to World.worldTime when this chunk was last saved */
    094    public long lastSaveTime;
    095
    096    /**
    097     * Updates to this chunk will not be sent to clients if this is false. This field is set to true the first time the
    098     * chunk is sent to a client, and never set to false.
    099     */
    100    public boolean sendUpdates;
    101
    102    /** Lowest value in the heightmap. */
    103    public int heightMapMinimum;
    104
    105    /**
    106     * Contains the current round-robin relight check index, and is implied as the relight check location as well.
    107     */
    108    private int queuedLightChecks;
    109    boolean field_76653_p;
    110
    111    public Chunk(World par1World, int par2, int par3)
    112    {
    113        this.storageArrays = new ExtendedBlockStorage[16];
    114        this.blockBiomeArray = new byte[256];
    115        this.precipitationHeightMap = new int[256];
    116        this.updateSkylightColumns = new boolean[256];
    117        this.isGapLightingUpdated = false;
    118        this.chunkTileEntityMap = new HashMap();
    119        this.isTerrainPopulated = false;
    120        this.isModified = false;
    121        this.hasEntities = false;
    122        this.lastSaveTime = 0L;
    123        this.sendUpdates = false;
    124        this.heightMapMinimum = 0;
    125        this.queuedLightChecks = 4096;
    126        this.field_76653_p = false;
    127        this.entityLists = new List[16];
    128        this.worldObj = par1World;
    129        this.xPosition = par2;
    130        this.zPosition = par3;
    131        this.heightMap = new int[256];
    132
    133        for (int k = 0; k < this.entityLists.length; ++k)
    134        {
    135            this.entityLists[k] = new ArrayList();
    136        }
    137
    138        Arrays.fill(this.precipitationHeightMap, -999);
    139        Arrays.fill(this.blockBiomeArray, (byte) - 1);
    140    }
    141
    142    public Chunk(World par1World, byte[] par2ArrayOfByte, int par3, int par4)
    143    {
    144        this(par1World, par3, par4);
    145        int k = par2ArrayOfByte.length / 256;
    146
    147        for (int l = 0; l < 16; ++l)
    148        {
    149            for (int i1 = 0; i1 < 16; ++i1)
    150            {
    151                for (int j1 = 0; j1 < k; ++j1)
    152                {
    153                    /* FORGE: The following change, a cast from unsigned byte to int,
    154                     * fixes a vanilla bug when generating new chunks that contain a block ID > 127 */
    155                    int b0 = par2ArrayOfByte[l << 11 | i1 << 7 | j1] & 0xFF;
    156
    157                    if (b0 != 0)
    158                    {
    159                        int k1 = j1 >> 4;
    160
    161                        if (this.storageArrays[k1] == null)
    162                        {
    163                            this.storageArrays[k1] = new ExtendedBlockStorage(k1 << 4, !par1World.provider.hasNoSky);
    164                        }
    165
    166                        this.storageArrays[k1].setExtBlockID(l, j1 & 15, i1, b0);
    167                    }
    168                }
    169            }
    170        }
    171    }
    172
    173    /**
    174     * Metadata sensitive Chunk constructor for use in new ChunkProviders that
    175     * use metadata sensitive blocks during generation.
    176     *
    177     * @param world The world this chunk belongs to
    178     * @param ids A ByteArray containing all the BlockID's to set this chunk to
    179     * @param metadata A ByteArray containing all the metadata to set this chunk to
    180     * @param chunkX The chunk's X position
    181     * @param chunkZ The Chunk's Z position
    182     */
    183    public Chunk(World world, byte[] ids, byte[] metadata, int chunkX, int chunkZ)
    184    {
    185        this(world, chunkX, chunkZ);
    186        int k = ids.length / 256;
    187
    188        for (int x = 0; x < 16; ++x)
    189        {
    190            for (int z = 0; z < 16; ++z)
    191            {
    192                for (int y = 0; y < k; ++y)
    193                {
    194                    int idx = x << 11 | z << 7 | y;
    195                   int id = ids[idx] & 0xFF;
    196                    int meta = metadata[idx];
    197
    198                    if (id != 0)
    199                    {
    200                        int l = y >> 4;
    201
    202                        if (this.storageArrays[l] == null)
    203                        {
    204                            this.storageArrays[l] = new ExtendedBlockStorage(l << 4, !world.provider.hasNoSky);
    205                        }
    206
    207                        this.storageArrays[l].setExtBlockID(x, y & 15, z, id);
    208                        this.storageArrays[l].setExtBlockMetadata(x, y & 15, z, meta);
    209                    }
    210                }
    211            }
    212        }
    213    }
    214
    215    /**
    216     * A Chunk Constructor which handles shorts to allow block ids > 256 (full 4096 range)
    217     * Meta data sensitive
    218     * NOTE: The x,y,z order of the array is different from the native Chunk constructor to allow for generation > y127
    219     * NOTE: This is possibly more efficient than the standard constructor due to less memory skipping
    220     *
    221     * @param world The world this chunk belongs to
    222     * @param ids A ShortArray containing all the BlockID's to set this chunk to (x is low order, z is mid, y is high)
    223     * @param metadata A ByteArray containing all the metadata to set this chunk to
    224     * @param chunkX The chunk's X position
    225     * @param chunkZ The Chunk's Z position
    226     */
    227    public Chunk(World world, short[] ids, byte[] metadata, int chunkX, int chunkZ)
    228    {
    229        this(world, chunkX, chunkZ);
    230        int max = ids.length / 256;
    231
    232        for (int y = 0; y < max; ++y)
    233        {
    234            for (int z = 0; z < 16; ++z)
    235            {
    236                for (int x = 0; x < 16; ++x)
    237                {
    238                    int idx = y << 8 | z << 4 | x;
    239                    int id = ids[idx] & 0xFFFFFF;
    240                    int meta = metadata[idx];
    241
    242                    if (id != 0) {
    243                        int storageBlock = y >> 4;
    244
    245                        if (this.storageArrays[storageBlock] == null) {
    246                                this.storageArrays[storageBlock] = new ExtendedBlockStorage(storageBlock << 4, !world.provider.hasNoSky);
    247                        }
    248
    249                        this.storageArrays[storageBlock].setExtBlockID(x, y & 15, z, id);
    250                        this.storageArrays[storageBlock].setExtBlockMetadata(x, y & 15, z, meta);
    251                    }
    252                }
    253            }
    254        }
    255    }
    256
    257    /**
    258     * Checks whether the chunk is at the X/Z location specified
    259     */
    260    public boolean isAtLocation(int par1, int par2)
    261    {
    262        return par1 == this.xPosition && par2 == this.zPosition;
    263    }
    264
    265    /**
    266     * Returns the value in the height map at this x, z coordinate in the chunk
    267     */
    268    public int getHeightValue(int par1, int par2)
    269    {
    270        return this.heightMap[par2 << 4 | par1];
    271    }
    272
    273    /**
    274     * Returns the topmost ExtendedBlockStorage instance for this Chunk that actually contains a block.
    275     */
    276    public int getTopFilledSegment()
    277    {
    278        for (int i = this.storageArrays.length - 1; i >= 0; --i)
    279        {
    280            if (this.storageArrays[i] != null)
    281            {
    282                return this.storageArrays[i].getYLocation();
    283            }
    284        }
    285
    286        return 0;
    287    }
    288
    289    /**
    290     * Returns the ExtendedBlockStorage array for this Chunk.
    291     */
    292    public ExtendedBlockStorage[] getBlockStorageArray()
    293    {
    294        return this.storageArrays;
    295    }
    296
    297    @SideOnly(Side.CLIENT)
    298
    299    /**
    300     * Generates the height map for a chunk from scratch
    301     */
    302    public void generateHeightMap()
    303    {
    304        int i = this.getTopFilledSegment();
    305
    306        for (int j = 0; j < 16; ++j)
    307        {
    308            int k = 0;
    309
    310            while (k < 16)
    311            {
    312                this.precipitationHeightMap[j + (k << 4)] = -999;
    313                int l = i + 16 - 1;
    314
    315                while (true)
    316                {
    317                    if (l > 0)
    318                    {
    319                        int i1 = this.getBlockID(j, l - 1, k);
    320
    321                        if (getBlockLightOpacity(j, l - 1, k) == 0)
    322                        {
    323                            --l;
    324                            continue;
    325                        }
    326
    327                        this.heightMap[k << 4 | j] = l;
    328                    }
    329
    330                    ++k;
    331                    break;
    332                }
    333            }
    334        }
    335
    336        this.isModified = true;
    337    }
    338
    339    /**
    340     * Generates the initial skylight map for the chunk upon generation or load.
    341     */
    342    public void generateSkylightMap()
    343    {
    344        int i = this.getTopFilledSegment();
    345        this.heightMapMinimum = Integer.MAX_VALUE;
    346        int j;
    347        int k;
    348
    349        for (j = 0; j < 16; ++j)
    350        {
    351            k = 0;
    352
    353            while (k < 16)
    354            {
    355                this.precipitationHeightMap[j + (k << 4)] = -999;
    356                int l = i + 16 - 1;
    357
    358                while (true)
    359                {
    360                    if (l > 0)
    361                    {
    362                        if (this.getBlockLightOpacity(j, l - 1, k) == 0)
    363                        {
    364                            --l;
    365                            continue;
    366                        }
    367
    368                        this.heightMap[k << 4 | j] = l;
    369
    370                        if (l < this.heightMapMinimum)
    371                        {
    372                            this.heightMapMinimum = l;
    373                        }
    374                    }
    375
    376                    if (!this.worldObj.provider.hasNoSky)
    377                    {
    378                        l = 15;
    379                        int i1 = i + 16 - 1;
    380
    381                        do
    382                        {
    383                            l -= this.getBlockLightOpacity(j, i1, k);
    384
    385                            if (l > 0)
    386                            {
    387                                ExtendedBlockStorage extendedblockstorage = this.storageArrays[i1 >> 4];
    388
    389                                if (extendedblockstorage != null)
    390                                {
    391                                    extendedblockstorage.setExtSkylightValue(j, i1 & 15, k, l);
    392                                    this.worldObj.markBlockForRenderUpdate((this.xPosition << 4) + j, i1, (this.zPosition << 4) + k);
    393                                }
    394                            }
    395
    396                            --i1;
    397                        }
    398                        while (i1 > 0 && l > 0);
    399                    }
    400
    401                    ++k;
    402                    break;
    403                }
    404            }
    405        }
    406
    407        this.isModified = true;
    408
    409        for (j = 0; j < 16; ++j)
    410        {
    411            for (k = 0; k < 16; ++k)
    412            {
    413                this.propagateSkylightOcclusion(j, k);
    414            }
    415        }
    416    }
    417
    418    /**
    419     * Propagates a given sky-visible block's light value downward and upward to neighboring blocks as necessary.
    420     */
    421    private void propagateSkylightOcclusion(int par1, int par2)
    422    {
    423        this.updateSkylightColumns[par1 + par2 * 16] = true;
    424        this.isGapLightingUpdated = true;
    425    }
    426
    427    /**
    428     * Runs delayed skylight updates.
    429     */
    430    private void updateSkylight_do()
    431    {
    432        this.worldObj.theProfiler.startSection("recheckGaps");
    433
    434        if (this.worldObj.doChunksNearChunkExist(this.xPosition * 16 + 8, 0, this.zPosition * 16 + 8, 16))
    435        {
    436            for (int i = 0; i < 16; ++i)
    437            {
    438                for (int j = 0; j < 16; ++j)
    439                {
    440                    if (this.updateSkylightColumns[i + j * 16])
    441                    {
    442                        this.updateSkylightColumns[i + j * 16] = false;
    443                        int k = this.getHeightValue(i, j);
    444                        int l = this.xPosition * 16 + i;
    445                        int i1 = this.zPosition * 16 + j;
    446                        int j1 = this.worldObj.getChunkHeightMapMinimum(l - 1, i1);
    447                        int k1 = this.worldObj.getChunkHeightMapMinimum(l + 1, i1);
    448                        int l1 = this.worldObj.getChunkHeightMapMinimum(l, i1 - 1);
    449                        int i2 = this.worldObj.getChunkHeightMapMinimum(l, i1 + 1);
    450
    451                        if (k1 < j1)
    452                        {
    453                            j1 = k1;
    454                        }
    455
    456                        if (l1 < j1)
    457                        {
    458                            j1 = l1;
    459                        }
    460
    461                        if (i2 < j1)
    462                        {
    463                            j1 = i2;
    464                        }
    465
    466                        this.checkSkylightNeighborHeight(l, i1, j1);
    467                        this.checkSkylightNeighborHeight(l - 1, i1, k);
    468                        this.checkSkylightNeighborHeight(l + 1, i1, k);
    469                        this.checkSkylightNeighborHeight(l, i1 - 1, k);
    470                        this.checkSkylightNeighborHeight(l, i1 + 1, k);
    471                    }
    472                }
    473            }
    474
    475            this.isGapLightingUpdated = false;
    476        }
    477
    478        this.worldObj.theProfiler.endSection();
    479    }
    480
    481    /**
    482     * Checks the height of a block next to a sky-visible block and schedules a lighting update as necessary.
    483     */
    484    private void checkSkylightNeighborHeight(int par1, int par2, int par3)
    485    {
    486        int l = this.worldObj.getHeightValue(par1, par2);
    487
    488        if (l > par3)
    489        {
    490            this.updateSkylightNeighborHeight(par1, par2, par3, l + 1);
    491        }
    492        else if (l < par3)
    493        {
    494            this.updateSkylightNeighborHeight(par1, par2, l, par3 + 1);
    495        }
    496    }
    497
    498    private void updateSkylightNeighborHeight(int par1, int par2, int par3, int par4)
    499    {
    500        if (par4 > par3 && this.worldObj.doChunksNearChunkExist(par1, 0, par2, 16))
    501        {
    502            for (int i1 = par3; i1 < par4; ++i1)
    503            {
    504                this.worldObj.updateLightByType(EnumSkyBlock.Sky, par1, i1, par2);
    505            }
    506
    507            this.isModified = true;
    508        }
    509    }
    510
    511    /**
    512     * Initiates the recalculation of both the block-light and sky-light for a given block inside a chunk.
    513     */
    514    private void relightBlock(int par1, int par2, int par3)
    515    {
    516        int l = this.heightMap[par3 << 4 | par1] & 255;
    517        int i1 = l;
    518
    519        if (par2 > l)
    520        {
    521            i1 = par2;
    522        }
    523
    524        while (i1 > 0 && this.getBlockLightOpacity(par1, i1 - 1, par3) == 0)
    525        {
    526            --i1;
    527        }
    528
    529        if (i1 != l)
    530        {
    531            this.worldObj.markBlocksDirtyVertical(par1 + this.xPosition * 16, par3 + this.zPosition * 16, i1, l);
    532            this.heightMap[par3 << 4 | par1] = i1;
    533            int j1 = this.xPosition * 16 + par1;
    534            int k1 = this.zPosition * 16 + par3;
    535            int l1;
    536            int i2;
    537
    538            if (!this.worldObj.provider.hasNoSky)
    539            {
    540                ExtendedBlockStorage extendedblockstorage;
    541
    542                if (i1 < l)
    543                {
    544                    for (l1 = i1; l1 < l; ++l1)
    545                    {
    546                        extendedblockstorage = this.storageArrays[l1 >> 4];
    547
    548                        if (extendedblockstorage != null)
    549                        {
    550                            extendedblockstorage.setExtSkylightValue(par1, l1 & 15, par3, 15);
    551                            this.worldObj.markBlockForRenderUpdate((this.xPosition << 4) + par1, l1, (this.zPosition << 4) + par3);
    552                        }
    553                    }
    554                }
    555                else
    556                {
    557                    for (l1 = l; l1 < i1; ++l1)
    558                    {
    559                        extendedblockstorage = this.storageArrays[l1 >> 4];
    560
    561                        if (extendedblockstorage != null)
    562                        {
    563                            extendedblockstorage.setExtSkylightValue(par1, l1 & 15, par3, 0);
    564                            this.worldObj.markBlockForRenderUpdate((this.xPosition << 4) + par1, l1, (this.zPosition << 4) + par3);
    565                        }
    566                    }
    567                }
    568
    569                l1 = 15;
    570
    571                while (i1 > 0 && l1 > 0)
    572                {
    573                    --i1;
    574                    i2 = this.getBlockLightOpacity(par1, i1, par3);
    575
    576                    if (i2 == 0)
    577                    {
    578                        i2 = 1;
    579                    }
    580
    581                    l1 -= i2;
    582
    583                    if (l1 < 0)
    584                    {
    585                        l1 = 0;
    586                    }
    587
    588                    ExtendedBlockStorage extendedblockstorage1 = this.storageArrays[i1 >> 4];
    589
    590                    if (extendedblockstorage1 != null)
    591                    {
    592                        extendedblockstorage1.setExtSkylightValue(par1, i1 & 15, par3, l1);
    593                    }
    594                }
    595            }
    596
    597            l1 = this.heightMap[par3 << 4 | par1];
    598            i2 = l;
    599            int j2 = l1;
    600
    601            if (l1 < l)
    602            {
    603                i2 = l1;
    604                j2 = l;
    605            }
    606
    607            if (l1 < this.heightMapMinimum)
    608            {
    609                this.heightMapMinimum = l1;
    610            }
    611
    612            if (!this.worldObj.provider.hasNoSky)
    613            {
    614                this.updateSkylightNeighborHeight(j1 - 1, k1, i2, j2);
    615                this.updateSkylightNeighborHeight(j1 + 1, k1, i2, j2);
    616                this.updateSkylightNeighborHeight(j1, k1 - 1, i2, j2);
    617                this.updateSkylightNeighborHeight(j1, k1 + 1, i2, j2);
    618                this.updateSkylightNeighborHeight(j1, k1, i2, j2);
    619            }
    620
    621            this.isModified = true;
    622        }
    623    }
    624
    625    public int getBlockLightOpacity(int par1, int par2, int par3)
    626    {
    627        int x = (xPosition << 4) + par1;
    628        int z = (zPosition << 4) + par3;
    629        Block block = Block.blocksList[getBlockID(par1, par2, par3)];
    630        return (block == null ? 0 : block.getLightOpacity(worldObj, x, par2, z));
    631    }
    632
    633    /**
    634     * Return the ID of a block in the chunk.
    635     */
    636    public int getBlockID(int par1, int par2, int par3)
    637    {
    638        if (par2 >> 4 >= this.storageArrays.length)
    639        {
    640            return 0;
    641        }
    642        else
    643        {
    644            ExtendedBlockStorage extendedblockstorage = this.storageArrays[par2 >> 4];
    645            return extendedblockstorage != null ? extendedblockstorage.getExtBlockID(par1, par2 & 15, par3) : 0;
    646        }
    647    }
    648
    649    /**
    650     * Return the metadata corresponding to the given coordinates inside a chunk.
    651     */
    652    public int getBlockMetadata(int par1, int par2, int par3)
    653    {
    654        if (par2 >> 4 >= this.storageArrays.length)
    655        {
    656            return 0;
    657        }
    658        else
    659        {
    660            ExtendedBlockStorage extendedblockstorage = this.storageArrays[par2 >> 4];
    661            return extendedblockstorage != null ? extendedblockstorage.getExtBlockMetadata(par1, par2 & 15, par3) : 0;
    662        }
    663    }
    664
    665    /**
    666     * Sets a blockID of a position within a chunk with metadata. Args: x, y, z, blockID, metadata
    667     */
    668    public boolean setBlockIDWithMetadata(int par1, int par2, int par3, int par4, int par5)
    669    {
    670        int j1 = par3 << 4 | par1;
    671
    672        if (par2 >= this.precipitationHeightMap[j1] - 1)
    673        {
    674            this.precipitationHeightMap[j1] = -999;
    675        }
    676
    677        int k1 = this.heightMap[j1];
    678        int l1 = this.getBlockID(par1, par2, par3);
    679        int i2 = this.getBlockMetadata(par1, par2, par3);
    680
    681        if (l1 == par4 && i2 == par5)
    682        {
    683            return false;
    684        }
    685        else
    686        {
    687            ExtendedBlockStorage extendedblockstorage = this.storageArrays[par2 >> 4];
    688            boolean flag = false;
    689
    690            if (extendedblockstorage == null)
    691            {
    692                if (par4 == 0)
    693                {
    694                    return false;
    695                }
    696
    697                extendedblockstorage = this.storageArrays[par2 >> 4] = new ExtendedBlockStorage(par2 >> 4 << 4, !this.worldObj.provider.hasNoSky);
    698                flag = par2 >= k1;
    699            }
    700
    701            int j2 = this.xPosition * 16 + par1;
    702            int k2 = this.zPosition * 16 + par3;
    703
    704            if (l1 != 0 && !this.worldObj.isRemote)
    705            {
    706                Block.blocksList[l1].onSetBlockIDWithMetaData(this.worldObj, j2, par2, k2, i2);
    707            }
    708
    709            extendedblockstorage.setExtBlockID(par1, par2 & 15, par3, par4);
    710
    711            if (l1 != 0)
    712            {
    713                if (!this.worldObj.isRemote)
    714                {
    715                    Block.blocksList[l1].breakBlock(this.worldObj, j2, par2, k2, l1, i2);
    716                }
    717                else if (Block.blocksList[l1] != null && Block.blocksList[l1].hasTileEntity(i2))
    718                {
    719                    TileEntity te = worldObj.getBlockTileEntity(j2, par2, k2);
    720                    if (te != null && te.shouldRefresh(l1, par4, i2, par5, worldObj, j2, par2, k2))
    721                    {
    722                        this.worldObj.removeBlockTileEntity(j2, par2, k2);
    723                    }
    724                }
    725            }
    726
    727            if (extendedblockstorage.getExtBlockID(par1, par2 & 15, par3) != par4)
    728            {
    729                return false;
    730            }
    731            else
    732            {
    733                extendedblockstorage.setExtBlockMetadata(par1, par2 & 15, par3, par5);
    734
    735                if (flag)
    736                {
    737                    this.generateSkylightMap();
    738                }
    739                else
    740                {
    741                    if (getBlockLightOpacity(par1, par2, par3) > 0)
    742                    {
    743                        if (par2 >= k1)
    744                        {
    745                            this.relightBlock(par1, par2 + 1, par3);
    746                        }
    747                    }
    748                    else if (par2 == k1 - 1)
    749                    {
    750                        this.relightBlock(par1, par2, par3);
    751                    }
    752
    753                    this.propagateSkylightOcclusion(par1, par3);
    754                }
    755
    756                TileEntity tileentity;
    757
    758                if (par4 != 0)
    759                {
    760                    if (!this.worldObj.isRemote)
    761                    {
    762                        Block.blocksList[par4].onBlockAdded(this.worldObj, j2, par2, k2);
    763                    }
    764
    765                    if (Block.blocksList[par4] != null && Block.blocksList[par4].hasTileEntity(par5))
    766                    {
    767                        tileentity = this.getChunkBlockTileEntity(par1, par2, par3);
    768
    769                        if (tileentity == null)
    770                        {
    771                            tileentity = Block.blocksList[par4].createTileEntity(this.worldObj, par5);
    772                            this.worldObj.setBlockTileEntity(j2, par2, k2, tileentity);
    773                        }
    774
    775                        if (tileentity != null)
    776                        {
    777                            tileentity.updateContainingBlockInfo();
    778                            tileentity.blockMetadata = par5;
    779                        }
    780                    }
    781                }
    782
    783                this.isModified = true;
    784                return true;
    785            }
    786        }
    787    }
    788
    789    /**
    790     * Set the metadata of a block in the chunk
    791     */
    792    public boolean setBlockMetadata(int par1, int par2, int par3, int par4)
    793    {
    794        ExtendedBlockStorage extendedblockstorage = this.storageArrays[par2 >> 4];
    795
    796        if (extendedblockstorage == null)
    797        {
    798            return false;
    799        }
    800        else
    801        {
    802            int i1 = extendedblockstorage.getExtBlockMetadata(par1, par2 & 15, par3);
    803
    804            if (i1 == par4)
    805            {
    806                return false;
    807            }
    808            else
    809            {
    810                this.isModified = true;
    811                extendedblockstorage.setExtBlockMetadata(par1, par2 & 15, par3, par4);
    812                int j1 = extendedblockstorage.getExtBlockID(par1, par2 & 15, par3);
    813
    814                if (j1 > 0 && Block.blocksList[j1] != null && Block.blocksList[j1].hasTileEntity(par4))
    815                {
    816                    TileEntity tileentity = this.getChunkBlockTileEntity(par1, par2, par3);
    817
    818                    if (tileentity != null)
    819                    {
    820                        tileentity.updateContainingBlockInfo();
    821                        tileentity.blockMetadata = par4;
    822                    }
    823                }
    824
    825                return true;
    826            }
    827        }
    828    }
    829
    830    /**
    831     * Gets the amount of light saved in this block (doesn't adjust for daylight)
    832     */
    833    public int getSavedLightValue(EnumSkyBlock par1EnumSkyBlock, int par2, int par3, int par4)
    834    {
    835        ExtendedBlockStorage extendedblockstorage = this.storageArrays[par3 >> 4];
    836        return extendedblockstorage == null ? (this.canBlockSeeTheSky(par2, par3, par4) ? par1EnumSkyBlock.defaultLightValue : 0) : (par1EnumSkyBlock == EnumSkyBlock.Sky ? (this.worldObj.provider.hasNoSky ? 0 : extendedblockstorage.getExtSkylightValue(par2, par3 & 15, par4)) : (par1EnumSkyBlock == EnumSkyBlock.Block ? extendedblockstorage.getExtBlocklightValue(par2, par3 & 15, par4) : par1EnumSkyBlock.defaultLightValue));
    837    }
    838
    839    /**
    840     * Sets the light value at the coordinate. If enumskyblock is set to sky it sets it in the skylightmap and if its a
    841     * block then into the blocklightmap. Args enumSkyBlock, x, y, z, lightValue
    842     */
    843    public void setLightValue(EnumSkyBlock par1EnumSkyBlock, int par2, int par3, int par4, int par5)
    844    {
    845        ExtendedBlockStorage extendedblockstorage = this.storageArrays[par3 >> 4];
    846
    847        if (extendedblockstorage == null)
    848        {
    849            extendedblockstorage = this.storageArrays[par3 >> 4] = new ExtendedBlockStorage(par3 >> 4 << 4, !this.worldObj.provider.hasNoSky);
    850            this.generateSkylightMap();
    851        }
    852
    853        this.isModified = true;
    854
    855        if (par1EnumSkyBlock == EnumSkyBlock.Sky)
    856        {
    857            if (!this.worldObj.provider.hasNoSky)
    858            {
    859                extendedblockstorage.setExtSkylightValue(par2, par3 & 15, par4, par5);
    860            }
    861        }
    862        else if (par1EnumSkyBlock == EnumSkyBlock.Block)
    863        {
    864            extendedblockstorage.setExtBlocklightValue(par2, par3 & 15, par4, par5);
    865        }
    866    }
    867
    868    /**
    869     * Gets the amount of light on a block taking into account sunlight
    870     */
    871    public int getBlockLightValue(int par1, int par2, int par3, int par4)
    872    {
    873        ExtendedBlockStorage extendedblockstorage = this.storageArrays[par2 >> 4];
    874
    875        if (extendedblockstorage == null)
    876        {
    877            return !this.worldObj.provider.hasNoSky && par4 < EnumSkyBlock.Sky.defaultLightValue ? EnumSkyBlock.Sky.defaultLightValue - par4 : 0;
    878        }
    879        else
    880        {
    881            int i1 = this.worldObj.provider.hasNoSky ? 0 : extendedblockstorage.getExtSkylightValue(par1, par2 & 15, par3);
    882
    883            if (i1 > 0)
    884            {
    885                isLit = true;
    886            }
    887
    888            i1 -= par4;
    889            int j1 = extendedblockstorage.getExtBlocklightValue(par1, par2 & 15, par3);
    890
    891            if (j1 > i1)
    892            {
    893                i1 = j1;
    894            }
    895
    896            return i1;
    897        }
    898    }
    899
    900    /**
    901     * Adds an entity to the chunk. Args: entity
    902     */
    903    public void addEntity(Entity par1Entity)
    904    {
    905        this.hasEntities = true;
    906        int i = MathHelper.floor_double(par1Entity.posX / 16.0D);
    907        int j = MathHelper.floor_double(par1Entity.posZ / 16.0D);
    908
    909        if (i != this.xPosition || j != this.zPosition)
    910        {
    911            this.worldObj.getWorldLogAgent().logSevere("Wrong location! " + par1Entity);
    912            Thread.dumpStack();
    913        }
    914
    915        int k = MathHelper.floor_double(par1Entity.posY / 16.0D);
    916
    917        if (k < 0)
    918        {
    919            k = 0;
    920        }
    921
    922        if (k >= this.entityLists.length)
    923        {
    924            k = this.entityLists.length - 1;
    925        }
    926
    927        MinecraftForge.EVENT_BUS.post(new EntityEvent.EnteringChunk(par1Entity, this.xPosition, this.zPosition, par1Entity.chunkCoordX, par1Entity.chunkCoordZ));
    928        par1Entity.addedToChunk = true;
    929        par1Entity.chunkCoordX = this.xPosition;
    930        par1Entity.chunkCoordY = k;
    931        par1Entity.chunkCoordZ = this.zPosition;
    932        this.entityLists[k].add(par1Entity);
    933    }
    934
    935    /**
    936     * removes entity using its y chunk coordinate as its index
    937     */
    938    public void removeEntity(Entity par1Entity)
    939    {
    940        this.removeEntityAtIndex(par1Entity, par1Entity.chunkCoordY);
    941    }
    942
    943    /**
    944     * Removes entity at the specified index from the entity array.
    945     */
    946    public void removeEntityAtIndex(Entity par1Entity, int par2)
    947    {
    948        if (par2 < 0)
    949        {
    950            par2 = 0;
    951        }
    952
    953        if (par2 >= this.entityLists.length)
    954        {
    955            par2 = this.entityLists.length - 1;
    956        }
    957
    958        this.entityLists[par2].remove(par1Entity);
    959    }
    960
    961    /**
    962     * Returns whether is not a block above this one blocking sight to the sky (done via checking against the heightmap)
    963     */
    964    public boolean canBlockSeeTheSky(int par1, int par2, int par3)
    965    {
    966        return par2 >= this.heightMap[par3 << 4 | par1];
    967    }
    968
    969    /**
    970     * Gets the TileEntity for a given block in this chunk
    971     */
    972    public TileEntity getChunkBlockTileEntity(int par1, int par2, int par3)
    973    {
    974        ChunkPosition chunkposition = new ChunkPosition(par1, par2, par3);
    975        TileEntity tileentity = (TileEntity)this.chunkTileEntityMap.get(chunkposition);
    976
    977        if (tileentity != null && tileentity.isInvalid())
    978        {
    979            chunkTileEntityMap.remove(chunkposition);
    980            tileentity = null;
    981        }
    982
    983        if (tileentity == null)
    984        {
    985            int l = this.getBlockID(par1, par2, par3);
    986            int meta = this.getBlockMetadata(par1, par2, par3);
    987
    988            if (l <= 0 || !Block.blocksList[l].hasTileEntity(meta))
    989            {
    990                return null;
    991            }
    992
    993            if (tileentity == null)
    994            {
    995                tileentity = Block.blocksList[l].createTileEntity(this.worldObj, meta);
    996                this.worldObj.setBlockTileEntity(this.xPosition * 16 + par1, par2, this.zPosition * 16 + par3, tileentity);
    997            }
    998
    999            tileentity = (TileEntity)this.chunkTileEntityMap.get(chunkposition);
    1000        }
    1001
    1002        return tileentity;
    1003    }
    1004
    1005    /**
    1006     * Adds a TileEntity to a chunk
    1007     */
    1008    public void addTileEntity(TileEntity par1TileEntity)
    1009    {
    1010        int i = par1TileEntity.xCoord - this.xPosition * 16;
    1011        int j = par1TileEntity.yCoord;
    1012        int k = par1TileEntity.zCoord - this.zPosition * 16;
    1013        this.setChunkBlockTileEntity(i, j, k, par1TileEntity);
    1014
    1015        if (this.isChunkLoaded)
    1016        {
    1017            this.worldObj.addTileEntity(par1TileEntity);
    1018        }
    1019    }
    1020
    1021    /**
    1022     * Sets the TileEntity for a given block in this chunk
    1023     */
    1024    public void setChunkBlockTileEntity(int par1, int par2, int par3, TileEntity par4TileEntity)
    1025    {
    1026        ChunkPosition chunkposition = new ChunkPosition(par1, par2, par3);
    1027        par4TileEntity.setWorldObj(this.worldObj);
    1028        par4TileEntity.xCoord = this.xPosition * 16 + par1;
    1029        par4TileEntity.yCoord = par2;
    1030        par4TileEntity.zCoord = this.zPosition * 16 + par3;
    1031
    1032        Block block = Block.blocksList[getBlockID(par1, par2, par3)];
    1033        if (block != null && block.hasTileEntity(getBlockMetadata(par1, par2, par3)))
    1034        {
    1035            if (this.chunkTileEntityMap.containsKey(chunkposition))
    1036            {
    1037                ((TileEntity)this.chunkTileEntityMap.get(chunkposition)).invalidate();
    1038            }
    1039
    1040            par4TileEntity.validate();
    1041            this.chunkTileEntityMap.put(chunkposition, par4TileEntity);
    1042        }
    1043    }
    1044
    1045    /**
    1046     * Removes the TileEntity for a given block in this chunk
    1047     */
    1048    public void removeChunkBlockTileEntity(int par1, int par2, int par3)
    1049    {
    1050        ChunkPosition chunkposition = new ChunkPosition(par1, par2, par3);
    1051
    1052        if (this.isChunkLoaded)
    1053        {
    1054            TileEntity tileentity = (TileEntity)this.chunkTileEntityMap.remove(chunkposition);
    1055
    1056            if (tileentity != null)
    1057            {
    1058                tileentity.invalidate();
    1059            }
    1060        }
    1061    }
    1062
    1063    /**
    1064     * Called when this Chunk is loaded by the ChunkProvider
    1065     */
    1066    public void onChunkLoad()
    1067    {
    1068        this.isChunkLoaded = true;
    1069        this.worldObj.addTileEntity(this.chunkTileEntityMap.values());
    1070
    1071        for (int i = 0; i < this.entityLists.length; ++i)
    1072        {
    1073            this.worldObj.addLoadedEntities(this.entityLists[i]);
    1074        }
    1075        MinecraftForge.EVENT_BUS.post(new ChunkEvent.Load(this));
    1076    }
    1077
    1078    /**
    1079     * Called when this Chunk is unloaded by the ChunkProvider
    1080     */
    1081    public void onChunkUnload()
    1082    {
    1083        this.isChunkLoaded = false;
    1084        Iterator iterator = this.chunkTileEntityMap.values().iterator();
    1085
    1086        while (iterator.hasNext())
    1087        {
    1088            TileEntity tileentity = (TileEntity)iterator.next();
    1089            this.worldObj.markTileEntityForDespawn(tileentity);
    1090        }
    1091
    1092        for (int i = 0; i < this.entityLists.length; ++i)
    1093        {
    1094            this.worldObj.unloadEntities(this.entityLists[i]);
    1095        }
    1096        MinecraftForge.EVENT_BUS.post(new ChunkEvent.Unload(this));
    1097    }
    1098
    1099    /**
    1100     * Sets the isModified flag for this Chunk
    1101     */
    1102    public void setChunkModified()
    1103    {
    1104        this.isModified = true;
    1105    }
    1106
    1107    /**
    1108     * Fills the given list of all entities that intersect within the given bounding box that aren't the passed entity
    1109     * Args: entity, aabb, listToFill
    1110     */
    1111    public void getEntitiesWithinAABBForEntity(Entity par1Entity, AxisAlignedBB par2AxisAlignedBB, List par3List, IEntitySelector par4IEntitySelector)
    1112    {
    1113        int i = MathHelper.floor_double((par2AxisAlignedBB.minY - World.MAX_ENTITY_RADIUS) / 16.0D);
    1114        int j = MathHelper.floor_double((par2AxisAlignedBB.maxY + World.MAX_ENTITY_RADIUS) / 16.0D);
    1115
    1116        if (i < 0)
    1117        {
    1118            i = 0;
    1119            j = Math.max(i, j);
    1120        }
    1121
    1122        if (j >= this.entityLists.length)
    1123        {
    1124            j = this.entityLists.length - 1;
    1125            i = Math.min(i, j);
    1126        }
    1127
    1128        for (int k = i; k <= j; ++k)
    1129        {
    1130            List list1 = this.entityLists[k];
    1131
    1132            for (int l = 0; l < list1.size(); ++l)
    1133            {
    1134                Entity entity1 = (Entity)list1.get(l);
    1135
    1136                if (entity1 != par1Entity && entity1.boundingBox.intersectsWith(par2AxisAlignedBB) && (par4IEntitySelector == null || par4IEntitySelector.isEntityApplicable(entity1)))
    1137                {
    1138                    par3List.add(entity1);
    1139                    Entity[] aentity = entity1.getParts();
    1140
    1141                    if (aentity != null)
    1142                    {
    1143                        for (int i1 = 0; i1 < aentity.length; ++i1)
    1144                        {
    1145                            entity1 = aentity[i1];
    1146
    1147                            if (entity1 != par1Entity && entity1.boundingBox.intersectsWith(par2AxisAlignedBB) && (par4IEntitySelector == null || par4IEntitySelector.isEntityApplicable(entity1)))
    1148                            {
    1149                                par3List.add(entity1);
    1150                            }
    1151                        }
    1152                    }
    1153                }
    1154            }
    1155        }
    1156    }
    1157
    1158    /**
    1159     * Gets all entities that can be assigned to the specified class. Args: entityClass, aabb, listToFill
    1160     */
    1161    public void getEntitiesOfTypeWithinAAAB(Class par1Class, AxisAlignedBB par2AxisAlignedBB, List par3List, IEntitySelector par4IEntitySelector)
    1162    {
    1163        int i = MathHelper.floor_double((par2AxisAlignedBB.minY - World.MAX_ENTITY_RADIUS) / 16.0D);
    1164        int j = MathHelper.floor_double((par2AxisAlignedBB.maxY + World.MAX_ENTITY_RADIUS) / 16.0D);
    1165
    1166        if (i < 0)
    1167        {
    1168            i = 0;
    1169        }
    1170        else if (i >= this.entityLists.length)
    1171        {
    1172            i = this.entityLists.length - 1;
    1173        }
    1174
    1175        if (j >= this.entityLists.length)
    1176        {
    1177            j = this.entityLists.length - 1;
    1178        }
    1179        else if (j < 0)
    1180        {
    1181            j = 0;
    1182        }
    1183
    1184        for (int k = i; k <= j; ++k)
    1185        {
    1186            List list1 = this.entityLists[k];
    1187
    1188            for (int l = 0; l < list1.size(); ++l)
    1189            {
    1190                Entity entity = (Entity)list1.get(l);
    1191
    1192                if (par1Class.isAssignableFrom(entity.getClass()) && entity.boundingBox.intersectsWith(par2AxisAlignedBB) && (par4IEntitySelector == null || par4IEntitySelector.isEntityApplicable(entity)))
    1193                {
    1194                    par3List.add(entity);
    1195                }
    1196            }
    1197        }
    1198    }
    1199
    1200    /**
    1201     * Returns true if this Chunk needs to be saved
    1202     */
    1203    public boolean needsSaving(boolean par1)
    1204    {
    1205        if (par1)
    1206        {
    1207            if (this.hasEntities && this.worldObj.getTotalWorldTime() != this.lastSaveTime || this.isModified)
    1208            {
    1209                return true;
    1210            }
    1211        }
    1212        else if (this.hasEntities && this.worldObj.getTotalWorldTime() >= this.lastSaveTime + 600L)
    1213        {
    1214            return true;
    1215        }
    1216
    1217        return this.isModified;
    1218    }
    1219
    1220    public Random getRandomWithSeed(long par1)
    1221    {
    1222        return new Random(this.worldObj.getSeed() + (long)(this.xPosition * this.xPosition * 4987142) + (long)(this.xPosition * 5947611) + (long)(this.zPosition * this.zPosition) * 4392871L + (long)(this.zPosition * 389711) ^ par1);
    1223    }
    1224
    1225    public boolean isEmpty()
    1226    {
    1227        return false;
    1228    }
    1229
    1230    public void populateChunk(IChunkProvider par1IChunkProvider, IChunkProvider par2IChunkProvider, int par3, int par4)
    1231    {
    1232        if (!this.isTerrainPopulated && par1IChunkProvider.chunkExists(par3 + 1, par4 + 1) && par1IChunkProvider.chunkExists(par3, par4 + 1) && par1IChunkProvider.chunkExists(par3 + 1, par4))
    1233        {
    1234            par1IChunkProvider.populate(par2IChunkProvider, par3, par4);
    1235        }
    1236
    1237        if (par1IChunkProvider.chunkExists(par3 - 1, par4) && !par1IChunkProvider.provideChunk(par3 - 1, par4).isTerrainPopulated && par1IChunkProvider.chunkExists(par3 - 1, par4 + 1) && par1IChunkProvider.chunkExists(par3, par4 + 1) && par1IChunkProvider.chunkExists(par3 - 1, par4 + 1))
    1238        {
    1239            par1IChunkProvider.populate(par2IChunkProvider, par3 - 1, par4);
    1240        }
    1241
    1242        if (par1IChunkProvider.chunkExists(par3, par4 - 1) && !par1IChunkProvider.provideChunk(par3, par4 - 1).isTerrainPopulated && par1IChunkProvider.chunkExists(par3 + 1, par4 - 1) && par1IChunkProvider.chunkExists(par3 + 1, par4 - 1) && par1IChunkProvider.chunkExists(par3 + 1, par4))
    1243        {
    1244            par1IChunkProvider.populate(par2IChunkProvider, par3, par4 - 1);
    1245        }
    1246
    1247        if (par1IChunkProvider.chunkExists(par3 - 1, par4 - 1) && !par1IChunkProvider.provideChunk(par3 - 1, par4 - 1).isTerrainPopulated && par1IChunkProvider.chunkExists(par3, par4 - 1) && par1IChunkProvider.chunkExists(par3 - 1, par4))
    1248        {
    1249            par1IChunkProvider.populate(par2IChunkProvider, par3 - 1, par4 - 1);
    1250        }
    1251    }
    1252
    1253    /**
    1254     * Gets the height to which rain/snow will fall. Calculates it if not already stored.
    1255     */
    1256    public int getPrecipitationHeight(int par1, int par2)
    1257    {
    1258        int k = par1 | par2 << 4;
    1259        int l = this.precipitationHeightMap[k];
    1260
    1261        if (l == -999)
    1262        {
    1263            int i1 = this.getTopFilledSegment() + 15;
    1264            l = -1;
    1265
    1266            while (i1 > 0 && l == -1)
    1267            {
    1268                int j1 = this.getBlockID(par1, i1, par2);
    1269                Material material = j1 == 0 ? Material.air : Block.blocksList[j1].blockMaterial;
    1270
    1271                if (!material.blocksMovement() && !material.isLiquid())
    1272                {
    1273                    --i1;
    1274                }
    1275                else
    1276                {
    1277                    l = i1 + 1;
    1278                }
    1279            }
    1280
    1281            this.precipitationHeightMap[k] = l;
    1282        }
    1283
    1284        return l;
    1285    }
    1286
    1287    /**
    1288     * Checks whether skylight needs updated; if it does, calls updateSkylight_do
    1289     */
    1290    public void updateSkylight()
    1291    {
    1292        if (this.isGapLightingUpdated && !this.worldObj.provider.hasNoSky)
    1293        {
    1294            this.updateSkylight_do();
    1295        }
    1296    }
    1297
    1298    /**
    1299     * Gets a ChunkCoordIntPair representing the Chunk's position.
    1300     */
    1301    public ChunkCoordIntPair getChunkCoordIntPair()
    1302    {
    1303        return new ChunkCoordIntPair(this.xPosition, this.zPosition);
    1304    }
    1305
    1306    /**
    1307     * Returns whether the ExtendedBlockStorages containing levels (in blocks) from arg 1 to arg 2 are fully empty
    1308     * (true) or not (false).
    1309     */
    1310    public boolean getAreLevelsEmpty(int par1, int par2)
    1311    {
    1312        if (par1 < 0)
    1313        {
    1314            par1 = 0;
    1315        }
    1316
    1317        if (par2 >= 256)
    1318        {
    1319            par2 = 255;
    1320        }
    1321
    1322        for (int k = par1; k <= par2; k += 16)
    1323        {
    1324            ExtendedBlockStorage extendedblockstorage = this.storageArrays[k >> 4];
    1325
    1326            if (extendedblockstorage != null && !extendedblockstorage.isEmpty())
    1327            {
    1328                return false;
    1329            }
    1330        }
    1331
    1332        return true;
    1333    }
    1334
    1335    public void setStorageArrays(ExtendedBlockStorage[] par1ArrayOfExtendedBlockStorage)
    1336    {
    1337        this.storageArrays = par1ArrayOfExtendedBlockStorage;
    1338    }
    1339
    1340    @SideOnly(Side.CLIENT)
    1341
    1342    /**
    1343     * Initialise this chunk with new binary data
    1344     */
    1345    public void fillChunk(byte[] par1ArrayOfByte, int par2, int par3, boolean par4)
    1346    {
    1347        Iterator iterator = chunkTileEntityMap.values().iterator();
    1348        while(iterator.hasNext())
    1349        {
    1350            TileEntity tileEntity = (TileEntity)iterator.next();
    1351            tileEntity.updateContainingBlockInfo();
    1352            tileEntity.getBlockMetadata();
    1353            tileEntity.getBlockType();
    1354        }
    1355
    1356        int k = 0;
    1357        boolean flag1 = !this.worldObj.provider.hasNoSky;
    1358        int l;
    1359
    1360        for (l = 0; l < this.storageArrays.length; ++l)
    1361        {
    1362            if ((par2 & 1 << l) != 0)
    1363            {
    1364                if (this.storageArrays[l] == null)
    1365                {
    1366                    this.storageArrays[l] = new ExtendedBlockStorage(l << 4, flag1);
    1367                }
    1368
    1369                byte[] abyte1 = this.storageArrays[l].getBlockLSBArray();
    1370                System.arraycopy(par1ArrayOfByte, k, abyte1, 0, abyte1.length);
    1371                k += abyte1.length;
    1372            }
    1373            else if (par4 && this.storageArrays[l] != null)
    1374            {
    1375                this.storageArrays[l] = null;
    1376            }
    1377        }
    1378
    1379        NibbleArray nibblearray;
    1380
    1381        for (l = 0; l < this.storageArrays.length; ++l)
    1382        {
    1383            if ((par2 & 1 << l) != 0 && this.storageArrays[l] != null)
    1384            {
    1385                nibblearray = this.storageArrays[l].getMetadataArray();
    1386                System.arraycopy(par1ArrayOfByte, k, nibblearray.data, 0, nibblearray.data.length);
    1387                k += nibblearray.data.length;
    1388            }
    1389        }
    1390
    1391        for (l = 0; l < this.storageArrays.length; ++l)
    1392        {
    1393            if ((par2 & 1 << l) != 0 && this.storageArrays[l] != null)
    1394            {
    1395                nibblearray = this.storageArrays[l].getBlocklightArray();
    1396                System.arraycopy(par1ArrayOfByte, k, nibblearray.data, 0, nibblearray.data.length);
    1397                k += nibblearray.data.length;
    1398            }
    1399        }
    1400
    1401        if (flag1)
    1402        {
    1403            for (l = 0; l < this.storageArrays.length; ++l)
    1404            {
    1405                if ((par2 & 1 << l) != 0 && this.storageArrays[l] != null)
    1406                {
    1407                    nibblearray = this.storageArrays[l].getSkylightArray();
    1408                    System.arraycopy(par1ArrayOfByte, k, nibblearray.data, 0, nibblearray.data.length);
    1409                    k += nibblearray.data.length;
    1410                }
    1411            }
    1412        }
    1413
    1414        for (l = 0; l < this.storageArrays.length; ++l)
    1415        {
    1416            if ((par3 & 1 << l) != 0)
    1417            {
    1418                if (this.storageArrays[l] == null)
    1419                {
    1420                    k += 2048;
    1421                }
    1422                else
    1423                {
    1424                    nibblearray = this.storageArrays[l].getBlockMSBArray();
    1425
    1426                    if (nibblearray == null)
    1427                    {
    1428                        nibblearray = this.storageArrays[l].createBlockMSBArray();
    1429                    }
    1430
    1431                    System.arraycopy(par1ArrayOfByte, k, nibblearray.data, 0, nibblearray.data.length);
    1432                    k += nibblearray.data.length;
    1433                }
    1434            }
    1435            else if (par4 && this.storageArrays[l] != null && this.storageArrays[l].getBlockMSBArray() != null)
    1436            {
    1437                this.storageArrays[l].clearMSBArray();
    1438            }
    1439        }
    1440
    1441        if (par4)
    1442        {
    1443            System.arraycopy(par1ArrayOfByte, k, this.blockBiomeArray, 0, this.blockBiomeArray.length);
    1444            int i1 = k + this.blockBiomeArray.length;
    1445        }
    1446
    1447        for (l = 0; l < this.storageArrays.length; ++l)
    1448        {
    1449            if (this.storageArrays[l] != null && (par2 & 1 << l) != 0)
    1450            {
    1451                this.storageArrays[l].removeInvalidBlocks();
    1452            }
    1453        }
    1454
    1455        this.generateHeightMap();
    1456
    1457        List<TileEntity> invalidList = new ArrayList<TileEntity>();
    1458        iterator = chunkTileEntityMap.values().iterator();
    1459        while (iterator.hasNext())
    1460        {
    1461            TileEntity tileEntity = (TileEntity)iterator.next();
    1462            int x = tileEntity.xCoord & 15;
    1463            int y = tileEntity.yCoord;
    1464            int z = tileEntity.zCoord & 15;
    1465            Block block = tileEntity.getBlockType();
    1466            if (block == null || block.blockID != getBlockID(x, y, z) || tileEntity.getBlockMetadata() != getBlockMetadata(x, y, z))
    1467            {
    1468                invalidList.add(tileEntity);
    1469            }
    1470            tileEntity.updateContainingBlockInfo();
    1471        }
    1472
    1473        for (TileEntity tileEntity : invalidList)
    1474        {
    1475            tileEntity.invalidate();
    1476        }
    1477    }
    1478
    1479    /**
    1480     * This method retrieves the biome at a set of coordinates
    1481     */
    1482    public BiomeGenBase getBiomeGenForWorldCoords(int par1, int par2, WorldChunkManager par3WorldChunkManager)
    1483    {
    1484        int k = this.blockBiomeArray[par2 << 4 | par1] & 255;
    1485
    1486        if (k == 255)
    1487        {
    1488            BiomeGenBase biomegenbase = par3WorldChunkManager.getBiomeGenAt((this.xPosition << 4) + par1, (this.zPosition << 4) + par2);
    1489            k = biomegenbase.biomeID;
    1490            this.blockBiomeArray[par2 << 4 | par1] = (byte)(k & 255);
    1491        }
    1492
    1493        return BiomeGenBase.biomeList[k] == null ? BiomeGenBase.plains : BiomeGenBase.biomeList[k];
    1494    }
    1495
    1496    /**
    1497     * Returns an array containing a 16x16 mapping on the X/Z of block positions in this Chunk to biome IDs.
    1498     */
    1499    public byte[] getBiomeArray()
    1500    {
    1501        return this.blockBiomeArray;
    1502    }
    1503
    1504    /**
    1505     * Accepts a 256-entry array that contains a 16x16 mapping on the X/Z plane of block positions in this Chunk to
    1506     * biome IDs.
    1507     */
    1508    public void setBiomeArray(byte[] par1ArrayOfByte)
    1509    {
    1510        this.blockBiomeArray = par1ArrayOfByte;
    1511    }
    1512
    1513    /**
    1514     * Resets the relight check index to 0 for this Chunk.
    1515     */
    1516    public void resetRelightChecks()
    1517    {
    1518        this.queuedLightChecks = 0;
    1519    }
    1520
    1521    /**
    1522     * Called once-per-chunk-per-tick, and advances the round-robin relight check index per-storage-block by up to 8
    1523     * blocks at a time. In a worst-case scenario, can potentially take up to 1.6 seconds, calculated via
    1524     * (4096/(8*16))/20, to re-check all blocks in a chunk, which could explain both lagging light updates in certain
    1525     * cases as well as Nether relight
    1526     */
    1527    public void enqueueRelightChecks()
    1528    {
    1529        for (int i = 0; i < 8; ++i)
    1530        {
    1531            if (this.queuedLightChecks >= 4096)
    1532            {
    1533                return;
    1534            }
    1535
    1536            int j = this.queuedLightChecks % 16;
    1537            int k = this.queuedLightChecks / 16 % 16;
    1538            int l = this.queuedLightChecks / 256;
    1539            ++this.queuedLightChecks;
    1540            int i1 = (this.xPosition << 4) + k;
    1541            int j1 = (this.zPosition << 4) + l;
    1542
    1543            for (int k1 = 0; k1 < 16; ++k1)
    1544            {
    1545                int l1 = (j << 4) + k1;
    1546
    1547                if (this.storageArrays[j] == null && (k1 == 0 || k1 == 15 || k == 0 || k == 15 || l == 0 || l == 15) || this.storageArrays[j] != null && this.storageArrays[j].getExtBlockID(k, k1, l) == 0)
    1548                {
    1549                    if (Block.lightValue[this.worldObj.getBlockId(i1, l1 - 1, j1)] > 0)
    1550                    {
    1551                        this.worldObj.updateAllLightTypes(i1, l1 - 1, j1);
    1552                    }
    1553
    1554                    if (Block.lightValue[this.worldObj.getBlockId(i1, l1 + 1, j1)] > 0)
    1555                    {
    1556                        this.worldObj.updateAllLightTypes(i1, l1 + 1, j1);
    1557                    }
    1558
    1559                    if (Block.lightValue[this.worldObj.getBlockId(i1 - 1, l1, j1)] > 0)
    1560                    {
    1561                        this.worldObj.updateAllLightTypes(i1 - 1, l1, j1);
    1562                    }
    1563
    1564                    if (Block.lightValue[this.worldObj.getBlockId(i1 + 1, l1, j1)] > 0)
    1565                    {
    1566                        this.worldObj.updateAllLightTypes(i1 + 1, l1, j1);
    1567                    }
    1568
    1569                    if (Block.lightValue[this.worldObj.getBlockId(i1, l1, j1 - 1)] > 0)
    1570                    {
    1571                        this.worldObj.updateAllLightTypes(i1, l1, j1 - 1);
    1572                    }
    1573
    1574                    if (Block.lightValue[this.worldObj.getBlockId(i1, l1, j1 + 1)] > 0)
    1575                    {
    1576                        this.worldObj.updateAllLightTypes(i1, l1, j1 + 1);
    1577                    }
    1578
    1579                    this.worldObj.updateAllLightTypes(i1, l1, j1);
    1580                }
    1581            }
    1582        }
    1583    }
    1584
    1585    /** FORGE: Used to remove only invalid TileEntities */
    1586    public void cleanChunkBlockTileEntity(int x, int y, int z)
    1587    {
    1588        ChunkPosition position = new ChunkPosition(x, y, z);
    1589        if (isChunkLoaded)
    1590        {
    1591            TileEntity entity = (TileEntity)chunkTileEntityMap.get(position);
    1592            if (entity != null && entity.isInvalid())
    1593            {
    1594                chunkTileEntityMap.remove(position);
    1595            }
    1596        }
    1597    }
    1598}