diff --git a/src/main/java/bwapi/Bullet.java b/src/main/java/bwapi/Bullet.java
index d935b78f..367bdebf 100644
--- a/src/main/java/bwapi/Bullet.java
+++ b/src/main/java/bwapi/Bullet.java
@@ -2,8 +2,6 @@
import bwapi.ClientData.BulletData;
-import java.util.Objects;
-
/**
* An object representing a bullet or missile spawned from an attack.
*
diff --git a/src/main/java/bwapi/Cache.java b/src/main/java/bwapi/Cache.java
index 1905f9f3..dfd871a1 100644
--- a/src/main/java/bwapi/Cache.java
+++ b/src/main/java/bwapi/Cache.java
@@ -1,5 +1,14 @@
package bwapi;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
class Cache {
private int frame = -1;
@@ -35,4 +44,83 @@ class BooleanCache extends Cache{}
class OrderCache extends Cache{}
class UnitTypeCache extends Cache{}
class UpgradeTypeCache extends Cache{}
-class TechTypeCache extends Cache{}
\ No newline at end of file
+class TechTypeCache extends Cache{}
+
+class ConnectedUnitCache {
+ private int lastUpdate = -1;
+ private final Map> connectedUnits = new HashMap<>();
+ private final Function condition;
+ private final Game game;
+
+ ConnectedUnitCache(final Game game, final Function condition) {
+ this.game = game;
+ this.condition = condition;
+ }
+
+ /**
+ * Lazily update connectedUnits. Only users of the calls pay for it, and only
+ * pay once per frame.
+ * Avoids previous O(n^2) implementation which would be costly for
+ * lategame carrier fights
+ */
+ List getConnected(final Unit unit) {
+ final int frame = game.getFrameCount();
+ if (lastUpdate < frame) {
+ connectedUnits.values().forEach(List::clear);
+ for (final Unit u : game.getAllUnits()) {
+ final Unit owner = condition.apply(u);
+ if (owner != null) {
+ connectedUnits.computeIfAbsent(owner, k -> new ArrayList<>()).add(u);
+ }
+ }
+ lastUpdate = frame;
+ }
+ List connected = connectedUnits.get(unit);
+ return connected == null ?
+ Collections.emptyList() :
+ Collections.unmodifiableList(connected);
+ }
+
+ void reset() {
+ lastUpdate = -1;
+ connectedUnits.clear();
+ }
+}
+
+/**
+ * Lazily filters and caches an unmodifiable List at most once per frame.
+ * Only callers of the corresponding getter pay for the filtering, and at most once per frame.
+ */
+class UnitListCache {
+ private final Game game;
+ private final Supplier> source;
+ private final Predicate filter;
+ private int lastFrame = -1;
+ private List cached = Collections.emptyList();
+
+ UnitListCache(final Game game, final Predicate filter) {
+ this(game, game::getAllUnits, filter);
+ }
+
+ UnitListCache(final Game game, final Supplier> source, final Predicate filter) {
+ this.game = game;
+ this.source = source;
+ this.filter = filter;
+ }
+
+ List get() {
+ final int frame = game.getFrameCount();
+ if (lastFrame != frame) {
+ cached = source.get().stream()
+ .filter(filter)
+ .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
+ lastFrame = frame;
+ }
+ return cached;
+ }
+
+ void reset() {
+ lastFrame = -1;
+ cached = Collections.emptyList();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/bwapi/Color.java b/src/main/java/bwapi/Color.java
index 61d666b8..2db12b34 100644
--- a/src/main/java/bwapi/Color.java
+++ b/src/main/java/bwapi/Color.java
@@ -2,7 +2,6 @@
import java.util.HashMap;
import java.util.Map;
-import java.util.Objects;
/**
* The Color object is used in drawing routines to specify the color to use.
diff --git a/src/main/java/bwapi/CommandTemp.java b/src/main/java/bwapi/CommandTemp.java
index d7836ffa..b7b3bcf1 100644
--- a/src/main/java/bwapi/CommandTemp.java
+++ b/src/main/java/bwapi/CommandTemp.java
@@ -330,7 +330,7 @@ void execute(boolean isCurrentFrame) {
case Cancel_Train_Slot:
if (command.extra != 0) {
if (eventType == EventType.Resource) {
- UnitType unitType = unit.getTrainingQueue().get(command.extra);
+ UnitType unitType = unit.getTrainingQueueAt(command.extra);
player.self().minerals.setOrAdd(unitType.mineralPrice(), frame);
player.self().gas.setOrAdd(unitType.gasPrice(), frame);
@@ -351,7 +351,7 @@ void execute(boolean isCurrentFrame) {
case Cancel_Train: {
switch (eventType) {
case Resource: {
- UnitType unitType = unit.getTrainingQueue().get(unit.getTrainingQueueCount() - 1);
+ UnitType unitType = unit.getTrainingQueueAt(unit.getTrainingQueueCount() - 1);
player.self().minerals.setOrAdd(unitType.mineralPrice(), frame);
player.self().gas.setOrAdd(unitType.gasPrice(), frame);
@@ -366,14 +366,14 @@ void execute(boolean isCurrentFrame) {
case Order: {
unit.self().trainingQueueCount.setOrAdd(-1, frame);
- UnitType unitType = unit.getTrainingQueue().get(unit.getTrainingQueueCount());
+ UnitType unitType = unit.getTrainingQueueAt(unit.getTrainingQueueCount());
player.self().supplyUsed[unitType.getRace().id]
.setOrAdd(-unitType.supplyRequired(), frame);
if (unit.getTrainingQueueCount() == 0) {
unit.self().buildType.set(UnitType.None, frame);
} else {
- UnitType ut = unit.getTrainingQueue().get(unit.getTrainingQueueCount() - 1);
+ UnitType ut = unit.getTrainingQueueAt(unit.getTrainingQueueCount() - 1);
// Actual time decreases, but we'll let it be the buildTime until latency catches up.
unit.self().remainingTrainTime.set(ut.buildTime(), frame);
unit.self().buildType.set(ut, frame);
diff --git a/src/main/java/bwapi/ConnectedUnitCache.java b/src/main/java/bwapi/ConnectedUnitCache.java
deleted file mode 100644
index cb241709..00000000
--- a/src/main/java/bwapi/ConnectedUnitCache.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package bwapi;
-
-import java.util.*;
-import java.util.function.Function;
-
-class ConnectedUnitCache {
- private int lastUpdate = -1;
- private final Map> connectedUnits = new HashMap<>();
- private final Function condition;
- private final Game game;
-
- ConnectedUnitCache(final Game game, final Function condition) {
- this.game = game;
- this.condition = condition;
- }
-
- /**
- * Lazily update connectedUnits. Only users of the calls pay for it, and only
- * pay once per frame.
- * Avoids previous O(n^2) implementation which would be costly for
- * lategame carrier fights
- */
- List getConnected(final Unit unit) {
- final int frame = game.getFrameCount();
- if (lastUpdate < frame) {
- connectedUnits.values().forEach(List::clear);
- for (final Unit u : game.getAllUnits()) {
- final Unit owner = condition.apply(u);
- if (owner != null) {
- connectedUnits.computeIfAbsent(owner, k -> new ArrayList<>()).add(u);
- }
- }
- lastUpdate = frame;
- }
- List connected = connectedUnits.get(unit);
- return connected == null ?
- Collections.emptyList() :
- Collections.unmodifiableList(connected);
- }
- void reset() {
- lastUpdate = -1;
- connectedUnits.clear();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/bwapi/Force.java b/src/main/java/bwapi/Force.java
index bc3027d9..34507150 100644
--- a/src/main/java/bwapi/Force.java
+++ b/src/main/java/bwapi/Force.java
@@ -3,7 +3,6 @@
import bwapi.ClientData.ForceData;
import java.util.List;
-import java.util.Objects;
import java.util.stream.Collectors;
/**
diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java
index 6e853aee..254623f0 100644
--- a/src/main/java/bwapi/Game.java
+++ b/src/main/java/bwapi/Game.java
@@ -42,7 +42,7 @@ public class Game {
private static final int REGION_DATA_SIZE = 5000;
- private final Set visibleUnits = new HashSet<>();
+ private final BitSet visibleUnits = new BitSet();
private List allUnits;
private final ClientData clientData;
@@ -102,6 +102,10 @@ public class Game {
final ConnectedUnitCache loadedUnitsCache = new ConnectedUnitCache(this, Unit::getTransport);
final ConnectedUnitCache interceptorsCache = new ConnectedUnitCache(this, Unit::getCarrier);
final ConnectedUnitCache larvaCache = new ConnectedUnitCache(this, Unit::getHatchery);
+ private final UnitListCache minerals = new UnitListCache(this, u -> u.getType().isMineralField());
+ private final UnitListCache geysers = new UnitListCache(this, u -> u.getType() == Resource_Vespene_Geyser);
+ private final UnitListCache neutralUnits = new UnitListCache(this, u -> u.getPlayer().equals(neutral()));
+ private final UnitListCache selfPylons = new UnitListCache(this, () -> self().getUnits(), u -> u.getType() == Protoss_Pylon);
final SideEffectQueue sideEffects = new SideEffectQueue();
@@ -155,6 +159,10 @@ void init() {
loadedUnitsCache.reset();
interceptorsCache.reset();
larvaCache.reset();
+ minerals.reset();
+ geysers.reset();
+ neutralUnits.reset();
+ selfPylons.reset();
final int forceCount = gameData().getForceCount();
forces = new Force[forceCount];
@@ -312,20 +320,24 @@ void unitCreate(final int id) {
void unitShow(final int id) {
unitCreate(id);
- visibleUnits.add(id);
+ visibleUnits.set(id);
}
void unitHide(final int id) {
- visibleUnits.remove(id);
+ visibleUnits.clear(id);
}
void onFrame(final int frame) {
if (frame > 0) {
- allUnits = visibleUnits.stream()
- .map(i -> units[i])
- .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
+ final List visible = new ArrayList<>(visibleUnits.cardinality());
+ for (int i = visibleUnits.nextSetBit(0); i >= 0; i = visibleUnits.nextSetBit(i + 1)) {
+ visible.add(units[i]);
+ }
+ allUnits = Collections.unmodifiableList(visible);
+ }
+ for (final Unit u : getAllUnits()) {
+ u.updatePosition(frame);
}
- getAllUnits().forEach(u -> u.updatePosition(frame));
updatePlayers();
}
@@ -368,9 +380,7 @@ public List getAllUnits() {
* @return List containing @minerals
*/
public List getMinerals() {
- return getAllUnits().stream()
- .filter(u -> u.getType().isMineralField())
- .collect(Collectors.toList());
+ return minerals.get();
}
/**
@@ -379,9 +389,7 @@ public List getMinerals() {
* @return List containing @geysers
*/
public List getGeysers() {
- return getAllUnits().stream()
- .filter(u -> u.getType() == Resource_Vespene_Geyser)
- .collect(Collectors.toList());
+ return geysers.get();
}
/**
@@ -391,9 +399,7 @@ public List getGeysers() {
* @return List containing all neutral units.
*/
public List getNeutralUnits() {
- return getAllUnits().stream()
- .filter(u -> u.getPlayer().equals(neutral()))
- .collect(Collectors.toList());
+ return neutralUnits.get();
}
/**
@@ -690,16 +696,16 @@ public List getUnitsOnTile(final int tileX, final int tileY) {
* given build tile.
*/
public List getUnitsOnTile(final int tileX, final int tileY, final UnitFilter pred) {
- final TilePosition tile = new TilePosition(tileX, tileY);
- if (!tile.isValid(this)) {
+ final int x = tileX * TilePosition.SIZE_IN_PIXELS;
+ final int y = tileY * TilePosition.SIZE_IN_PIXELS;
+ if (tileX < 0 || tileY < 0 || x >= mapPixelWidth() || y >= mapPixelHeight()) {
return Collections.emptyList();
}
- final Position p = tile.toPosition();
- return getUnitsInRectangle(p.x, p.y, p.x + 32, p.y + 32, pred);
+ return getUnitsInRectangle(x, y, x + 32, y + 32, pred);
}
public List getUnitsInRectangle(final int left, final int top, final int right, final int bottom) {
- return getUnitsInRectangle(left, top, right, bottom, u -> true);
+ return getUnitsInRectangle(left, top, right, bottom, null);
}
/**
@@ -714,13 +720,17 @@ public List getUnitsInRectangle(final int left, final int top, final int r
* given rectangle bounds.
*/
public List getUnitsInRectangle(final int left, final int top, final int right, final int bottom, final UnitFilter pred) {
- return getAllUnits().stream()
- .filter(u -> left <= u.getRight() && top <= u.getBottom() && right >= u.getLeft() && bottom >= u.getTop() && pred.test(u))
- .collect(Collectors.toList());
+ final List result = new ArrayList<>();
+ for (final Unit u : getAllUnits()) {
+ if (Unit.overlapsRectangle(u, left, top, right, bottom) && (pred == null || pred.test(u))) {
+ result.add(u);
+ }
+ }
+ return result;
}
public List getUnitsInRectangle(final Position leftTop, final Position rightBottom) {
- return getUnitsInRectangle(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y, u -> true);
+ return getUnitsInRectangle(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y, null);
}
public List getUnitsInRectangle(final Position leftTop, final Position rightBottom, final UnitFilter pred) {
@@ -1018,7 +1028,7 @@ public boolean hasPowerPrecise(final int x, final int y) {
* @return true if the type at the given position will have power, false if the type at the given position will be unpowered.
*/
public boolean hasPowerPrecise(final int x, final int y, final UnitType unitType) {
- return isValidPosition(x, y) && hasPower(x, y, unitType, self().getUnits().stream().filter(u -> u.getType() == Protoss_Pylon).collect(Collectors.toList()));
+ return isValidPosition(x, y) && hasPower(x, y, unitType, selfPylons.get());
}
public boolean hasPowerPrecise(final Position position) {
@@ -1103,13 +1113,13 @@ public boolean canBuildHere(final TilePosition position, final UnitType type) {
*/
public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder, final boolean checkExplored) {
// lt = left top, rb = right bottom
- final TilePosition lt = builder != null && type.isAddon() ?
- position.add(new TilePosition(4, 1)) : // addon build offset
- position;
- final TilePosition rb = lt.add(type.tileSize());
+ final int ltX = builder != null && type.isAddon() ? position.x + 4 : position.x;
+ final int ltY = builder != null && type.isAddon() ? position.y + 1 : position.y;
+ final int rbX = ltX + type.tileWidth();
+ final int rbY = ltY + type.tileHeight();
// Map limit check
- if (!lt.isValid(this) || !(rb.toPosition().subtract(new Position(1, 1)).isValid(this))) {
+ if (!isValidTile(ltX, ltY) || !isValidPosition(rbX * TilePosition.SIZE_IN_PIXELS - 1, rbY * TilePosition.SIZE_IN_PIXELS - 1)) {
return false;
}
@@ -1117,7 +1127,8 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi
//matches one of them (and the type is still vespene geyser)
if (type.isRefinery()) {
for (final Unit g : getGeysers()) {
- if (g.getTilePosition().equals(lt)) {
+ final TilePosition tp = g.getTilePosition();
+ if (tp.x == ltX && tp.y == ltY) {
return !g.isVisible() || g.getType() == Resource_Vespene_Geyser;
}
}
@@ -1125,8 +1136,8 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi
}
// Tile buildability check
- for (int x = lt.x; x < rb.x; ++x) {
- for (int y = lt.y; y < rb.y; ++y) {
+ for (int x = ltX; x < rbX; ++x) {
+ for (int y = ltY; y < rbY; ++y) {
// Check if tile is buildable/unoccupied and explored.
if (!isBuildable(x, y) || (checkExplored && !isExplored(x, y))) {
return false;
@@ -1134,29 +1145,39 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi
}
}
+ final int targPosX = ltX * TilePosition.SIZE_IN_PIXELS + (type.tileWidth() * TilePosition.SIZE_IN_PIXELS) / 2;
+ final int targPosY = ltY * TilePosition.SIZE_IN_PIXELS + (type.tileHeight() * TilePosition.SIZE_IN_PIXELS) / 2;
+
// Check if builder is capable of reaching the building site
if (builder != null) {
- if (!builder.getType().isBuilding()) {
- if (!builder.hasPath(lt.toPosition().add(type.tileSize().toPosition().divide(2)))) {
+ final UnitType builderType = builder.getType();
+ if (!builderType.isBuilding()) {
+ if (!builder.hasPath(new Position(targPosX, targPosY))) {
return false;
}
- } else if (!builder.getType().isFlyingBuilding() && type != Zerg_Nydus_Canal && !type.isFlagBeacon()) {
+ } else if (!builderType.isFlyingBuilding() && type != Zerg_Nydus_Canal && !type.isFlagBeacon()) {
return false;
}
}
// Ground getUnit dimension check
if (type != Special_Start_Location) {
- final Position targPos = lt.toPosition().add(type.tileSize().toPosition().divide(2));
- final List unitsInRect = getUnitsInRectangle(
- targPos.subtract(new Position(type.dimensionLeft(), type.dimensionUp())),
- targPos.add(new Position(type.dimensionRight(), type.dimensionDown())),
- u -> !u.isFlying() && !u.isLoaded() && (builder != u || type == Zerg_Nydus_Canal));
-
- for (final Unit u : unitsInRect) {
- // Addons can be placed over units that can move, pushing them out of the way
- if (!(type.isAddon() && u.getType().canMove())) {
- return false;
+ final int rectLeft = targPosX - type.dimensionLeft();
+ final int rectTop = targPosY - type.dimensionUp();
+ final int rectRight = targPosX + type.dimensionRight();
+ final int rectBottom = targPosY + type.dimensionDown();
+ final boolean isAddon = type.isAddon();
+ final boolean isNydus = type == Zerg_Nydus_Canal;
+
+ for (final Unit u : getAllUnits()) {
+ if (Unit.overlapsRectangle(u, rectLeft, rectTop, rectRight, rectBottom)
+ && !u.isFlying()
+ && !u.isLoaded()
+ && (builder != u || isNydus)) {
+ // Addons can be placed over units that can move, pushing them out of the way
+ if (!(isAddon && u.getType().canMove())) {
+ return false;
+ }
}
}
@@ -1164,8 +1185,8 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi
// Note: Zerg structures that don't require creep can still be placed on creep
final boolean needsCreep = type.requiresCreep();
if (type.getRace() != Zerg || needsCreep) {
- for (int x = lt.x; x < rb.x; ++x) {
- for (int y = lt.y; y < rb.y; ++y) {
+ for (int x = ltX; x < rbX; ++x) {
+ for (int y = ltY; y < rbY; ++y) {
if (needsCreep != hasCreep(x, y)) {
return false;
}
@@ -1174,7 +1195,7 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi
}
// Power Check
- if (type.requiresPsi() && !hasPower(lt, type)) {
+ if (type.requiresPsi() && !hasPower(ltX, ltY, type)) {
return false;
}
@@ -1187,19 +1208,19 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi
if ((isVisible(tp) || isVisible(tp.x + 1, tp.y)) && !m.exists()) {
continue; // tile position is visible, but mineral is not => mineral does not exist
}
- if (tp.x > lt.x - 5 &&
- tp.y > lt.y - 4 &&
- tp.x < lt.x + 7 &&
- tp.y < lt.y + 6) {
+ if (tp.x > ltX - 5 &&
+ tp.y > ltY - 4 &&
+ tp.x < ltX + 7 &&
+ tp.y < ltY + 6) {
return false;
}
}
for (final Unit g : getStaticGeysers()) {
final TilePosition tp = g.getInitialTilePosition();
- if (tp.x > lt.x - 7 &&
- tp.y > lt.y - 5 &&
- tp.x < lt.x + 7 &&
- tp.y < lt.y + 6) {
+ if (tp.x > ltX - 7 &&
+ tp.y > ltY - 5 &&
+ tp.x < ltX + 7 &&
+ tp.y < ltY + 6) {
return false;
}
}
@@ -1209,7 +1230,7 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi
// then lands at the new location before building the addon), so we need to do similar checks for the
// location that the building will be when it builds the addon.
if (builder != null && !builder.getType().isAddon() && type.isAddon()) {
- return canBuildHere(lt.subtract(new TilePosition(4, 1)), builder.getType(), builder, checkExplored);
+ return canBuildHere(new TilePosition(ltX - 4, ltY - 1), builder.getType(), builder, checkExplored);
}
//if the build site passes all these tests, return true.
@@ -1282,7 +1303,7 @@ public boolean canMake(final UnitType type, final Unit builder) {
}
// Check if there is room
- if (builder.getInterceptorCount() + builder.getTrainingQueue().size() >= max_amt) {
+ if (builder.getInterceptorCount() + builder.getTrainingQueueCount() >= max_amt) {
return false;
}
break;
@@ -1295,7 +1316,7 @@ public boolean canMake(final UnitType type, final Unit builder) {
}
// check if there is room
- if (builder.getScarabCount() + builder.getTrainingQueue().size() >= max_amt) {
+ if (builder.getScarabCount() + builder.getTrainingQueueCount() >= max_amt) {
return false;
}
break;
diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java
index b296c93f..c2f7ff0a 100644
--- a/src/main/java/bwapi/PerformanceMetric.java
+++ b/src/main/java/bwapi/PerformanceMetric.java
@@ -1,7 +1,5 @@
package bwapi;
-import com.sun.jna.platform.win32.Kernel32;
-
import java.text.DecimalFormat;
import java.util.ArrayList;
@@ -9,7 +7,7 @@
* Aggregates labeled time series data.
*/
public class PerformanceMetric {
- public class RunningTotal {
+ public static class RunningTotal {
private int samples = 0;
private double last = 0d;
private double mean = 0d;
diff --git a/src/main/java/bwapi/Player.java b/src/main/java/bwapi/Player.java
index e2d8710b..283d57f1 100644
--- a/src/main/java/bwapi/Player.java
+++ b/src/main/java/bwapi/Player.java
@@ -3,8 +3,6 @@
import bwapi.ClientData.PlayerData;
import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
import static bwapi.UnitType.*;
import static bwapi.UpgradeType.*;
@@ -28,7 +26,9 @@ public class Player implements Comparable {
private final PlayerType playerType;
private final TilePosition startLocation;
private final Color color;
+ private final UnitListCache units;
private PlayerSelf self = null;
+
PlayerSelf self() {
if (self == null) {
self = new PlayerSelf();
@@ -36,6 +36,10 @@ PlayerSelf self() {
return self;
}
+ boolean hasLatCom() {
+ return self != null && game.isLatComEnabled();
+ }
+
Player(final PlayerData playerData, final int id, final Game game) {
this.playerData = playerData;
this.game = game;
@@ -44,6 +48,7 @@ PlayerSelf self() {
this.playerType = PlayerType.idToEnum[playerData.getType()];
this.startLocation = new TilePosition(playerData.getStartLocationX(), playerData.getStartLocationY());
this.color = new Color(playerData.getColor());
+ this.units = new UnitListCache(game, u -> equals(u.getPlayer()));
}
/**
@@ -77,9 +82,7 @@ public String getName() {
* This does not include units that are loaded into transports, @Bunkers, @Refineries, @Assimilators, or @Extractors.
*/
public List getUnits() {
- return game.getAllUnits().stream()
- .filter(u -> equals(u.getPlayer()))
- .collect(Collectors.toList());
+ return units.get();
}
/**
@@ -208,8 +211,8 @@ public boolean leftGame() {
*/
public int minerals() {
int minerals = playerData.getMinerals();
- if (game.isLatComEnabled() && self().minerals.valid(game.getFrameCount())) {
- return minerals + self().minerals.get();
+ if (hasLatCom() && self.minerals.valid(game.getFrameCount())) {
+ return minerals + self.minerals.get();
}
return minerals;
}
@@ -223,8 +226,8 @@ public int minerals() {
*/
public int gas() {
int gas = playerData.getGas();
- if (game.isLatComEnabled() && self().gas.valid(game.getFrameCount())) {
- return gas + self().gas.get();
+ if (hasLatCom() && self.gas.valid(game.getFrameCount())) {
+ return gas + self.gas.get();
}
return gas;
}
@@ -367,8 +370,8 @@ public int supplyUsed(Race race) {
return 0;
}
int supplyUsed = playerData.getSupplyUsed(race.id);
- if (game.isLatComEnabled() && self().supplyUsed[race.id].valid(game.getFrameCount())) {
- return supplyUsed + self().supplyUsed[race.id].get();
+ if (hasLatCom() && self.supplyUsed[race.id].valid(game.getFrameCount())) {
+ return supplyUsed + self.supplyUsed[race.id].get();
}
return supplyUsed;
}
@@ -519,8 +522,8 @@ public boolean hasResearched(final TechType tech) {
* @see #hasResearched
*/
public boolean isResearching(final TechType tech) {
- if (game.isLatComEnabled() && self().isResearching[tech.id].valid(game.getFrameCount())) {
- return self().isResearching[tech.id].get();
+ if (hasLatCom() && self.isResearching[tech.id].valid(game.getFrameCount())) {
+ return self.isResearching[tech.id].get();
}
return playerData.isResearching(tech.id);
}
@@ -533,8 +536,8 @@ public boolean isResearching(final TechType tech) {
* @see Unit#upgrade
*/
public boolean isUpgrading(final UpgradeType upgrade) {
- if (game.isLatComEnabled() && self().isUpgrading[upgrade.id].valid(game.getFrameCount())) {
- return self().isUpgrading[upgrade.id].get();
+ if (hasLatCom() && self.isUpgrading[upgrade.id].valid(game.getFrameCount())) {
+ return self.isUpgrading[upgrade.id].get();
}
return playerData.isUpgrading(upgrade.id);
}
diff --git a/src/main/java/bwapi/Point.java b/src/main/java/bwapi/Point.java
index 28bff09c..50f50f92 100644
--- a/src/main/java/bwapi/Point.java
+++ b/src/main/java/bwapi/Point.java
@@ -35,7 +35,7 @@ public double getDistance(T point) {
return Math.sqrt(dx * dx + dy * dy);
}
- private static int getApproxDistance(final int x1, final int y1, final int x2, final int y2) {
+ static int getApproxDistance(final int x1, final int y1, final int x2, final int y2) {
int max = Math.abs(x1 - x2);
int min = Math.abs(y1 - y2);
if (max < min) {
diff --git a/src/main/java/bwapi/Region.java b/src/main/java/bwapi/Region.java
index 25bbb83f..d8543755 100644
--- a/src/main/java/bwapi/Region.java
+++ b/src/main/java/bwapi/Region.java
@@ -6,7 +6,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
/**
* Region objects are created by Starcraft: Broodwar to contain several tiles with the same
diff --git a/src/main/java/bwapi/TechType.java b/src/main/java/bwapi/TechType.java
index 23e549bb..9b1e0507 100644
--- a/src/main/java/bwapi/TechType.java
+++ b/src/main/java/bwapi/TechType.java
@@ -187,8 +187,13 @@ public enum TechType {
static final TechType[] idToEnum = new TechType[46 + 1];
+ private List whatUses;
+
static {
- Arrays.stream(TechType.values()).forEach(v -> idToEnum[v.id] = v);
+ for (final TechType v : TechType.values()) {
+ idToEnum[v.id] = v;
+ v.whatUses = Collections.unmodifiableList(Arrays.asList(techWhatUses[v.id]));
+ }
}
public final int id;
@@ -294,7 +299,7 @@ public boolean targetsPosition() {
* @return Set of UnitTypes that can use this ability when researched.
*/
public List whatUses() {
- return Collections.unmodifiableList(Arrays.asList(techWhatUses[id]));
+ return whatUses;
}
/**
diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java
index 53a3a0da..66278573 100644
--- a/src/main/java/bwapi/Unit.java
+++ b/src/main/java/bwapi/Unit.java
@@ -59,8 +59,11 @@ public class Unit implements Comparable {
private int lastCommandFrame;
private UnitCommand lastCommand;
- // Don't make non-latcom users pay for latcom in memory usage
+ // Don't make non-latcom users (or uncommanded/enemy/neutral units) pay for latcom in memory usage.
+ // self() is only called by CommandTemp when mutating latency-compensated state; getters check hasLatCom()
+ // so we don't allocate 37 Cache objects per unit merely by reading unit properties.
private UnitSelf self = null;
+
UnitSelf self() {
if (self == null) {
self = new UnitSelf();
@@ -68,12 +71,17 @@ UnitSelf self() {
return self;
}
+ boolean hasLatCom() {
+ return self != null && game.isLatComEnabled();
+ }
+
Unit(final UnitData unitData, int id, final Game game) {
this.unitData = unitData;
this.game = game;
- updatePosition(0);
+ position = new Position(unitData.getPositionX(), unitData.getPositionY());
+ lastPositionUpdate = 0;
initialType = getType();
initialResources = getResources();
@@ -153,8 +161,8 @@ public Player getPlayer() {
* @see #getInitialType
*/
public UnitType getType() {
- if (game.isLatComEnabled() && self().type.valid(game.getFrameCount())) {
- return self().type.get();
+ if (hasLatCom() && self.type.valid(game.getFrameCount())) {
+ return self.type.get();
}
return UnitType.idToEnum[unitData.getType()];
}
@@ -310,8 +318,8 @@ public int getBottom() {
*/
public int getHitPoints() {
int hitpoints = unitData.getHitPoints();
- if (game.isLatComEnabled() && self().hitPoints.valid(game.getFrameCount())) {
- return hitpoints + self().hitPoints.get();
+ if (hasLatCom() && self.hitPoints.valid(game.getFrameCount())) {
+ return hitpoints + self.hitPoints.get();
}
return hitpoints;
}
@@ -337,8 +345,8 @@ public int getShields() {
*/
public int getEnergy() {
int energy = unitData.getEnergy();
- if (game.isLatComEnabled() && self().energy.valid(game.getFrameCount())) {
- return energy + self().energy.get();
+ if (hasLatCom() && self.energy.valid(game.getFrameCount())) {
+ return energy + self.energy.get();
}
return energy;
}
@@ -388,26 +396,41 @@ public int getDistance(final Position target) {
}
/////// Compute distance
+ final UnitType type = getType();
+ final Position pos = this.position != null ? this.position : getPosition();
+ final int left, right, top, bottom;
+ if (type != null && pos != null) {
+ left = pos.x - type.dimensionLeft();
+ right = pos.x + type.dimensionRight();
+ top = pos.y - type.dimensionUp();
+ bottom = pos.y + type.dimensionDown();
+ } else {
+ left = getLeft();
+ right = getRight();
+ top = getTop();
+ bottom = getBottom();
+ }
+
// compute x distance
- int xDist = getLeft() - target.x;
+ int xDist = left - target.x;
if (xDist < 0) {
- xDist = target.x - (getRight() + 1);
+ xDist = target.x - (right + 1);
if (xDist < 0) {
xDist = 0;
}
}
// compute y distance
- int yDist = getTop() - target.y;
+ int yDist = top - target.y;
if (yDist < 0) {
- yDist = target.y - (getBottom() + 1);
+ yDist = target.y - (bottom + 1);
if (yDist < 0) {
yDist = 0;
}
}
// compute actual distance
- return Position.Origin.getApproxDistance(new Position(xDist, yDist));
+ return Point.getApproxDistance(0, 0, xDist, yDist);
}
public int getDistance(final Unit target) {
@@ -423,32 +446,57 @@ public int getDistance(final Unit target) {
/////// Compute distance
+ final UnitType thisType = getType();
+ final Position thisPos = this.position != null ? this.position : getPosition();
+ final int thisLeft, thisRight, thisTop, thisBottom;
+ if (thisType != null && thisPos != null) {
+ thisLeft = thisPos.x - thisType.dimensionLeft();
+ thisRight = thisPos.x + thisType.dimensionRight();
+ thisTop = thisPos.y - thisType.dimensionUp();
+ thisBottom = thisPos.y + thisType.dimensionDown();
+ } else {
+ thisLeft = getLeft();
+ thisRight = getRight();
+ thisTop = getTop();
+ thisBottom = getBottom();
+ }
+
// retrieve left/top/right/bottom values for calculations
- final int left = target.getLeft() - 1;
- final int top = target.getTop() - 1;
- final int right = target.getRight() + 1;
- final int bottom = target.getBottom() + 1;
+ final UnitType targetType = target.getType();
+ final Position targetPos = target.position != null ? target.position : target.getPosition();
+ final int left, top, right, bottom;
+ if (targetType != null && targetPos != null) {
+ left = targetPos.x - targetType.dimensionLeft() - 1;
+ top = targetPos.y - targetType.dimensionUp() - 1;
+ right = targetPos.x + targetType.dimensionRight() + 1;
+ bottom = targetPos.y + targetType.dimensionDown() + 1;
+ } else {
+ left = target.getLeft() - 1;
+ top = target.getTop() - 1;
+ right = target.getRight() + 1;
+ bottom = target.getBottom() + 1;
+ }
// compute x distance
- int xDist = getLeft() - right;
+ int xDist = thisLeft - right;
if (xDist < 0) {
- xDist = left - getRight();
+ xDist = left - thisRight;
if (xDist < 0) {
xDist = 0;
}
}
// compute y distance
- int yDist = getTop() - bottom;
+ int yDist = thisTop - bottom;
if (yDist < 0) {
- yDist = top - getBottom();
+ yDist = top - thisBottom;
if (yDist < 0) {
yDist = 0;
}
}
// compute actual distance
- return Position.Origin.getApproxDistance(new Position(xDist, yDist));
+ return Point.getApproxDistance(0, 0, xDist, yDist);
}
/**
@@ -804,8 +852,8 @@ public int getStasisTimer() {
* @see #isPlagued
*/
public int getStimTimer() {
- if (game.isLatComEnabled() && self().stimTimer.valid(game.getFrameCount())) {
- return self().stimTimer.get();
+ if (hasLatCom() && self.stimTimer.valid(game.getFrameCount())) {
+ return self.stimTimer.get();
}
return unitData.getStimTimer();
}
@@ -819,8 +867,8 @@ public int getStimTimer() {
* incomplete unit will be when completed.
*/
public UnitType getBuildType() {
- if (game.isLatComEnabled() && self().buildType.valid(game.getFrameCount())) {
- return self().buildType.get();
+ if (hasLatCom() && self.buildType.valid(game.getFrameCount())) {
+ return self.buildType.get();
}
return UnitType.idToEnum[unitData.getBuildType()];
}
@@ -845,8 +893,8 @@ public List getTrainingQueue() {
* It exists as a more performant alternative to getTrainingQueue().
*/
public UnitType getTrainingQueueAt(int i) {
- if (game.isLatComEnabled() && self().trainingQueue[i].valid(game.getFrameCount())) {
- return self().trainingQueue[i].get();
+ if (hasLatCom() && self.trainingQueue[i].valid(game.getFrameCount())) {
+ return self.trainingQueue[i].get();
}
return UnitType.idToEnum[unitData.getTrainingQueue(i)];
}
@@ -859,8 +907,8 @@ public UnitType getTrainingQueueAt(int i) {
*/
public int getTrainingQueueCount() {
int count = unitData.getTrainingQueueCount();
- if (game.isLatComEnabled() && self().trainingQueueCount.valid(game.getFrameCount())) {
- return count + self().trainingQueueCount.get();
+ if (hasLatCom() && self.trainingQueueCount.valid(game.getFrameCount())) {
+ return count + self.trainingQueueCount.get();
}
return count;
}
@@ -876,8 +924,8 @@ public int getTrainingQueueCount() {
* @see #getRemainingResearchTime
*/
public TechType getTech() {
- if (game.isLatComEnabled() && self().tech.valid(game.getFrameCount())) {
- return self().tech.get();
+ if (hasLatCom() && self.tech.valid(game.getFrameCount())) {
+ return self.tech.get();
}
return TechType.idToEnum[unitData.getTech()];
}
@@ -893,8 +941,8 @@ public TechType getTech() {
* @see #getRemainingUpgradeTime
*/
public UpgradeType getUpgrade() {
- if (game.isLatComEnabled() && self().upgrade.valid(game.getFrameCount())) {
- return self().upgrade.get();
+ if (hasLatCom() && self.upgrade.valid(game.getFrameCount())) {
+ return self.upgrade.get();
}
return UpgradeType.idToEnum[unitData.getUpgrade()];
}
@@ -906,8 +954,8 @@ public UpgradeType getUpgrade() {
* @return Number of frames remaining until the unit's completion.
*/
public int getRemainingBuildTime() {
- if (game.isLatComEnabled() && self().remainingBuildTime.valid(game.getFrameCount())) {
- return self().remainingBuildTime.get();
+ if (hasLatCom() && self.remainingBuildTime.valid(game.getFrameCount())) {
+ return self.remainingBuildTime.get();
}
return unitData.getRemainingBuildTime();
}
@@ -927,8 +975,8 @@ public int getRemainingBuildTime() {
* @see #getTrainingQueue
*/
public int getRemainingTrainTime() {
- if (game.isLatComEnabled() && self().remainingTrainTime.valid(game.getFrameCount())) {
- return self().remainingTrainTime.get();
+ if (hasLatCom() && self.remainingTrainTime.valid(game.getFrameCount())) {
+ return self.remainingTrainTime.get();
}
return unitData.getRemainingTrainTime();
}
@@ -946,8 +994,8 @@ public int getRemainingTrainTime() {
* @see #getTech
*/
public int getRemainingResearchTime() {
- if (game.isLatComEnabled() && self().remainingResearchTime.valid(game.getFrameCount())) {
- return self().remainingResearchTime.get();
+ if (hasLatCom() && self.remainingResearchTime.valid(game.getFrameCount())) {
+ return self.remainingResearchTime.get();
}
return unitData.getRemainingResearchTime();
}
@@ -963,8 +1011,8 @@ public int getRemainingResearchTime() {
* @see #getUpgrade
*/
public int getRemainingUpgradeTime() {
- if (game.isLatComEnabled() && self().remainingUpgradeTime.valid(game.getFrameCount())) {
- return self().remainingUpgradeTime.get();
+ if (hasLatCom() && self.remainingUpgradeTime.valid(game.getFrameCount())) {
+ return self.remainingUpgradeTime.get();
}
return unitData.getRemainingUpgradeTime();
}
@@ -985,8 +1033,8 @@ public int getRemainingUpgradeTime() {
* another unit.
*/
public Unit getBuildUnit() {
- if (game.isLatComEnabled() && self().buildUnit.valid(game.getFrameCount())) {
- return game.getUnit(self().buildUnit.get());
+ if (hasLatCom() && self.buildUnit.valid(game.getFrameCount())) {
+ return game.getUnit(self.buildUnit.get());
}
return game.getUnit(unitData.getBuildUnit());
}
@@ -1000,8 +1048,8 @@ public Unit getBuildUnit() {
* @see #getOrderTarget
*/
public Unit getTarget() {
- if (game.isLatComEnabled() && self().target.valid(game.getFrameCount())) {
- return game.getUnit(self().target.get());
+ if (hasLatCom() && self.target.valid(game.getFrameCount())) {
+ return game.getUnit(self.target.get());
}
return game.getUnit(unitData.getTarget());
}
@@ -1013,8 +1061,8 @@ public Unit getTarget() {
* @return Target position of a movement action.
*/
public Position getTargetPosition() {
- if (game.isLatComEnabled() && self().targetPositionX.valid(game.getFrameCount())) {
- return new Position(self().targetPositionX.get(), self().targetPositionY.get());
+ if (hasLatCom() && self.targetPositionX.valid(game.getFrameCount())) {
+ return new Position(self.targetPositionX.get(), self.targetPositionY.get());
}
return new Position(unitData.getTargetPositionX(), unitData.getTargetPositionY());
}
@@ -1026,8 +1074,8 @@ public Position getTargetPosition() {
* @return The primary {@link Order} that the unit is executing.
*/
public Order getOrder() {
- if (game.isLatComEnabled() && self().order.valid(game.getFrameCount())) {
- return self().order.get();
+ if (hasLatCom() && self.order.valid(game.getFrameCount())) {
+ return self.order.get();
}
return Order.idToEnum[unitData.getOrder()];
}
@@ -1040,8 +1088,8 @@ public Order getOrder() {
* @return The secondary {@link Order} that the unit is executing.
*/
public Order getSecondaryOrder() {
- if (game.isLatComEnabled() && self().secondaryOrder.valid(game.getFrameCount())) {
- return self().secondaryOrder.get();
+ if (hasLatCom() && self.secondaryOrder.valid(game.getFrameCount())) {
+ return self.secondaryOrder.get();
}
return Order.idToEnum[unitData.getSecondaryOrder()];
}
@@ -1057,8 +1105,8 @@ public Order getSecondaryOrder() {
* @see #getOrder
*/
public Unit getOrderTarget() {
- if (game.isLatComEnabled() && self().orderTarget.valid(game.getFrameCount())) {
- return game.getUnit(self().orderTarget.get());
+ if (hasLatCom() && self.orderTarget.valid(game.getFrameCount())) {
+ return game.getUnit(self.orderTarget.get());
}
return game.getUnit(unitData.getOrderTarget());
}
@@ -1073,8 +1121,8 @@ public Unit getOrderTarget() {
* @see #getOrder
*/
public Position getOrderTargetPosition() {
- if (game.isLatComEnabled() && self().orderTargetPositionX.valid(game.getFrameCount())) {
- return new Position(self().orderTargetPositionX.get(), self().orderTargetPositionY.get());
+ if (hasLatCom() && self.orderTargetPositionX.valid(game.getFrameCount())) {
+ return new Position(self.orderTargetPositionX.get(), self.orderTargetPositionY.get());
}
return new Position(unitData.getOrderTargetPositionX(), unitData.getOrderTargetPositionY());
}
@@ -1091,8 +1139,8 @@ public Position getOrderTargetPosition() {
* @see #getRallyUnit
*/
public Position getRallyPosition() {
- if (game.isLatComEnabled() && self().rallyPositionX.valid(game.getFrameCount())) {
- return new Position(self().rallyPositionX.get(), self().rallyPositionY.get());
+ if (hasLatCom() && self.rallyPositionX.valid(game.getFrameCount())) {
+ return new Position(self.rallyPositionX.get(), self.rallyPositionY.get());
}
return new Position(unitData.getRallyPositionX(), unitData.getRallyPositionY());
}
@@ -1110,8 +1158,8 @@ public Position getRallyPosition() {
* @see #getRallyPosition
*/
public Unit getRallyUnit() {
- if (game.isLatComEnabled() && self().rallyUnit.valid(game.getFrameCount())) {
- return game.getUnit(self().rallyUnit.get());
+ if (hasLatCom() && self.rallyUnit.valid(game.getFrameCount())) {
+ return game.getUnit(self.rallyUnit.get());
}
return game.getUnit(unitData.getRallyUnit());
}
@@ -1527,8 +1575,8 @@ public boolean isCloaked() {
* @return true if this unit is completed, and false if it is under construction or inaccessible.
*/
public boolean isCompleted() {
- if (game.isLatComEnabled() && self().isCompleted.valid(game.getFrameCount())) {
- return self().isCompleted.get();
+ if (hasLatCom() && self.isCompleted.valid(game.getFrameCount())) {
+ return self.isCompleted.get();
}
return unitData.isCompleted();
}
@@ -1544,8 +1592,8 @@ public boolean isCompleted() {
* @see #haltConstruction
*/
public boolean isConstructing() {
- if (game.isLatComEnabled() && self().isConstructing.valid(game.getFrameCount())) {
- return self().isConstructing.get();
+ if (hasLatCom() && self.isConstructing.valid(game.getFrameCount())) {
+ return self.isConstructing.get();
}
return unitData.isConstructing();
}
@@ -1607,8 +1655,8 @@ public boolean isFollowing() {
boolean isGathering() {
- if (game.isLatComEnabled() && self().isGathering.valid(game.getFrameCount())) {
- return self().isGathering.get();
+ if (hasLatCom() && self.isGathering.valid(game.getFrameCount())) {
+ return self.isGathering.get();
}
return unitData.isGathering();
}
@@ -1722,8 +1770,8 @@ public boolean isHoldingPosition() {
* @see Unit#stop
*/
public boolean isIdle() {
- if (game.isLatComEnabled() && self().isIdle.valid(game.getFrameCount())) {
- return self().isIdle.get();
+ if (hasLatCom() && self.isIdle.valid(game.getFrameCount())) {
+ return self.isIdle.get();
}
return unitData.isIdle();
}
@@ -1848,8 +1896,8 @@ public boolean isMaelstrommed() {
* @see #getRemainingBuildTime
*/
public boolean isMorphing() {
- if (game.isLatComEnabled() && self().isMorphing.valid(game.getFrameCount())) {
- return self().isMorphing.get();
+ if (hasLatCom() && self.isMorphing.valid(game.getFrameCount())) {
+ return self.isMorphing.get();
}
return unitData.isMorphing();
}
@@ -1861,8 +1909,8 @@ public boolean isMorphing() {
* @see #stop
*/
public boolean isMoving() {
- if (game.isLatComEnabled() && self().isMoving.valid(game.getFrameCount())) {
- return self().isMoving.get();
+ if (hasLatCom() && self.isMoving.valid(game.getFrameCount())) {
+ return self.isMoving.get();
}
return unitData.isMoving();
}
@@ -2007,8 +2055,8 @@ public boolean isStuck() {
* @see #getRemainingTrainTime
*/
public boolean isTraining() {
- if (game.isLatComEnabled() && self().isTraining.valid(game.getFrameCount())) {
- return self().isTraining.get();
+ if (hasLatCom() && self.isTraining.valid(game.getFrameCount())) {
+ return self.isTraining.get();
}
return unitData.isTraining();
}
@@ -5845,7 +5893,7 @@ public boolean canCancelTrainSlot(int slot, boolean checkCanIssueCommandType, bo
return false;
}
- return isTraining() && slot >= 0 && getTrainingQueue().size() > slot;
+ return isTraining() && slot >= 0 && getTrainingQueueCount() > slot;
}
public boolean canCancelMorph() {
@@ -6406,10 +6454,28 @@ public int compareTo(final Unit other) {
return id - other.id;
}
+ static boolean overlapsRectangle(final Unit u, final int rectLeft, final int rectTop, final int rectRight, final int rectBottom) {
+ final Position pos = u.position;
+ if (pos != null) {
+ final UnitType type = u.getType();
+ if (type != null) {
+ return rectLeft <= pos.x + type.dimensionRight()
+ && rectTop <= pos.y + type.dimensionDown()
+ && rectRight >= pos.x - type.dimensionLeft()
+ && rectBottom >= pos.y - type.dimensionUp();
+ }
+ }
+ return rectLeft <= u.getRight() && rectTop <= u.getBottom() && rectRight >= u.getLeft() && rectBottom >= u.getTop();
+ }
+
void updatePosition(final int frame) {
if (frame > lastPositionUpdate) {
lastPositionUpdate = frame;
- position = new Position(unitData.getPositionX(), unitData.getPositionY());
+ final int newX = unitData.getPositionX();
+ final int newY = unitData.getPositionY();
+ if (((position.x ^ newX) | (position.y ^ newY)) != 0) {
+ position = new Position(newX, newY);
+ }
}
}
}
diff --git a/src/main/java/bwapi/UnitType.java b/src/main/java/bwapi/UnitType.java
index 940b2b00..67d66d7a 100644
--- a/src/main/java/bwapi/UnitType.java
+++ b/src/main/java/bwapi/UnitType.java
@@ -265,7 +265,7 @@ public enum UnitType {
* @return The maximum width of all unit types, in pixels.
*/
public static int maxUnitWidth() {
- return Arrays.stream(UnitType.values()).max(Comparator.comparingInt(UnitType::width)).get().width();
+ return UnitTypeContainer.maxUnitWidth;
}
/**
@@ -275,7 +275,7 @@ public static int maxUnitWidth() {
* @return The maximum height of all unit types, in pixels.
*/
public static int maxUnitHeight() {
- return Arrays.stream(UnitType.values()).max(Comparator.comparingInt(UnitType::height)).get().height();
+ return UnitTypeContainer.maxUnitHeight;
}
/**
@@ -298,17 +298,7 @@ public Race getRace() {
* Returns pair({@link UnitType#None},0) If this unit type cannot be made by the player.
*/
public Pair whatBuilds() {
- // Retrieve the type
- final UnitType type = UnitTypeContainer.whatBuilds[id];
- int count = 1;
- // Set count to 0 if there is no whatBuilds and 2 if it's an archon
- if (type == UnitType.None) {
- count = 0;
- } else if (this == UnitType.Protoss_Archon || this == UnitType.Protoss_Dark_Archon) {
- count = 2;
- }
- // Return the desired pair
- return new Pair<>(type, count);
+ return UnitTypeContainer.whatBuildsPairs[id];
}
/**
@@ -365,7 +355,7 @@ public TechType cloakingTech() {
* @return List of TechTypes containing ability information.
*/
public List abilities() {
- return Collections.unmodifiableList(Arrays.asList(UnitTypeContainer.unitTechs[id]));
+ return UnitTypeContainer.unitTechsLists[id];
}
/**
@@ -374,7 +364,7 @@ public List abilities() {
* @return List of UpgradeTypes containing upgrade types that will impact this unit type.
*/
public List upgrades() {
- return Collections.unmodifiableList(Arrays.asList(UnitTypeContainer.upgrades[id]));
+ return UnitTypeContainer.upgradesLists[id];
}
/**
@@ -578,7 +568,7 @@ public int tileHeight() {
* @return {@link TilePosition} containing the width (x) and height (y) of the unit type, in tiles.
*/
public TilePosition tileSize() {
- return new TilePosition(tileWidth(), tileHeight());
+ return UnitTypeContainer.tileSizes[id];
}
/**
@@ -1194,7 +1184,7 @@ public boolean canBuildAddon() {
* @since 4.1.2
*/
public List buildsWhat() {
- return Collections.unmodifiableList(Arrays.asList(UnitTypeContainer.buildsWhat[id]));
+ return UnitTypeContainer.buildsWhatLists[id];
}
/**
@@ -1209,7 +1199,7 @@ public List buildsWhat() {
* @since 4.1.2
*/
public List researchesWhat() {
- return Collections.unmodifiableList(Arrays.asList(UnitTypeContainer.researchesWhat[id]));
+ return UnitTypeContainer.researchesWhatLists[id];
}
/**
@@ -1223,7 +1213,7 @@ public List researchesWhat() {
* @since 4.1.2
*/
public List upgradesWhat() {
- return Collections.unmodifiableList(Arrays.asList(UnitTypeContainer.upgradesWhat[id]));
+ return UnitTypeContainer.upgradesWhatLists[id];
}
/**
diff --git a/src/main/java/bwapi/UnitTypeContainer.java b/src/main/java/bwapi/UnitTypeContainer.java
index 0ac0cd03..8d889491 100644
--- a/src/main/java/bwapi/UnitTypeContainer.java
+++ b/src/main/java/bwapi/UnitTypeContainer.java
@@ -1,6 +1,8 @@
package bwapi;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -1448,6 +1450,59 @@ class UnitTypeContainer {
{},// Factories,
{}// Unknown,
};
+ static final TilePosition[] tileSizes = new TilePosition[UnitType.values().length];
+ @SuppressWarnings("unchecked")
+ static final Pair[] whatBuildsPairs = new Pair[UnitType.values().length];
+ @SuppressWarnings("unchecked")
+ static final List[] unitTechsLists = new List[UnitType.values().length];
+ @SuppressWarnings("unchecked")
+ static final List[] upgradesLists = new List[UnitType.values().length];
+ @SuppressWarnings("unchecked")
+ static final List[] buildsWhatLists = new List[UnitType.values().length];
+ @SuppressWarnings("unchecked")
+ static final List[] researchesWhatLists = new List[UnitType.values().length];
+ @SuppressWarnings("unchecked")
+ static final List[] upgradesWhatLists = new List[UnitType.values().length];
+ static final int maxUnitWidth;
+ static final int maxUnitHeight;
+
+ static {
+ int maxW = 0;
+ int maxH = 0;
+ for (final UnitType ut : UnitType.values()) {
+ final int id = ut.id;
+ tileSizes[id] = new TilePosition(
+ unitDimensions[id][UnitDimensions.tileWidth],
+ unitDimensions[id][UnitDimensions.tileHeight]);
+
+ final UnitType builderType = whatBuilds[id];
+ int count = 1;
+ if (builderType == UnitType.None) {
+ count = 0;
+ } else if (ut == UnitType.Protoss_Archon || ut == UnitType.Protoss_Dark_Archon) {
+ count = 2;
+ }
+ whatBuildsPairs[id] = new Pair<>(builderType, count);
+
+ unitTechsLists[id] = Collections.unmodifiableList(Arrays.asList(unitTechs[id]));
+ upgradesLists[id] = Collections.unmodifiableList(Arrays.asList(upgrades[id]));
+ buildsWhatLists[id] = Collections.unmodifiableList(Arrays.asList(buildsWhat[id]));
+ researchesWhatLists[id] = Collections.unmodifiableList(Arrays.asList(researchesWhat[id]));
+ upgradesWhatLists[id] = Collections.unmodifiableList(Arrays.asList(upgradesWhat[id]));
+
+ final int w = unitDimensions[id][UnitDimensions.left] + 1 + unitDimensions[id][UnitDimensions.right];
+ final int h = unitDimensions[id][UnitDimensions.up] + 1 + unitDimensions[id][UnitDimensions.down];
+ if (w > maxW) {
+ maxW = w;
+ }
+ if (h > maxH) {
+ maxH = h;
+ }
+ }
+ maxUnitWidth = maxW;
+ maxUnitHeight = maxH;
+ }
+
static final List