use mutable move result to avoid instantianing ten million move result objects

This commit is contained in:
Leijurv 2018-10-05 12:24:52 -07:00
parent 6fff4c5254
commit cb589219d8
No known key found for this signature in database
GPG Key ID: 44A3EA646EADAC6A
7 changed files with 144 additions and 108 deletions

View File

@ -26,7 +26,7 @@ import baritone.pathing.movement.Moves;
import baritone.pathing.path.IPath;
import baritone.utils.BlockStateInterface;
import baritone.utils.Helper;
import baritone.utils.pathing.MoveResult;
import baritone.utils.pathing.MutableMoveResult;
import java.util.HashSet;
import java.util.Optional;
@ -60,6 +60,7 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
bestSoFar[i] = startNode;
}
CalculationContext calcContext = new CalculationContext();
MutableMoveResult res = new MutableMoveResult();
HashSet<Long> favored = favoredPositions.orElse(null);
BlockStateInterface.clearCachedChunk();
long startTime = System.nanoTime() / 1000000L;
@ -104,28 +105,29 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
continue;
}
}
MoveResult res = moves.apply(calcContext, currentNode.x, currentNode.y, currentNode.z);
res.reset();
moves.apply(calcContext, currentNode.x, currentNode.y, currentNode.z, res);
numMovementsConsidered++;
double actionCost = res.cost;
if (actionCost >= ActionCosts.COST_INF) {
continue;
}
// check destination after verifying it's not COST_INF -- some movements return a static IMPOSSIBLE object with COST_INF and destination being 0,0,0 to avoid allocating a new result for every failed calculation
if (!moves.dynamicXZ && (res.destX != newX || res.destZ != newZ)) {
throw new IllegalStateException(moves + " " + res.destX + " " + newX + " " + res.destZ + " " + newZ);
if (!moves.dynamicXZ && (res.x != newX || res.z != newZ)) {
throw new IllegalStateException(moves + " " + res.x + " " + newX + " " + res.z + " " + newZ);
}
if (!moves.dynamicY && res.destY != currentNode.y + moves.yOffset) {
throw new IllegalStateException(moves + " " + res.destX + " " + newX + " " + res.destZ + " " + newZ);
if (!moves.dynamicY && res.y != currentNode.y + moves.yOffset) {
throw new IllegalStateException(moves + " " + res.x + " " + newX + " " + res.z + " " + newZ);
}
if (actionCost <= 0) {
throw new IllegalStateException(moves + " calculated implausible cost " + actionCost);
}
long hashCode = posHash(res.destX, res.destY, res.destZ);
long hashCode = posHash(res.x, res.y, res.z);
if (favoring && favored.contains(hashCode)) {
// see issue #18
actionCost *= favorCoeff;
}
PathNode neighbor = getNodeAtPosition(res.destX, res.destY, res.destZ, hashCode);
PathNode neighbor = getNodeAtPosition(res.x, res.y, res.z, hashCode);
double tentativeCost = currentNode.cost + actionCost;
if (tentativeCost < neighbor.cost) {
if (tentativeCost < 0) {

View File

@ -139,6 +139,7 @@ class Path implements IPath {
for (Moves moves : Moves.values()) {
Movement move = moves.apply0(src);
if (move.getDest().equals(dest)) {
move.recalculateCost(); // have to calculate the cost at calculation time so we can accurately judge whether a cost increase happened between cached calculation and real execution
return move;
}
}

View File

@ -19,7 +19,7 @@ package baritone.pathing.movement;
import baritone.pathing.movement.movements.*;
import baritone.utils.pathing.BetterBlockPos;
import baritone.utils.pathing.MoveResult;
import baritone.utils.pathing.MutableMoveResult;
import net.minecraft.util.EnumFacing;
/**
@ -151,68 +151,72 @@ public enum Moves {
DESCEND_EAST(+1, 0, 0, false, true) {
@Override
public Movement apply0(BetterBlockPos src) {
MoveResult res = apply(new CalculationContext(), src.x, src.y, src.z);
if (res.destY == src.y - 1) {
return new MovementDescend(src, new BetterBlockPos(res.destX, res.destY, res.destZ));
MutableMoveResult res = new MutableMoveResult();
apply(new CalculationContext(), src.x, src.y, src.z, res);
if (res.y == src.y - 1) {
return new MovementDescend(src, new BetterBlockPos(res.x, res.y, res.z));
} else {
return new MovementFall(src, new BetterBlockPos(res.destX, res.destY, res.destZ));
return new MovementFall(src, new BetterBlockPos(res.x, res.y, res.z));
}
}
@Override
public MoveResult apply(CalculationContext context, int x, int y, int z) {
return MovementDescend.cost(context, x, y, z, x + 1, z);
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
MovementDescend.cost(context, x, y, z, x + 1, z, result);
}
},
DESCEND_WEST(-1, 0, 0, false, true) {
@Override
public Movement apply0(BetterBlockPos src) {
MoveResult res = apply(new CalculationContext(), src.x, src.y, src.z);
if (res.destY == src.y - 1) {
return new MovementDescend(src, new BetterBlockPos(res.destX, res.destY, res.destZ));
MutableMoveResult res = new MutableMoveResult();
apply(new CalculationContext(), src.x, src.y, src.z, res);
if (res.y == src.y - 1) {
return new MovementDescend(src, new BetterBlockPos(res.x, res.y, res.z));
} else {
return new MovementFall(src, new BetterBlockPos(res.destX, res.destY, res.destZ));
return new MovementFall(src, new BetterBlockPos(res.x, res.y, res.z));
}
}
@Override
public MoveResult apply(CalculationContext context, int x, int y, int z) {
return MovementDescend.cost(context, x, y, z, x - 1, z);
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
MovementDescend.cost(context, x, y, z, x - 1, z, result);
}
},
DESCEND_NORTH(0, 0, -1, false, true) {
@Override
public Movement apply0(BetterBlockPos src) {
MoveResult res = apply(new CalculationContext(), src.x, src.y, src.z);
if (res.destY == src.y - 1) {
return new MovementDescend(src, new BetterBlockPos(res.destX, res.destY, res.destZ));
MutableMoveResult res = new MutableMoveResult();
apply(new CalculationContext(), src.x, src.y, src.z, res);
if (res.y == src.y - 1) {
return new MovementDescend(src, new BetterBlockPos(res.x, res.y, res.z));
} else {
return new MovementFall(src, new BetterBlockPos(res.destX, res.destY, res.destZ));
return new MovementFall(src, new BetterBlockPos(res.x, res.y, res.z));
}
}
@Override
public MoveResult apply(CalculationContext context, int x, int y, int z) {
return MovementDescend.cost(context, x, y, z, x, z - 1);
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
MovementDescend.cost(context, x, y, z, x, z - 1, result);
}
},
DESCEND_SOUTH(0, 0, +1, false, true) {
@Override
public Movement apply0(BetterBlockPos src) {
MoveResult res = apply(new CalculationContext(), src.x, src.y, src.z);
if (res.destY == src.y - 1) {
return new MovementDescend(src, new BetterBlockPos(res.destX, res.destY, res.destZ));
MutableMoveResult res = new MutableMoveResult();
apply(new CalculationContext(), src.x, src.y, src.z, res);
if (res.y == src.y - 1) {
return new MovementDescend(src, new BetterBlockPos(res.x, res.y, res.z));
} else {
return new MovementFall(src, new BetterBlockPos(res.destX, res.destY, res.destZ));
return new MovementFall(src, new BetterBlockPos(res.x, res.y, res.z));
}
}
@Override
public MoveResult apply(CalculationContext context, int x, int y, int z) {
return MovementDescend.cost(context, x, y, z, x, z + 1);
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
MovementDescend.cost(context, x, y, z, x, z + 1, result);
}
},
@ -271,8 +275,8 @@ public enum Moves {
}
@Override
public MoveResult apply(CalculationContext context, int x, int y, int z) {
return MovementParkour.cost(context, x, y, z, EnumFacing.NORTH);
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
MovementParkour.cost(context, x, y, z, EnumFacing.NORTH, result);
}
},
@ -283,8 +287,8 @@ public enum Moves {
}
@Override
public MoveResult apply(CalculationContext context, int x, int y, int z) {
return MovementParkour.cost(context, x, y, z, EnumFacing.SOUTH);
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
MovementParkour.cost(context, x, y, z, EnumFacing.SOUTH, result);
}
},
@ -295,8 +299,8 @@ public enum Moves {
}
@Override
public MoveResult apply(CalculationContext context, int x, int y, int z) {
return MovementParkour.cost(context, x, y, z, EnumFacing.EAST);
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
MovementParkour.cost(context, x, y, z, EnumFacing.EAST, result);
}
},
@ -307,8 +311,8 @@ public enum Moves {
}
@Override
public MoveResult apply(CalculationContext context, int x, int y, int z) {
return MovementParkour.cost(context, x, y, z, EnumFacing.WEST);
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
MovementParkour.cost(context, x, y, z, EnumFacing.WEST, result);
}
};
@ -333,11 +337,14 @@ public enum Moves {
public abstract Movement apply0(BetterBlockPos src);
public MoveResult apply(CalculationContext context, int x, int y, int z) {
public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
if (dynamicXZ || dynamicY) {
throw new UnsupportedOperationException();
}
return new MoveResult(x + xOffset, y + yOffset, z + zOffset, cost(context, x, y, z));
result.x = x + xOffset;
result.y = y + yOffset;
result.z = z + zOffset;
result.cost = cost(context, x, y, z);
}
public double cost(CalculationContext context, int x, int y, int z) {

View File

@ -26,15 +26,13 @@ import baritone.pathing.movement.MovementState.MovementStatus;
import baritone.utils.BlockStateInterface;
import baritone.utils.InputOverrideHandler;
import baritone.utils.pathing.BetterBlockPos;
import baritone.utils.pathing.MoveResult;
import baritone.utils.pathing.MutableMoveResult;
import net.minecraft.block.Block;
import net.minecraft.block.BlockFalling;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.util.math.BlockPos;
import static baritone.utils.pathing.MoveResult.IMPOSSIBLE;
public class MovementDescend extends Movement {
private int numTicks = 0;
@ -51,32 +49,33 @@ public class MovementDescend extends Movement {
@Override
protected double calculateCost(CalculationContext context) {
MoveResult result = cost(context, src.x, src.y, src.z, dest.x, dest.z);
if (result.destY != dest.y) {
MutableMoveResult result = new MutableMoveResult();
cost(context, src.x, src.y, src.z, dest.x, dest.z, result);
if (result.y != dest.y) {
return COST_INF; // doesn't apply to us, this position is a fall not a descend
}
return result.cost;
}
public static MoveResult cost(CalculationContext context, int x, int y, int z, int destX, int destZ) {
public static void cost(CalculationContext context, int x, int y, int z, int destX, int destZ, MutableMoveResult res) {
Block fromDown = BlockStateInterface.get(x, y - 1, z).getBlock();
if (fromDown == Blocks.LADDER || fromDown == Blocks.VINE) {
return IMPOSSIBLE;
return;
}
double totalCost = 0;
IBlockState destDown = BlockStateInterface.get(destX, y - 1, destZ);
totalCost += MovementHelper.getMiningDurationTicks(context, destX, y - 1, destZ, destDown, false);
if (totalCost >= COST_INF) {
return IMPOSSIBLE;
return;
}
totalCost += MovementHelper.getMiningDurationTicks(context, destX, y, destZ, false);
if (totalCost >= COST_INF) {
return IMPOSSIBLE;
return;
}
totalCost += MovementHelper.getMiningDurationTicks(context, destX, y + 1, destZ, true); // only the top block in the 3 we need to mine needs to consider the falling blocks above
if (totalCost >= COST_INF) {
return IMPOSSIBLE;
return;
}
// A
@ -91,11 +90,12 @@ public class MovementDescend extends Movement {
IBlockState below = BlockStateInterface.get(destX, y - 2, destZ);
if (!MovementHelper.canWalkOn(destX, y - 2, destZ, below)) {
return dynamicFallCost(context, x, y, z, destX, destZ, totalCost, below);
dynamicFallCost(context, x, y, z, destX, destZ, totalCost, below, res);
return;
}
if (destDown.getBlock() == Blocks.LADDER || destDown.getBlock() == Blocks.VINE) {
return IMPOSSIBLE;
return;
}
// we walk half the block plus 0.3 to get to the edge, then we walk the other 0.2 while simultaneously falling (math.max because of how it's in parallel)
@ -105,25 +105,28 @@ public class MovementDescend extends Movement {
walk = WALK_ONE_OVER_SOUL_SAND_COST;
}
totalCost += walk + Math.max(FALL_N_BLOCKS_COST[1], CENTER_AFTER_FALL_COST);
return new MoveResult(destX, y - 1, destZ, totalCost);
res.x = destX;
res.y = y - 1;
res.z = destZ;
res.cost = totalCost;
}
public static MoveResult dynamicFallCost(CalculationContext context, int x, int y, int z, int destX, int destZ, double frontBreak, IBlockState below) {
public static void dynamicFallCost(CalculationContext context, int x, int y, int z, int destX, int destZ, double frontBreak, IBlockState below, MutableMoveResult res) {
if (frontBreak != 0 && BlockStateInterface.get(destX, y + 2, destZ).getBlock() instanceof BlockFalling) {
// if frontBreak is 0 we can actually get through this without updating the falling block and making it actually fall
// but if frontBreak is nonzero, we're breaking blocks in front, so don't let anything fall through this column,
// and potentially replace the water we're going to fall into
return IMPOSSIBLE;
return;
}
if (!MovementHelper.canWalkThrough(destX, y - 2, destZ, below) && below.getBlock() != Blocks.WATER) {
return IMPOSSIBLE;
return;
}
for (int fallHeight = 3; true; fallHeight++) {
int newY = y - fallHeight;
if (newY < 0) {
// when pathing in the end, where you could plausibly fall into the void
// this check prevents it from getting the block at y=-1 and crashing
return IMPOSSIBLE;
return;
}
IBlockState ontoBlock = BlockStateInterface.get(destX, newY, destZ);
double tentativeCost = WALK_OFF_BLOCK_COST + FALL_N_BLOCKS_COST[fallHeight] + frontBreak;
@ -131,31 +134,43 @@ public class MovementDescend extends Movement {
// lilypads are canWalkThrough, but we can't end a fall that should be broken by water if it's covered by a lilypad
// however, don't return impossible in the lilypad scenario, because we could still jump right on it (water that's below a lilypad is canWalkOn so it works)
if (Baritone.settings().assumeWalkOnWater.get()) {
return IMPOSSIBLE; // TODO fix
return; // TODO fix
}
// found a fall into water
return new MoveResult(destX, newY, destZ, tentativeCost); // TODO incorporate water swim up cost?
res.x = destX;
res.y = newY;
res.z = destZ;
res.cost = tentativeCost;// TODO incorporate water swim up cost?
return;
}
if (ontoBlock.getBlock() == Blocks.FLOWING_WATER) {
return IMPOSSIBLE;
return;
}
if (MovementHelper.canWalkThrough(destX, newY, destZ, ontoBlock)) {
continue;
}
if (!MovementHelper.canWalkOn(destX, newY, destZ, ontoBlock)) {
return IMPOSSIBLE;
return;
}
if (MovementHelper.isBottomSlab(ontoBlock)) {
return IMPOSSIBLE; // falling onto a half slab is really glitchy, and can cause more fall damage than we'd expect
return; // falling onto a half slab is really glitchy, and can cause more fall damage than we'd expect
}
if (context.hasWaterBucket() && fallHeight <= context.maxFallHeightBucket() + 1) {
return new MoveResult(destX, newY + 1, destZ, tentativeCost + context.placeBlockCost()); // this is the block we're falling onto, so dest is +1
res.x = destX;
res.y = newY + 1;// this is the block we're falling onto, so dest is +1
res.z = destZ;
res.cost = tentativeCost + context.placeBlockCost();
return;
}
if (fallHeight <= context.maxFallHeightNoWater() + 1) {
// fallHeight = 4 means onto.up() is 3 blocks down, which is the max
return new MoveResult(destX, newY + 1, destZ, tentativeCost);
res.x = destX;
res.y = newY + 1;
res.z = destZ;
res.cost = tentativeCost;
return;
} else {
return IMPOSSIBLE;
return;
}
}
}

View File

@ -30,7 +30,7 @@ import baritone.utils.InputOverrideHandler;
import baritone.utils.RayTraceUtils;
import baritone.utils.Utils;
import baritone.utils.pathing.BetterBlockPos;
import baritone.utils.pathing.MoveResult;
import baritone.utils.pathing.MutableMoveResult;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
@ -49,8 +49,9 @@ public class MovementFall extends Movement {
@Override
protected double calculateCost(CalculationContext context) {
MoveResult result = MovementDescend.cost(context, src.x, src.y, src.z, dest.x, dest.z);
if (result.destY != dest.y) {
MutableMoveResult result = new MutableMoveResult();
MovementDescend.cost(context, src.x, src.y, src.z, dest.x, dest.z, result);
if (result.y != dest.y) {
return COST_INF; // doesn't apply to us, this position is a descend not a fall
}
return result.cost;

View File

@ -28,7 +28,7 @@ import baritone.utils.Helper;
import baritone.utils.InputOverrideHandler;
import baritone.utils.Utils;
import baritone.utils.pathing.BetterBlockPos;
import baritone.utils.pathing.MoveResult;
import baritone.utils.pathing.MutableMoveResult;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
@ -39,8 +39,6 @@ import net.minecraft.util.math.Vec3d;
import java.util.Objects;
import static baritone.utils.pathing.MoveResult.IMPOSSIBLE;
public class MovementParkour extends Movement {
private static final EnumFacing[] HORIZONTALS_BUT_ALSO_DOWN____SO_EVERY_DIRECTION_EXCEPT_UP = {EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.EAST, EnumFacing.WEST, EnumFacing.DOWN};
@ -56,70 +54,75 @@ public class MovementParkour extends Movement {
}
public static MovementParkour cost(CalculationContext context, BetterBlockPos src, EnumFacing direction) {
MoveResult res = cost(context, src.x, src.y, src.z, direction);
int dist = Math.abs(res.destX - src.x) + Math.abs(res.destZ - src.z);
MutableMoveResult res = new MutableMoveResult();
cost(context, src.x, src.y, src.z, direction, res);
int dist = Math.abs(res.x - src.x) + Math.abs(res.z - src.z);
return new MovementParkour(src, dist, direction);
}
public static MoveResult cost(CalculationContext context, int x, int y, int z, EnumFacing dir) {
public static void cost(CalculationContext context, int x, int y, int z, EnumFacing dir, MutableMoveResult res) {
if (!Baritone.settings().allowParkour.get()) {
return IMPOSSIBLE;
return;
}
IBlockState standingOn = BlockStateInterface.get(x, y - 1, z);
if (standingOn.getBlock() == Blocks.VINE || standingOn.getBlock() == Blocks.LADDER || MovementHelper.isBottomSlab(standingOn)) {
return IMPOSSIBLE;
return;
}
int xDiff = dir.getXOffset();
int zDiff = dir.getZOffset();
IBlockState adj = BlockStateInterface.get(x + xDiff, y - 1, z + zDiff);
if (MovementHelper.avoidWalkingInto(adj.getBlock()) && adj.getBlock() != Blocks.WATER && adj.getBlock() != Blocks.FLOWING_WATER) { // magma sucks
return IMPOSSIBLE;
return;
}
if (MovementHelper.canWalkOn(x + xDiff, y - 1, z + zDiff, adj)) { // don't parkour if we could just traverse (for now)
return IMPOSSIBLE;
return;
}
if (!MovementHelper.fullyPassable(x + xDiff, y, z + zDiff)) {
return IMPOSSIBLE;
return;
}
if (!MovementHelper.fullyPassable(x + xDiff, y + 1, z + zDiff)) {
return IMPOSSIBLE;
return;
}
if (!MovementHelper.fullyPassable(x + xDiff, y + 2, z + zDiff)) {
return IMPOSSIBLE;
return;
}
if (!MovementHelper.fullyPassable(x, y + 2, z)) {
return IMPOSSIBLE;
return;
}
for (int i = 2; i <= (context.canSprint() ? 4 : 3); i++) {
// TODO perhaps dest.up(3) doesn't need to be fullyPassable, just canWalkThrough, possibly?
for (int y2 = 0; y2 < 4; y2++) {
if (!MovementHelper.fullyPassable(x + xDiff * i, y + y2, z + zDiff * i)) {
return IMPOSSIBLE;
return;
}
}
if (MovementHelper.canWalkOn(x + xDiff * i, y - 1, z + zDiff * i)) {
return new MoveResult(x + xDiff * i, y, z + zDiff * i, costFromJumpDistance(i));
res.x = x + xDiff * i;
res.y = y;
res.z = z + zDiff * i;
res.cost = costFromJumpDistance(i);
return;
}
}
if (!context.canSprint()) {
return IMPOSSIBLE;
return;
}
if (!Baritone.settings().allowParkourPlace.get()) {
return IMPOSSIBLE;
return;
}
if (!Baritone.settings().allowPlace.get()) {
Helper.HELPER.logDirect("allowParkourPlace enabled but allowPlace disabled?");
return IMPOSSIBLE;
return;
}
int destX = x + 4 * xDiff;
int destZ = z + 4 * zDiff;
IBlockState toPlace = BlockStateInterface.get(destX, y - 1, destZ);
if (!context.hasThrowaway()) {
return IMPOSSIBLE;
return;
}
if (toPlace.getBlock() != Blocks.AIR && !BlockStateInterface.isWater(toPlace.getBlock()) && !MovementHelper.isReplacable(destX, y - 1, destZ, toPlace)) {
return IMPOSSIBLE;
return;
}
for (int i = 0; i < 5; i++) {
int againstX = destX + HORIZONTALS_BUT_ALSO_DOWN____SO_EVERY_DIRECTION_EXCEPT_UP[i].getXOffset();
@ -128,10 +131,13 @@ public class MovementParkour extends Movement {
continue;
}
if (MovementHelper.canPlaceAgainst(againstX, y - 1, againstZ)) {
return new MoveResult(destX, y, destZ, costFromJumpDistance(4) + context.placeBlockCost());
res.x = destX;
res.y = y;
res.z = destZ;
res.cost = costFromJumpDistance(4) + context.placeBlockCost();
return;
}
}
return IMPOSSIBLE;
}
private static double costFromJumpDistance(int dist) {
@ -150,8 +156,9 @@ public class MovementParkour extends Movement {
@Override
protected double calculateCost(CalculationContext context) {
MoveResult res = cost(context, src.x, src.y, src.z, direction);
if (res.destX != dest.x || res.destZ != dest.z) {
MutableMoveResult res = new MutableMoveResult();
cost(context, src.x, src.y, src.z, direction, res);
if (res.x != dest.x || res.z != dest.z) {
return COST_INF;
}
return res.cost;

View File

@ -17,24 +17,27 @@
package baritone.utils.pathing;
import static baritone.api.pathing.movement.ActionCosts.COST_INF;
import baritone.api.pathing.movement.ActionCosts;
/**
* The result of a calculated movement, with destination x, y, z, and the cost of performing the movement
*
* @author leijurv
*/
public final class MoveResult {
public static final MoveResult IMPOSSIBLE = new MoveResult(0, 0, 0, COST_INF);
public final int destX;
public final int destY;
public final int destZ;
public final double cost;
public final class MutableMoveResult {
public int x;
public int y;
public int z;
public double cost;
public MoveResult(int x, int y, int z, double cost) {
this.destX = x;
this.destY = y;
this.destZ = z;
this.cost = cost;
public MutableMoveResult() {
reset();
}
public final void reset() {
x = 0;
y = 0;
z = 0;
cost = ActionCosts.COST_INF;
}
}