Compare commits

..

4 commits

Author SHA1 Message Date
449281e60e Merge remote-tracking branch 'origin' 2026-06-02 13:16:28 -03:00
7c4647dd53 Add config option to enable debug messages and return to overworld when sleeping on dreamlands.
Change PlayerStorage to save player current world to avoid desync and correct when player joins the world. This fixes the desync when joining the server after deleting the dreamlands world.
2026-06-02 13:15:30 -03:00
1216ea57f8 Switch to GPL-3 or later 2026-05-31 23:17:33 -03:00
da0bde149f Add LICENSE 2026-05-31 23:10:24 -03:00
6 changed files with 149 additions and 43 deletions

12
LICENSE Normal file
View file

@ -0,0 +1,12 @@
SweetDreams
Copyright (C) 2026 Adrian Victor
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

View file

@ -2,48 +2,72 @@ package org.adrianvictor.sweetdreams;
import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityPortalEnterEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerBedEnterEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.util.Vector;
import java.util.Objects;
import java.util.UUID;
public class EventListener implements Listener {
private final PlayerStorage storage;
private final PlayerTeleporter tp;
private final YamlConfiguration configuration;
private final World dreamlands;
private final SweetDreams plugin;
public EventListener() {
this.storage = SweetDreams.getPlugin().getStorage();
this.tp = new PlayerTeleporter();
this.configuration = SweetDreams.getPlugin().getConfiguration().getPluginConfig();
this.dreamlands = SweetDreams.getPlugin().getWorld();
this.plugin = SweetDreams.getPlugin();
}
@EventHandler(ignoreCancelled = true)
public void onPlayerBedEnterEvent(PlayerBedEnterEvent event) {
if (event.getPlayer().getWorld() == SweetDreams.getPlugin().getWorld()) {
storage.savePlayerSpawnPoint(event.getPlayer());
event.getPlayer().sendMessage("Respawn point set.");
event.setCancelled(true);
return;
}
Long time = event.getPlayer().getWorld().getTime();
long time = event.getPlayer().getWorld().getTime();
if (!(time >= 13000 && time <= 23000)) {
event.setCancelled(true);
return;
}
tp.teleport(event.getPlayer(), false);
tp.teleport(event.getPlayer(), false, "You feel free...", Sound.BLOCK_BEACON_ACTIVATE);
event.setCancelled(true);
}
@EventHandler
public void onInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
Block type = event.getClickedBlock();
if (event.getAction() != Action.LEFT_CLICK_BLOCK)
return;
if (player.getWorld() != dreamlands || type == null)
return;
if (type.getType().name().endsWith("_BED")) {
storage.savePlayerSpawnPoint(event.getPlayer());
event.getPlayer().sendMessage("Respawn point set.");
tp.teleport(event.getPlayer(), false, "Maybe you'll return to this dream....", Sound.BLOCK_BEACON_DEACTIVATE);
event.setCancelled(true);
}
}
@EventHandler
public void onPlayerSetSpawn(PlayerSetSpawnEvent event) {
if (event.getPlayer().getWorld() != SweetDreams.getPlugin().getWorld())
@ -80,4 +104,27 @@ public class EventListener implements Listener {
tp.teleport(player, true);
event.setCancelled(true);
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
Player player = event.getPlayer();
PlayerStorage.StorageWorldType actualWorld = storage.getActualWorld(player);
plugin.sendDebugMessage(
"Player %s logged in on %s with inventory from %s. (flag: %s, world: %s)"
.formatted(player.getName(), player.getWorld().getName(), actualWorld.getKey(), storage.getFlag(player).getKey(), actualWorld.getKey())
);
if (storage.exists(uuid) && storage.getFlag(player) != actualWorld) {
plugin.getLogger().warning(
"Player %s was desynced! Changing inventory back to %s."
.formatted(player.getName(), actualWorld.getKey())
);
storage.loadPlayer(player, actualWorld);
player.sendMessage("Your dream ended prematurely...");
player.playSound(player.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 1.0f, 1.0f);
player.teleport(Objects.requireNonNullElse(player.getRespawnLocation(), actualWorld.getWorld().getSpawnLocation()));
}
}
}

View file

