Skip to content

Commit 8ab545a

Browse files
committed
Update nearby endpoint - Add Nation target type, and better townblock checks
1 parent fb7c820 commit 8ab545a

5 files changed

Lines changed: 124 additions & 49 deletions

File tree

docs/nearby.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ Example **POST** request with a nearby query based on a town
1414
{
1515
"target_type": "TOWN",
1616
"target": "Melbourne",
17-
"search_type": "TOWN",
18-
"radius": 100
17+
"search_type": "TOWN", // Returns a list of towns within the specified radius
18+
"radius": 100,
19+
"strict": true // Optional field. Default is false.
1920
}
2021
]
2122
}
@@ -31,14 +32,17 @@ Example **POST** request with a nearby query based on coordinates
3132
2000,
3233
10000
3334
],
34-
"search_type": "TOWN",
35+
"search_type": "NATION", // Returns a list of nations whose capitals fall within the specified radius
3536
"radius": 10000
3637
}
3738
]
3839
}
3940
```
4041

41-
Example **POST** response for a nearby query with `search_type` set to `TOWN` (the only type currently available)
42+
Note: When checking if a town is in range, the distance between the center and the town's homeblock is checked. If the distance is smaller than the radius, the town is added to the response.
43+
If it's out of range but only by <=300 blocks, and `strict` is set to false (or not specified), all the town's townblocks are checked
44+
45+
Example **POST** response for a nearby query with `search_type` set to `TOWN`
4246
```json5
4347
[
4448
[

src/main/java/net/earthmc/emcapi/endpoint/NearbyEndpoint.java

Lines changed: 94 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.google.gson.JsonObject;
66
import com.palmergames.bukkit.towny.TownyAPI;
77
import com.palmergames.bukkit.towny.TownySettings;
8+
import com.palmergames.bukkit.towny.object.Nation;
89
import com.palmergames.bukkit.towny.object.Town;
910
import com.palmergames.bukkit.towny.object.TownBlock;
1011
import com.palmergames.bukkit.towny.object.WorldCoord;
@@ -23,6 +24,7 @@
2324

2425
import java.util.ArrayList;
2526
import java.util.List;
27+
import java.util.Objects;
2628

2729
public class NearbyEndpoint extends PostEndpoint<NearbyContext> {
2830

@@ -43,89 +45,139 @@ public NearbyContext getObjectOrNull(JsonElement element, @Nullable String key)
4345
NearbyType searchType;
4446
try {
4547
targetType = NearbyType.valueOf(targetTypeString);
48+
} catch (IllegalArgumentException ignored) {
49+
throw new BadRequestResponse("Invalid target type");
50+
}
51+
try {
4652
searchType = NearbyType.valueOf(searchTypeString);
47-
} catch (IllegalArgumentException e) {
48-
throw new BadRequestResponse("Your target or search type is invalid");
53+
if (searchType == NearbyType.COORDINATE) {
54+
throw new BadRequestResponse("Search type cannot be coordinate. Select town or nation.");
55+
}
56+
} catch (IllegalArgumentException ignored) {
57+
throw new BadRequestResponse("Invalid search type");
4958
}
5059

5160
Integer radius = JSONUtil.getJsonElementAsIntegerOrNull(jsonObject.get("radius"));
52-
if (radius == null) throw new BadRequestResponse("You did not specify a radius");
61+
if (radius == null || radius < 0) throw new BadRequestResponse("Invalid radius provided");
5362

63+
boolean strict = Objects.requireNonNullElse(JSONUtil.getJsonElementAsBooleanOrNull(jsonObject.get("strict")), false);
5464
JsonElement targetElement = jsonObject.get("target");
55-
if (targetType.equals(NearbyType.COORDINATE)) {
56-
JsonArray jsonArray = JSONUtil.getJsonElementAsJsonArrayOrNull(targetElement);
57-
if (jsonArray == null) throw new BadRequestResponse("Your target is not a valid JSON array");
58-
59-
Pair<Integer, Integer> pair = new Pair<>(jsonArray.get(0).getAsInt(), jsonArray.get(1).getAsInt());
65+
return switch (targetType) {
66+
case COORDINATE -> {
67+
JsonArray jsonArray = JSONUtil.getJsonElementAsJsonArrayOrNull(targetElement);
68+
if (jsonArray == null) throw new BadRequestResponse("Your target is not a valid JSON array");
6069

61-
return new NearbyContext(targetType, pair, searchType, radius);
62-
} else if (targetType.equals(NearbyType.TOWN)) {
63-
String target = JSONUtil.getJsonElementAsStringOrNull(targetElement);
64-
if (target == null) throw new BadRequestResponse("Your target is not a valid string");
70+
Pair<Integer, Integer> pair = new Pair<>(jsonArray.get(0).getAsInt(), jsonArray.get(1).getAsInt());
6571

66-
return new NearbyContext(targetType, target, searchType, radius);
67-
}
72+
yield new NearbyContext(targetType, pair, searchType, radius, strict);
73+
}
74+
case TOWN, NATION -> {
75+
String target = JSONUtil.getJsonElementAsStringOrNull(targetElement);
76+
if (target == null) throw new BadRequestResponse("Your target is not a valid string");
6877

69-
return null;
78+
yield new NearbyContext(targetType, target, searchType, radius, strict);
79+
}
80+
};
7081
}
7182

7283
@Override
7384
public JsonElement getJsonElement(NearbyContext context, @Nullable String key) {
7485
NearbyType targetType = context.getTargetType();
7586
int radius = context.getRadius();
76-
switch (targetType) {
87+
NearbyType searchType = context.getSearchType();
88+
boolean strict = context.isStrict();
89+
90+
return switch (targetType) {
7791
case COORDINATE -> {
7892
Pair<Integer, Integer> pair = context.getTargetCoordinate();
7993

80-
return lookupNearbyCoordinate(pair.getFirst(), pair.getSecond(), radius);
81-
}
82-
case TOWN -> {
83-
String townName = context.getTargetString();
84-
85-
return lookupNearbyTown(townName, radius);
94+
yield lookupNearCoordinates(pair.getFirst(), pair.getSecond(), radius, strict, searchType);
8695
}
96+
case TOWN -> lookupNearTown(context.getTargetString(), radius, strict, searchType);
97+
case NATION -> lookupNearNation(context.getTargetString(), radius, strict, searchType);
8798
};
88-
89-
return null;
9099
}
91100

92-
public JsonArray lookupNearbyCoordinate(Integer x, Integer z, Integer radius) {
93-
if (x == null || z == null) throw new BadRequestResponse("Invalid coordinates provided");
94-
if (radius == null || radius < 0) throw new BadRequestResponse("Invalid radius provided");
95-
96-
Location location = new Location(Bukkit.getWorlds().get(0), x, 0, z);
101+
private JsonArray lookupNearCoordinates(int x, int z, int radius, boolean strict, NearbyType searchType) {
102+
Location location = new Location(Bukkit.getWorlds().getFirst(), x, 0, z);
97103

98-
return getJsonArrayOfNearbyTowns(WorldCoord.parseWorldCoord(location), radius, null);
104+
WorldCoord worldCoord = WorldCoord.parseWorldCoord(location);
105+
return searchType == NearbyType.TOWN ? getJsonArrayOfNearbyTowns(worldCoord, radius, strict, null) : getNearbyNations(worldCoord, radius, strict, null);
99106
}
100107

101-
public JsonElement lookupNearbyTown(String townString, Integer radius) {
108+
private JsonElement lookupNearTown(String townString, int radius, boolean strict, NearbyType searchType) {
102109
if (townString == null) throw new BadRequestResponse("Invalid town provided");
103110

104111
Town town = TownyAPI.getInstance().getTown(townString);
105112
if (town == null) throw new BadRequestResponse(townString + " is not a real town");
106113

107-
if (radius == null || radius < 0) throw new BadRequestResponse("Invalid radius provided");
108-
109114
TownBlock homeBlock = town.getHomeBlockOrNull();
110115
if (homeBlock == null) throw new BadRequestResponse("The specified town has no homeblock");
111116

112-
return getJsonArrayOfNearbyTowns(homeBlock.getWorldCoord(), radius, town);
117+
return searchType == NearbyType.TOWN ? getJsonArrayOfNearbyTowns(homeBlock.getWorldCoord(), radius, strict, town) : getNearbyNations(homeBlock.getWorldCoord(), radius, strict, town.isCapital() ? town.getNationOrNull() : null);
113118
}
114119

115-
private JsonArray getJsonArrayOfNearbyTowns(WorldCoord worldCoord, int radius, Town town) {
116-
List<Town> towns = new ArrayList<>();
120+
private JsonElement lookupNearNation(String nationName, int radius, boolean strict, NearbyType searchType) {
121+
if (nationName == null) throw new BadRequestResponse("Invalid nation provided");
117122

118-
for (Town otherTown : TownyAPI.getInstance().getTowns()) {
119-
if (town != null && town == otherTown) continue; // Don't add the town we are looking nearby
123+
Nation nation = TownyAPI.getInstance().getNation(nationName);
124+
if (nation == null) throw new BadRequestResponse(nationName + " is not a real nation");
120125

121-
TownBlock homeBlock = otherTown.getHomeBlockOrNull();
122-
if (homeBlock == null) continue;
126+
Town capital = nation.getCapital();
127+
if (capital == null) throw new BadRequestResponse("The specified nation has no capital");
123128

124-
WorldCoord homeBlockWorldCoord = homeBlock.getWorldCoord();
129+
TownBlock homeBlock = capital.getHomeBlockOrNull();
130+
if (homeBlock == null) throw new BadRequestResponse("The specified nation's capital has no homeblock");
125131

126-
if (MathUtil.distance(worldCoord, homeBlockWorldCoord) * TownySettings.getTownBlockSize() <= radius) towns.add(otherTown);
132+
return searchType == NearbyType.TOWN ? getJsonArrayOfNearbyTowns(homeBlock.getWorldCoord(), radius, strict, capital) : getNearbyNations(homeBlock.getWorldCoord(), radius, strict, nation);
133+
}
134+
135+
private JsonArray getJsonArrayOfNearbyTowns(WorldCoord center, int radius, boolean strict, Town excludeTown) {
136+
List<Town> towns = new ArrayList<>();
137+
138+
for (Town town : TownyAPI.getInstance().getTowns()) {
139+
if (excludeTown != null && excludeTown.equals(town)) continue;
140+
141+
if (isTownInRange(town, center, radius, strict)) {
142+
towns.add(town);
143+
}
127144
}
128145

129146
return EndpointUtils.getTownArray(towns);
130147
}
148+
149+
private JsonArray getNearbyNations(WorldCoord center, int radius, boolean strict, Nation excludeNation) {
150+
List<Nation> nations = new ArrayList<>();
151+
152+
for (Nation nation : TownyAPI.getInstance().getNations()) {
153+
if (excludeNation != null && excludeNation.equals(nation)) continue;
154+
155+
Town capital = nation.getCapital();
156+
if (capital == null) continue;
157+
158+
if (isTownInRange(capital, center, radius, strict)) {
159+
nations.add(nation);
160+
}
161+
}
162+
163+
return EndpointUtils.getNationArray(nations);
164+
}
165+
166+
private boolean isTownInRange(Town town, WorldCoord center, int radius, boolean strict) {
167+
TownBlock homeBlock = town.getHomeBlockOrNull();
168+
if (homeBlock == null) return false;
169+
WorldCoord homeBlockWorldCoord = homeBlock.getWorldCoord();
170+
double distance = MathUtil.distance(center, homeBlockWorldCoord) * TownySettings.getTownBlockSize();
171+
if (distance <= radius) {
172+
return true;
173+
}
174+
if (!strict && distance <= radius + 300) { // Homeblock is less than 300 blocks out of reach, check all the town's chunks
175+
for (TownBlock townBlock : town.getTownBlocks()) {
176+
if (MathUtil.distance(center, townBlock.getWorldCoord()) * TownySettings.getTownBlockSize() <= radius) {
177+
return true;
178+
}
179+
}
180+
}
181+
return false;
182+
}
131183
}

src/main/java/net/earthmc/emcapi/object/nearby/NearbyContext.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,24 @@ public class NearbyContext {
99
private final Pair<Integer, Integer> targetCoordinate;
1010
private final NearbyType searchType;
1111
private final int radius;
12+
private final boolean strict;
1213

13-
public NearbyContext(NearbyType targetType, String target, NearbyType searchType, int radius) {
14+
public NearbyContext(NearbyType targetType, String target, NearbyType searchType, int radius, boolean strict) {
1415
this.targetType = targetType;
1516
this.targetString = target;
1617
this.targetCoordinate = null;
1718
this.searchType = searchType;
1819
this.radius = radius;
20+
this.strict = strict;
1921
}
2022

21-
public NearbyContext(NearbyType targetType, Pair<Integer, Integer> targetCoordinate, NearbyType searchType, int radius) {
23+
public NearbyContext(NearbyType targetType, Pair<Integer, Integer> targetCoordinate, NearbyType searchType, int radius, boolean strict) {
2224
this.targetType = targetType;
2325
this.targetString = null;
2426
this.targetCoordinate = targetCoordinate;
2527
this.searchType = searchType;
2628
this.radius = radius;
29+
this.strict = strict;
2730
}
2831

2932
public NearbyType getTargetType() {
@@ -45,4 +48,8 @@ public NearbyType getSearchType() {
4548
public int getRadius() {
4649
return radius;
4750
}
51+
52+
public boolean isStrict() {
53+
return strict;
54+
}
4855
}

src/main/java/net/earthmc/emcapi/object/nearby/NearbyType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
*/
66
public enum NearbyType {
77
COORDINATE,
8-
TOWN
8+
TOWN,
9+
NATION
910
}

src/main/java/net/earthmc/emcapi/util/JSONUtil.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ public static Integer getJsonElementAsIntegerOrNull(JsonElement element) {
4040
return primitive.getAsInt();
4141
}
4242

43+
public static Boolean getJsonElementAsBooleanOrNull(JsonElement element) {
44+
if (element == null) return null;
45+
46+
if (!element.isJsonPrimitive()) return null;
47+
48+
JsonPrimitive primitive = element.getAsJsonPrimitive();
49+
if (!primitive.isBoolean()) return null;
50+
51+
return primitive.getAsBoolean();
52+
}
53+
4354
public static JsonArray getJsonElementAsJsonArrayOrNull(JsonElement element) {
4455
if (element == null) return null;
4556

0 commit comments

Comments
 (0)