Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a1ac274
docs: barrel loot implementation plan
May 24, 2026
dc7ceda
feat(lootify): add tiered barrel food loot map
May 24, 2026
14d3bf4
refactor(lootify): source mean/stddev defaults from field, not literals
May 24, 2026
82e8b6e
feat(lootify): ship treasure_barrel_food premade (mean=1, stddev=0.7)
May 24, 2026
ce3a438
feat(lootify): generator-level barrel loot config (default on)
May 24, 2026
5f5d699
feat(lootify): module-level barrel loot config (default on)
May 24, 2026
7fc7416
feat(lootify): per-schematic barrelTreasureFilename override
May 24, 2026
3fb04bf
feat(lootify): inherent barrel detection + routing in schematic pipeline
May 24, 2026
5abca34
fix(lootify): correct schematic treasure-file resolution in fillChests
May 24, 2026
dd5e2b6
feat(lootify): inherent barrel detection + fill in module pipeline
May 24, 2026
e906bc7
fix(lootify): correct treasure lookup + soften failure handling in Sc…
May 24, 2026
461a352
feat(lootify): per-module barrel treasure + opt-out in module pipeline
May 24, 2026
04e7eed
style(lootify): import Set/HashSet instead of fully-qualifying
May 24, 2026
dafb849
fix(lootify): drop spurious eager barrel-treasure lookup in ModulesCo…
May 24, 2026
a97639c
fix(lootify): use treasure_barrel_food.yml in references; normalize l…
May 24, 2026
7086cf7
BetterStructures 2.4.0:
May 28, 2026
99d77a7
Remove completed planning docs
May 30, 2026
ac82055
Add docs/ to .gitignore
Jun 3, 2026
3b60757
BetterStructures 2.5.0: in-game plugin updates, setup overhaul, world…
Jun 21, 2026
c766939
Add CraftEngine & Other Plugins CustomBlock Support
MaXoNeRYT Jun 23, 2026
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
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/

# IDE
.idea/
.run/
*.iml

# Test server
Expand All @@ -17,3 +18,14 @@ Thumbs.db
codex-build.log
.claude/
Get
# Local design docs and plan notes - not tracked
docs/

# Local IDE/tooling and generated debris
.vscode/
.classpath
.project
.settings/
target/
out/
dependency-reduced-pom.xml
17 changes: 15 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group = 'com.magmaguy'
version = '2.3.1'
version = '2.5.0'

repositories {
mavenCentral()
Expand Down Expand Up @@ -65,7 +65,20 @@ shadowJar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier.set(null)
archiveFileName.set(project.name + ".jar")
destinationDirectory.set(new File("testbed/plugins"))
}

// --- Shared dist mirror -----------------------------------------------------
// Copy the finished shaded jar into the top-level dist/ folder so the testbeds
// and the release tool pick every build up from one place. Gated on MC_DIST_DIR
// (or -PmcDistDir) so clones/CI that don't set it just build to build/libs and
// no machine-specific path is ever committed.
def mcDistDir = System.getenv('MC_DIST_DIR') ?: project.findProperty('mcDistDir')
if (mcDistDir) {
tasks.register('mirrorToDist', Copy) {
from tasks.named('shadowJar')
into mcDistDir
}
tasks.named('shadowJar') { finalizedBy('mirrorToDist') }
}

