diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9ae1405 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/src/main/java/org/adrianvictor/sweetdreams/EventListener.java b/src/main/java/org/adrianvictor/sweetdreams/EventListener.java index 9a593e4..4ec5e40 100644 --- a/src/main/java/org/adrianvictor/sweetdreams/EventListener.java +++ b/src/main/java/org/adrianvictor/sweetdreams/EventListener.java @@ -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())); + } + } } diff --git a/src/main/java/org/adrianvictor/sweetdreams/PlayerStorage.java b/src/main/java/org/adrianvictor/sweetdreams/PlayerStorage.java index fdcd206..94a1d86 100644 --- a/src/main/java/org/adrianvictor/sweetdreams/PlayerStorage.java +++ b/src/main/java/org/adrianvictor/sweetdreams/PlayerStorage.java @@ -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 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"; diff --git a/src/main/java/org/adrianvictor/sweetdreams/PlayerTeleporter.java b/src/main/java/org/adrianvictor/sweetdreams/PlayerTeleporter.java index 00cff9a..66ced6c 100644 --- a/src/main/java/org/adrianvictor/sweetdreams/PlayerTeleporter.java +++ b/src/main/java/org/adrianvictor/sweetdreams/PlayerTeleporter.java @@ -18,11 +18,9 @@ public class PlayerTeleporter { private final Set 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(); diff --git a/src/main/java/org/adrianvictor/sweetdreams/SweetDreams.java b/src/main/java/org/adrianvictor/sweetdreams/SweetDreams.java index 1863a07..b018604 100644 --- a/src/main/java/org/adrianvictor/sweetdreams/SweetDreams.java +++ b/src/main/java/org/adrianvictor/sweetdreams/SweetDreams.java @@ -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); + } + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1c4166f..525f823 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -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 \ No newline at end of file + dirtLayers: 3 + +debugMessages: false \ No newline at end of file