001package net.minecraft.pathfinding;
002
003import net.minecraft.block.Block;
004import net.minecraft.block.material.Material;
005import net.minecraft.entity.Entity;
006import net.minecraft.util.IntHashMap;
007import net.minecraft.util.MathHelper;
008import net.minecraft.world.IBlockAccess;
009
010public class PathFinder
011{
012    /** Used to find obstacles */
013    private IBlockAccess worldMap;
014
015    /** The path being generated */
016    private Path path = new Path();
017
018    /** The points in the path */
019    private IntHashMap pointMap = new IntHashMap();
020
021    /** Selection of path points to add to the path */
022    private PathPoint[] pathOptions = new PathPoint[32];
023
024    /** should the PathFinder go through wodden door blocks */
025    private boolean isWoddenDoorAllowed;
026
027    /**
028     * should the PathFinder disregard BlockMovement type materials in its path
029     */
030    private boolean isMovementBlockAllowed;
031    private boolean isPathingInWater;
032
033    /** tells the FathFinder to not stop pathing underwater */
034    private boolean canEntityDrown;
035
036    public PathFinder(IBlockAccess par1IBlockAccess, boolean par2, boolean par3, boolean par4, boolean par5)
037    {
038        this.worldMap = par1IBlockAccess;
039        this.isWoddenDoorAllowed = par2;
040        this.isMovementBlockAllowed = par3;
041        this.isPathingInWater = par4;
042        this.canEntityDrown = par5;
043    }
044
045    /**
046     * Creates a path from one entity to another within a minimum distance
047     */
048    public PathEntity createEntityPathTo(Entity par1Entity, Entity par2Entity, float par3)
049    {
050        return this.createEntityPathTo(par1Entity, par2Entity.posX, par2Entity.boundingBox.minY, par2Entity.posZ, par3);
051    }
052
053    /**
054     * Creates a path from an entity to a specified location within a minimum distance
055     */
056    public PathEntity createEntityPathTo(Entity par1Entity, int par2, int par3, int par4, float par5)
057    {
058        return this.createEntityPathTo(par1Entity, (double)((float)par2 + 0.5F), (double)((float)par3 + 0.5F), (double)((float)par4 + 0.5F), par5);
059    }
060
061    /**
062     * Internal implementation of creating a path from an entity to a point
063     */
064    private PathEntity createEntityPathTo(Entity par1Entity, double par2, double par4, double par6, float par8)
065    {
066        this.path.clearPath();
067        this.pointMap.clearMap();
068        boolean flag = this.isPathingInWater;
069        int i = MathHelper.floor_double(par1Entity.boundingBox.minY + 0.5D);
070
071        if (this.canEntityDrown && par1Entity.isInWater())
072        {
073            i = (int)par1Entity.boundingBox.minY;
074
075            for (int j = this.worldMap.getBlockId(MathHelper.floor_double(par1Entity.posX), i, MathHelper.floor_double(par1Entity.posZ)); j == Block.waterMoving.blockID || j == Block.waterStill.blockID; j = this.worldMap.getBlockId(MathHelper.floor_double(par1Entity.posX), i, MathHelper.floor_double(par1Entity.posZ)))
076            {
077                ++i;
078            }
079
080            flag = this.isPathingInWater;
081            this.isPathingInWater = false;
082        }
083        else
084        {
085            i = MathHelper.floor_double(par1Entity.boundingBox.minY + 0.5D);
086        }
087
088        PathPoint pathpoint = this.openPoint(MathHelper.floor_double(par1Entity.boundingBox.minX), i, MathHelper.floor_double(par1Entity.boundingBox.minZ));
089        PathPoint pathpoint1 = this.openPoint(MathHelper.floor_double(par2 - (double)(par1Entity.width / 2.0F)), MathHelper.floor_double(par4), MathHelper.floor_double(par6 - (double)(par1Entity.width / 2.0F)));
090        PathPoint pathpoint2 = new PathPoint(MathHelper.floor_float(par1Entity.width + 1.0F), MathHelper.floor_float(par1Entity.height + 1.0F), MathHelper.floor_float(par1Entity.width + 1.0F));
091        PathEntity pathentity = this.addToPath(par1Entity, pathpoint, pathpoint1, pathpoint2, par8);
092        this.isPathingInWater = flag;
093        return pathentity;
094    }
095
096    /**
097     * Adds a path from start to end and returns the whole path (args: unused, start, end, unused, maxDistance)
098     */
099    private PathEntity addToPath(Entity par1Entity, PathPoint par2PathPoint, PathPoint par3PathPoint, PathPoint par4PathPoint, float par5)
100    {
101        par2PathPoint.totalPathDistance = 0.0F;
102        par2PathPoint.distanceToNext = par2PathPoint.func_75832_b(par3PathPoint);
103        par2PathPoint.distanceToTarget = par2PathPoint.distanceToNext;
104        this.path.clearPath();
105        this.path.addPoint(par2PathPoint);
106        PathPoint pathpoint3 = par2PathPoint;
107
108        while (!this.path.isPathEmpty())
109        {
110            PathPoint pathpoint4 = this.path.dequeue();
111
112            if (pathpoint4.equals(par3PathPoint))
113            {
114                return this.createEntityPath(par2PathPoint, par3PathPoint);
115            }
116
117            if (pathpoint4.func_75832_b(par3PathPoint) < pathpoint3.func_75832_b(par3PathPoint))
118            {
119                pathpoint3 = pathpoint4;
120            }
121
122            pathpoint4.isFirst = true;
123            int i = this.findPathOptions(par1Entity, pathpoint4, par4PathPoint, par3PathPoint, par5);
124
125            for (int j = 0; j < i; ++j)
126            {
127                PathPoint pathpoint5 = this.pathOptions[j];
128                float f1 = pathpoint4.totalPathDistance + pathpoint4.func_75832_b(pathpoint5);
129
130                if (!pathpoint5.isAssigned() || f1 < pathpoint5.totalPathDistance)
131                {
132                    pathpoint5.previous = pathpoint4;
133                    pathpoint5.totalPathDistance = f1;
134                    pathpoint5.distanceToNext = pathpoint5.func_75832_b(par3PathPoint);
135
136                    if (pathpoint5.isAssigned())
137                    {
138                        this.path.changeDistance(pathpoint5, pathpoint5.totalPathDistance + pathpoint5.distanceToNext);
139                    }
140                    else
141                    {
142                        pathpoint5.distanceToTarget = pathpoint5.totalPathDistance + pathpoint5.distanceToNext;
143                        this.path.addPoint(pathpoint5);
144                    }
145                }
146            }
147        }
148
149        if (pathpoint3 == par2PathPoint)
150        {
151            return null;
152        }
153        else
154        {
155            return this.createEntityPath(par2PathPoint, pathpoint3);
156        }
157    }
158
159    /**
160     * populates pathOptions with available points and returns the number of options found (args: unused1, currentPoint,
161     * unused2, targetPoint, maxDistance)
162     */
163    private int findPathOptions(Entity par1Entity, PathPoint par2PathPoint, PathPoint par3PathPoint, PathPoint par4PathPoint, float par5)
164    {
165        int i = 0;
166        byte b0 = 0;
167
168        if (this.getVerticalOffset(par1Entity, par2PathPoint.xCoord, par2PathPoint.yCoord + 1, par2PathPoint.zCoord, par3PathPoint) == 1)
169        {
170            b0 = 1;
171        }
172
173        PathPoint pathpoint3 = this.getSafePoint(par1Entity, par2PathPoint.xCoord, par2PathPoint.yCoord, par2PathPoint.zCoord + 1, par3PathPoint, b0);
174        PathPoint pathpoint4 = this.getSafePoint(par1Entity, par2PathPoint.xCoord - 1, par2PathPoint.yCoord, par2PathPoint.zCoord, par3PathPoint, b0);
175        PathPoint pathpoint5 = this.getSafePoint(par1Entity, par2PathPoint.xCoord + 1, par2PathPoint.yCoord, par2PathPoint.zCoord, par3PathPoint, b0);
176        PathPoint pathpoint6 = this.getSafePoint(par1Entity, par2PathPoint.xCoord, par2PathPoint.yCoord, par2PathPoint.zCoord - 1, par3PathPoint, b0);
177
178        if (pathpoint3 != null && !pathpoint3.isFirst && pathpoint3.distanceTo(par4PathPoint) < par5)
179        {
180            this.pathOptions[i++] = pathpoint3;
181        }
182
183        if (pathpoint4 != null && !pathpoint4.isFirst && pathpoint4.distanceTo(par4PathPoint) < par5)
184        {
185            this.pathOptions[i++] = pathpoint4;
186        }
187
188        if (pathpoint5 != null && !pathpoint5.isFirst && pathpoint5.distanceTo(par4PathPoint) < par5)
189        {
190            this.pathOptions[i++] = pathpoint5;
191        }
192
193        if (pathpoint6 != null && !pathpoint6.isFirst && pathpoint6.distanceTo(par4PathPoint) < par5)
194        {
195            this.pathOptions[i++] = pathpoint6;
196        }
197
198        return i;
199    }
200
201    /**
202     * Returns a point that the entity can safely move to
203     */
204    private PathPoint getSafePoint(Entity par1Entity, int par2, int par3, int par4, PathPoint par5PathPoint, int par6)
205    {
206        PathPoint pathpoint1 = null;
207        int i1 = this.getVerticalOffset(par1Entity, par2, par3, par4, par5PathPoint);
208
209        if (i1 == 2)
210        {
211            return this.openPoint(par2, par3, par4);
212        }
213        else
214        {
215            if (i1 == 1)
216            {
217                pathpoint1 = this.openPoint(par2, par3, par4);
218            }
219
220            if (pathpoint1 == null && par6 > 0 && i1 != -3 && i1 != -4 && this.getVerticalOffset(par1Entity, par2, par3 + par6, par4, par5PathPoint) == 1)
221            {
222                pathpoint1 = this.openPoint(par2, par3 + par6, par4);
223                par3 += par6;
224            }
225
226            if (pathpoint1 != null)
227            {
228                int j1 = 0;
229                int k1 = 0;
230
231                while (par3 > 0)
232                {
233                    k1 = this.getVerticalOffset(par1Entity, par2, par3 - 1, par4, par5PathPoint);
234
235                    if (this.isPathingInWater && k1 == -1)
236                    {
237                        return null;
238                    }
239
240                    if (k1 != 1)
241                    {
242                        break;
243                    }
244
245                    if (j1++ >= par1Entity.func_82143_as())
246                    {
247                        return null;
248                    }
249
250                    --par3;
251
252                    if (par3 > 0)
253                    {
254                        pathpoint1 = this.openPoint(par2, par3, par4);
255                    }
256                }
257
258                if (k1 == -2)
259                {
260                    return null;
261                }
262            }
263
264            return pathpoint1;
265        }
266    }
267
268    /**
269     * Returns a mapped point or creates and adds one
270     */
271    private final PathPoint openPoint(int par1, int par2, int par3)
272    {
273        int l = PathPoint.makeHash(par1, par2, par3);
274        PathPoint pathpoint = (PathPoint)this.pointMap.lookup(l);
275
276        if (pathpoint == null)
277        {
278            pathpoint = new PathPoint(par1, par2, par3);
279            this.pointMap.addKey(l, pathpoint);
280        }
281
282        return pathpoint;
283    }
284
285    /**
286     * Checks if an entity collides with blocks at a position. Returns 1 if clear, 0 for colliding with any solid block,
287     * -1 for water(if avoiding water) but otherwise clear, -2 for lava, -3 for fence, -4 for closed trapdoor, 2 if
288     * otherwise clear except for open trapdoor or water(if not avoiding)
289     */
290    public int getVerticalOffset(Entity par1Entity, int par2, int par3, int par4, PathPoint par5PathPoint)
291    {
292        return func_82565_a(par1Entity, par2, par3, par4, par5PathPoint, this.isPathingInWater, this.isMovementBlockAllowed, this.isWoddenDoorAllowed);
293    }
294
295    public static int func_82565_a(Entity par0Entity, int par1, int par2, int par3, PathPoint par4PathPoint, boolean par5, boolean par6, boolean par7)
296    {
297        boolean flag3 = false;
298
299        for (int l = par1; l < par1 + par4PathPoint.xCoord; ++l)
300        {
301            for (int i1 = par2; i1 < par2 + par4PathPoint.yCoord; ++i1)
302            {
303                for (int j1 = par3; j1 < par3 + par4PathPoint.zCoord; ++j1)
304                {
305                    int k1 = par0Entity.worldObj.getBlockId(l, i1, j1);
306
307                    if (k1 > 0)
308                    {
309                        if (k1 == Block.trapdoor.blockID)
310                        {
311                            flag3 = true;
312                        }
313                        else if (k1 != Block.waterMoving.blockID && k1 != Block.waterStill.blockID)
314                        {
315                            if (!par7 && k1 == Block.doorWood.blockID)
316                            {
317                                return 0;
318                            }
319                        }
320                        else
321                        {
322                            if (par5)
323                            {
324                                return -1;
325                            }
326
327                            flag3 = true;
328                        }
329
330                        Block block = Block.blocksList[k1];
331                        int l1 = block.getRenderType();
332
333                        if (par0Entity.worldObj.blockGetRenderType(l, i1, j1) == 9)
334                        {
335                            int i2 = MathHelper.floor_double(par0Entity.posX);
336                            int j2 = MathHelper.floor_double(par0Entity.posY);
337                            int k2 = MathHelper.floor_double(par0Entity.posZ);
338
339                            if (par0Entity.worldObj.blockGetRenderType(i2, j2, k2) != 9 && par0Entity.worldObj.blockGetRenderType(i2, j2 - 1, k2) != 9)
340                            {
341                                return -3;
342                            }
343                        }
344                        else if (!block.getBlocksMovement(par0Entity.worldObj, l, i1, j1) && (!par6 || k1 != Block.doorWood.blockID))
345                        {
346                            if (l1 == 11 || k1 == Block.fenceGate.blockID || l1 == 32)
347                            {
348                                return -3;
349                            }
350
351                            if (k1 == Block.trapdoor.blockID)
352                            {
353                                return -4;
354                            }
355
356                            Material material = block.blockMaterial;
357
358                            if (material != Material.lava)
359                            {
360                                return 0;
361                            }
362
363                            if (!par0Entity.handleLavaMovement())
364                            {
365                                return -2;
366                            }
367                        }
368                    }
369                }
370            }
371        }
372
373        return flag3 ? 2 : 1;
374    }
375
376    /**
377     * Returns a new PathEntity for a given start and end point
378     */
379    private PathEntity createEntityPath(PathPoint par1PathPoint, PathPoint par2PathPoint)
380    {
381        int i = 1;
382        PathPoint pathpoint2;
383
384        for (pathpoint2 = par2PathPoint; pathpoint2.previous != null; pathpoint2 = pathpoint2.previous)
385        {
386            ++i;
387        }
388
389        PathPoint[] apathpoint = new PathPoint[i];
390        pathpoint2 = par2PathPoint;
391        --i;
392
393        for (apathpoint[i] = par2PathPoint; pathpoint2.previous != null; apathpoint[i] = pathpoint2)
394        {
395            pathpoint2 = pathpoint2.previous;
396            --i;
397        }
398
399        return new PathEntity(apathpoint);
400    }
401}