tasks.withType(JavaCompile) {
Expand Down
44 changes: 40 additions & 4 deletions src/main/java/com/magmaguy/betterstructures/BetterStructures.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
import com.magmaguy.magmacore.initialization.PluginInitializationConfig;
import com.magmaguy.magmacore.initialization.PluginInitializationContext;
import com.magmaguy.magmacore.initialization.PluginInitializationState;
import com.magmaguy.magmacore.nightbreak.NightbreakDownloadContentCommand;
import com.magmaguy.magmacore.nightbreak.NightbreakDownloadEverythingCommand;
import com.magmaguy.magmacore.nightbreak.NightbreakDownloadPluginUpdateCommand;
import com.magmaguy.magmacore.nightbreak.NightbreakPluginSpec;
import com.magmaguy.magmacore.nightbreak.NightbreakPluginUpdater;
import com.magmaguy.magmacore.nightbreak.NightbreakPluginStateRegistry;
import com.magmaguy.magmacore.nightbreak.NightbreakRecommendedPluginsCommand;
import com.magmaguy.magmacore.util.Logger;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
Expand All @@ -32,8 +39,17 @@
import org.bukkit.plugin.java.JavaPlugin;

import java.io.IOException;
import java.util.ArrayList;

public final class BetterStructures extends JavaPlugin {
public static final NightbreakPluginSpec NIGHTBREAK_PLUGIN_SPEC = new NightbreakPluginSpec(
"BetterStructures",
"bs",
"betterstructures.*",
"betterstructures.setup",
"betterstructures.initialize",
"https://nightbreak.io/plugin/betterstructures/",
"Reloaded BetterStructures.");

@Override
public void onEnable() {
Expand All @@ -51,14 +67,20 @@ public void onEnable() {
throw new RuntimeException(e);
}
MagmaCore.onEnable(this);
MagmaCore.exportSharedAssets(this);
MagmaCore.startInitialization(this,
new PluginInitializationConfig("BetterStructures", "betterstructures.*", 16),
this::asyncInitialization,
this::syncInitialization,
() -> {
Logger.info("BetterStructures fully initialized!");
if (MetadataHandler.pendingReloadSender != null) {
Logger.sendMessage(MetadataHandler.pendingReloadSender, "Reloaded BetterStructures.");
NightbreakPluginUpdater.autoDownloadPluginUpdateIfEnabled(this, NIGHTBREAK_PLUGIN_SPEC);
CommandSender pendingReloadSender = NightbreakPluginStateRegistry.consumePendingReloadSender(this);
if (pendingReloadSender == null) {
pendingReloadSender = MetadataHandler.pendingReloadSender;
}
if (pendingReloadSender != null) {
Logger.sendMessage(pendingReloadSender, NIGHTBREAK_PLUGIN_SPEC.reloadSuccessMessage());
MetadataHandler.pendingReloadSender = null;
}
},
Expand Down Expand Up @@ -150,8 +172,22 @@ private void syncInitialization(PluginInitializationContext initializationContex
commandManager.registerCommand(new VersionCommand());
commandManager.registerCommand(new SetupCommand());
commandManager.registerCommand(new FirstTimeSetupCommand());
commandManager.registerCommand(new DownloadAllContentCommand());
commandManager.registerCommand(new UpdateContentCommand());
commandManager.registerCommand(new NightbreakRecommendedPluginsCommand(this, NIGHTBREAK_PLUGIN_SPEC));
commandManager.registerCommand(new NightbreakDownloadPluginUpdateCommand(this, NIGHTBREAK_PLUGIN_SPEC));
commandManager.registerCommand(new NightbreakDownloadEverythingCommand<>(this,
NIGHTBREAK_PLUGIN_SPEC,
() -> new ArrayList<>(BSPackage.getBsPackages().values()),
ReloadCommand::reload));
commandManager.registerCommand(new NightbreakDownloadContentCommand<>(this,
NIGHTBREAK_PLUGIN_SPEC,
() -> new ArrayList<>(BSPackage.getBsPackages().values()),
ReloadCommand::reload,
false));
commandManager.registerCommand(new NightbreakDownloadContentCommand<>(this,
NIGHTBREAK_PLUGIN_SPEC,
() -> new ArrayList<>(BSPackage.getBsPackages().values()),
ReloadCommand::reload,
true));
commandManager.registerCommand(new GenerateModulesCommand());
commandManager.registerCommand(new BetterStructuresCommand());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.magmaguy.betterstructures.buildingfitter.util.FitUndergroundDeepBuilding;
import com.magmaguy.betterstructures.buildingfitter.util.LocationProjector;
import com.magmaguy.betterstructures.buildingfitter.util.SchematicPicker;
import com.magmaguy.betterstructures.chests.ChestContents;
import com.magmaguy.betterstructures.config.DefaultConfig;
import com.magmaguy.betterstructures.config.generators.GeneratorConfigFields;
import com.magmaguy.betterstructures.schematics.SchematicContainer;
Expand Down Expand Up @@ -288,30 +289,47 @@ private void clearTrees(Location location) {
}

private void fillChests() {
if (schematicContainer.getGeneratorConfigFields().getChestContents() != null)
for (Vector chestPosition : schematicContainer.getChestLocations()) {
Location chestLocation = LocationProjector.project(location, schematicOffset, chestPosition);
if (!(chestLocation.getBlock().getState() instanceof Container container)) {
Logger.warn("Expected a container for " + chestLocation.getBlock().getType() + " but didn't get it. Skipping this loot!");
continue;
}
GeneratorConfigFields gen = schematicContainer.getGeneratorConfigFields();
boolean barrelsEnabled = gen.isGenerateLootInBarrels() && gen.getBarrelContents() != null;
boolean chestsEnabled = gen.getChestContents() != null;
if (!barrelsEnabled && !chestsEnabled) return;

for (Vector chestPosition : schematicContainer.getChestLocations()) {
Location chestLocation = LocationProjector.project(location, schematicOffset, chestPosition);
if (!(chestLocation.getBlock().getState() instanceof Container container)) {
Logger.warn("Expected a container for " + chestLocation.getBlock().getType() + " but didn't get it. Skipping this loot!");
continue;
}

String treasureFilename;
if (schematicContainer.getChestContents() != null) {
schematicContainer.getChestContents().rollChestContents(container);
treasureFilename = schematicContainer.getSchematicConfigField().getTreasureFile();
} else {
schematicContainer.getGeneratorConfigFields().getChestContents().rollChestContents(container);
treasureFilename = schematicContainer.getGeneratorConfigFields().getTreasureFilename();
}
boolean isBarrel = container.getBlock().getType() == Material.BARREL;
if (isBarrel && !barrelsEnabled) continue;
if (!isBarrel && !chestsEnabled) continue;

ChestContents contents;
String treasureFilename;
if (isBarrel) {
contents = schematicContainer.getBarrelContents();
String schematicBarrelFile = schematicContainer.getSchematicConfigField().getBarrelTreasureFilename();
treasureFilename = (schematicBarrelFile != null && !schematicBarrelFile.isEmpty())
? schematicBarrelFile
: gen.getBarrelTreasureFilename();
} else {
contents = schematicContainer.getChestContents();
String schematicTreasureFile = schematicContainer.getSchematicConfigField().getTreasureFile();
treasureFilename = (schematicTreasureFile != null && !schematicTreasureFile.isEmpty())
? schematicTreasureFile
: gen.getTreasureFilename();
}

ChestFillEvent chestFillEvent = new ChestFillEvent(container, treasureFilename);
Bukkit.getServer().getPluginManager().callEvent(chestFillEvent);
if (!chestFillEvent.isCancelled()) {
container.update(true);
if (contents == null) continue;
contents.rollChestContents(container);

}
ChestFillEvent chestFillEvent = new ChestFillEvent(container, treasureFilename);
Bukkit.getServer().getPluginManager().callEvent(chestFillEvent);
if (!chestFillEvent.isCancelled()) {
container.update(true);
}
}
}

private void spawnEntities() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ public BetterStructuresCommand() {
@Override
public void execute(CommandData commandData) {
Logger.sendMessage(commandData.getCommandSender(), "BetterStructures is a plugin that adds random structures to your Minecraft world!");
Logger.sendMessage(commandData.getCommandSender(), "You can check installed content and download structure packs in the &2/betterstructures setup &fcommand.");
Logger.sendMessage(commandData.getCommandSender(), "If your Nightbreak account is linked, you can install everything with &2/betterstructures downloadall&f.");
Logger.sendMessage(commandData.getCommandSender(), "Use &2/bs setup &fto manage structure packs, plugin updates, and update settings.");
Logger.sendMessage(commandData.getCommandSender(), "Use &2/bs recommendedplugins &fto see plugins that work well with BetterStructures.");
Logger.sendMessage(commandData.getCommandSender(), "Use &2/bs downloadall &fto check plugin and content updates from one command.");
Logger.sendMessage(commandData.getCommandSender(), "Once a pack is installed, structures will automatically generate in freshly generated chunks. You do not have to run any commands for this to happen.");
Logger.sendMessage(commandData.getCommandSender(), "By default, OPs will get notified about new structures generating until they disable these messages.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.magmaguy.magmacore.command.SenderType;
import com.magmaguy.magmacore.nightbreak.NightbreakAccount;
import com.magmaguy.magmacore.nightbreak.NightbreakContentManager;
import com.magmaguy.magmacore.nightbreak.NightbreakSetupMenuHelper;
import com.magmaguy.magmacore.util.Logger;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
Expand All @@ -25,11 +26,11 @@ public class DownloadAllContentCommand extends AdvancedCommand {
static final AtomicBoolean IS_BULK_DOWNLOADING = new AtomicBoolean(false);

public DownloadAllContentCommand() {
super(List.of("downloadall"));
super(List.of("downloadallcontent"));
setPermission("betterstructures.setup");
setSenderType(SenderType.ANY);
setDescription("Downloads all BetterStructures content available through Nightbreak.");
setUsage("/bs downloadall");
setDescription("Downloads all available BetterStructures content.");
setUsage("/bs downloadallcontent");
}

@Override
Expand All @@ -39,7 +40,11 @@ public void execute(CommandData commandData) {

public static void execute(CommandSender sender, boolean updatesOnly) {
if (!NightbreakAccount.hasToken()) {
Logger.sendSimpleMessage(sender, "&cLink your Nightbreak account first with &a/nightbreaklogin <token>&c.");
Logger.sendSimpleMessage(sender, "&cConnect this server first with &a/nightbreaklogin <token>&c.");
return;
}
if (NightbreakAccount.hasAuthFailure()) {
NightbreakSetupMenuHelper.sendTokenUpdatePrompt(sender, "BetterStructures");
return;
}

Expand All @@ -54,7 +59,7 @@ public static void execute(CommandSender sender, boolean updatesOnly) {
IS_BULK_DOWNLOADING.set(false);
Logger.sendSimpleMessage(sender, updatesOnly
? "&aAll BetterStructures content is already up to date."
: "&aAll BetterStructures Nightbreak content is already downloaded and up to date.");
: "&aAll BetterStructures content is already downloaded and up to date.");
return;
}

Expand Down Expand Up @@ -112,6 +117,7 @@ private static void downloadNext(JavaPlugin plugin,
if (success) {
completed.incrementAndGet();
} else {
if (abortIfAuthFailure(sender, player)) return;
failed.incrementAndGet();
failedNames.add(bsPackage.getDisplayName());
}
Expand Down Expand Up @@ -149,6 +155,7 @@ private static void downloadNext(JavaPlugin plugin,
: "&aDownloaded " + bsPackage.getDisplayName() + "&a.");
}
} else {
if (abortIfAuthFailure(sender, player)) return;
failed.incrementAndGet();
failedNames.add(bsPackage.getDisplayName());
if (player == null || player.isOnline()) {
Expand All @@ -158,4 +165,12 @@ private static void downloadNext(JavaPlugin plugin,
downloadNext(plugin, packages, index + 1, importsFolder, sender, player, completed, failed, failedNames, updatesOnly);
});
}

private static boolean abortIfAuthFailure(CommandSender sender, Player player) {
if (!NightbreakAccount.hasAuthFailure()) return false;
IS_BULK_DOWNLOADING.set(false);
CommandSender target = player != null && !player.isOnline() ? Bukkit.getConsoleSender() : sender;
NightbreakSetupMenuHelper.sendTokenUpdatePrompt(target, "BetterStructures");
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

public class UpdateContentCommand extends AdvancedCommand {
public UpdateContentCommand() {
super(List.of("updatecontent", "updateall"));
super(List.of("updatecontent", "updateallcontent"));
setPermission("betterstructures.setup");
setSenderType(SenderType.ANY);
setDescription("Downloads updates for outdated BetterStructures Nightbreak content.");
setDescription("Downloads updates for outdated BetterStructures content.");
setUsage("/bs updatecontent");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.magmaguy.magmacore.config.ConfigurationEngine;
import com.magmaguy.magmacore.config.ConfigurationFile;
import com.magmaguy.magmacore.nightbreak.NightbreakPluginUpdater;
import lombok.Getter;

import java.util.List;
Expand Down Expand Up @@ -76,6 +77,8 @@ public class DefaultConfig extends ConfigurationFile {

@Getter
private static int spawnProtectionRadius;
@Getter
private static boolean autoDownloadPluginUpdates;

public DefaultConfig() {
super("config.yml");
Expand Down Expand Up @@ -120,6 +123,7 @@ public void initializeValues() {
percentageOfTickUsedForPregeneration = ConfigurationEngine.setDouble(List.of("Sets the maximum percentage of a tick that BetterStructures will use for world pregeneration when using the pregenerate command.", "Ranges from 0.01 to 1, where 0.01 is 1% and 1 is 100%.", "This controls how much of each server tick is dedicated to generating chunks, allowing you to balance generation speed with server performance.", "Lower values will generate chunks more slowly but reduce server lag, while higher values will generate faster but may impact server performance."), fileConfiguration, "percentageOfTickUsedForPregeneration", 0.1);
pregenerationTPSPauseThreshold = ConfigurationEngine.setDouble(List.of("The TPS threshold at which chunk pregeneration will pause to protect server performance.", "When server TPS drops below this value, pregeneration will pause until TPS recovers.", "Default: 12.0"), fileConfiguration, "pregenerationTPSPauseThreshold", 12.0);
pregenerationTPSResumeThreshold = ConfigurationEngine.setDouble(List.of("The TPS threshold at which chunk pregeneration will resume after being paused.", "Pregeneration will only resume when server TPS is at or above this value.", "Should be higher than the pause threshold to prevent rapid pause/resume cycles.", "Default: 14.0"), fileConfiguration, "pregenerationTPSResumeThreshold", 14.0);
autoDownloadPluginUpdates = NightbreakPluginUpdater.setAutoDownloadConfigDefault(fileConfiguration);

// Initialize the distances from configuration
distanceSurface = ConfigurationEngine.setInt(
Expand Down Expand Up @@ -193,4 +197,4 @@ public void initializeValues() {

ConfigurationEngine.fileSaverOnlyDefaults(fileConfiguration, file);
}
}
}
Loading