251 lines
9.0 KiB
Java
251 lines
9.0 KiB
Java
package games.dmg.spigotbase;
|
|
|
|
import java.io.IOException;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import net.kyori.adventure.text.Component;
|
|
import net.kyori.adventure.text.format.NamedTextColor;
|
|
import org.bukkit.ChatColor;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.World;
|
|
import org.bukkit.block.Block;
|
|
import org.bukkit.NamespacedKey;
|
|
import org.bukkit.Server;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.EventHandler;
|
|
import org.bukkit.event.EventPriority;
|
|
import org.bukkit.event.Listener;
|
|
import org.bukkit.event.block.Action;
|
|
import org.bukkit.event.inventory.PrepareItemCraftEvent;
|
|
import org.bukkit.event.player.PlayerInteractEvent;
|
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
|
import org.bukkit.inventory.ItemStack;
|
|
import org.bukkit.inventory.ShapedRecipe;
|
|
import org.bukkit.inventory.meta.ItemMeta;
|
|
import org.bukkit.inventory.meta.CompassMeta;
|
|
import org.bukkit.persistence.PersistentDataType;
|
|
|
|
final class PocketBaseKeystoneService implements Listener {
|
|
private static final Set<Material> RETURN_HAZARDS = Set.of(
|
|
Material.LAVA,
|
|
Material.FIRE,
|
|
Material.SOUL_FIRE,
|
|
Material.MAGMA_BLOCK,
|
|
Material.CACTUS,
|
|
Material.CAMPFIRE,
|
|
Material.SOUL_CAMPFIRE,
|
|
Material.POWDER_SNOW
|
|
);
|
|
private final Server server;
|
|
private final PocketBaseManager pocketBases;
|
|
private final KeystoneReturnStore returns;
|
|
private final NamespacedKey markerKey;
|
|
private final Logger logger;
|
|
|
|
PocketBaseKeystoneService(
|
|
Server server,
|
|
PocketBaseManager pocketBases,
|
|
KeystoneReturnStore returns,
|
|
NamespacedKey markerKey,
|
|
Logger logger
|
|
) {
|
|
this.server = server;
|
|
this.pocketBases = pocketBases;
|
|
this.returns = returns;
|
|
this.markerKey = markerKey;
|
|
this.logger = logger;
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.HIGH)
|
|
public void onPrepareCraft(PrepareItemCraftEvent event) {
|
|
if (!(event.getRecipe() instanceof ShapedRecipe shaped)
|
|
|| !shaped.getKey().equals(markerKey)
|
|
|| !(event.getView().getPlayer() instanceof Player player)) {
|
|
return;
|
|
}
|
|
if (pocketBases.state(player.getUniqueId()).level() < 1) {
|
|
event.getInventory().setResult(null);
|
|
}
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
|
public void onUse(PlayerInteractEvent event) {
|
|
Action action = event.getAction();
|
|
if ((action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK)
|
|
|| !isKeystone(event.getItem(), markerKey)) {
|
|
return;
|
|
}
|
|
event.setCancelled(true);
|
|
Player player = event.getPlayer();
|
|
UUID playerId = player.getUniqueId();
|
|
if (pocketBases.state(playerId).level() < 1) {
|
|
player.sendMessage(ChatColor.RED
|
|
+ "Unlock Pocket Base I before using a Pocket Base Keystone.");
|
|
return;
|
|
}
|
|
Optional<UUID> currentPocket = pocketBases.ownerForPocketWorld(
|
|
player.getWorld().getUID()
|
|
);
|
|
if (currentPocket.isPresent()) {
|
|
if (!currentPocket.orElseThrow().equals(playerId)) {
|
|
player.sendMessage(ChatColor.RED
|
|
+ "You can only use a keystone to leave your own Pocket Base.");
|
|
return;
|
|
}
|
|
returnToSavedDestination(player);
|
|
return;
|
|
}
|
|
Location origin = player.getLocation();
|
|
World originWorld = origin.getWorld();
|
|
if (originWorld == null) {
|
|
player.sendMessage(ChatColor.RED + "Your return location could not be recorded.");
|
|
return;
|
|
}
|
|
KeystoneReturnLocation destination = new KeystoneReturnLocation(
|
|
originWorld.getUID(),
|
|
originWorld.getName(),
|
|
origin.getX(),
|
|
origin.getY(),
|
|
origin.getZ(),
|
|
origin.getYaw(),
|
|
origin.getPitch()
|
|
);
|
|
try {
|
|
returns.record(playerId, destination);
|
|
} catch (IOException exception) {
|
|
logger.log(Level.SEVERE, "Could not save a Pocket Base Keystone return", exception);
|
|
player.sendMessage(ChatColor.RED
|
|
+ "Your return location could not be saved, so you were not teleported.");
|
|
return;
|
|
}
|
|
teleport(player, pocketBases.pocketArrival(playerId));
|
|
}
|
|
|
|
private void returnToSavedDestination(Player player) {
|
|
Optional<KeystoneReturnLocation> saved = returns.destination(player.getUniqueId());
|
|
if (saved.isEmpty()) {
|
|
player.sendMessage(ChatColor.RED + "No Pocket Base Keystone return is recorded.");
|
|
return;
|
|
}
|
|
KeystoneReturnLocation destination = saved.orElseThrow();
|
|
World world = server.getWorld(destination.worldId());
|
|
if (world == null) {
|
|
player.sendMessage(ChatColor.RED + "Your recorded return world is unavailable.");
|
|
return;
|
|
}
|
|
Location location = new Location(
|
|
world,
|
|
destination.x(),
|
|
destination.y(),
|
|
destination.z(),
|
|
destination.yaw(),
|
|
destination.pitch()
|
|
);
|
|
if (!isSafe(location)) {
|
|
player.sendMessage(ChatColor.RED + "Your recorded return location is not safe.");
|
|
return;
|
|
}
|
|
teleport(player, location);
|
|
}
|
|
|
|
private static void teleport(Player player, Location destination) {
|
|
float previousFallDistance = player.getFallDistance();
|
|
player.setFallDistance(0.0F);
|
|
if (!player.teleport(destination, PlayerTeleportEvent.TeleportCause.PLUGIN)) {
|
|
player.setFallDistance(previousFallDistance);
|
|
player.sendMessage(ChatColor.RED + "Pocket Base Keystone travel failed.");
|
|
}
|
|
}
|
|
|
|
private static boolean isSafe(Location location) {
|
|
World world = location.getWorld();
|
|
int x = location.getBlockX();
|
|
int y = location.getBlockY();
|
|
int z = location.getBlockZ();
|
|
if (world == null || y <= world.getMinHeight() || y + 1 >= world.getMaxHeight()) {
|
|
return false;
|
|
}
|
|
Block ground = world.getBlockAt(x, y - 1, z);
|
|
Block feet = world.getBlockAt(x, y, z);
|
|
Block head = world.getBlockAt(x, y + 1, z);
|
|
return feet.isPassable() && head.isPassable()
|
|
&& !feet.isLiquid() && !head.isLiquid()
|
|
&& !ground.isPassable() && !ground.isLiquid()
|
|
&& !RETURN_HAZARDS.contains(ground.getType())
|
|
&& !RETURN_HAZARDS.contains(feet.getType())
|
|
&& !RETURN_HAZARDS.contains(head.getType());
|
|
}
|
|
|
|
static boolean isKeystone(ItemStack item, NamespacedKey markerKey) {
|
|
if (item == null || item.getType() != Material.RECOVERY_COMPASS) {
|
|
return false;
|
|
}
|
|
ItemMeta metadata = item.getItemMeta();
|
|
if (metadata == null) {
|
|
return false;
|
|
}
|
|
Byte marker = metadata.getPersistentDataContainer().get(
|
|
markerKey, PersistentDataType.BYTE
|
|
);
|
|
return marker != null && marker == (byte) 1;
|
|
}
|
|
|
|
void registerRecipe() {
|
|
server.addRecipe(recipe(markerKey, createKeystone(markerKey)));
|
|
}
|
|
|
|
static ItemStack createKeystone(NamespacedKey markerKey) {
|
|
ItemStack item = new ItemStack(Material.RECOVERY_COMPASS);
|
|
ItemMeta metadata = item.getItemMeta();
|
|
if (!(metadata instanceof CompassMeta)) {
|
|
throw new IllegalStateException("Recovery Compass metadata is unavailable");
|
|
}
|
|
metadata.displayName(Component.text(
|
|
"Pocket Base Keystone", NamedTextColor.LIGHT_PURPLE
|
|
));
|
|
metadata.lore(List.of(
|
|
Component.text(
|
|
"Right-click to enter your Pocket Base.", NamedTextColor.GRAY
|
|
),
|
|
Component.text("Use it again inside to return.", NamedTextColor.GRAY)
|
|
));
|
|
metadata.getPersistentDataContainer().set(
|
|
markerKey, PersistentDataType.BYTE, (byte) 1
|
|
);
|
|
item.setItemMeta(metadata);
|
|
return item;
|
|
}
|
|
|
|
static RecipeSpec recipeSpecification() {
|
|
return new RecipeSpec(
|
|
"ONO", "NEN", "ONO", Material.OBSIDIAN,
|
|
Material.NETHERITE_INGOT, Material.ENDER_PEARL
|
|
);
|
|
}
|
|
|
|
static ShapedRecipe recipe(NamespacedKey key, ItemStack result) {
|
|
RecipeSpec specification = recipeSpecification();
|
|
ShapedRecipe recipe = new ShapedRecipe(key, result);
|
|
recipe.shape(specification.top(), specification.middle(), specification.bottom());
|
|
recipe.setIngredient('O', specification.obsidian());
|
|
recipe.setIngredient('N', specification.netherite());
|
|
recipe.setIngredient('E', specification.enderPearl());
|
|
return recipe;
|
|
}
|
|
|
|
record RecipeSpec(
|
|
String top,
|
|
String middle,
|
|
String bottom,
|
|
Material obsidian,
|
|
Material netherite,
|
|
Material enderPearl
|
|
) {
|
|
}
|
|
}
|