Add config options for mechanics and world generation, add runnable to kick players from dreamlands when a day starts, move teleporting logic to PlayerTeleporter, make player drop items when dying, play different sounds and send different messages depending on kick reason.

This commit is contained in:
天クマ 2026-05-31 21:45:29 -03:00
commit b346834774
8 changed files with 226 additions and 88 deletions

View file

@ -10,12 +10,15 @@ public class Configuration {
private final YamlConfiguration playerStorage = new YamlConfiguration();
private final File playerStorageFile;
private final YamlConfiguration pluginConfig = new YamlConfiguration();
private final File pluginConfigFile = new File("");
private final File pluginConfigFile;
private final SweetDreams plugin;
public Configuration() {
plugin = SweetDreams.getPlugin();
playerStorageFile = new File(plugin.getDataFolder(), "playerstorage.yml");
pluginConfigFile = new File(plugin.getDataFolder(), "config.yml");
plugin.saveResource("config.yml", false);
if (playerStorageFile.exists()) {
try {
@ -25,6 +28,15 @@ public class Configuration {
throw new RuntimeException(e);
}
}
if (pluginConfigFile.exists()) {
try {
pluginConfig.load(pluginConfigFile);
} catch (IOException | InvalidConfigurationException e) {
plugin.getLogger().severe("Error loading plugin configuration: " + e.getMessage());
throw new RuntimeException(e);
}
}
}
public void savePlayerStorage() {

View file

@ -1,10 +1,8 @@
package org.adrianvictor.sweetdreams;
import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@ -14,20 +12,18 @@ import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerBedEnterEvent;
import org.bukkit.util.Vector;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
public class EventListener implements Listener {
private final PlayerStorage storage;
private final Set<UUID> teleporting = new HashSet<>();
private final PlayerTeleporter tp;
private final YamlConfiguration configuration;
public EventListener() {
this.storage = SweetDreams.getPlugin().getStorage();
this.tp = new PlayerTeleporter();
this.configuration = SweetDreams.getPlugin().getConfiguration().getPluginConfig();
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onPlayerBedEnterEvent(PlayerBedEnterEvent event) {
if (event.getPlayer().getWorld() == SweetDreams.getPlugin().getWorld()) {
storage.savePlayerSpawnPoint(event.getPlayer());
@ -43,7 +39,7 @@ public class EventListener implements Listener {
return;
}
teleport(event.getPlayer());
tp.teleport(event.getPlayer(), false);
event.setCancelled(true);
}
@ -53,8 +49,6 @@ public class EventListener implements Listener {
if (event.getPlayer().getWorld() != SweetDreams.getPlugin().getWorld())
return;
// TODO save player spawn point on dreamlands
event.setCancelled(true);
}
@ -69,8 +63,9 @@ public class EventListener implements Listener {
if (event.getEntity().getWorld() != SweetDreams.getPlugin().getWorld()) return;
if (event.getEntity() instanceof Player player) {
if (event.getCause() == EntityDamageEvent.DamageCause.VOID) {
teleport(player);
tp.teleport(player, true);
// player holds the momentum from the fall to the void after teleport
player.setVelocity(new Vector(0, 0, 0));
player.setFallDistance(0);
}
@ -82,41 +77,7 @@ public class EventListener implements Listener {
if (event.getEntity().getWorld() != SweetDreams.getPlugin().getWorld()) return;
Player player = event.getPlayer();
teleport(player);
tp.teleport(player, true);
event.setCancelled(true);
}
private void teleport(Player player) {
UUID uuid = player.getUniqueId();
if (!teleporting.add(uuid)) return;
Bukkit.getScheduler().runTask(SweetDreams.getPlugin(), () -> {
try {
World playerWorld = player.getWorld();
if (playerWorld == SweetDreams.getPlugin().getWorld()) {
storage.savePlayer(player, PlayerStorage.StorageWorldType.SKYLANDS);
player.teleport(Objects.requireNonNullElse(
player.getRespawnLocation(),
Bukkit.getWorld("world").getSpawnLocation()
));
player.getInventory().clear();
storage.loadPlayer(player, PlayerStorage.StorageWorldType.OVERWORLD);
player.sendMessage("You woke up from a bad dream...");
player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_DEATH, 1.0f, 1.0f);
} else {
World world = SweetDreams.getPlugin().getWorld();
storage.savePlayer(player, PlayerStorage.StorageWorldType.OVERWORLD);
player.getInventory().clear();
player.teleport(storage.loadPlayer(player, PlayerStorage.StorageWorldType.SKYLANDS));
}
} finally {
teleporting.remove(uuid);
}
});
}
}

View file

@ -0,0 +1,26 @@
package org.adrianvictor.sweetdreams;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
public class KickerRunnable implements Runnable {
private final World world;
private final PlayerTeleporter teleporter;
public KickerRunnable() {
world = SweetDreams.getPlugin().getWorld();
teleporter = new PlayerTeleporter();
}
@Override
public void run() {
long time = Bukkit.getWorld("world").getTime();
if (!(time >= 13000 && time <= 23000)) {
for (Player p : world.getPlayers()) {
teleporter.teleport(p.getPlayer(), false);
}
}
}
}

View file

@ -0,0 +1,85 @@
package org.adrianvictor.sweetdreams;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
public class PlayerTeleporter {
private final Set<UUID> teleporting = new HashSet<>();
private final PlayerStorage storage = SweetDreams.getPlugin().getStorage();
public void teleport(Player player, boolean bad) {
UUID uuid = player.getUniqueId();
Sound sound = bad ? Sound.ENTITY_ENDERMAN_SCREAM : Sound.ENTITY_PLAYER_LEVELUP;
YamlConfiguration configuration = SweetDreams.getPlugin().getConfiguration().getPluginConfig();
String message = bad ? "You woke up from a bad dream..." : "You woke up!";
boolean clearInventory = (bad && configuration.getBoolean("keepInventory", true));
if (!teleporting.add(uuid)) return;
Bukkit.getScheduler().runTask(SweetDreams.getPlugin(), () -> {
try {
World playerWorld = player.getWorld();
if (playerWorld == SweetDreams.getPlugin().getWorld()) {
if (clearInventory)
spreadPlayerItems(player);
switchInventories(player, PlayerStorage.StorageWorldType.SKYLANDS, PlayerStorage.StorageWorldType.OVERWORLD);
player.teleport(Objects.requireNonNullElse(
player.getRespawnLocation(),
Bukkit.getWorld(configuration.getString("defaultWorld", "world")).getSpawnLocation()
));
if (configuration.getBoolean("sendMessageOnWakeUp", true)) {
player.sendMessage(message);
}
if (configuration.getBoolean("playSoundOnWakeUp", true)) {
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
}
} else {
switchInventories(player, PlayerStorage.StorageWorldType.OVERWORLD, PlayerStorage.StorageWorldType.SKYLANDS);
player.teleport(storage.loadPlayer(player, PlayerStorage.StorageWorldType.SKYLANDS));
}
} finally {
teleporting.remove(uuid);
}
});
}
private void switchInventories(Player player, PlayerStorage.StorageWorldType from, PlayerStorage.StorageWorldType to) {
storage.savePlayer(player, from);
player.getInventory().clear();
storage.loadPlayer(player, to);
}
private void spreadPlayerItems(Player player) {
for (ItemStack item : player.getInventory().getContents()) {
if (item != null && !item.isEmpty()) {
Item droppedItem = player.getWorld().dropItem(player.getLocation(), item);
droppedItem.setVelocity(
new Vector(
Math.random() - 0.5,
Math.random() * 0.5,
Math.random() - 0.5
)
);
}
}
player.getInventory().clear();
}
}

View file

@ -2,6 +2,7 @@ package org.adrianvictor.sweetdreams;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.WorldInfo;
@ -12,6 +13,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class SkyIslandsGenerator extends ChunkGenerator {
private final YamlConfiguration cfg;
private enum IslandType {
ROUND,
LONG,
@ -23,13 +26,20 @@ public class SkyIslandsGenerator extends ChunkGenerator {
private record Blob(double x, double y, double z, double rx, double ry, double rz) {}
private static final int CELL_SIZE = 30;
private static final int MIN_RADIUS = 6;
private static final int MAX_RADIUS = 16;
private static final int MIN_Y = 80;
private static final int MAX_Y = 180;
private final int CELL_SIZE;
private final int MIN_Y;
private final int MAX_Y;
private final int TREE_RANDOM_VALUE;
private final int TREE_PLACEMENT_BOUND_X_MIN;
private final int TREE_PLACEMENT_BOUND_X_MAX;
private final int TREE_PLACEMENT_BOUND_Y_MIN;
private final int TREE_PLACEMENT_BOUND_Y_MAX;
private final int TREE_MIN_HEIGHT;
private final int TREE_MAX_HEIGHT;
private final float ISLAND_CHANCE;
private final int ORE_MAX_DEPTH;
private final int ORE_MAX_SPREAD;
private final int DIRT_LAYERS;
private record LeafOffset(int dx, int dy, int dz) {}
@ -52,6 +62,24 @@ public class SkyIslandsGenerator extends ChunkGenerator {
LEAF_OFFSETS = offsets.toArray(LeafOffset[]::new);
}
public SkyIslandsGenerator() {
this.cfg = SweetDreams.getPlugin().getConfiguration().getPluginConfig();
this.CELL_SIZE = cfg.getInt("worldGeneration.cellSize", 30);
this.MIN_Y = cfg.getInt("worldGeneration.minY", 80);
this.MAX_Y = cfg.getInt("worldGeneration.maxY", 180);
this.TREE_RANDOM_VALUE = cfg.getInt("worldGeneration.treeRandomValue", 500);
this.ISLAND_CHANCE = (float) cfg.getDouble("worldGeneration.islandChance", 0.8);
this.TREE_PLACEMENT_BOUND_X_MIN = cfg.getInt("worldGeneration.treePlacementBoundMinX", 2);
this.TREE_PLACEMENT_BOUND_Y_MIN = cfg.getInt("worldGeneration.treePlacementBoundMinY", 2);
this.TREE_PLACEMENT_BOUND_X_MAX = cfg.getInt("worldGeneration.treePlacementBoundMaxX", 14);
this.TREE_PLACEMENT_BOUND_Y_MAX = cfg.getInt("worldGeneration.treePlacementBoundMaxY", 14);
this.ORE_MAX_DEPTH = cfg.getInt("worldGeneration.oreMaxDepth", 20);
this.ORE_MAX_SPREAD = cfg.getInt("worldGeneration.oreMaxSpread", 5);
this.TREE_MAX_HEIGHT = cfg.getInt("worldGeneration.treeMaxHeight", 3);
this.TREE_MIN_HEIGHT = cfg.getInt("worldGeneration.treeMinHeight", 4);
this.DIRT_LAYERS = cfg.getInt("worldGeneration.dirtLayers", 3);
}
@Override
public @NonNull List<BlockPopulator> getDefaultPopulators(@NonNull World world) {
return Collections.singletonList(new SkylandsBlockPopulator());
@ -80,7 +108,7 @@ public class SkyIslandsGenerator extends ChunkGenerator {
for (int gz = cellZ - 2; gz <= cellZ + 2; gz++) {
SplittableRandom r = new SplittableRandom(hash(seed, gx, gz));
if (r.nextDouble() > 0.8) {
if (r.nextDouble() > this.ISLAND_CHANCE) {
continue;
}
@ -180,7 +208,7 @@ public class SkyIslandsGenerator extends ChunkGenerator {
private void decorateTerrain(ChunkData chunkData, Random random, short[][] highestY) {
generateSurfaceLayers(chunkData, highestY);
generateOres(chunkData, random);
generateOres(chunkData, random, highestY);
generateTrees(chunkData, random, highestY);
}
@ -198,7 +226,7 @@ public class SkyIslandsGenerator extends ChunkGenerator {
chunkData.setBlock(x, highest, z, Material.GRASS_BLOCK);
for (int y = highest - 1; y >= highest - 3; y--) {
for (int y = highest - 1; y >= highest - this.DIRT_LAYERS; y--) {
if (y >= minHeight) {
chunkData.setBlock(x, y, z, Material.DIRT);
}
@ -207,27 +235,29 @@ public class SkyIslandsGenerator extends ChunkGenerator {
}
}
private void generateOres(ChunkData chunkData, Random random) {
generateOreVeins(chunkData, random, Material.COAL_ORE, 25, 10);
generateOreVeins(chunkData, random, Material.IRON_ORE, 15, 8);
generateOreVeins(chunkData, random, Material.GOLD_ORE, 6, 6);
generateOreVeins(chunkData, random, Material.DIAMOND_ORE, 2, 4);
private void generateOres(ChunkData chunkData, Random random, short[][] highestY) {
generateOreVeins(chunkData, random, Material.COAL_ORE, 25, 10, highestY);
generateOreVeins(chunkData, random, Material.IRON_ORE, 15, 8, highestY);
generateOreVeins(chunkData, random, Material.GOLD_ORE, 6, 6, highestY);
generateOreVeins(chunkData, random, Material.DIAMOND_ORE, 2, 4, highestY);
}
private void generateOreVeins(ChunkData chunkData, Random random, Material ore, int veins, int size) {
private void generateOreVeins(ChunkData chunkData, Random random, Material ore, int veins, int size, short[][] highestY) {
for (int i = 0; i < veins; i++) {
int startX = random.nextInt(16);
int startY = chunkData.getMinHeight() + random.nextInt(chunkData.getMaxHeight() - chunkData.getMinHeight());
int startZ = random.nextInt(16);
if (highestY[startX][startZ] == Short.MIN_VALUE) continue;
int startY = highestY[startX][startZ] - random.nextInt(this.ORE_MAX_DEPTH);
for (int j = 0; j < size; j++) {
int x = startX + random.nextInt(5) - 2;
int y = startY + random.nextInt(5) - 2;
int z = startZ + random.nextInt(5) - 2;
int x = startX + random.nextInt(this.ORE_MAX_SPREAD) - 2;
int y = startY + random.nextInt(this.ORE_MAX_SPREAD) - 2;
int z = startZ + random.nextInt(this.ORE_MAX_SPREAD) - 2;
if (x < 0 || x >= 16) continue;
if (z < 0 || z >= 16) continue;
@ -242,10 +272,10 @@ public class SkyIslandsGenerator extends ChunkGenerator {
}
private void generateTrees(ChunkData chunkData, Random random, short[][] highestY) {
for (int x = 2; x < 14; x++) {
for (int z = 2; z < 14; z++) {
for (int x = this.TREE_PLACEMENT_BOUND_X_MIN; x < TREE_PLACEMENT_BOUND_X_MAX; x++) {
for (int z = TREE_PLACEMENT_BOUND_Y_MIN; z < TREE_PLACEMENT_BOUND_Y_MAX; z++) {
if (random.nextInt(500) != 0) {
if (random.nextInt(this.TREE_RANDOM_VALUE) != 0) {
continue;
}
@ -265,7 +295,7 @@ public class SkyIslandsGenerator extends ChunkGenerator {
}
private void placeTree(ChunkData chunkData, int x, int y, int z, Random random) {
int height = 4 + random.nextInt(3);
int height = this.TREE_MIN_HEIGHT + random.nextInt(this.TREE_MAX_HEIGHT);
for (int dy = 0; dy < height; dy++) {
chunkData.setBlock(x, y + dy, z, Material.OAK_LOG);

View file

@ -1,7 +1,5 @@
package org.adrianvictor.sweetdreams;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.LimitedRegion;
import org.bukkit.generator.WorldInfo;
@ -17,6 +15,5 @@ public class SkylandsBlockPopulator extends BlockPopulator {
int chunkX,
int chunkZ,
@NonNull LimitedRegion region
) {
}
) {}
}

View file

@ -22,14 +22,11 @@ public final class SweetDreams extends JavaPlugin {
boolean generated = false;
for (World world: getServer().getWorlds()) {
if (world.getName().equals("world_sweetdreams")) {
Bukkit.unloadWorld(world, true);
File worldFolder = new File(Bukkit.getWorldContainer(), "world_sweetdreams");
if (worldFolder.exists()) {
deleteFolder(worldFolder);
}
// this.world = world;
// generated = true;
if (world.getName().equals(
configuration.getPluginConfig().getString("dreamlandsWorld", "world_sweetdreams"))
) {
this.world = world;
generated = true;
}
}
@ -38,6 +35,8 @@ public final class SweetDreams extends JavaPlugin {
}
getServer().getPluginManager().registerEvents(new EventListener(), this);
Bukkit.getScheduler().runTaskTimer(this, new KickerRunnable(), 20L, 20L);
}
private void deleteFolder(File folder) {

View file

@ -0,0 +1,28 @@
dreamlandsWorld: "world_sweetdreams"
# World to watch for day start when kicking players from the dreamland
# /!\ Don't change unless you know what you're doing
defaultWorld: "world"
kickOnDayStart: true
playSoundOnWakeUp: true
sendMessageOnWakeUp: true
keepInventory: true
# Won't affect already generated chunks
# /!\ Don't change unless you know what you're doing
worldGeneration:
cellSize: 30
minY: 80
maxY: 180
islandChance: 0.8
# The greater this is, rarer trees are. I'd recommend changing from 100 to 100 to se results.
treeRandomValue: 500
treePlacementBoundMinX: 2
treePlacementBoundMinY: 2
treePlacementBoundMaxX: 14
treePlacementBoundMaxY: 14
treeMaxHeight: 3 # Max random number that will be ADDED to the minimum size
treeMinHeight: 4
oreMaxDepth: 20
dirtLayers: 3