Skip to content

Commit bfb33aa

Browse files
author
Jasper Geurtz
committed
Align JBWAPI behavior and bug fixes with C++ BWAPI 4.4.0
- CommandTemp: subtract gas cost on Upgrade; fix Cancel_Train_Slot queue shift OOB - Unit: fix canBuildAddon(UnitType) overload call, canAttackGrouped(Position) check, isTargetable visibility check, canTargetUnit(Unit) default checkCommandibility, isVisible replay fallback/bounds check, and build/train/useTech special-case redirects - Bullet: fix isVisible replay fallback and player ID bounds check - Player: fix isEnemy/isNeutral delegation to PlayerData and supplyTotal/supplyUsed Race.None and bounds checks - Region: fix updateNeighbours accessibility check to compare regionGroupID - Game: refresh allies/enemies/observers each frame and fix drawEllipseScreen coordinate type - BuildingPlacer: avoid mutating original PlacementReserve in ReserveDefault - UnitCommand: include Repair in isQueued - Add unit tests for all BWAPI 4.4.0 alignment fixes
1 parent 858048c commit bfb33aa

15 files changed

Lines changed: 510 additions & 101 deletions

‎src/main/java/bwapi/BuildingPlacer.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ static void ReserveExistingAddonPlacement(final PlacementReserve reserve, final
260260

261261
static void ReserveDefault(final PlacementReserve reserve, final UnitType type, final TilePosition desiredPosition, final Game game) {
262262
reserve.backup();
263-
final PlacementReserve original = reserve;
263+
final PlacementReserve original = new PlacementReserve(reserve);
264264

265265
// Reserve some space around some specific units
266266
for (final Unit it : game.self().getUnits()) {
@@ -368,6 +368,15 @@ static class PlacementReserve {
368368
backup();
369369
}
370370

371+
PlacementReserve(final PlacementReserve other) {
372+
maxSearch = other.maxSearch;
373+
reset();
374+
for (int i = 0; i < MAX_RANGE; i++) {
375+
System.arraycopy(other.data[i], 0, data[i], 0, MAX_RANGE);
376+
System.arraycopy(other.save[i], 0, save[i], 0, MAX_RANGE);
377+
}
378+
}
379+
371380
// Checks if the given x/y value is valid for the Placement position
372381
static boolean isValidPos(final int x, final int y) {
373382
return x >= 0 && x < MAX_RANGE && y >= 0 && y < MAX_RANGE;

‎src/main/java/bwapi/Bullet.java‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,17 @@ public boolean isVisible() {
180180
* @param player If this parameter is specified, then the Bullet's visibility to the given player is checked. If this parameter is omitted, then a default value of null is used, which will check if the BWAPI player has vision of the {@link Bullet}.
181181
* @return true if the {@link Bullet} is visible to the specified player, false if the {@link Bullet} is not visible to the specified player.
182182
*/
183-
public boolean isVisible(final Player player) {
184-
if (player == null) {
185-
return isVisible();
183+
public boolean isVisible(Player player) {
184+
player = player == null ? game.self() : player;
185+
if (player != null) {
186+
return player.hasValidId() && bulletData.isVisible(player.getID());
186187
}
187-
return bulletData.isVisible(player.getID());
188+
for (int i = 0; i < Player.MAX_VALID_ID; ++i) {
189+
if (bulletData.isVisible(i)) {
190+
return true;
191+
}
192+
}
193+
return false;
188194
}
189195

190196
@Override

‎src/main/java/bwapi/CommandTemp.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ void execute(boolean isCurrentFrame) {
336336

337337
// Shift training queue back one slot after the cancelled unit
338338
for (int i = command.extra; i < 4; ++i) {
339-
unit.self().trainingQueue[i].set(unit.getTrainingQueue().get(i + 1), frame);
339+
unit.self().trainingQueue[i].set(unit.getTrainingQueueAt(i + 1), frame);
340340
}
341341

342342
unit.self().trainingQueueCount.setOrAdd(-1, frame);
@@ -790,7 +790,7 @@ void execute(boolean isCurrentFrame) {
790790
unit.self().remainingUpgradeTime.set(upgradeType.upgradeTime(level + 1), frame);
791791

792792
player.self().minerals.setOrAdd(-upgradeType.mineralPrice(level + 1), frame);
793-
player.self().gas.setOrAdd(upgradeType.gasPrice(level + 1), frame);
793+
player.self().gas.setOrAdd(-upgradeType.gasPrice(level + 1), frame);
794794

795795
player.self().isUpgrading[upgradeType.id].set(true, frame);
796796
}

‎src/main/java/bwapi/Game.java‎

Lines changed: 79 additions & 69 deletions
Large diffs are not rendered by default.

‎src/main/java/bwapi/Player.java‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
* @see Race
2020
*/
2121
public class Player implements Comparable<Player> {
22+
static final int MAX_VALID_ID = 9;
23+
2224
private final PlayerData playerData;
2325
private final Game game;
2426
private final int id;
@@ -53,6 +55,10 @@ public int getID() {
5355
return id;
5456
}
5557

58+
boolean hasValidId() {
59+
return id >= 0 && id < MAX_VALID_ID;
60+
}
61+
5662
/**
5763
* Retrieves the name of the player.
5864
*
@@ -141,7 +147,7 @@ public boolean isEnemy(final Player player) {
141147
if (player == null || isNeutral() || player.isNeutral() || isObserver() || player.isObserver()) {
142148
return false;
143149
}
144-
return !playerData.isAlly(player.getID());
150+
return playerData.isEnemy(player.getID());
145151
}
146152

147153
/**
@@ -150,7 +156,7 @@ public boolean isEnemy(final Player player) {
150156
* @return true if this player is the neutral player, false if this player is any other player.
151157
*/
152158
public boolean isNeutral() {
153-
return equals(game.neutral());
159+
return playerData.isNeutral();
154160
}
155161

156162
/**
@@ -339,8 +345,9 @@ public int supplyTotal() {
339345
* @return The total supply available for this player and the given race.
340346
* @see #supplyUsed
341347
*/
342-
public int supplyTotal(final Race race) {
343-
return playerData.getSupplyTotal(race.id);
348+
public int supplyTotal(Race race) {
349+
race = race == Race.None ? getRace() : race;
350+
return race.hasValidId() ? playerData.getSupplyTotal(race.id) : 0;
344351
}
345352

346353
public int supplyUsed() {
@@ -354,7 +361,11 @@ public int supplyUsed() {
354361
* @return The supply that is in use for this player and the given race.
355362
* @see #supplyTotal
356363
*/
357-
public int supplyUsed(final Race race) {
364+
public int supplyUsed(Race race) {
365+
race = race == Race.None ? getRace() : race;
366+
if (!race.hasValidId()) {
367+
return 0;
368+
}
358369
int supplyUsed = playerData.getSupplyUsed(race.id);
359370
if (game.isLatComEnabled() && self().supplyUsed[race.id].valid(game.getFrameCount())) {
360371
return supplyUsed + self().supplyUsed[race.id].get();
@@ -544,6 +555,8 @@ public Color getColor() {
544555
* @return character code to use for text in Broodwar.
545556
*/
546557
public Text getTextColor() {
558+
// Note: C++ BWAPI 4.4.0 uses Colors::White (255) and Colors::Green (117) due to a bug,
559+
// whereas Player 7 white is 84 (DirtyWhite) and Player 9 / Ice green is 185 (DarkGreen).
547560
switch (getColor().id) {
548561
case 111: // red
549562
return Text.BrightRed;

‎src/main/java/bwapi/Race.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,18 @@ public enum Race {
5858
Arrays.stream(Race.values()).forEach(v -> idToEnum[v.id] = v);
5959
}
6060

61+
static final int MAX_VALID_ID = 3;
62+
6163
public final int id;
6264

6365
Race(final int id) {
6466
this.id = id;
6567
}
6668

69+
boolean hasValidId() {
70+
return id >= 0 && id < MAX_VALID_ID;
71+
}
72+
6773
/**
6874
* Retrieves the default worker type for this {@link Race}.
6975
* <p>

‎src/main/java/bwapi/Region.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ void updateNeighbours() {
6464
final Region region = game.getRegion(regionData.getNeighbors(i));
6565
neighbours.add(region);
6666
final int d = getDistance(region);
67-
if (region.isAccessible()) {
67+
if (this.getRegionGroupID() == region.getRegionGroupID()) {
6868
if (d < accessibleBestDist) {
6969
closestAccessibleRegion = region;
7070
accessibleBestDist = d;

‎src/main/java/bwapi/Unit.java‎

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,8 +2092,17 @@ public boolean isVisible() {
20922092
* fog of war will be accessible, but isVisible will still return false.
20932093
* @see #exists
20942094
*/
2095-
public boolean isVisible(final Player player) {
2096-
return unitData.isVisible(player.getID());
2095+
public boolean isVisible(Player player) {
2096+
player = player == null ? game.self() : player;
2097+
if (player != null) {
2098+
return player.hasValidId() && unitData.isVisible(player.getID());
2099+
}
2100+
for (int i = 0; i < Player.MAX_VALID_ID; ++i) {
2101+
if (unitData.isVisible(i)) {
2102+
return true;
2103+
}
2104+
}
2105+
return false;
20972106
}
20982107

20992108
/**
@@ -2107,6 +2116,9 @@ public boolean isTargetable() {
21072116
if (!exists()) {
21082117
return false;
21092118
}
2119+
if (!isVisible() && !game.isFlagEnabled(Flag.CompleteMapInformation)) {
2120+
return false;
2121+
}
21102122
final UnitType ut = getType();
21112123
if (!isCompleted() &&
21122124
!ut.isBuilding() &&
@@ -2205,7 +2217,7 @@ public boolean attack(final Unit target, final boolean shiftQueueCommand) {
22052217
}
22062218

22072219
public boolean build(final UnitType type) {
2208-
return issueCommand(UnitCommand.train(this, type));
2220+
return build(type, TilePosition.None);
22092221
}
22102222

22112223
/**
@@ -2225,6 +2237,9 @@ public boolean build(final UnitType type) {
22252237
* @see Unit#canBuild
22262238
*/
22272239
public boolean build(final UnitType type, final TilePosition target) {
2240+
if (TilePosition.None.equals(target)) {
2241+
return train(type);
2242+
}
22282243
return issueCommand(UnitCommand.build(this, target, type));
22292244
}
22302245

@@ -2269,7 +2284,23 @@ public boolean buildAddon(final UnitType type) {
22692284
* @see Unit#isTraining
22702285
* @see Unit#canTrain
22712286
*/
2272-
public boolean train(final UnitType type) {
2287+
public boolean train() {
2288+
return train(UnitType.None);
2289+
}
2290+
2291+
public boolean train(UnitType type) {
2292+
if (type == UnitType.None) {
2293+
switch (getType()) {
2294+
case Protoss_Carrier:
2295+
case Hero_Gantrithor:
2296+
type = Protoss_Interceptor;
2297+
break;
2298+
case Protoss_Reaver:
2299+
case Hero_Warbringer:
2300+
type = Protoss_Scarab;
2301+
break;
2302+
}
2303+
}
22732304
return issueCommand(UnitCommand.train(this, type));
22742305
}
22752306

@@ -2876,6 +2907,9 @@ public boolean useTech(final TechType tech, final Position target) {
28762907
}
28772908

28782909
public boolean useTech(final TechType tech, final Unit target) {
2910+
if (target == null) {
2911+
return issueCommand(UnitCommand.useTech(this, tech));
2912+
}
28792913
return issueCommand(UnitCommand.useTech(this, tech, target));
28802914
}
28812915

@@ -3506,18 +3540,7 @@ public boolean canIssueCommandTypeGrouped(UnitCommandType ct, boolean checkComma
35063540
}
35073541

35083542
public boolean canTargetUnit(Unit targetUnit) {
3509-
if (targetUnit == null || !targetUnit.exists()) {
3510-
return false;
3511-
}
3512-
final UnitType targetType = targetUnit.getType();
3513-
if (!targetUnit.isCompleted() && !targetType.isBuilding() && !targetUnit.isMorphing() &&
3514-
targetType != Protoss_Archon && targetType != Protoss_Dark_Archon) {
3515-
return false;
3516-
}
3517-
return targetType != Spell_Scanner_Sweep &&
3518-
targetType != Spell_Dark_Swarm &&
3519-
targetType != Spell_Disruption_Web &&
3520-
targetType != Special_Map_Revealer;
3543+
return canTargetUnit(targetUnit, true);
35213544
}
35223545

35233546
/**
@@ -3534,7 +3557,7 @@ public boolean canTargetUnit(Unit targetUnit, boolean checkCommandibility) {
35343557
if (checkCommandibility && !canCommand()) {
35353558
return false;
35363559
}
3537-
return canTargetUnit(targetUnit);
3560+
return targetUnit != null && targetUnit.isTargetable();
35383561
}
35393562

35403563
public boolean canAttack() {
@@ -3668,7 +3691,7 @@ public boolean canAttackGrouped(Position target, boolean checkCanTargetUnit, boo
36683691
if (checkCommandibilityGrouped && !canCommandGrouped(false)) {
36693692
return false;
36703693
}
3671-
return canAttackMoveGrouped(false, false) || canAttackUnitGrouped(false, false);
3694+
return canAttackMoveGrouped(false, false);
36723695
}
36733696

36743697
public boolean canAttackGrouped(Unit target, boolean checkCanTargetUnit, boolean checkCanIssueCommandType, boolean checkCommandibilityGrouped, boolean checkCommandibility) {
@@ -4051,7 +4074,7 @@ public boolean canBuildAddon(UnitType uType, boolean checkCanIssueCommandType, b
40514074
if (checkCommandibility && !canCommand()) {
40524075
return false;
40534076
}
4054-
if (checkCanIssueCommandType && !canBuildAddon(uType, false)) {
4077+
if (checkCanIssueCommandType && !canBuildAddon(false)) {
40554078
return false;
40564079
}
40574080
if (!game.canMake(uType, this)) {

‎src/main/java/bwapi/UnitCommand.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ private UnitCommand(final Unit unit, final UnitCommandType type) {
2020
}
2121

2222
private <T extends Point<T>> void assignTarget(Point<T> target) {
23+
// Note: C++ BWAPI clamps target via target.makeValid() to [0, mapWidth] and [0, mapHeight].
2324
this.x = target.x;
2425
this.y = target.y;
2526
}
@@ -401,6 +402,7 @@ public boolean isQueued() {
401402
type == Follow ||
402403
type == Gather ||
403404
type == Return_Cargo ||
405+
type == Repair ||
404406
type == Load ||
405407
type == Unload_All ||
406408
type == Unload_All_Position ||
@@ -409,6 +411,7 @@ public boolean isQueued() {
409411
&& extra != 0;
410412
}
411413

414+
// Note: JBWAPI includes `unit` in equals and hashCode, C++ BWAPI does not.
412415
@Override
413416
public boolean equals(Object o) {
414417
if (this == o) return true;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package bwapi;
2+
3+
import org.junit.Test;
4+
5+
import static org.junit.Assert.assertFalse;
6+
import static org.junit.Assert.assertTrue;
7+
import static org.mockito.Mockito.mock;
8+
import static org.mockito.Mockito.when;
9+
10+
public class BulletTest {
11+
12+
@Test
13+
public void isVisibleShouldFallbackToAnyPlayerWhenSelfIsNullAndCheckValidPlayerId() {
14+
Game game = mock(Game.class);
15+
when(game.self()).thenReturn(null);
16+
17+
ClientData.BulletData bulletData = mock(ClientData.BulletData.class);
18+
Bullet bullet = new Bullet(bulletData, 0, game);
19+
20+
assertFalse(bullet.isVisible());
21+
22+
when(bulletData.isVisible(2)).thenReturn(true);
23+
assertTrue(bullet.isVisible());
24+
assertTrue(bullet.isVisible(null));
25+
26+
ClientData.PlayerData neutralData = mock(ClientData.PlayerData.class);
27+
when(neutralData.getName()).thenReturn("Neutral");
28+
Player neutral = new Player(neutralData, 11, game);
29+
when(bulletData.isVisible(11)).thenReturn(true);
30+
assertFalse(bullet.isVisible(neutral));
31+
}
32+
}

0 commit comments

Comments
 (0)