@ -1,15 +1,23 @@
package org.adrianvictor.sweetdreams;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import java.util.UUID;
public class PlayerStorage {
private final Configuration config;
private final Configuration configuration;
private final YamlConfiguration config;
private final YamlConfiguration storage;
private final NamespacedKey worldFlagKey = new NamespacedKey(SweetDreams.getPlugin(), "world_flag");
private final World overworld;
private final World skylands;
// Map<UUID, ItemStack[]> inventories = new HashMap<>();
public enum StorageWorldType {
@ -25,11 +33,22 @@ public class PlayerStorage {
public String getKey() {
return key;
}
public World getWorld() {
if (this.getKey().equals("overworld")) {
return Bukkit.getWorld(SweetDreams.getPlugin().getConfiguration().getPluginConfig().getString("defaultWorld", "world"));
}
return SweetDreams.getPlugin().getWorld();
}
}
public PlayerStorage(Configuration config) {
this.config = config;
this.configuration = config;
this.storage = config.getPlayerStorage();
this.config = config.getPluginConfig();
this.overworld = Bukkit.getWorld(this.config.getString("defaultWorld", "world"));
this.skylands = SweetDreams.getPlugin().getWorld();
}
public void savePlayer(Player player, StorageWorldType type) {
@ -64,7 +83,7 @@ public class PlayerStorage {
storage.set(base + ".stats.food", player.getFoodLevel());
storage.set(base + ".stats.saturation", player.getSaturation());
config.savePlayerStorage();
configuration.savePlayerStorage();
}
public Location loadPlayer(Player player, StorageWorldType type) {
@ -124,6 +143,15 @@ public class PlayerStorage {
player.setFoodLevel(storage.getInt(base + ".stats.food", 20));
player.setSaturation((float) storage.getDouble(base + ".stats.saturation", 5.0));
storage.set("players." + uuid + ".worldFlag", type.name());
player.getPersistentDataContainer().set(
worldFlagKey,
PersistentDataType.STRING,
type.name()
);
configuration.savePlayerStorage();
Object rawSpawn = storage.get(base + ".spawn");
if (rawSpawn instanceof Location spawn) {
return spawn;
@ -132,6 +160,27 @@ public class PlayerStorage {
}
}
public boolean exists(UUID uuid) {
String base = "players." + uuid;
return storage.contains(base);
}
public StorageWorldType getFlag(Player player) {
UUID uuid = player.getUniqueId();
String worldFlag = storage.getString("players." + uuid + ".worldFlag", null);
if (worldFlag == null) {
worldFlag = player.getPersistentDataContainer().get(worldFlagKey, PersistentDataType.STRING);
}
return StorageWorldType.valueOf(worldFlag);
}
public StorageWorldType getActualWorld(Player player) {
return player.getWorld() == SweetDreams.getPlugin().getWorld() ?
StorageWorldType.SKYLANDS : StorageWorldType.OVERWORLD;
}
public void savePlayerSpawnPoint(Player player) {
UUID uuid = player.getUniqueId();
String base = "players." + uuid + ".skylands";

View file

@ -18,11 +18,9 @@ public class PlayerTeleporter {
private final Set<UUID> teleporting = new HashSet<>();
private final PlayerStorage storage = SweetDreams.getPlugin().getStorage();
public void teleport(Player player, boolean bad) {
public void teleport(Player player, boolean bad, String message, Sound sound) {
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;
@ -50,6 +48,13 @@ public class PlayerTeleporter {
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
}
} else {
if (configuration.getBoolean("sendMessageOnWakeUp", true)) {
player.sendMessage(message);
}
if (configuration.getBoolean("playSoundOnWakeUp", true)) {
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
}
switchInventories(player, PlayerStorage.StorageWorldType.OVERWORLD, PlayerStorage.StorageWorldType.SKYLANDS);
player.teleport(storage.loadPlayer(player, PlayerStorage.StorageWorldType.SKYLANDS));
}
@ -59,6 +64,12 @@ public class PlayerTeleporter {
});
}
public void teleport(Player player, boolean bad) {
String message = bad ? "You woke up from a bad dream..." : "You woke up!";
Sound sound = bad ? Sound.ENTITY_ENDERMAN_SCREAM : Sound.ENTITY_PLAYER_LEVELUP;
teleport(player, bad, message, sound);
}
private void switchInventories(Player player, PlayerStorage.StorageWorldType from, PlayerStorage.StorageWorldType to) {
storage.savePlayer(player, from);
player.getInventory().clear();

View file

@ -1,6 +1,9 @@
package org.adrianvictor.sweetdreams;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.plugin.java.JavaPlugin;
@ -17,40 +20,15 @@ public final class SweetDreams extends JavaPlugin {
public void onEnable() {
plugin = this;
configuration = new Configuration();
String worldName = configuration.getPluginConfig().getString("dreamlandsWorld", "world_sweetdreams");
storage = new PlayerStorage(configuration);
boolean generated = false;
for (World world: getServer().getWorlds()) {
if (world.getName().equals(
configuration.getPluginConfig().getString("dreamlandsWorld", "world_sweetdreams"))
) {
this.world = world;
generated = true;
}
}
if (!generated) {
world = createSkylandsWorld("world_sweetdreams");
}
world = createSkylandsWorld(worldName);
getServer().getPluginManager().registerEvents(new EventListener(), this);
Bukkit.getScheduler().runTaskTimer(this, new KickerRunnable(), 20L, 20L);
}
private void deleteFolder(File folder) {
if (folder.isDirectory()) {
File[] files = folder.listFiles();
if (files != null) {
for (File file : files) {
deleteFolder(file);
}
}
}
folder.delete();
}
@Override
public void onDisable() {
// Plugin shutdown logic
@ -82,4 +60,10 @@ public final class SweetDreams extends JavaPlugin {
public PlayerStorage getStorage() {
return storage;
}
public void sendDebugMessage(String message) {
if (configuration.getPluginConfig().getBoolean("debugMessages", false)) {
getLogger().info("[DEBUG] " + message);
}
}
}

View file

@ -8,6 +8,7 @@ kickOnDayStart: true
playSoundOnWakeUp: true
sendMessageOnWakeUp: true
keepInventory: true
sleepingOnDreamReturnsToOverworld: true
# Won't affect already generated chunks
# /!\ Don't change unless you know what you're doing
@ -25,4 +26,6 @@ worldGeneration:
treeMaxHeight: 3 # Max random number that will be ADDED to the minimum size
treeMinHeight: 4
oreMaxDepth: 20
dirtLayers: 3
dirtLayers: 3
debugMessages: false