diff --git a/ocraft-s2client-api/pom.xml b/ocraft-s2client-api/pom.xml
index 70ec52b0..e33a1bac 100644
--- a/ocraft-s2client-api/pom.xml
+++ b/ocraft-s2client-api/pom.xml
@@ -3,7 +3,7 @@
ocraft-s2client
com.github.ocraft
- 0.4.21-SNAPSHOT
+ 0.4.22-SNAPSHOT
4.0.0
diff --git a/ocraft-s2client-benchmark/pom.xml b/ocraft-s2client-benchmark/pom.xml
index 9ef9b2dc..23f90da3 100644
--- a/ocraft-s2client-benchmark/pom.xml
+++ b/ocraft-s2client-benchmark/pom.xml
@@ -3,7 +3,7 @@
ocraft-s2client
com.github.ocraft
- 0.4.21-SNAPSHOT
+ 0.4.22-SNAPSHOT
4.0.0
diff --git a/ocraft-s2client-bot/pom.xml b/ocraft-s2client-bot/pom.xml
index 4b04184f..5f48781a 100644
--- a/ocraft-s2client-bot/pom.xml
+++ b/ocraft-s2client-bot/pom.xml
@@ -3,7 +3,7 @@
ocraft-s2client
com.github.ocraft
- 0.4.21-SNAPSHOT
+ 0.4.22-SNAPSHOT
4.0.0
diff --git a/ocraft-s2client-bot/src/main/java/com/github/ocraft/s2client/bot/gateway/ExpansionParameters.java b/ocraft-s2client-bot/src/main/java/com/github/ocraft/s2client/bot/gateway/ExpansionParameters.java
index ffc74fdd..506b810d 100644
--- a/ocraft-s2client-bot/src/main/java/com/github/ocraft/s2client/bot/gateway/ExpansionParameters.java
+++ b/ocraft-s2client-bot/src/main/java/com/github/ocraft/s2client/bot/gateway/ExpansionParameters.java
@@ -31,7 +31,9 @@
/**
* Some nice parameters that generally work but may require tuning for certain maps.
+ * @deprecated this class is no longer used in the calculateExpansionLocations() algorithm
*/
+@Deprecated
public final class ExpansionParameters {
private final List radiuses = new ArrayList<>();
diff --git a/ocraft-s2client-bot/src/main/java/com/github/ocraft/s2client/bot/gateway/QueryInterface.java b/ocraft-s2client-bot/src/main/java/com/github/ocraft/s2client/bot/gateway/QueryInterface.java
index 9a9e8d57..83a2945b 100644
--- a/ocraft-s2client-bot/src/main/java/com/github/ocraft/s2client/bot/gateway/QueryInterface.java
+++ b/ocraft-s2client-bot/src/main/java/com/github/ocraft/s2client/bot/gateway/QueryInterface.java
@@ -39,6 +39,7 @@
import com.github.ocraft.s2client.protocol.unit.Unit;
import java.util.*;
+import java.util.stream.Collectors;
import static java.util.Arrays.asList;
@@ -117,20 +118,19 @@ public interface QueryInterface {
* optional unit tags and returns a matching array of booleans indicating if placement is possible.
*
* @param queries Placement queries.
- * @return Array of booleans indicating if placement is possible.
+ * @return List of booleans indicating if placement is possible.
*/
List placement(List queries);
/**
- * Calculates expansion locations, this call can take on the order of 100ms since it makes blocking queries to SC2
- * so call it once and cache the results.
+ * Calculates expansion locations
+ * Note: bases that are blocked by destructible rocks or small minerals are included in this list
*
- * @param debug If filled out CalculateExpansionLocations will render spheres to show what it calculated.
+ * @param debug If provided CalculateExpansionLocations will render boxes to show what it calculated.
*/
- default List calculateExpansionLocations(
- ObservationInterface observation, DebugInterface debug, ExpansionParameters parameters) {
+ default List calculateExpansionLocations(ObservationInterface observation, DebugInterface debug) {
List resources = observation.getUnits(unitInPool -> {
- Set fields = new HashSet<>(asList(
+ Set nodes = new HashSet<>(asList(
Units.NEUTRAL_MINERAL_FIELD, Units.NEUTRAL_MINERAL_FIELD750,
Units.NEUTRAL_RICH_MINERAL_FIELD, Units.NEUTRAL_RICH_MINERAL_FIELD750,
Units.NEUTRAL_PURIFIER_MINERAL_FIELD, Units.NEUTRAL_PURIFIER_MINERAL_FIELD750,
@@ -141,124 +141,97 @@ default List calculateExpansionLocations(
Units.NEUTRAL_SPACE_PLATFORM_GEYSER, Units.NEUTRAL_PURIFIER_VESPENE_GEYSER,
Units.NEUTRAL_SHAKURAS_VESPENE_GEYSER, Units.NEUTRAL_RICH_VESPENE_GEYSER
));
- return fields.contains(unitInPool.unit().getType());
+ return nodes.contains(unitInPool.unit().getType());
});
List expansionLocations = new ArrayList<>();
- Map> clusters = cluster(resources, parameters.getClusterDistance());
-
- Map querySize = new LinkedHashMap<>();
- List queries = new ArrayList<>();
- for (Map.Entry> cluster : clusters.entrySet()) {
- if (debug != null) {
- for (double r : parameters.getRadiuses()) {
- debug.debugSphereOut(cluster.getKey(), (float) r, Color.GREEN);
- }
- }
-
- // Get the required queries for this cluster.
- int queryCount = 0;
- for (double r : parameters.getRadiuses()) {
- List calculatedQueries = calculateQueries(
- r, parameters.getCircleStepSize(), cluster.getKey().toPoint2d());
- queries.addAll(calculatedQueries);
- queryCount += calculatedQueries.size();
- }
-
- querySize.put(cluster.getKey(), queryCount);
- }
-
- List results = this.placement(queries);
- int startIndex = 0;
- for (Map.Entry> cluster : clusters.entrySet()) {
- double distance = Double.MAX_VALUE;
- Point2d closest = null;
-
- // For each query for the cluster minimum distance location that is valid.
- for (int j = startIndex, e = startIndex + querySize.get(cluster.getKey()); j < e; ++j) {
- if (!results.get(j)) {
+ Map> clusters = cluster(resources, 15);
+ for (Map.Entry> cluster : clusters.entrySet()) {
+
+ Point2d basePos = cluster.getKey();
+ List nodes = cluster.getValue();
+
+ //estimate base position
+ basePos = estimateBasePos(basePos, nodes);
+
+ //adjust basePos by grid restraints on each resource node in the cluster
+ while (true) {
+ Point2d finalBasePos = basePos;
+ nodes = nodes.stream()
+ .sorted(Comparator.comparing(u -> u.unit().getPosition().toPoint2d().distance(finalBasePos)))
+ .collect(Collectors.toList());
+ Point2d adjustedPoint = pushAwayFromNodes(basePos, nodes);
+ if (adjustedPoint != null) {
+ basePos = adjustedPoint;
continue;
}
-
- Point2d p = queries.get(j).getTarget();
-
- double d = p.distance(cluster.getKey().toPoint2d());
- if (d < distance) {
- distance = d;
- closest = p;
+ adjustedPoint = pullTowardsNodes(basePos, nodes);
+ if (adjustedPoint != null) {
+ basePos = adjustedPoint;
+ continue;
}
+ break;
}
-
- if (closest != null) {
- Point expansion = Point.of(
- closest.getX(),
- closest.getY(),
- cluster.getValue().get(0).unit().getPosition().getZ());
- if (debug != null) {
- debug.debugSphereOut(expansion, 0.35f, Color.RED);
- }
-
- expansionLocations.add(expansion);
+ Point basePoint = basePos.toPoint((float)Math.ceil(observation.terrainHeight(basePos)));
+ if (debug != null) {
+ debug.debugBoxOut(basePoint.add(2.5f, 2.5f, 0), basePoint.sub(2.5f, 2.5f, 0), Color.RED);
}
-
- startIndex += querySize.get(cluster.getKey());
+ expansionLocations.add(basePoint);
}
-
return expansionLocations;
}
- default List calculateExpansionLocations(ObservationInterface observation) {
- return calculateExpansionLocations(observation, null, ExpansionParameters.preset());
- }
- default List calculateExpansionLocations(ObservationInterface observation, DebugInterface debug) {
- return calculateExpansionLocations(observation, debug, ExpansionParameters.preset());
+ /**
+ * Calculates expansion locations
+ * Note: bases that are blocked by destructible rocks or small minerals are included in this list
+ *
+ * @param debug If provided CalculateExpansionLocations will render boxes to show what it calculated.
+ * @deprecated use {@link #calculateExpansionLocations(ObservationInterface observation, DebugInterface debug)} instead.
+ */
+ @Deprecated
+ default List calculateExpansionLocations(
+ ObservationInterface observation, DebugInterface debug, ExpansionParameters parameters) {
+ return calculateExpansionLocations(observation, debug);
}
- private List calculateQueries(double radius, double stepSize, Point2d center) {
- List queries = new ArrayList<>();
-
- Point2d previousGrid = Point2d.of(Float.MAX_VALUE, Float.MAX_VALUE);
- // Find a buildable location on the circumference of the sphere
- for (double degree = 0.0; degree < 360.0; degree += stepSize) {
- Point2d point = pointOnCircle(radius, center, degree);
-
- QueryBuildingPlacement query = QueryBuildingPlacement
- .placeBuilding()
- .useAbility(Abilities.BUILD_COMMAND_CENTER)
- .on(point)
- .build();
-
- Point2d currentGrid = Point2d.of((float) Math.floor(point.getX()), (float) Math.floor(point.getY()));
-
- if (!previousGrid.equals(currentGrid)) {
- queries.add(query);
- }
-
- previousGrid = currentGrid;
- }
-
- return queries;
+ /**
+ * Calculates expansion locations
+ * Note: bases that are blocked by destructible rocks or small minerals are included in this list
+ */
+ default List calculateExpansionLocations(ObservationInterface observation) {
+ return calculateExpansionLocations(observation, null);
}
- private static Point2d pointOnCircle(double radius, Point2d center, double degree) {
- return Point2d.of(
- (float) (radius * Math.cos(Math.toRadians(degree)) + center.getX()),
- (float) (radius * Math.sin(Math.toRadians(degree)) + center.getY()));
+ /**
+ * Clusters units within some distance of each other and returns a list of them and their center of mass.
+ * @param units List of minerals and vespian geyser units
+ * @param distanceApart Range to consider as apart of the same base
+ * @return Map of minerals/gas clusters (one entry for each base)
+ */
+ static Map> cluster(List units, double distanceApart) {
+ return cluster(units, distanceApart,true);
}
+
/**
* Clusters units within some distance of each other and returns a list of them and their center of mass.
+ * @param units List of minerals and vespian geyser units
+ * @param distanceApart Range to consider as apart of the same base
+ * @param isElevationResticted if true, minerals/gas will only be apart of the same cluster if they are at the same elevation
+ * @return Map of minerals/gas clusters (one entry for each base)
*/
- static Map> cluster(List units, double distanceApart) {
- Map> clusters = new LinkedHashMap<>();
+ static Map> cluster(List units, double distanceApart, boolean isElevationResticted) {
+ Map> clusters = new LinkedHashMap<>();
for (UnitInPool u : units) {
double distance = Double.MAX_VALUE;
- Map.Entry> targetCluster = null;
+ Point2d unitPos = u.unit().getPosition().toPoint2d();
+ Map.Entry> targetCluster = null;
+
// Find the cluster this mineral patch is closest to.
- for (Map.Entry> cluster : clusters.entrySet()) {
- double d = u.unit().getPosition().distance(cluster.getKey());
- if (d < distance) {
+ for (Map.Entry> cluster : clusters.entrySet()) {
+ double d = unitPos.distance(cluster.getKey());
+ if (d < distance && (!isElevationResticted || isSameElevation(u.unit().getPosition(), cluster))) {
distance = d;
targetCluster = cluster;
}
@@ -268,23 +241,125 @@ static Map> cluster(List units, double dista
if (targetCluster == null || distance > distanceApart) {
ArrayList unitsInCluster = new ArrayList<>();
unitsInCluster.add(u);
- clusters.put(u.unit().getPosition(), unitsInCluster);
+ clusters.put(unitPos, unitsInCluster);
continue;
}
// Otherwise append to that cluster and update it's center of mass.
-
if (targetCluster.getValue() == null) {
targetCluster.setValue(new ArrayList<>());
}
targetCluster.getValue().add(u);
+ Point2d centerOfCluster = getCenterPos(targetCluster.getValue());
+ clusters.put(centerOfCluster, clusters.remove(targetCluster.getKey()));
+ }
+ return clusters;
+ }
- int size = targetCluster.getValue().size();
- Point centerOfMass = targetCluster.getKey().mul(size - 1.0f).add(u.unit().getPosition()).div(size);
- clusters.put(centerOfMass, clusters.remove(targetCluster.getKey()));
+ private static Point2d getCenterPos(List unitList) {
+ float minX, maxX, minY, maxY;
+ minX = minY = Float.MAX_VALUE;
+ maxX = maxY = 0;
+ for (UnitInPool u : unitList) {
+ Point2d p = u.unit().getPosition().toPoint2d();
+ minX = Math.min(p.getX(), minX);
+ maxX = Math.max(p.getX(), maxX);
+ minY = Math.min(p.getY(), minY);
+ maxY = Math.max(p.getY(), maxY);
}
+ return Point2d.of((minX+maxX)/2f, (minY+maxY)/2f);
+ }
- return clusters;
+ private static Point2d estimateBasePos(Point2d basePos, List nodes) {
+ for (int i=0; i<6; i++) {
+ Point2d finalBestGuess = basePos;
+ Point2d closestNodePos = nodes.stream()
+ .min(Comparator.comparing(node -> node.unit().getPosition().toPoint2d().distance(finalBestGuess))).get()
+ .unit().getPosition().toPoint2d();
+ basePos = closestNodePos.towards(basePos, 6.2f);
+ }
+ basePos = Point2d.of(basePos.getX(), basePos.getY()).toNearestHalfPoint();
+ return basePos;
+ }
+
+ private static Point2d pullTowardsNodes(Point2d basePos, List nodes) {
+ for (int i = nodes.size() - 1; i >= 0; i--) {
+ UnitInPool node = nodes.get(i);
+ Point2d nodePos = node.unit().getPosition().toPoint2d().roundToHalfPointAccuracy();
+ boolean isMineralNode = node.unit().getType().toString().contains("MINERAL");
+ float xMinDistCenter = isMineralNode ? 6.5f : 7;
+ float yMinDistCenter = isMineralNode ? 6f : 7;
+ float xMaxDist = isMineralNode ? 7.5f : 7;
+ float yMaxDist = 7;
+ float xDist = Math.abs(nodePos.getX() - basePos.getX());
+ float yDist = Math.abs(nodePos.getY() - basePos.getY());
+ if (xDist < yDist) {
+ if (xDist < xMinDistCenter && yDist > yMaxDist) {
+ return moveYFromNodeBy(basePos, nodePos, yMaxDist);
+ }
+ }
+ else {
+ if (yDist < yMinDistCenter && xDist > xMaxDist) {
+ return moveXFromNodeBy(basePos, nodePos, xMaxDist);
+ }
+ }
+ }
+ return null;
+ }
+
+ private static Point2d pushAwayFromNodes(Point2d basePos, List nodes) {
+ for (UnitInPool node : nodes) {
+ Point2d nodePos = node.unit().getPosition().toPoint2d().roundToHalfPointAccuracy();
+ boolean isMineralNode = node.unit().getType().toString().contains("MINERAL");
+ float xMinDistCenter = isMineralNode ? 6.5f : 7;
+ float xMinDistCorner = isMineralNode ? 5.5f : 6;
+ float yMinDistCenter = isMineralNode ? 6f : 7;
+ float yMinDistCorner = isMineralNode ? 5f : 6;
+ float xDist = Math.abs(nodePos.getX() - basePos.getX());
+ float yDist = Math.abs(nodePos.getY() - basePos.getY());
+ if (xDist < yDist) {
+ if (xDist < xMinDistCorner && yDist < yMinDistCenter) {
+ return moveYFromNodeBy(basePos, nodePos, yMinDistCenter);
+ }
+ else if (xDist < xMinDistCenter && yDist < yMinDistCorner) {
+ return moveYFromNodeBy(basePos, nodePos, yMinDistCorner);
+ }
+ }
+ else {
+ if (yDist < yMinDistCorner && xDist < xMinDistCenter) {
+ return moveXFromNodeBy(basePos, nodePos, xMinDistCenter);
+ }
+ else if (yDist < yMinDistCenter && xDist < xMinDistCorner) {
+ return moveXFromNodeBy(basePos, nodePos, xMinDistCorner);
+ }
+ }
+ }
+ return null;
+ }
+
+ private static Point2d moveXFromNodeBy(Point2d origin, Point2d nodePos, float distance) {
+ float newX;
+ if (origin.getX() < nodePos.getX()) {
+ newX = nodePos.getX() - distance;
+ }
+ else {
+ newX = nodePos.getX() + distance;
+ }
+ return Point2d.of(newX, origin.getY());
}
+ private static Point2d moveYFromNodeBy(Point2d origin, Point2d nodePos, float distance) {
+ float newY;
+ if (origin.getY() < nodePos.getY()) {
+ newY = nodePos.getY() - distance;
+ }
+ else {
+ newY = nodePos.getY() + distance;
+ }
+ return Point2d.of(origin.getX(), newY);
+ }
+
+ private static boolean isSameElevation(Point newNode, Map.Entry> cluster) {
+ return cluster.getValue().stream().allMatch(node -> Math.abs(node.unit().getPosition().getZ() - newNode.getZ()) < 1.2);
+ }
}
diff --git a/ocraft-s2client-bot/src/test/java/com/github/ocraft/s2client/bot/gateway/QueryInterfaceTest.java b/ocraft-s2client-bot/src/test/java/com/github/ocraft/s2client/bot/gateway/QueryInterfaceTest.java
index c3109181..4940987e 100644
--- a/ocraft-s2client-bot/src/test/java/com/github/ocraft/s2client/bot/gateway/QueryInterfaceTest.java
+++ b/ocraft-s2client-bot/src/test/java/com/github/ocraft/s2client/bot/gateway/QueryInterfaceTest.java
@@ -27,6 +27,7 @@
*/
import com.github.ocraft.s2client.protocol.spatial.Point;
+import com.github.ocraft.s2client.protocol.spatial.Point2d;
import com.github.ocraft.s2client.protocol.unit.Tag;
import org.junit.jupiter.api.Test;
@@ -46,13 +47,13 @@ void clustersUnits() {
UnitInPool unit03 = new UnitInPool(Tag.of(3L)).update(mockUnit(Point.of(0.5f, 0.1f, 0.0f)), 0, true);
UnitInPool unit04 = new UnitInPool(Tag.of(4L)).update(mockUnit(Point.of(9.5f, 5.2f, 3.1f)), 0, true);
- Map> clusters = QueryInterface.cluster(List.of(unit01, unit02, unit03, unit04), 1.0f);
+ Map> clusters = QueryInterface.cluster(List.of(unit01, unit02, unit03, unit04), 1.0f);
- Map> expectedClusters = Map.of(
- Point.of(0.4f, 0.05f, 0.1f), List.of(unit02, unit03),
- Point.of(9.75f, 5.1f, 3.05f), List.of(unit01, unit04));
+ Map> expectedClusters = Map.of(
+ Point2d.of(0.4f, 0.05f), List.of(unit02, unit03),
+ Point2d.of(9.75f, 5.1f), List.of(unit01, unit04)
+ );
assertThat(clusters).containsAllEntriesOf(expectedClusters);
-
}
}
diff --git a/ocraft-s2client-protocol/pom.xml b/ocraft-s2client-protocol/pom.xml
index 61779d84..c2fb04a5 100644
--- a/ocraft-s2client-protocol/pom.xml
+++ b/ocraft-s2client-protocol/pom.xml
@@ -3,7 +3,7 @@
ocraft-s2client
com.github.ocraft
- 0.4.21-SNAPSHOT
+ 0.4.22-SNAPSHOT
4.0.0
diff --git a/ocraft-s2client-protocol/src/main/java/com/github/ocraft/s2client/protocol/spatial/Point.java b/ocraft-s2client-protocol/src/main/java/com/github/ocraft/s2client/protocol/spatial/Point.java
index 750ab721..34425064 100644
--- a/ocraft-s2client-protocol/src/main/java/com/github/ocraft/s2client/protocol/spatial/Point.java
+++ b/ocraft-s2client-protocol/src/main/java/com/github/ocraft/s2client/protocol/spatial/Point.java
@@ -105,6 +105,10 @@ public Point add(Point pointToAdd) {
return new Point(x + pointToAdd.getX(), y + pointToAdd.getY(), z + pointToAdd.getZ());
}
+ public Point add(float addX, float addY, float addZ) {
+ return new Point(x + addX, y + addY, z + addZ);
+ }
+
public Point sub(Point pointToSubtract) {
return new Point(x - pointToSubtract.getX(), y - pointToSubtract.getY(), z - pointToSubtract.getZ());
}
@@ -151,9 +155,9 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
- int result = (x != +0.0f ? Float.floatToIntBits(x) : 0);
- result = 31 * result + (y != +0.0f ? Float.floatToIntBits(y) : 0);
- result = 31 * result + (z != +0.0f ? Float.floatToIntBits(z) : 0);
+ int result = (x != 0.0f ? Float.floatToIntBits(x) : 0);
+ result = 31 * result + (y != 0.0f ? Float.floatToIntBits(y) : 0);
+ result = 31 * result + (z != 0.0f ? Float.floatToIntBits(z) : 0);
return result;
}
diff --git a/ocraft-s2client-protocol/src/main/java/com/github/ocraft/s2client/protocol/spatial/Point2d.java b/ocraft-s2client-protocol/src/main/java/com/github/ocraft/s2client/protocol/spatial/Point2d.java
index 1c5ed0c9..d6de4b3c 100644
--- a/ocraft-s2client-protocol/src/main/java/com/github/ocraft/s2client/protocol/spatial/Point2d.java
+++ b/ocraft-s2client-protocol/src/main/java/com/github/ocraft/s2client/protocol/spatial/Point2d.java
@@ -29,6 +29,7 @@
import SC2APIProtocol.Common;
import com.github.ocraft.s2client.protocol.Sc2ApiSerializable;
import com.github.ocraft.s2client.protocol.Strings;
+import com.github.ocraft.s2client.protocol.unit.Unit;
import static com.github.ocraft.s2client.protocol.DataExtractor.tryGet;
import static com.github.ocraft.s2client.protocol.Errors.required;
@@ -110,10 +111,7 @@ public Point2d mul(float mulBy) {
}
public double distance(Point2d b) {
- float x1 = x - b.getX();
- float y1 = y - b.getY();
-
- return Math.sqrt(x1 * x1 + y1 * y1);
+ return Math.hypot(x - b.getX(), y - b.getY());
}
public float dot(Point2d b) {
@@ -124,6 +122,78 @@ public Point toPoint(float z) {
return Point.of(x, y, z);
}
+ public float getAngle(Unit b) {
+ return getAngle(b.getPosition().toPoint2d());
+ }
+ // 0 = right, pi/2 = up, pi = left, 3pi/2 = down
+ public float getAngle(Point2d b) {
+ return (float)((Math.atan2(b.getY() - y, b.getX() - x) + Math.PI*2) % (Math.PI*2));
+ }
+
+ public Point2d addDistanceByAngle(double angleInRads, float distance) {
+ return Point2d.of(
+ distance * (float)Math.cos(angleInRads) + x,
+ distance * (float)Math.sin(angleInRads) + y
+ );
+ }
+
+ public Point2d midPoint(Point2d b) {
+ return this.add(b).div(2);
+ }
+
+ public Point2d rotate(Point2d pivotPoint, double angleInRads) {
+ double sin = Math.sin(angleInRads);
+ double cos = Math.cos(angleInRads);
+
+ //subtract pivot point
+ Point2d origin = this.sub(pivotPoint);
+
+ //rotate point
+ float xNew = (float)(origin.getX() * cos - origin.getY() * sin);
+ float yNew = (float)(origin.getX() * sin + origin.getY() * cos);
+
+ //add back the pivot point
+ float x = xNew + pivotPoint.getX();
+ float y = yNew + pivotPoint.getY();
+
+ return Point2d.of(x, y);
+ }
+
+ public Point2d roundToHalfPointAccuracy() {
+ return Point2d.of(Math.round(x * 2) / 2f, Math.round(y * 2) / 2f);
+ }
+
+ //useful for 1x1, 3x3, and 5x5 structure placements
+ public Point2d toNearestHalfPoint() {
+ return Point2d.of((int)x + 0.5f, (int)y + 0.5f);
+ }
+
+ //useful for 2x2 structure placements
+ public Point2d toNearestWholePoint() {
+ return Point2d.of(Math.round(x), Math.round(y));
+ }
+
+ public Point2d towards(Unit b, float distance) {
+ return towards(b.getPosition().toPoint2d(), distance);
+ }
+
+ public Point2d towards(Point2d b, float distance) {
+ if (this.equals(b)) {
+ return b;
+ }
+ Point2d vector = unitVector(b);
+ return this.add(vector.mul(distance));
+ }
+
+ public Point2d unitVector(Point2d b) {
+ return b.sub(this).normalize();
+ }
+
+ public Point2d normalize() {
+ float length = (float)Math.hypot(x, y);
+ return this.div(length);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -137,8 +207,8 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
- int result = (x != +0.0f ? Float.floatToIntBits(x) : 0);
- result = 31 * result + (y != +0.0f ? Float.floatToIntBits(y) : 0);
+ int result = (x != 0.0f ? Float.floatToIntBits(x) : 0);
+ result = 31 * result + (y != 0.0f ? Float.floatToIntBits(y) : 0);
return result;
}
diff --git a/ocraft-s2client-sample/pom.xml b/ocraft-s2client-sample/pom.xml
index 54e5b6d4..86798d3a 100644
--- a/ocraft-s2client-sample/pom.xml
+++ b/ocraft-s2client-sample/pom.xml
@@ -3,7 +3,7 @@
ocraft-s2client
com.github.ocraft
- 0.4.21-SNAPSHOT
+ 0.4.22-SNAPSHOT
4.0.0
diff --git a/ocraft-s2client-test/pom.xml b/ocraft-s2client-test/pom.xml
index e3c184fd..0c386a9b 100644
--- a/ocraft-s2client-test/pom.xml
+++ b/ocraft-s2client-test/pom.xml
@@ -3,7 +3,7 @@
ocraft-s2client
com.github.ocraft
- 0.4.21-SNAPSHOT
+ 0.4.22-SNAPSHOT
4.0.0
diff --git a/pom.xml b/pom.xml
index 34669466..cc323309 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.github.ocraft
ocraft-s2client
pom
- 0.4.21-SNAPSHOT
+ 0.4.22-SNAPSHOT
ocraft-s2client-test