Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

<h1 align="Center">BuildTeamTools</h1>

[Rail Generator documentation](docs/rail-generator.md)

<p align="center">
<b>An easy to use toolset for Build Teams in the BuildTheEarth project.</b>
<br/>
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ dependencies {

testImplementation("org.junit.jupiter:junit-jupiter:5.12.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.12.2")
testRuntimeOnly(libs.paper.api)
testImplementation(libs.paper.api)
}

fun Project.versionDetails(): VersionDetails {
Expand Down
162 changes: 162 additions & 0 deletions docs/rail-generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Rail Generator

## Introduction

The Rail Generator creates railway tracks along a WorldEdit selection. You can use the built-in Standard type or create reusable custom types with different rail blocks, ballast palettes, sleepers, parallel tracks and overhead systems.

## Preparation

Use Google Maps or Google Earth to identify the centre line of the railway, then use `/tpll` to position yourself along it. See [How to use /tpll](https://resources.buildtheearth.net/doc/how-to-use-tpll-zMxUzzwEGb) and [Building Roads](https://resources.buildtheearth.net/doc/building-roads-gpryvN4ZXn) for coordinate-selection techniques that also apply to railways.

1. Switch to `//sel convex`.
2. Left-click the first point with the WorldEdit selection tool.
3. Right-click the remaining points in path order along the railway's centre line.
4. Generate using a command, or open `/gen` and choose Rail to select or configure a type.

Convex selections are recommended. Polygonal (`//sel poly`) and cuboid (`//sel cuboid`) selections are also supported; a cuboid is useful for connecting two points. Keep the selection close to the intended route. Large selections and overlapping parallel tracks can be rejected during preparation.

## Commands

| Command | Action |
| --- | --- |
| `/gen` | Opens the generator menu. Choose Rail to open the rail type menu. |
| `/gen rail` | Generates with the Standard type. |
| `/gen rail -t custom-1` | Generates with a saved type's identifier. |
| `/gen rail -t standard -c 2 -s 5` | Generates two tracks with five blocks between track centre lines. |
| `/gen undo` | Undoes the most recent generation. |
| `/gen redo` | Redoes the undone generation. |
| `/gen history` | Shows your generation history. |
| `/gen rail help`, `/gen rail info`, `/gen rail ?` | Links to the documentation. |

`-t` selects a rail type, `-c` overrides its track count (1–8), and `-s` overrides all track spacings (3–32 blocks). Omitted options use the type's settings. Per-gap spacings can be configured in the editor. The undo and redo commands belong directly to `/gen`; `/gen rail undo` and `/gen rail redo` are not supported.

## Rail type menu

Left-click a type to select or deselect it, then click Next to generate. Selected types glow. Browse using the page arrows, or use Search to filter by identifier or display name. Right-click Search to clear the filter.

Management actions are shown only when you have the corresponding permission:

| Action | Control | Permission suffix |
| --- | --- | --- |
| Create a type | Create a Rail Type | `create` |
| Copy Standard | Right-click Standard, or press the drop key (normally Q) while hovering over it | `create` |
| Copy a custom type | Press the drop key while hovering over the type | `create` |
| Edit a custom type | Right-click the type | `edit` |
| Reload saved types | Reload Rail Types | `edit` |
| Delete a custom type | Shift+right-click the type | `delete` |
| Select types for bulk deletion | Shift+left-click custom types, then click Bulk Delete | `delete` |

These suffixes use the prefix `btt.generator.rail.`. Deletion is permanent. Standard cannot be edited or deleted; its copy is saved as a separate custom type. Custom types are shared server-wide, rather than owned privately by their creator.

Without permission for a type's special click action, the click selects or deselects the type as usual. Unavailable management buttons are hidden and do nothing when clicked.

A copy retains the source's configuration, including the full ballast palette, and receives a new identifier when saved. If you leave its name unset, it uses the first available `Custom Rail N` display name. Renaming `Custom Rail 1` frees that visible name for the next unnamed type, while the original type keeps its stable identifier.

## Custom type editor

Configure the following settings before clicking Save Rail Type or Update Rail Type:

| Setting | Behaviour |
| --- | --- |
| Name and menu icon | Sets the visible name and icon. |
| Rail block | One material or a mix for the rails. Anvils, chipped anvils and damaged anvils appear first in the picker. |
| Blocks below the rails | One or more ballast materials used between and below the rails. The generator distributes the palette across these positions. |
| Sleeper block and spacing | Sets one material or a mix for sleepers and their interval (0–16 blocks). A spacing of 0 disables sleepers. |
| Track count | Generates 1–8 parallel tracks. Multiple tracks require the `multiple` permission. |
| Track spacings | Use +/- for a common spacing, or click the centre control to configure each gap separately (3–32 blocks). |
| Overhead settings | Enables/disables poles and wires. Poles, top supports and wires each have an independent material mix. Also configures pole spacing (1–512), offset (1–16) and height (3–16). |
| Track switches | Stores a setting reserved for future switch generation; enabling it does not currently generate switches. |

### Choosing blocks

The picker opens with the current materials already selected. Click a material to select or deselect it. **Every building-material setting supports multiple materials across pages**: rails, blocks below the rails, sleepers, poles, top supports and wires. Each setting has its own mix. Only the menu icon accepts a single item. Click Next to apply the selection; Back discards changes made in the picker. At least one material must remain selected before you can apply it.

To add your own material:

1. Open the block picker for the setting you want to change.
2. Click a block in your inventory at the bottom, so it is attached to your mouse cursor.
3. Click **Use Your Own Block** (the hopper). Its material is added to the selection.
4. Return the item to your inventory and repeat for any additional materials.
5. Click **Next** to apply the mix, then save the rail type in the editor.

Your inventory's blocks are also included in the displayed choices. Items are not consumed or transferred into the menu. A block held in your hotbar is not enough: it must be attached to the cursor when clicking the hopper. Non-block items are accepted only for the menu icon. This selects the material; custom item metadata and block-state properties are not copied.

The generator distributes each mix over its block positions. Keeping the same configuration and positions produces the same material choices on repeated generations. One selected material gives a uniform result. Material order and repeated entries in a saved mix are preserved when copying or editing unrelated settings. Repeated entries give a material more weight; deselecting it removes all of its entries. Rail orientation and overhead connection properties are applied to the material chosen at each position.

## Permissions

| Permission | Allows |
| --- | --- |
| `btt.generator.use` | Access to generator commands and the generator menu. |
| `btt.generator.rail.use` | Rail generation. |
| `btt.generator.rail.menu` | Opening the rail type menu. |
| `btt.generator.rail.create` | Creating and copying types. |
| `btt.generator.rail.edit` | Editing custom types and reloading types from disk. |
| `btt.generator.rail.delete` | Deleting custom types, individually or in bulk. |
| `btt.generator.rail.multiple` | Generating more than one track. |

## Saved rail types

Custom types are stored in `plugins/BuildTeamTools/modules/generator/rail-types.yml`. Saving from the editor persists the configuration across restarts. Administrators can edit this file and use Reload Rail Types to load changes without restarting the server.

The file stores names, icons, all material mixes, sleepers, track counts and spacings, overhead settings and the reserved switch setting. `schema-version` is managed by the plugin; do not change it manually. Standard remains available as a built-in type.

Schema version 5 stores all building materials as lists under the existing keys. Older single-material values such as `rail-block: ANVIL` are automatically converted to a one-item list when loading an older file, and remain accepted when entered manually. Existing ballast mixes retain their order and repeated entries.

Example custom type:

```yaml
schema-version: 5
rail-types:
mixed-railway:
display-name: Mixed Railway
icon: RAIL
rail-block: [ANVIL, CHIPPED_ANVIL]
blocks-below: [GRAVEL, STONE, COBBLESTONE]
sleeper-block: [SPRUCE_PLANKS, DARK_OAK_PLANKS]
sleeper-spacing: 4
track-count: 2
track-spacing: 5
track-spacings: [5]
overhead:
poles:
enabled: true
block: [LIGHT_GRAY_CONCRETE, GRAY_CONCRETE]
spacing: 16
offset: 3
height: 6
support:
block: [STONE, ANDESITE]
wires:
enabled: true
block: [IRON_BARS, CHAIN]
track-switches:
enabled: false
```

## Generation limits

Server settings are stored in `plugins/BuildTeamTools/modules/generator/config.yml`. The following defaults match the bundled configuration:

```yaml
rail:
max-control-points: 2000
max-path-points: 75000
max-block-placements: 300000
max-prepared-region-volume: 6000000
max-prepared-region-axis-length: 2048
block-placement-batch-size: 750
```

| Setting | Purpose | Allowed range |
| --- | --- | --- |
| `max-control-points` | Limits the selection points used to calculate the route. | 2–2,000 |
| `max-path-points` | Limits calculated path points. | 2–75,000 |
| `max-block-placements` | Limits final block placements per generation. | 1–300,000 |
| `max-prepared-region-volume` | Limits the terrain region prepared for generation. | 1–6,000,000 |
| `max-prepared-region-axis-length` | Limits the prepared region on each axis. | 1–2,048 |
| `block-placement-batch-size` | Controls how many block changes are applied per scheduler tick. | 1–2,000 |

Values outside these ranges are clamped to the supported bounds. `config-version` is managed by the plugin and must not be changed manually.

Lower limits on servers where large generations cause lag or memory pressure. In particular, reduce block placements, prepared region volume and batch size. Smaller batches spread placement over more ticks. These defaults are upper limits, not a guarantee of suitability for a particular amount of server memory.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class BuildTeamToolsCommand implements CommandExecutor, TabCompleter {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String @NotNull [] args) {

if (!sender.hasPermission(Permissions.BUILD_TEAM_TOOLS)) {
Utils.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS);
Permissions.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS);
return true;
}

Expand All @@ -40,7 +40,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N
switch (args[0].toLowerCase()) {
case "communicators" -> {
if (!sender.hasPermission(Permissions.BUILD_TEAM_TOOLS_COMMUNICATORS)) {
Utils.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_COMMUNICATORS);
Permissions.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_COMMUNICATORS);
return true;
}

Expand Down Expand Up @@ -70,7 +70,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N

private static void debugCommand(@NonNull CommandSender sender, String @NonNull [] args) {
if (!sender.hasPermission(Permissions.BUILD_TEAM_TOOLS_DEBUG)) {
Utils.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_DEBUG);
Permissions.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_DEBUG);
return;
}

