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 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.configuration.file.YamlConfiguration;
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;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.entity.EntityPortalEnterEvent;
import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBedEnterEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import java.util.Objects;
import java.util.UUID;
public class EventListener implements Listener { public class EventListener implements Listener {
private final PlayerStorage storage; private final PlayerStorage storage;
private final PlayerTeleporter tp; private final PlayerTeleporter tp;
private final YamlConfiguration configuration; private final YamlConfiguration configuration;
private final World dreamlands;
private final SweetDreams plugin;
public EventListener() { public EventListener() {
this.storage = SweetDreams.getPlugin().getStorage(); this.storage = SweetDreams.getPlugin().getStorage();
this.tp = new PlayerTeleporter(); this.tp = new PlayerTeleporter();
this.configuration = SweetDreams.getPlugin().getConfiguration().getPluginConfig(); this.configuration = SweetDreams.getPlugin().getConfiguration().getPluginConfig();
this.dreamlands = SweetDreams.getPlugin().getWorld();
this.plugin = SweetDreams.getPlugin();
} }
@EventHandler(ignoreCancelled = true) @EventHandler(ignoreCancelled = true)
public void onPlayerBedEnterEvent(PlayerBedEnterEvent event) { public void onPlayerBedEnterEvent(PlayerBedEnterEvent event) {
if (event.getPlayer().getWorld() == SweetDreams.getPlugin().getWorld()) { long time = event.getPlayer().getWorld().getTime();
storage.savePlayerSpawnPoint(event.getPlayer());
event.getPlayer().sendMessage("Respawn point set.");
event.setCancelled(true);
return;
}
Long time = event.getPlayer().getWorld().getTime();
if (!(time >= 13000 && time <= 23000)) { if (!(time >= 13000 && time <= 23000)) {
event.setCancelled(true); event.setCancelled(true);
return; return;
} }
tp.teleport(event.getPlayer(), false); tp.teleport(event.getPlayer(), false, "You feel free...", Sound.BLOCK_BEACON_ACTIVATE);
event.setCancelled(true); 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 @EventHandler
public void onPlayerSetSpawn(PlayerSetSpawnEvent event) { public void onPlayerSetSpawn(PlayerSetSpawnEvent event) {
if (event.getPlayer().getWorld() != SweetDreams.getPlugin().getWorld()) if (event.getPlayer().getWorld() != SweetDreams.getPlugin().getWorld())
@ -80,4 +104,27 @@ public class EventListener implements Listener {
tp.teleport(player, true); tp.teleport(player, true);
event.setCancelled(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; package org.adrianvictor.sweetdreams;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import java.util.UUID; import java.util.UUID;
public class PlayerStorage { public class PlayerStorage {
private final Configuration config; private final Configuration configuration;
private final YamlConfiguration config;
private final YamlConfiguration storage; 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<>(); // Map<UUID, ItemStack[]> inventories = new HashMap<>();
public enum StorageWorldType { public enum StorageWorldType {
@ -25,11 +33,22 @@ public class PlayerStorage {
public String getKey() { public String getKey() {
return key; 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) { public PlayerStorage(Configuration config) {
this.config = config; this.configuration = config;
this.storage = config.getPlayerStorage(); 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) { 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.food", player.getFoodLevel());
storage.set(base + ".stats.saturation", player.getSaturation()); storage.set(base + ".stats.saturation", player.getSaturation());
config.savePlayerStorage(); configuration.savePlayerStorage();
} }
public Location loadPlayer(Player player, StorageWorldType type) { public Location loadPlayer(Player player, StorageWorldType type) {
@ -124,6 +143,15 @@ public class PlayerStorage {
player.setFoodLevel(storage.getInt(base + ".stats.food", 20)); player.setFoodLevel(storage.getInt(base + ".stats.food", 20));
player.setSaturation((float) storage.getDouble(base + ".stats.saturation", 5.0)); 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"); Object rawSpawn = storage.get(base + ".spawn");
if (rawSpawn instanceof Location spawn) { if (rawSpawn instanceof Location spawn) {
return 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) { public void savePlayerSpawnPoint(Player player) {
UUID uuid = player.getUniqueId(); UUID uuid = player.getUniqueId();
String base = "players." + uuid + ".skylands"; String base = "players." + uuid + ".skylands";

View file

@ -18,11 +18,9 @@ public class PlayerTeleporter {
private final Set<UUID> teleporting = new HashSet<>(); private final Set<UUID> teleporting = new HashSet<>();
private final PlayerStorage storage = SweetDreams.getPlugin().getStorage(); 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(); UUID uuid = player.getUniqueId();
Sound sound = bad ? Sound.ENTITY_ENDERMAN_SCREAM : Sound.ENTITY_PLAYER_LEVELUP;
YamlConfiguration configuration = SweetDreams.getPlugin().getConfiguration().getPluginConfig(); 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)); boolean clearInventory = (bad && configuration.getBoolean("keepInventory", true));
if (!teleporting.add(uuid)) return; if (!teleporting.add(uuid)) return;
@ -50,6 +48,13 @@ public class PlayerTeleporter {
player.playSound(player.getLocation(), sound, 1.0f, 1.0f); player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
} }
} else { } 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); switchInventories(player, PlayerStorage.StorageWorldType.OVERWORLD, PlayerStorage.StorageWorldType.SKYLANDS);
player.teleport(storage.loadPlayer(player, 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) { private void switchInventories(Player player, PlayerStorage.StorageWorldType from, PlayerStorage.StorageWorldType to) {
storage.savePlayer(player, from); storage.savePlayer(player, from);
player.getInventory().clear(); player.getInventory().clear();

View file

@ -1,6 +1,9 @@
package org.adrianvictor.sweetdreams; 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.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.WorldCreator; import org.bukkit.WorldCreator;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -17,40 +20,15 @@ public final class SweetDreams extends JavaPlugin {
public void onEnable() { public void onEnable() {
plugin = this; plugin = this;
configuration = new Configuration(); configuration = new Configuration();
String worldName = configuration.getPluginConfig().getString("dreamlandsWorld", "world_sweetdreams");
storage = new PlayerStorage(configuration); storage = new PlayerStorage(configuration);
world = createSkylandsWorld(worldName);
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");
}
getServer().getPluginManager().registerEvents(new EventListener(), this); getServer().getPluginManager().registerEvents(new EventListener(), this);
Bukkit.getScheduler().runTaskTimer(this, new KickerRunnable(), 20L, 20L); 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 @Override
public void onDisable() { public void onDisable() {
// Plugin shutdown logic // Plugin shutdown logic
@ -82,4 +60,10 @@ public final class SweetDreams extends JavaPlugin {
public PlayerStorage getStorage() { public PlayerStorage getStorage() {
return storage; 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 playSoundOnWakeUp: true
sendMessageOnWakeUp: true sendMessageOnWakeUp: true
keepInventory: true keepInventory: true
sleepingOnDreamReturnsToOverworld: true
# Won't affect already generated chunks # Won't affect already generated chunks
# /!\ Don't change unless you know what you're doing # /!\ 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 treeMaxHeight: 3 # Max random number that will be ADDED to the minimum size
treeMinHeight: 4 treeMinHeight: 4
oreMaxDepth: 20 oreMaxDepth: 20
dirtLayers: 3 dirtLayers: 3
debugMessages: false