Skip to content

Commit

Permalink
Corrected an edge-case whereby non-cautious mobs would try to step-up…
Browse files Browse the repository at this point in the history
… into narrow cavities with a ceiling half a block too low for them to tolerate.
  • Loading branch information
MadMartian committed Mar 15, 2021
1 parent 014c5bd commit 8cef83e
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.extollit.gaming.ai.path;

import com.extollit.gaming.ai.path.model.*;
import com.extollit.linalg.immutable.AxisAlignedBBox;
import com.extollit.linalg.immutable.Vec3i;

import static com.extollit.gaming.ai.path.PassibilityHelpers.impedesMovement;
import static java.lang.Math.floor;

abstract class AbstractNodeCalculator implements INodeCalculator {
Expand All @@ -27,44 +29,56 @@ public final void applySubject(IPathingEntity subject) {
this.capabilities = subject.capabilities();
}

protected final Passibility verticalClearanceAt(FlagSampler sampler, int max, byte flags, Passibility passibility, Vec3i d, int x, int y, int z, float partY) {
protected final Passibility verticalClearanceAt(FlagSampler sampler, int max, byte flags, Passibility passibility, Vec3i d, int x, int y, int z, final float minPartY) {
byte clearanceFlags = flags;
final int
yMax = y + max,
yN = Math.max(y, y - d.y) + this.tall;
yN = Math.max(y, y - d.y) + this.tall,
yNa = yN - 1;
int yt = y;

for (int yNa = yN + (int)floor(partY);
while (yt < yNa && yt < yMax) {
passibility = passibility.between(clearance(clearanceFlags));

yt < yNa && yt < yMax;
clearanceFlags = sampler.flagsAt(x, ++yt, z);
}

clearanceFlags = sampler.flagsAt(x, ++yt, z)
)
passibility = passibility.between(clearance(clearanceFlags));
if (yt >= yNa)
passibility = headClearance(passibility, minPartY, x, yt, z, clearanceFlags);

if (yt < yN && yt < yMax && (insufficientHeadClearance(clearanceFlags, partY, x, yt, z)))
passibility = passibility.between(clearance(clearanceFlags));
return passibility;
}

protected final Passibility headClearance(Passibility passibility, float partyY, int x, int y, int z, byte flags) {
if (insufficientHeadClearance(flags, partyY, x, y, z))
passibility = Passibility.impassible;
else if (partyY >= 0)
passibility = passibility.between(clearance(flags));
return passibility;
}

protected final boolean insufficientHeadClearance(byte flags, float partialY0, int x, int yN, int z) {
return bottomOffsetAt(flags, x, yN, z) + partialY0 > 0;
protected final boolean insufficientHeadClearance(byte flags, float partialY0, int x, int y, int z) {
return bottomOffsetAt(flags, x, y, z) + partialY0 > 0;
}

private float bottomOffsetAt(byte flags, int x, int y, int z) {
if (Element.air.in(flags)
|| Logic.climbable(flags)
|| Element.earth.in(flags) && Logic.nothing.in(flags)
|| swimmingRequiredFor(flags)
)
if (!impedesMovement(flags, this.capabilities)
|| swimmingRequiredFor(flags)
)
return 0;

if (Element.earth.in(flags) && Logic.nothing.in(flags))
return 1;

final IBlockObject block = this.instanceSpace.blockObjectAt(x, y, z);
if (!block.isImpeding())
return 0;

return (float) block.bounds().min.y;
if (block.isFullyBounded())
return 1;

final AxisAlignedBBox bounds = block.bounds();
return (float)(1 - bounds.min.y);
}

private final Passibility clearance(byte flags) {
Expand All @@ -77,10 +91,10 @@ protected final float topOffsetAt(FlagSampler sampler, int x, int y, int z) {

protected final float topOffsetAt(byte flags, int x, int y, int z) {
if (Element.air.in(flags)
|| Logic.climbable(flags)
|| Element.earth.in(flags) && Logic.nothing.in(flags)
|| Element.water.in(flags) && (capabilities.aquatic() || !capabilities.swimmer())
)
|| Logic.climbable(flags)
|| Element.earth.in(flags) && Logic.nothing.in(flags)
|| swimmingRequiredFor(flags) && (capabilities.aquatic() || !capabilities.swimmer())
)
return 0;

if (swimmingRequiredFor(flags))
Expand All @@ -106,20 +120,22 @@ protected final float topOffsetAt(byte flags, int x, int y, int z) {

protected final Passibility originHeadClearance(FlagSampler sampler, Passibility passibility, Vec3i origin, int minY, float minPartY) {
final int
yN = minY + this.tall,
yNa = yN + (int)floor(minPartY);
yN0 = origin.y + this.tall,
yN = Math.max(minY, origin.y) + this.tall,
yNa = yN - 1;

for (int x = origin.x, xN = origin.x + this.discreteSize; x < xN; ++x)
for (int z = origin.z, zN = origin.z + this.discreteSize; z < zN; ++z)
for (int y = origin.y + this.tall; y < yNa; ++y)
for (int y = yN0; y < yNa; ++y)
passibility = passibility.between(clearance(sampler.flagsAt(x, y, z)));

if (yNa < yN)
if (yN > yN0)
for (int x = origin.x, xN = origin.x + this.discreteSize; x < xN; ++x)
for (int z = origin.z, zN = origin.z + this.discreteSize; z < zN; ++z) {
final byte flags = sampler.flagsAt(x, yNa, z);
if (insufficientHeadClearance(flags, minPartY, x, yNa, z))
passibility = passibility.between(clearance(flags));
passibility = headClearance(passibility, minPartY, x, yNa, z, flags);
if (passibility == Passibility.impassible)
return Passibility.impassible;
}

return passibility;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,14 @@ public Node passibleNodeNear(Vec3i coords0, Vec3i origin, FlagSampler flagSample
else
passibility = passibility.between(passibilityFrom(flags, capabilities));

final float partY0 = topOffsetAt(
flagSampler,
x - d.x,
y0 - d.y - 1,
z - d.z
);
final float partY = topOffsetAt(flagsBeneath, x, yb, z);
passibility = verticalClearanceAt(flagSampler, this.tall, flags, passibility, d, x, y0, z, partY);
passibility = verticalClearanceAt(flagSampler, this.tall, flags, passibility, d, x, y0, z, Math.min(partY, partY0));

if (y0 > minY) {
minY = y0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public Node passibleNodeNear(Vec3i coords0, Vec3i origin, final FlagSampler flag
) {
int y = y0;

float partY = topOffsetAt(
final float partY0 = topOffsetAt(
flagSampler,
x - d.x,
y - d.y - 1,
Expand All @@ -77,7 +77,7 @@ public Node passibleNodeNear(Vec3i coords0, Vec3i origin, final FlagSampler flag
byte flags = flagSampler.flagsAt(x, y, z);
final boolean impedesMovement;
if (impedesMovement = impedesMovement(flags, capabilities)) {
final float partialDisparity = partY - topOffsetAt(flags, x, y++, z);
final float partialDisparity = partY0 - topOffsetAt(flags, x, y++, z);
flags = flagSampler.flagsAt(x, y, z);

if (partialDisparity < 0 || impedesMovement(flags, capabilities)) {
Expand All @@ -92,13 +92,13 @@ public Node passibleNodeNear(Vec3i coords0, Vec3i origin, final FlagSampler flag
while (climbsLadders && Logic.climbable(flags));
}

if (impedesMovement(flags = flagSampler.flagsAt(x, --y, z), capabilities) && (impedesMovement(flags = flagSampler.flagsAt(x, ++y, z), capabilities) || partY < 0))
if (impedesMovement(flags = flagSampler.flagsAt(x, --y, z), capabilities) && (impedesMovement(flags = flagSampler.flagsAt(x, ++y, z), capabilities) || partY0 < 0))
return new Node(coords0, Passibility.impassible, flagSampler.volatility() > 0);
}
}
partY = topOffsetAt(flagSampler, x, y - 1, z);
float partY = topOffsetAt(flagSampler, x, y - 1, z);
final int ys;
passibility = verticalClearanceAt(flagSampler, this.tall, flags, passibility, d, x, ys = y, z, partY);
passibility = verticalClearanceAt(flagSampler, this.tall, flags, passibility, d, x, ys = y, z, Math.min(partY, partY0));

boolean swimable = false;
{
Expand Down Expand Up @@ -129,7 +129,7 @@ public Node passibleNodeNear(Vec3i coords0, Vec3i origin, final FlagSampler flag
}

partY = topOffsetAt(flags, x, y++, z);
passibility = verticalClearanceAt(flagSampler, ys - y, flagSampler.flagsAt(x, y, z), passibility, d, x, y, z, partY);
passibility = verticalClearanceAt(flagSampler, ys - y, flagSampler.flagsAt(x, y, z), passibility, d, x, y, z, Math.min(partY, partY0));

if (y > minY) {
minY = y;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ protected void advance(IPathingEntity mockedPathing, IPath path) {
pos(coordinates.x + 0.5, coordinates.y + 0.1, coordinates.z + 0.5);
}

protected void lava(final int x, final int y, final int z) {
when(occlusionProvider.elementAt(x, y, z)).thenReturn(Element.fire.mask);
when(instanceSpace.blockObjectAt(x, y, z)).thenReturn(TestingBlocks.lava);
}

protected void solid(final int x, final int y, final int z) {
when(occlusionProvider.elementAt(x, y, z)).thenReturn(Element.earth.mask);
when(instanceSpace.blockObjectAt(x, y, z)).thenReturn(TestingBlocks.stone);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package com.extollit.gaming.ai.path;

import com.extollit.gaming.ai.path.model.IInstanceSpace;
import com.extollit.gaming.ai.path.model.INodeCalculator;
import com.extollit.gaming.ai.path.model.Node;
import com.extollit.gaming.ai.path.model.Passibility;
import com.extollit.linalg.immutable.Vec3i;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class ClearanceNodeCalculatorTests extends AbstractNodeCalculatorTests {
@Override
@Before
public void setup() {
super.setup();

cautious(false);
super.calculator.applySubject(super.pathingEntity);
}

@Override
protected INodeCalculator createCalculator(IInstanceSpace instanceSpace) {
return new GroundNodeCalculator(instanceSpace);
}

@Test
public void sourceSlabCeilingNope() {
solid(0, -1, 0);
slabUp(0, 2, 0);
solid(0, 0, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 0, 1), ORIGIN, super.flagSampler);

assertEquals(Passibility.impassible, actual.passibility());
}

@Test
public void sourceSlabCeilingYep() {
solid(0, -1, 0);
slabUp(0, 2, 0);
slabDown(0, 0, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 0, 1), ORIGIN, super.flagSampler);

assertEquals(Passibility.passible, actual.passibility());
}

@Test
public void sourceSlabCeilingControl() {
solid(0, -1, 0);
solid(0, 3, 0);
solid(0, 0, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 0, 1), ORIGIN, super.flagSampler);

assertEquals(Passibility.passible, actual.passibility());
}

@Test
public void sourceBurningHead() {
solid(0, -1, 0);
lava(0, 2, 0);
solid(0, 0, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 0, 1), ORIGIN, super.flagSampler);

assertEquals(Passibility.dangerous, actual.passibility());
}

@Test
public void targetSlabCeilingNope() {
solid(0, 0, 0);
solid(0, 0, 1);
solid(0, 1, 1);
slabUp(0, 3, 0);
slabUp(0, 3, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 1, 1), new Vec3i(0, 1, 0), super.flagSampler);

assertEquals(Passibility.impassible, actual.passibility());
}

@Test
public void targetSlabCeilingYep() {
slabDown(0, 0, 0);
solid(0, 0, 1);
slabDown(0, 1, 1);
slabUp(0, 3, 0);
slabUp(0, 3, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 1, 1), new Vec3i(0, 1, 0), super.flagSampler);

assertEquals(Passibility.passible, actual.passibility());
}

@Test
public void targetSlabCeilingControl() {
solid(0, 0, 0);
solid(0, 0, 1);
solid(0, 1, 1);
solid(0, 4, 0);
solid(0, 4, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 1, 1), new Vec3i(0, 1, 0), super.flagSampler);

assertEquals(Passibility.passible, actual.passibility());
}

@Test
public void targetBurningHead() {
solid(0, 0, 0);
solid(0, 0, 1);
solid(0, 1, 1);
solid(0, 4, 0);
solid(0, 4, 1);
lava(0, 3, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 1, 1), new Vec3i(0, 1, 0), super.flagSampler);

assertEquals(Passibility.dangerous, actual.passibility());
}

@Test
public void stuckDownHereWithYou() {
solid(0, 0, 0);
solid(0, 0, 1);
solid(0, 1, 1);
fuzzy(0, 3, 0);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 1, 1), new Vec3i(0, 1, 0), super.flagSampler);

assertEquals(Passibility.impassible, actual.passibility());
}

@Test
public void slabAcross() {
slabDown(0, -1, 0);
slabDown(0, -1, 1);
slabUp(0, 1, 0);
slabUp(0, 1, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 0, 1), ORIGIN, super.flagSampler);

assertEquals(Passibility.passible, actual.passibility());
}

@Test
public void targetNarrowNope() {
solid(0, -1, 0);
slabDown(0, 0, 1);
solid(0, 2, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 0, 1), ORIGIN, super.flagSampler);

assertEquals(Passibility.impassible, actual.passibility());
}

@Test
public void targetNarrowYep() {
solid(0, -1, 0);
slabDown(0, 0, 1);
slabUp(0, 2, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 0, 1), ORIGIN, super.flagSampler);

assertEquals(Passibility.passible, actual.passibility());
}

@Test
public void sourceNarrowNope() {
slabDown(0, 0, 0);
solid(0, -1, 1);
solid(0, 2, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 1, 1), new Vec3i(0, 1, 0), super.flagSampler);

assertEquals(Passibility.impassible, actual.passibility());
}

@Test
public void sourceNarrowYep() {
slabDown(0, 0, 0);
solid(0, -1, 1);
slabUp(0, 2, 1);

final Node actual = calculator.passibleNodeNear(new Vec3i(0, 1, 1), new Vec3i(0, 1, 0), super.flagSampler);

assertEquals(Passibility.passible, actual.passibility());
}
}
Loading

0 comments on commit 8cef83e

Please sign in to comment.