Skip to content
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,12 @@ call is ambiguous between overloads, so pass `new String[0]`.
Locale strings can also carry `[actionbar]`, `[title]`, `[subtitle]` and
`[sound:...]` markers — BentoBox routes and formats those itself, so prefer
them over calling `sendActionBar`/`playSound` around a message.
**Never name an item or a port by hand** (GitHub #7): `[material]` values
come from `economy.ItemNames.label(user, material)` — the locale's
`tradewinds.materials.<key>` line if written, else a `<lang_or>` tag the
client translates itself — and `[name]` for a port from
`PortNames.display(addon, user, spec)`, which transliterates the name's
syllables through `tradewinds.name-token.*`. `IslandSpec.name()` is the
island's identity (registry, PDC, logs, admin arguments), never its label.
`PriceEngine.prettify` is for logs and admin audits only. Enum names reach a
player only through `getLocaleKey()`.
69 changes: 69 additions & 0 deletions src/main/java/world/bentobox/tradewinds/PortNames.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package world.bentobox.tradewinds;

import java.util.List;
import java.util.Locale;

import world.bentobox.bentobox.api.user.User;
import world.bentobox.tradewinds.ocean.IslandSpec;
import world.bentobox.tradewinds.ocean.NameGenerator;
import world.bentobox.tradewinds.ocean.OceanEngine;

/**
* How a port is named to a player (GitHub #7).
* <p>
* Port names are procedural, so no locale can list them - but every name is a
* short sequence of syllable tokens, and a locale CAN list those. Each token
* is looked up as {@code tradewinds.name-token.<token>}; a Chinese locale maps
* "la" to one character and "ve" to another, and "Lave" comes out in Chinese
* for every Chinese-speaking sailor, the same for all of them. A blank or
* missing line leaves that token as it is, so en-US reads unchanged.
* <p>
* {@link IslandSpec#name()} stays the canonical English name and the island's
* identity everywhere that is not a player's screen: island registry, resident
* tags, logs, admin commands that take a name as an argument. This class is
* display only.
*
* @author tastybento
*/
public final class PortNames {

private static final String TOKEN_PREFIX = "tradewinds.name-token.";
private static final String SPAWN_KEY = "tradewinds.spawn.name";

private PortNames() {
// Static use only
}

/**
* The name of a port as this player should read it.
*
* @param addon the addon, for the ocean the port belongs to
* @param user the viewer - decides which locale's tokens apply
* @param spec the port
* @return the transliterated name, or the canonical name where the ocean
* engine is not up or the spec is not one of its islands
*/
public static String display(TradeWinds addon, User user, IslandSpec spec) {
if (OceanEngine.SPAWN_NAME.equals(spec.name())) {
// The one island with a given name rather than a generated one
return user.getTranslation(SPAWN_KEY);
}
OceanEngine engine = addon.getOceanEngine();
if (engine == null) {
return spec.name();
}
List<String> tokens = engine.nameTokens(spec);
if (!NameGenerator.join(tokens).equals(spec.name())) {
// Not this ocean's island (a renamed or hand-built spec): no tokens to map
return spec.name();
}
StringBuilder sb = new StringBuilder();
for (String token : tokens) {
String syllable = token.toLowerCase(Locale.ENGLISH);
String mapped = user.getTranslationOrNothing(TOKEN_PREFIX + syllable);
sb.append(mapped.isBlank() ? syllable : mapped);
}
// Capitalize the way the canonical name is; a no-op for scripts without case
return Character.toUpperCase(sb.charAt(0)) + sb.substring(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private void reportIslandCustoms(User user, TradeWinds addon, IslandSpec island)
addon.getReputationService().standing(user.getUniqueId()),
addon.getSettings().getScanUpstandingFactor(), addon.getSettings().getScanOffenderFactor());
user.sendMessage("tradewinds.commands.admin.customs.island", "[name]", island.name(), "[band]",
island.band().getDisplayName());
user.getTranslation(island.band().getLocaleKey()));
user.sendMessage("tradewinds.commands.admin.customs.scan-chance", VALUE_PLACEHOLDER,
String.format("%.0f%%", effective * 100));
user.sendMessage("tradewinds.commands.admin.customs.buys", VALUE_PLACEHOLDER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ public boolean execute(User user, String label, List<String> args) {
user.sendMessage("tradewinds.commands.admin.islands.entry",
"[index]", String.valueOf(i + 1),
TextVariables.NAME, s.name(),
"[type]", s.type().name(),
"[type]", user.getTranslation(s.type().getLocaleKey()),
"[tech]", String.valueOf(s.techLevel()),
"[band]", s.band().getDisplayName(),
"[band]", user.getTranslation(s.band().getLocaleKey()),
"[x]", String.valueOf(s.centerX()),
"[z]", String.valueOf(s.centerZ()),
"[distance]", String.valueOf(dist));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.tradewinds.economy.ItemNames;
import world.bentobox.tradewinds.PortNames;
import world.bentobox.tradewinds.TradeWinds;
import world.bentobox.tradewinds.ocean.OceanEngine;
import world.bentobox.tradewinds.ocean.IslandSpec;
Expand Down Expand Up @@ -91,8 +93,8 @@ private void chartList(TradeWinds addon, User user) {
int cost = routeCost(addon, origin, spec, x, z);
boolean reachable = cost <= fuelAboard;
user.sendMessage(reachable ? "tradewinds.chart.entry-reachable" : "tradewinds.chart.entry-far",
TextVariables.NAME, spec.name(),
"[type]", spec.type().name(),
TextVariables.NAME, PortNames.display(addon, user, spec),
"[type]", user.getTranslation(spec.type().getLocaleKey()),
"[tech]", String.valueOf(spec.techLevel()),
"[band]", user.getTranslation(spec.band().getLocaleKey()),
"[distance]", String.valueOf((int) Math.sqrt(spec.distanceSquared(x, z))),
Expand Down Expand Up @@ -123,7 +125,7 @@ private void reportBoat(TradeWinds addon, User user, int x, int z, boolean old)
return;
}
var hold = boatRecord.get();
String material = world.bentobox.tradewinds.economy.PriceEngine.prettify(hold.getMaterial());
String material = ItemNames.label(user, org.bukkit.Material.matchMaterial(hold.getMaterial()));
if (hold.getWorld() == null || hold.getWorld().isEmpty()) {
user.sendMessage(key + "-lost", MATERIAL_PLACEHOLDER, material);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.tradewinds.PortNames;
import world.bentobox.tradewinds.TradeWinds;
import world.bentobox.tradewinds.economy.Money;
import world.bentobox.tradewinds.economy.TradeCategory;
Expand All @@ -32,6 +33,7 @@ public class TWPricesCommand extends CompositeCommand {

private static final int MAX_ROWS = 12;
private static final String VALUE_PLACEHOLDER = "[value]";
private static final String KEY_NONE = "tradewinds.general.none";

public TWPricesCommand(CompositeCommand parent) {
super(parent, "prices");
Expand Down Expand Up @@ -105,7 +107,7 @@ private void sortAndDisplayHeader(User user, world.bentobox.tradewinds.dataobjec
known.sort(Comparator.comparingInt(
(IslandSpec spec) -> data.loggedPrices(spec).getOrDefault(sought.name(), 0)).reversed());
user.sendMessage("tradewinds.commands.prices.header-category", VALUE_PLACEHOLDER,
prettyCategory(sought));
user.getTranslation(sought.getLocaleKey()));
} else {
known.sort(Comparator.comparingLong(data::lastSeenPrices).reversed());
user.sendMessage("tradewinds.commands.prices.header");
Expand All @@ -121,11 +123,13 @@ private void displayRows(User user, TradeWinds addon, world.bentobox.tradewinds.
if (price == null || price <= 0) {
continue;
}
user.sendMessage("tradewinds.commands.prices.row-category", "[name]", spec.name(), "[price]",
user.sendMessage("tradewinds.commands.prices.row-category", "[name]",
PortNames.display(addon, user, spec), "[price]",
Money.format(addon, price), "[age]", age);
} else {
user.sendMessage("tradewinds.commands.prices.row", "[name]", spec.name(), VALUE_PLACEHOLDER,
best(addon, prices), "[age]", age);
user.sendMessage("tradewinds.commands.prices.row", "[name]", PortNames.display(addon, user, spec),
VALUE_PLACEHOLDER,
best(user, addon, prices), "[age]", age);
}
}
}
Expand All @@ -134,11 +138,11 @@ private void displayRows(User user, TradeWinds addon, world.bentobox.tradewinds.
* The best-paying category remembered at a port - the one line worth showing
* when not filtering.
*/
private String best(TradeWinds addon, Map<String, Integer> prices) {
private String best(User user, TradeWinds addon, Map<String, Integer> prices) {
return prices.entrySet().stream().max(Map.Entry.comparingByValue())
.map(entry -> prettyCategory(category(entry.getKey())) + " "
.map(entry -> prettyCategory(user, category(entry.getKey())) + " "
+ Money.format(addon, entry.getValue()))
.orElse("-");
.orElse(user.getTranslation(KEY_NONE));
}

private static TradeCategory category(String name) {
Expand All @@ -150,9 +154,8 @@ private static TradeCategory category(String name) {
return null;
}

private static String prettyCategory(TradeCategory category) {
return category == null ? "-"
: world.bentobox.tradewinds.economy.PriceEngine.prettify(category.name());
private static String prettyCategory(User user, TradeCategory category) {
return user.getTranslation(category == null ? KEY_NONE : category.getLocaleKey());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public boolean execute(User user, String label, List<String> args) {
String name = addon.getPlayers().getName(entry.playerId());
user.sendMessage("tradewinds.rank.board-entry",
"[place]", String.valueOf(place),
TextVariables.NAME, name.isEmpty() ? "?" : name,
TextVariables.NAME,
name.isEmpty() ? user.getTranslation("tradewinds.general.unknown") : name,
TextVariables.NUMBER, String.valueOf(entry.charted()),
RANK_PLACEHOLDER, user.getTranslation(ranks.rankFor(entry.charted()).localeKey()));
}
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/world/bentobox/tradewinds/crime/CustomsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.hooks.VaultHook;
import world.bentobox.tradewinds.PortNames;
import world.bentobox.tradewinds.TradeWinds;
import world.bentobox.tradewinds.ocean.IslandSpec;
import world.bentobox.tradewinds.ocean.SecurityBand;
Expand Down Expand Up @@ -218,9 +219,10 @@ private void detect(Player player, IslandSpec island, int aboard, boolean flagge
seize(player, island);
return;
}
String port = PortNames.display(addon, user, island);
user.sendMessage(flagged ? "tradewinds.customs.flagged" : "tradewinds.customs.detected",
NAME_VAR, island.name(), TextVariables.NUMBER, String.valueOf(aboard));
user.sendMessage("tradewinds.customs.detected-chat", NAME_VAR, island.name(), TextVariables.NUMBER,
NAME_VAR, port, TextVariables.NUMBER, String.valueOf(aboard));
user.sendMessage("tradewinds.customs.detected-chat", NAME_VAR, port, TextVariables.NUMBER,
String.valueOf(aboard));
chases.put(player.getUniqueId(), new Chase(island, System.currentTimeMillis(), units));
addon.log("Customs at " + island.name() + " detected " + aboard + " contraband on " + player.getName()
Expand All @@ -234,8 +236,9 @@ private void detect(Player player, IslandSpec island, int aboard, boolean flagge
private void seize(Player player, IslandSpec island) {
int seized = confiscate(player);
double fine = charge(player, seized);
User.getInstance(player).sendMessage("tradewinds.customs.seized", TextVariables.NUMBER,
String.valueOf(seized), "[amount]", format(fine), NAME_VAR, island.name());
User user = User.getInstance(player);
user.sendMessage("tradewinds.customs.seized", TextVariables.NUMBER,
String.valueOf(seized), "[amount]", format(fine), NAME_VAR, PortNames.display(addon, user, island));
addon.getReputationService().recordCrime(player, Crime.SMUGGLING);
}

Expand Down Expand Up @@ -294,8 +297,9 @@ private double nearestUnitDistance(Player player, Chase chase) {
void caught(Player player, Chase chase) {
int seized = confiscate(player);
double fine = charge(player, seized);
User.getInstance(player).sendMessage("tradewinds.customs.caught", TextVariables.NUMBER,
String.valueOf(seized), "[amount]", format(fine), NAME_VAR, chase.island().name());
User user = User.getInstance(player);
user.sendMessage("tradewinds.customs.caught", TextVariables.NUMBER, String.valueOf(seized), "[amount]",
format(fine), NAME_VAR, PortNames.display(addon, user, chase.island()));
addon.getReputationService().recordCrime(player, Crime.SMUGGLING);
}

Expand Down Expand Up @@ -353,7 +357,8 @@ private String format(double amount) {
* of time. No flee flag: nobody ran, so the port has nothing to remember.
*/
private void calledOff(Player player, Chase chase) {
User.getInstance(player).sendMessage("tradewinds.customs.called-off", NAME_VAR, chase.island().name());
User user = User.getInstance(player);
user.sendMessage("tradewinds.customs.called-off", NAME_VAR, PortNames.display(addon, user, chase.island()));
}

/**
Expand All @@ -363,7 +368,8 @@ private void calledOff(Player player, Chase chase) {
private void escaped(Player player, Chase chase) {
String key = key(player, chase.island());
fleeFlags.put(key, System.currentTimeMillis() + addon.getSettings().getFleeFlagMinutes() * 60_000L);
User.getInstance(player).sendMessage("tradewinds.customs.escaped", NAME_VAR, chase.island().name());
User user = User.getInstance(player);
user.sendMessage("tradewinds.customs.escaped", NAME_VAR, PortNames.display(addon, user, chase.island()));
}

private void end(UUID id, Chase chase) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.bukkit.scheduler.BukkitTask;

import world.bentobox.bentobox.api.user.User;
import world.bentobox.tradewinds.PortNames;
import world.bentobox.tradewinds.TradeWinds;
import world.bentobox.tradewinds.ocean.IslandSpec;

Expand Down Expand Up @@ -122,7 +123,8 @@ private void update(Player player, Standing standing) {
return; // This band has nobody to send - lawless water
}
responses.put(player.getUniqueId(), new Response(island, units));
User.getInstance(player).sendMessage("tradewinds.police.responding", "[name]", island.name());
User user = User.getInstance(player);
user.sendMessage("tradewinds.police.responding", "[name]", PortNames.display(addon, user, island));
return;
}
// Mobs forget, and a target seated in a boat is forgotten fast: keep
Expand Down
Loading
Loading