Expand All @@ -88,7 +88,7 @@ private static void debugCommand(@NonNull CommandSender sender, String @NonNull

private static void cacheCommand(@NonNull CommandSender sender, String @NonNull [] args) {
if (!sender.hasPermission(Permissions.BUILD_TEAM_TOOLS_CACHE)) {
Utils.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_CACHE);
Permissions.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_CACHE);
return;
}

Expand All @@ -110,7 +110,7 @@ private static void cacheCommand(@NonNull CommandSender sender, String @NonNull

private static void reloadCommand(@NonNull CommandSender sender) {
if (!sender.hasPermission(Permissions.BUILD_TEAM_TOOLS_RELOAD)) {
Utils.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_RELOAD);
Permissions.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_RELOAD);
return;
}

Expand All @@ -127,7 +127,7 @@ private static void reloadCommand(@NonNull CommandSender sender) {

private void updateCommand(@NotNull CommandSender sender) {
if (!sender.hasPermission(Permissions.BUILD_TEAM_TOOLS_UPDATE)) {
Utils.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_UPDATE);
Permissions.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_UPDATE);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import net.buildtheearth.buildteamtools.BuildTeamTools;
import net.buildtheearth.buildteamtools.modules.ModuleComponent;
import net.buildtheearth.buildteamtools.modules.network.model.Permissions;
import net.buildtheearth.buildteamtools.utils.Utils;
import net.buildtheearth.buildteamtools.utils.io.ConfigPaths;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
Expand Down Expand Up @@ -84,7 +83,7 @@ public void update(CommandSender sender, boolean checkMsg) {

public void checkForUpdates(@NonNull CommandSender sender) {
if (!sender.hasPermission(Permissions.BUILD_TEAM_TOOLS_CHECK_FOR_UPDATES)) {
Utils.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_CHECK_FOR_UPDATES);
Permissions.sendNoPermissionMessage(sender, Permissions.BUILD_TEAM_TOOLS_CHECK_FOR_UPDATES);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Polygonal2DRegion;
import com.sk89q.worldedit.regions.Region;
import lombok.Getter;
import net.buildtheearth.buildteamtools.BuildTeamTools;
import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule;
import net.buildtheearth.buildteamtools.modules.generator.components.rail.configuration.RailTypeManager;
import net.buildtheearth.buildteamtools.modules.generator.components.rail.generation.RailScripts;
import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent;
import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType;
import net.buildtheearth.buildteamtools.modules.network.model.Permissions;
import org.bukkit.Sound;
import org.bukkit.entity.Player;

Expand All @@ -20,8 +25,12 @@ public class Rail extends GeneratorComponent {

private final Set<UUID> preparingPlayers = ConcurrentHashMap.newKeySet();

@Getter
private final RailTypeManager railTypeManager;

public Rail() {
super(GeneratorType.RAIL);
railTypeManager = new RailTypeManager(BuildTeamTools.getInstance().getDataFolder());
}

@Override
Expand All @@ -38,7 +47,7 @@ public boolean checkForPlayer(Player player) {
"Rail Generator supports cuboid, polygonal and convex WorldEdit selections."
)));
player.closeInventory();
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0F, 1.0F);
playSound(player, Sound.ENTITY_ITEM_BREAK);
return false;
}

Expand All @@ -50,6 +59,9 @@ private boolean isSupportedRailSelection(Region region) {

@Override
public void generate(Player player) {
if (!Permissions.checkPermission(player, Permissions.RAIL_GENERATOR_USE))
return;

if (GeneratorModule.getInstance().isGenerating(player) || !preparingPlayers.add(player.getUniqueId())) {
sendAlreadyGeneratingMessage(player);
return;
Expand All @@ -67,6 +79,10 @@ private void sendAlreadyGeneratingMessage(Player player) {
player.sendMessage(ChatHelper.PREFIX_COMPONENT.append(ChatHelper.getErrorComponent(
"Rail Generator is already running. Please wait until the current generation is finished."
)));
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0F, 1.0F);
playSound(player, Sound.ENTITY_ITEM_BREAK);
}

private void playSound(Player player, Sound sound) {
player.playSound(player, sound, 1.0F, 1.0F);
}
}
Loading