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 YamlConfiguration playerStorage = new YamlConfiguration();
private final File playerStorageFile; private final File playerStorageFile;
private final YamlConfiguration pluginConfig = new YamlConfiguration(); private final YamlConfiguration pluginConfig = new YamlConfiguration();
private final File pluginConfigFile = new File(""); private final File pluginConfigFile;
private final SweetDreams plugin; private final SweetDreams plugin;
public Configuration() { public Configuration() {
plugin = SweetDreams.getPlugin(); plugin = SweetDreams.getPlugin();
playerStorageFile = new File(plugin.getDataFolder(), "playerstorage.yml"); playerStorageFile = new File(plugin.getDataFolder(), "playerstorage.yml");
pluginConfigFile = new File(plugin.getDataFolder(), "config.yml");
plugin.saveResource("config.yml", false);
if (playerStorageFile.exists()) { if (playerStorageFile.exists()) {
try { try {
@ -25,6 +28,15 @@ public class Configuration {
throw new RuntimeException(e); 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() { public void savePlayerStorage() {

View file

@ -1,10 +1,8 @@
package org.adrianvictor.sweetdreams; package org.adrianvictor.sweetdreams;
import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent; import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@ -14,20 +12,18 @@ import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBedEnterEvent;
import org.bukkit.util.Vector; 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 { public class EventListener implements Listener {
private final PlayerStorage storage; private final PlayerStorage storage;
private final Set<UUID> teleporting = new HashSet<>(); private final PlayerTeleporter tp;
private final YamlConfiguration configuration;
public EventListener() { public EventListener() {
this.storage = SweetDreams.getPlugin().getStorage(); this.storage = SweetDreams.getPlugin().getStorage();
this.tp = new PlayerTeleporter();
this.configuration = SweetDreams.getPlugin().getConfiguration().getPluginConfig();
} }
@EventHandler @EventHandler(ignoreCancelled = true)
public void onPlayerBedEnterEvent(PlayerBedEnterEvent event) { public void onPlayerBedEnterEvent(PlayerBedEnterEvent event) {
if (event.getPlayer().getWorld() == SweetDreams.getPlugin().getWorld()) { if (event.getPlayer().getWorld() == SweetDreams.getPlugin().getWorld()) {
storage.savePlayerSpawnPoint(event.getPlayer()); storage.savePlayerSpawnPoint(event.getPlayer());
@ -43,7 +39,7 @@ public class EventListener implements Listener {
return; return;
} }
teleport(event.getPlayer()); tp.teleport(event.getPlayer(), false);
event.setCancelled(true); event.setCancelled(true);
} }
@ -53,8 +49,6 @@ public class EventListener implements Listener {
if (event.getPlayer().getWorld() != SweetDreams.getPlugin().getWorld()) if (event.getPlayer().getWorld() != SweetDreams.getPlugin().getWorld())
return; return;
// TODO save player spawn point on dreamlands
event.setCancelled(true); event.setCancelled(true);
} }
@ -69,8 +63,9 @@ public class EventListener implements Listener {
if (event.getEntity().getWorld() != SweetDreams.getPlugin().getWorld()) return; if (event.getEntity().getWorld() != SweetDreams.getPlugin().getWorld()) return;
if (event.getEntity() instanceof Player player) { if (event.getEntity() instanceof Player player) {
if (event.getCause() == EntityDamageEvent.DamageCause.VOID) { 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.setVelocity(new Vector(0, 0, 0));
player.setFallDistance(0); player.setFallDistance(0);
} }
@ -82,41 +77,7 @@ public class EventListener implements Listener {
if (event.getEntity().getWorld() != SweetDreams.getPlugin().getWorld()) return; if (event.getEntity().getWorld() != SweetDreams.getPlugin().getWorld()) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
teleport(player); tp.teleport(player, true);
event.setCancelled(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.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.WorldInfo; import org.bukkit.generator.WorldInfo;
@ -12,6 +13,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class SkyIslandsGenerator extends ChunkGenerator { public class SkyIslandsGenerator extends ChunkGenerator {
private final YamlConfiguration cfg;
private enum IslandType { private enum IslandType {
ROUND, ROUND,
LONG, 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 record Blob(double x, double y, double z, double rx, double ry, double rz) {}
private static final int CELL_SIZE = 30; private final int CELL_SIZE;
private final int MIN_Y;
private static final int MIN_RADIUS = 6; private final int MAX_Y;
private static final int MAX_RADIUS = 16; private final int TREE_RANDOM_VALUE;
private final int TREE_PLACEMENT_BOUND_X_MIN;
private static final int MIN_Y = 80; private final int TREE_PLACEMENT_BOUND_X_MAX;
private static final int MAX_Y = 180; 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) {} private record LeafOffset(int dx, int dy, int dz) {}
@ -52,6 +62,24 @@ public class SkyIslandsGenerator extends ChunkGenerator {
LEAF_OFFSETS = offsets.toArray(LeafOffset[]::new); 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 @Override
public @NonNull List<BlockPopulator> getDefaultPopulators(@NonNull World world) { public @NonNull List<BlockPopulator> getDefaultPopulators(@NonNull World world) {
return Collections.singletonList(new SkylandsBlockPopulator()); return Collections.singletonList(new SkylandsBlockPopulator());
@ -80,7 +108,7 @@ public class SkyIslandsGenerator extends ChunkGenerator {
for (int gz = cellZ - 2; gz <= cellZ + 2; gz++) { for (int gz = cellZ - 2; gz <= cellZ + 2; gz++) {
SplittableRandom r = new SplittableRandom(hash(seed, gx, gz)); SplittableRandom r = new SplittableRandom(hash(seed, gx, gz));
if (r.nextDouble() > 0.8) { if (r.nextDouble() > this.ISLAND_CHANCE) {
continue; continue;
} }
@ -180,7 +208,7 @@ public class SkyIslandsGenerator extends ChunkGenerator {
private void decorateTerrain(ChunkData chunkData, Random random, short[][] highestY) { private void decorateTerrain(ChunkData chunkData, Random random, short[][] highestY) {
generateSurfaceLayers(chunkData, highestY); generateSurfaceLayers(chunkData, highestY);
generateOres(chunkData, random); generateOres(chunkData, random, highestY);
generateTrees(chunkData, random, highestY); generateTrees(chunkData, random, highestY);
} }
@ -198,7 +226,7 @@ public class SkyIslandsGenerator extends ChunkGenerator {
chunkData.setBlock(x, highest, z, Material.GRASS_BLOCK); 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) { if (y >= minHeight) {
chunkData.setBlock(x, y, z, Material.DIRT); chunkData.setBlock(x, y, z, Material.DIRT);
} }
@ -207,27 +235,29 @@ public class SkyIslandsGenerator extends ChunkGenerator {
} }
} }
private void generateOres(ChunkData chunkData, Random random) { private void generateOres(ChunkData chunkData, Random random, short[][] highestY) {
generateOreVeins(chunkData, random, Material.COAL_ORE, 25, 10); generateOreVeins(chunkData, random, Material.COAL_ORE, 25, 10, highestY);
generateOreVeins(chunkData, random, Material.IRON_ORE, 15, 8); generateOreVeins(chunkData, random, Material.IRON_ORE, 15, 8, highestY);
generateOreVeins(chunkData, random, Material.GOLD_ORE, 6, 6); generateOreVeins(chunkData, random, Material.GOLD_ORE, 6, 6, highestY);
generateOreVeins(chunkData, random, Material.DIAMOND_ORE, 2, 4); 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++) { for (int i = 0; i < veins; i++) {
int startX = random.nextInt(16); int startX = random.nextInt(16);
int startY = chunkData.getMinHeight() + random.nextInt(chunkData.getMaxHeight() - chunkData.getMinHeight());
int startZ = random.nextInt(16); 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++) { for (int j = 0; j < size; j++) {
int x = startX + random.nextInt(5) - 2; int x = startX + random.nextInt(this.ORE_MAX_SPREAD) - 2;
int y = startY + random.nextInt(5) - 2; int y = startY + random.nextInt(this.ORE_MAX_SPREAD) - 2;
int z = startZ + random.nextInt(5) - 2; int z = startZ + random.nextInt(this.ORE_MAX_SPREAD) - 2;
if (x < 0 || x >= 16) continue; if (x < 0 || x >= 16) continue;
if (z < 0 || z >= 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) { private void generateTrees(ChunkData chunkData, Random random, short[][] highestY) {
for (int x = 2; x < 14; x++) { for (int x = this.TREE_PLACEMENT_BOUND_X_MIN; x < TREE_PLACEMENT_BOUND_X_MAX; x++) {
for (int z = 2; z < 14; z++) { 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; continue;
} }
@ -265,7 +295,7 @@ public class SkyIslandsGenerator extends ChunkGenerator {
} }
private void placeTree(ChunkData chunkData, int x, int y, int z, Random random) { 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++) { for (int dy = 0; dy < height; dy++) {
chunkData.setBlock(x, y + dy, z, Material.OAK_LOG); chunkData.setBlock(x, y + dy, z, Material.OAK_LOG);

View file

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

View file

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