Skip to content

Commit

Permalink
Biome Overhaul (#75)
Browse files Browse the repository at this point in the history
* Implement a modern GUI framework

* Biome update
- Biomes are now configurable by the administrator
- The biome GUI automatically adjusts to the biomes available to the player
- The biome command and its tab completion use the configured biomes
- The biome menu use the new GUI framework

* Add nullability annotations and checks

* Slight improvements to biome GUI rendering

* Add newer biome types to support spawning of all passive mobs.

Resolves #39
  • Loading branch information
minoneer authored Feb 5, 2025
1 parent 5bed2ba commit 4fbd7ad
Show file tree
Hide file tree
Showing 17 changed files with 809 additions and 258 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package us.talabrek.ultimateskyblock.biome;

import dk.lockfuglsang.minecraft.file.FileUtil;
import dk.lockfuglsang.minecraft.util.ItemStackUtil;
import org.bukkit.Material;
import org.bukkit.Registry;
import org.bukkit.block.Biome;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import static java.util.Objects.requireNonNull;

public class BiomeConfig {

private final Logger logger;
private final List<BiomeEntry> configuredBiomeEntries;
private final List<String> configuredBiomeKeys;

public BiomeConfig(Logger logger) {
this.logger = logger;
this.configuredBiomeEntries = loadBiomes();
this.configuredBiomeKeys = configuredBiomeEntries.stream()
.map(entry -> entry.biome().getKey().getKey())
.toList();
}

private @NotNull List<BiomeEntry> loadBiomes() {
FileConfiguration biomeConfig = FileUtil.getYmlConfiguration("biomes.yml");
ConfigurationSection biomeSection = biomeConfig.getConfigurationSection("biomes");
if (biomeSection == null) {
throw new IllegalStateException("You biomes.yml is corrupted, missing 'biomes' section.");
}
List<BiomeEntry> result = new ArrayList<>();
for (String biomeKey : biomeSection.getKeys(false)) {
ConfigurationSection section = requireNonNull(biomeSection.getConfigurationSection(biomeKey));
BiomeEntry biomeEntry = readBiomeEntry(biomeKey, section);
if (biomeEntry != null) {
result.add(biomeEntry);
}
}
return result;
}

public @NotNull List<BiomeEntry> getConfiguredBiomeEntries() {
return configuredBiomeEntries;
}

public @NotNull List<BiomeEntry> getAvailableBiomes(@NotNull Player player) {
return configuredBiomeEntries.stream()
.filter(entry -> player.hasPermission("usb.biome." + entry.biome().getKey().getKey()))
.toList();
}

public @NotNull List<String> getConfiguredBiomeKeys() {
return configuredBiomeKeys;
}

private @Nullable BiomeEntry readBiomeEntry(@NotNull String biomeKey, @NotNull ConfigurationSection section) {
String itemSpecification = section.getString("displayItem");
String name = section.getString("name");
String description = section.getString("description");

Biome biome = Registry.BIOME.match(biomeKey);
if (biome == null) {
logger.warning("Unknown biome key: " + biomeKey);
return null;
}

ItemStack displayItem;
try {
displayItem = ItemStackUtil.createItemStack(itemSpecification);
} catch (IllegalArgumentException e) {
logger.warning("Invalid item specification for biome " + biomeKey + ": " + itemSpecification);
displayItem = new ItemStack(Material.GRASS_BLOCK);
}

if (name == null) {
logger.warning("Missing name for biome " + biomeKey);
name = biomeKey;
}

if (description == null) {
logger.warning("Missing description for biome " + biomeKey);
description = "";
}

return new BiomeEntry(displayItem, biome, name, description);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package us.talabrek.ultimateskyblock.biome;

import org.bukkit.block.Biome;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;

public record BiomeEntry(
@NotNull ItemStack displayItem,
@NotNull Biome biome,
@NotNull String name,
@NotNull String description
) {

public BiomeEntry {
displayItem = displayItem.clone();
}

@Override
public @NotNull ItemStack displayItem() {
return displayItem.clone();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package us.talabrek.ultimateskyblock.biome;

import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import us.talabrek.ultimateskyblock.gui.InventoryButton;
import us.talabrek.ultimateskyblock.gui.InventoryGui;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static dk.lockfuglsang.minecraft.po.I18nUtil.tr;
import static java.util.Objects.requireNonNull;

public class BiomeGui extends InventoryGui {

private static final int MAX_INVENTORY_SIZE = 54;
private static final int MIN_INVENTORY_SIZE = 18;
private static final int ROW_SIZE = 9;
private static final List<String> RADIUS_OPTIONS =
List.of("10", "chunk", "20", "30", "40", "50", "60", "70", "80", "90", "100", "all");

private final List<BiomeEntry> biomes;
private final Biome currentBiome;
private final int inventorySize;
private String radius = "all";

public BiomeGui(@NotNull List<BiomeEntry> biomes, @NotNull Biome currentBiome) {
super(createInventory(computeInventorySize(biomes.size())));
this.biomes = requireNonNull(biomes);
this.currentBiome = requireNonNull(currentBiome);;
this.inventorySize = computeInventorySize(biomes.size());
}

private static int computeInventorySize(int biomesSize) {
int inventorySize = ((int) Math.ceil((double) biomesSize / (double) ROW_SIZE)) * ROW_SIZE;
inventorySize += ROW_SIZE; // Add one row for radius controls
inventorySize = Math.min(inventorySize, MAX_INVENTORY_SIZE);
inventorySize = Math.max(inventorySize, MIN_INVENTORY_SIZE);
return inventorySize;
}

private static Inventory createInventory(int size) {
return Bukkit.createInventory(null, size, "\u00a79" + tr("Island Biome"));
}

@Override
public void decorate(@NotNull Player player) {
decorateBiomes();
decorateRadiusControls();
decorateBackButton();
super.decorate(player);
}

private void decorateBiomes() {
int displayedBiomes = Math.min(biomes.size(), inventorySize - ROW_SIZE);
for (int i = 0; i < displayedBiomes; i++) {
BiomeEntry biomeEntry = biomes.get(i);
this.addButton(i, createBiomeButton(biomeEntry, currentBiome));
}
}

private InventoryButton createBiomeButton(BiomeEntry biomeEntry, Biome currentBiome) {
ItemStack displayItem = biomeEntry.displayItem().clone();
ItemMeta itemMeta = Objects.requireNonNull(displayItem.getItemMeta());
itemMeta.setDisplayName("\u00a7a" + tr("Biome: {0}", biomeEntry.name()));
List<String> lore = new ArrayList<>(
Arrays.stream(biomeEntry.description().split("\\R"))
.filter(line -> !line.isBlank())
.map(line -> "\u00a7f" + line)
.toList()
);
if (biomeEntry.biome().equals(currentBiome)) {
lore.add(tr("\u00a72\u00a7lThis is your current biome."));
itemMeta.addEnchant(Enchantment.LOYALTY, 1, true);
} else {
lore.add(tr("\u00a7e\u00a7lClick to change to this biome."));
}
itemMeta.setLore(lore);
displayItem.setItemMeta(itemMeta);

return new InventoryButton()
.creator((player) -> displayItem)
.consumer((player, event) -> {
player.performCommand("island biome " + biomeEntry.biome().getKey().getKey() + " " + radius);
player.closeInventory();
});
}

private void decorateRadiusControls() {
this.addButton(inventorySize - 6, creteMinusButton());
this.addButton(inventorySize - 5, createRadiusDisplay());
this.addButton(inventorySize - 4, createPlusButton());
}

private InventoryButton creteMinusButton() {
return new InventoryButton()
.creator((player) -> {
ItemStack displayItem = new ItemStack(Material.RED_CARPET);
ItemMeta itemMeta = requireNonNull(requireNonNull(displayItem.getItemMeta()));
itemMeta.setDisplayName(tr("\u00a7c-"));
List<String> lore = List.of(
tr("Decrease radius of biome change"),
tr("Current radius: {0}", formatRadius(radius))
);
itemMeta.setLore(lore);
displayItem.setItemMeta(itemMeta);
return displayItem;
})
.consumer((player, event) -> {
int ix = RADIUS_OPTIONS.indexOf(radius);
if (ix > 0) {
radius = RADIUS_OPTIONS.get(ix - 1);
}
decorate(player);
});
}

private InventoryButton createPlusButton() {
return new InventoryButton()
.creator((player) -> {
ItemStack displayItem = new ItemStack(Material.GREEN_CARPET);
ItemMeta itemMeta = requireNonNull(requireNonNull(displayItem.getItemMeta()));
itemMeta.setDisplayName(tr("\u00a72+"));
List<String> lore = List.of(
tr("Increase radius of biome change"),
tr("Current radius: {0}", formatRadius(radius))
);
itemMeta.setLore(lore);
displayItem.setItemMeta(itemMeta);
return displayItem;
})
.consumer((player, event) -> {
int ix = RADIUS_OPTIONS.indexOf(radius);
if (ix < RADIUS_OPTIONS.size() - 1) {
radius = RADIUS_OPTIONS.get(ix + 1);
}
decorate(player);
});
}

private InventoryButton createRadiusDisplay() {
return new InventoryButton()
.creator((player) -> {
ItemStack displayItem = new ItemStack(Material.GRASS_BLOCK);
ItemMeta itemMeta = requireNonNull(requireNonNull(displayItem.getItemMeta()));
itemMeta.setDisplayName(tr("Current radius: {0}", formatRadius(radius)));
displayItem.setItemMeta(itemMeta);
return displayItem;
});
}

private static String formatRadius(String radius) {
return switch (radius) {
case "chunk" -> tr("\u00a72chunk");
case "all" -> tr("\u00a7call");
default -> tr("\u00a7e{0}", radius);
};
}

private void decorateBackButton() {
InventoryButton backButton = new InventoryButton()
.creator((player) -> {
ItemStack displayItem = new ItemStack(Material.OAK_SIGN);
ItemMeta itemMeta = requireNonNull(displayItem.getItemMeta());
itemMeta.setDisplayName(tr("\u00a7cBack to Main Menu"));
itemMeta.setLore(List.of(tr("\u00a7eClick here to return to the main island screen.")));
displayItem.setItemMeta(itemMeta);
return displayItem;
})
.consumer((player, event) -> player.performCommand("island"));
this.addButton(inventorySize - 9, backButton);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package us.talabrek.ultimateskyblock.biome;

import org.bukkit.entity.Player;
import us.talabrek.ultimateskyblock.gui.GuiManager;
import us.talabrek.ultimateskyblock.island.IslandInfo;

public class Biomes {

private final GuiManager guiManager;
private final BiomeConfig biomeConfig;

public Biomes(GuiManager guiManager, BiomeConfig biomeConfig) {
this.guiManager = guiManager;
this.biomeConfig = biomeConfig;
}

public void openBiomeGui(Player player, IslandInfo islandInfo) {
BiomeGui biomeGui = new BiomeGui(biomeConfig.getAvailableBiomes(player), islandInfo.getIslandBiome());
guiManager.openGui(biomeGui, player);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import dk.lockfuglsang.minecraft.command.DocumentCommand;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import us.talabrek.ultimateskyblock.biome.BiomeConfig;
import us.talabrek.ultimateskyblock.command.admin.AbstractPlayerInfoCommand;
import us.talabrek.ultimateskyblock.command.admin.AdminChallengeCommand;
import us.talabrek.ultimateskyblock.command.admin.AdminIslandCommand;
Expand Down Expand Up @@ -44,12 +45,13 @@
* The new admin command, alias /usb
*/
public class AdminCommand extends BaseCommandExecutor {
public AdminCommand(final uSkyBlock plugin, ConfirmHandler confirmHandler, AnimationHandler animationHandler) {
public AdminCommand(final uSkyBlock plugin, ConfirmHandler confirmHandler, AnimationHandler animationHandler,
BiomeConfig biomeConfig) {
super("usb", null, marktr("Ultimate SkyBlock Admin"));
OnlinePlayerTabCompleter playerCompleter = new OnlinePlayerTabCompleter();
TabCompleter challengeCompleter = new ChallengeTabCompleter();
TabCompleter allPlayerCompleter = new AllPlayerTabCompleter(playerCompleter);
TabCompleter biomeCompleter = new BiomeTabCompleter();
TabCompleter biomeCompleter = new BiomeTabCompleter(biomeConfig);
addTab("oplayer", playerCompleter);
addTab("player", allPlayerCompleter);
addTab("island", allPlayerCompleter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import us.talabrek.ultimateskyblock.Settings;
import us.talabrek.ultimateskyblock.biome.BiomeConfig;
import us.talabrek.ultimateskyblock.biome.Biomes;
import us.talabrek.ultimateskyblock.command.completion.AllPlayerTabCompleter;
import us.talabrek.ultimateskyblock.command.completion.BiomeTabCompleter;
import us.talabrek.ultimateskyblock.command.completion.MemberTabCompleter;
Expand Down Expand Up @@ -49,7 +51,7 @@ public class IslandCommand extends BaseCommandExecutor {
private final uSkyBlock plugin;
private final SkyBlockMenu menu;

public IslandCommand(uSkyBlock plugin, SkyBlockMenu menu) {
public IslandCommand(uSkyBlock plugin, SkyBlockMenu menu, Biomes biomes, BiomeConfig biomeConfig) {
super("island|is", "usb.island.create", marktr("general island command"));
this.plugin = plugin;
this.menu = menu;
Expand All @@ -66,7 +68,7 @@ public IslandCommand(uSkyBlock plugin, SkyBlockMenu menu) {
addTab("island", playerTabCompleter);
addTab("player", playerTabCompleter);
addTab("oplayer", onlinePlayerTabCompleter);
addTab("biome", new BiomeTabCompleter());
addTab("biome", new BiomeTabCompleter(biomeConfig));
addTab("member", new MemberTabCompleter(plugin));
addTab("schematic", new SchematicTabCompleter(plugin));
addTab("perm", new PermissionTabCompleter(plugin));
Expand All @@ -85,7 +87,7 @@ public IslandCommand(uSkyBlock plugin, SkyBlockMenu menu) {
if (Settings.island_useTopTen) {
add(new TopCommand(plugin));
}
add(new BiomeCommand(plugin, menu));
add(new BiomeCommand(plugin, biomes, biomeConfig));
add(new LevelCommand(plugin));
add(new InfoCommand(plugin));
add(new InviteCommand(plugin, inviteHandler));
Expand All @@ -106,8 +108,7 @@ public boolean onCommand(CommandSender sender, Command command, String alias, St
if (!plugin.isRequirementsMet(sender, this, args)) {
return true;
}
if (sender instanceof Player) {
Player player = (Player) sender;
if (sender instanceof Player player) {
if (args.length == 0) {
player.openInventory(menu.displayIslandGUI(player));
return true;
Expand Down
Loading

0 comments on commit 4fbd7ad

Please sign in to comment.