feat: add fixes for paper nether warp issues
This commit is contained in:
parent
2c919bff06
commit
c9ea7c8aa4
33
build.gradle
33
build.gradle
|
@ -47,10 +47,11 @@ def isCanary = version.toString().contains('canary')
|
|||
|
||||
group = 'com.sekwah.advancedportals'
|
||||
|
||||
archivesBaseName = "Advanced-Portals"
|
||||
description = ""
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
|
@ -79,7 +80,7 @@ repositories {
|
|||
maven { url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" }
|
||||
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
|
||||
maven { url "https://nexus.velocitypowered.com/repository/maven-public/" }
|
||||
maven { url 'https://papermc.io/repo/repository/maven-public/' }
|
||||
maven { url 'https://repo.papermc.io/repository/maven-public/' }
|
||||
maven { url 'https://maven.enginehub.org/repo/' } // WorldEdit
|
||||
}
|
||||
|
||||
|
@ -94,7 +95,9 @@ dependencies {
|
|||
annotationProcessor "com.velocitypowered:velocity-api:3.1.1"
|
||||
|
||||
implementation "io.netty:netty-all:4.1.91.Final"
|
||||
compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT'
|
||||
//compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT'
|
||||
//compileOnly 'io.papermc.paper:paper-api:1.19-R0.1-SNAPSHOT'
|
||||
compileOnly "dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT"
|
||||
|
||||
implementation "com.sk89q.worldedit:worldedit-bukkit:7.2.14"
|
||||
//compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
@ -142,8 +145,11 @@ task discordupload {
|
|||
}
|
||||
|
||||
minecraftServerConfig {
|
||||
//jarUrl.set('https://download.getbukkit.org/spigot/spigot-1.19.3.jar')
|
||||
jarUrl.set('https://api.papermc.io/v2/projects/paper/versions/1.19.3/builds/381/downloads/paper-1.19.3-381.jar')
|
||||
//jarUrl.set('https://download.getbukkit.org/spigot/spigot-1.13.2.jar')
|
||||
//jarUrl.set('https://download.getbukkit.org/spigot/spigot-1.20.1.jar')
|
||||
//jarUrl.set('https://api.papermc.io/v2/projects/paper/versions/1.13.2/builds/657/downloads/paper-1.13.2-657.jar')
|
||||
jarUrl.set('https://api.papermc.io/v2/projects/paper/versions/1.20.1/builds/83/downloads/paper-1.20.1-83.jar')
|
||||
//jarUrl.set('https://api.papermc.io/v2/projects/folia/versions/1.20.1/builds/10/downloads/folia-1.20.1-10.jar')
|
||||
jvmArgument = ["-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005", "-DIReallyKnowWhatIAmDoingISwear=true"]
|
||||
}
|
||||
|
||||
|
@ -298,24 +304,21 @@ task curseforge {
|
|||
// releaseType = 'release'
|
||||
}
|
||||
|
||||
task copyPlugin {
|
||||
task copyPlugin() {
|
||||
doLast {
|
||||
copy {
|
||||
if (System.env.MC_SERVER_LOC == null) {
|
||||
throw new Exception('You must set the server location and jar to use')
|
||||
}
|
||||
println "$buildDir/libs/Advanced-Portals-${version}.jar"
|
||||
println "${System.env.MC_SERVER_LOC}/plugins/Advanced-Portals-${version}.jar"
|
||||
println "$buildDir/libs/Advanced-Portals-${getVersion()}.jar"
|
||||
println "$buildDir/MinecraftServer/plugins/Advanced-Portals-${getVersion()}.jar"
|
||||
try {
|
||||
delete fileTree("${System.env.MC_SERVER_LOC}/plugins/") {
|
||||
delete fileTree("$buildDir/MinecraftServer/plugins/") {
|
||||
include "*.jar"
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
println e.getLocalizedMessage()
|
||||
}
|
||||
from file("$buildDir/libs/Advanced-Portals-${version}.jar")
|
||||
into file("${System.env.MC_SERVER_LOC}/plugins/")
|
||||
from file("$buildDir/libs/Advanced-Portals-${getVersion()}.jar")
|
||||
into file("$buildDir/MinecraftServer/plugins")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.sekwah.advancedportals.bukkit.effects.WarpEffects;
|
|||
import com.sekwah.advancedportals.bukkit.listeners.*;
|
||||
import com.sekwah.advancedportals.bukkit.metrics.Metrics;
|
||||
import com.sekwah.advancedportals.bukkit.portals.Portal;
|
||||
import com.sekwah.advancedportals.bukkit.util.ForkDetector;
|
||||
import com.sekwah.advancedportals.bungee.BungeeMessages;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
@ -15,8 +16,8 @@ import org.bukkit.configuration.file.FileConfiguration;
|
|||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.print.Paper;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -81,7 +82,6 @@ public class AdvancedPortalsPlugin extends JavaPlugin {
|
|||
|
||||
for (Player player:
|
||||
this.getServer().getOnlinePlayers()) {
|
||||
player.removeMetadata(Listeners.HAS_WARPED, this);
|
||||
player.removeMetadata(Listeners.LAVA_WARPED, this);
|
||||
}
|
||||
|
||||
|
@ -137,9 +137,7 @@ public class AdvancedPortalsPlugin extends JavaPlugin {
|
|||
private boolean checkIfBungee()
|
||||
{
|
||||
// we check if the server is Spigot/Paper (because of the spigot.yml file)
|
||||
try {
|
||||
Class.forName("org.spigotmc.SpigotConfig");
|
||||
} catch (ClassNotFoundException e) {
|
||||
if(!ForkDetector.isSpigot()) {
|
||||
this.getServer().getConsoleSender().sendMessage( "\u00A7ePossibly unsupported version for bungee messages detected, channels won't be enabled." );
|
||||
getLogger().info("If you believe this shouldn't be the case please contact us on discord https://discord.sekwah.com/");
|
||||
return false;
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.sekwah.advancedportals.bukkit.config.ConfigAccessor;
|
|||
import com.sekwah.advancedportals.bukkit.effects.WarpEffects;
|
||||
import com.sekwah.advancedportals.bukkit.portals.AdvancedPortal;
|
||||
import com.sekwah.advancedportals.bukkit.portals.Portal;
|
||||
import com.sekwah.advancedportals.bukkit.util.ForkDetector;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
|
@ -14,8 +15,10 @@ import org.bukkit.Location;
|
|||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Destination {
|
||||
|
@ -146,12 +149,29 @@ public class Destination {
|
|||
if (player.getVehicle() != null && TELEPORT_RIDING) {
|
||||
|
||||
riding.eject();
|
||||
riding.teleport(loc, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
player.teleport(loc, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
riding.setPassenger(player);
|
||||
|
||||
if(ForkDetector.isFolia()) {
|
||||
riding.teleportAsync(loc, PlayerTeleportEvent.TeleportCause.PLUGIN).thenAccept((result) -> {
|
||||
if(result) {
|
||||
player.teleportAsync(loc, PlayerTeleportEvent.TeleportCause.PLUGIN).thenAccept((result2) -> {
|
||||
if(result2) {
|
||||
riding.setPassenger(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
riding.teleport(loc, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
player.teleport(loc, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
riding.setPassenger(player);
|
||||
}
|
||||
|
||||
} else {
|
||||
player.teleport(loc, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
if(ForkDetector.isFolia()) {
|
||||
player.teleportAsync(loc, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
} else {
|
||||
player.teleport(loc, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
}
|
||||
}
|
||||
|
||||
if (disp != null && disp.getArg("particleout") != null) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.sekwah.advancedportals.bukkit.config.ConfigAccessor;
|
|||
import com.sekwah.advancedportals.bukkit.destinations.Destination;
|
||||
import com.sekwah.advancedportals.bukkit.portals.AdvancedPortal;
|
||||
import com.sekwah.advancedportals.bukkit.portals.Portal;
|
||||
import com.sekwah.advancedportals.bukkit.util.ForkDetector;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Orientable;
|
||||
|
@ -26,6 +27,7 @@ import org.bukkit.metadata.FixedMetadataValue;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import com.sekwah.advancedportals.bukkit.util.FoliaHandler;
|
||||
|
||||
public class Listeners implements Listener {
|
||||
|
||||
|
@ -36,6 +38,8 @@ public class Listeners implements Listener {
|
|||
|
||||
public static String HAS_WARPED = "hasWarped";
|
||||
public static String LAVA_WARPED = "lavaWarped";
|
||||
// Just a backup in case it doesn't get removed.
|
||||
public static long MAX_SAFETY_TIME = 1000;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public Listeners(AdvancedPortalsPlugin plugin) {
|
||||
|
@ -55,8 +59,13 @@ public class Listeners implements Listener {
|
|||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
|
||||
int cleanPeriod = config.getConfig().getInt("CleanUpPeriod", 120);
|
||||
int period = 20 * 60 * cleanPeriod;
|
||||
Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new CooldownDataRemovalTask(), period, period);
|
||||
int period = 60 * cleanPeriod;
|
||||
if(ForkDetector.isFolia()) {
|
||||
FoliaHandler.repeatingTask(plugin, new CooldownDataRemovalTask(), period);
|
||||
} else {
|
||||
plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, new CooldownDataRemovalTask(),
|
||||
20L * period, 20L * period);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
|
@ -80,7 +89,9 @@ public class Listeners implements Listener {
|
|||
|
||||
@EventHandler
|
||||
public void onWorldChangeEvent(PlayerChangedWorldEvent event) {
|
||||
Portal.joinCooldownLock.writeLock().lock();
|
||||
Portal.joinCooldown.put(event.getPlayer().getName(), System.currentTimeMillis());
|
||||
Portal.joinCooldownLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
@ -96,7 +107,9 @@ public class Listeners implements Listener {
|
|||
public void onJoinEvent(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
Portal.joinCooldownLock.writeLock().lock();
|
||||
Portal.joinCooldown.put(player.getName(), System.currentTimeMillis());
|
||||
Portal.joinCooldownLock.writeLock().unlock();
|
||||
|
||||
Location loc = player.getLocation();
|
||||
Location eyeLoc = player.getEyeLocation();
|
||||
|
@ -137,10 +150,12 @@ public class Listeners implements Listener {
|
|||
if (delayed ? Portal.locationInPortal(portal, loc, 1)
|
||||
: Portal.locationInPortalTrigger(portal, loc)) {
|
||||
|
||||
player.setMetadata(HAS_WARPED, new FixedMetadataValue(plugin, true));
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new RemoveWarpData(player), 10);
|
||||
player.setMetadata(HAS_WARPED, new FixedMetadataValue(plugin, System.currentTimeMillis()));
|
||||
if(ForkDetector.isFolia()) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new RemoveWarpData(player), 10);
|
||||
}
|
||||
if (portal.getTriggers().contains(Material.LAVA)) {
|
||||
player.setMetadata(LAVA_WARPED, new FixedMetadataValue(plugin, true));
|
||||
player.setMetadata(LAVA_WARPED, new FixedMetadataValue(plugin, System.currentTimeMillis()));
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new RemoveLavaData(player), 10);
|
||||
}
|
||||
if (portal.inPortal.contains(player.getUniqueId()))
|
||||
|
@ -210,13 +225,18 @@ public class Listeners implements Listener {
|
|||
}
|
||||
|
||||
private void resizeMaps() {
|
||||
|
||||
Portal.cooldownLock.writeLock().lock();
|
||||
HashMap<String, HashMap<String, Long>> newCooldowns = new HashMap<String, HashMap<String, Long>>(Math.max(Portal.cooldown.size() * 2, 10));
|
||||
newCooldowns.putAll(Portal.cooldown);
|
||||
Portal.cooldown = newCooldowns;
|
||||
Portal.cooldownLock.writeLock().unlock();
|
||||
|
||||
Portal.joinCooldownLock.writeLock().lock();
|
||||
HashMap<String, Long> newJoinCooldowns = new HashMap<String, Long>(Math.max(Portal.joinCooldown.size() * 2, 10));
|
||||
newJoinCooldowns.putAll(Portal.joinCooldown);
|
||||
Portal.joinCooldown = newJoinCooldowns;
|
||||
Portal.joinCooldownLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,7 +283,7 @@ public class Listeners implements Listener {
|
|||
if (event.getEntity() instanceof Player && (event.getCause() == EntityDamageEvent.DamageCause.LAVA
|
||||
|| event.getCause() == EntityDamageEvent.DamageCause.FIRE
|
||||
|| event.getCause() == EntityDamageEvent.DamageCause.FIRE_TICK)) {
|
||||
if (event.getEntity().hasMetadata(LAVA_WARPED)
|
||||
if ((event.getEntity().hasMetadata(LAVA_WARPED) && event.getEntity().getMetadata(LAVA_WARPED).get(0).asLong() + MAX_SAFETY_TIME > System.currentTimeMillis())
|
||||
| Portal.inPortalTriggerRegion(event.getEntity().getLocation()))
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
@ -283,15 +303,33 @@ public class Listeners implements Listener {
|
|||
checkTriggerLocations(player, true, loc, eyeLoc);
|
||||
}
|
||||
|
||||
if (player.hasMetadata(HAS_WARPED) | Portal.inPortalRegion(event.getFrom(), 1))
|
||||
if ((player.hasMetadata(HAS_WARPED) && player.getMetadata(HAS_WARPED).get(0).asLong() + MAX_SAFETY_TIME > System.currentTimeMillis()) | Portal.inPortalRegion(event.getFrom(), 1))
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onTeleport(PlayerTeleportEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
if((event.getCause() == PlayerTeleportEvent.TeleportCause.END_GATEWAY
|
||||
|| event.getCause() == PlayerTeleportEvent.TeleportCause.END_PORTAL
|
||||
|| event.getCause() == PlayerTeleportEvent.TeleportCause.NETHER_PORTAL)
|
||||
&& (Portal.locationInPortal(event.getFrom(), 1) || player.hasMetadata(HAS_WARPED))) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
Location loc = event.getFrom();
|
||||
Location eyeLoc = new Location(loc.getWorld(), loc.getX(), loc.getY() + player.getEyeHeight(), loc.getZ());
|
||||
|
||||
checkTriggerLocations(player, true, loc, eyeLoc);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onEntityPortalEvent(EntityPortalEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(Portal.locationInPortal(event.getFrom(), 2)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.sekwah.advancedportals.bukkit.listeners;
|
|||
import com.sekwah.advancedportals.bukkit.AdvancedPortalsPlugin;
|
||||
import com.sekwah.advancedportals.bukkit.config.ConfigAccessor;
|
||||
import com.sekwah.advancedportals.bukkit.portals.Portal;
|
||||
import com.sekwah.advancedportals.bukkit.util.ForkDetector;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
|
@ -66,7 +67,8 @@ public class PortalPlacer implements Listener {
|
|||
if(!this.DISABLE_GATEWAY_BEAM) {
|
||||
return;
|
||||
}
|
||||
BlockState[] tileEntities = event.getChunk().getTileEntities();
|
||||
// TODO take a look at potentially switching to the paper logic if paper is in use, also you can check if the chunk even contains a portal.
|
||||
BlockState[] tileEntities = ForkDetector.isPaper() ? event.getChunk().getTileEntities(false) : event.getChunk().getTileEntities();
|
||||
for(BlockState block : tileEntities) {
|
||||
if(block.getType() == Material.END_GATEWAY) {
|
||||
if(Portal.inPortalRegion(block.getLocation(), 5)) {
|
||||
|
|
|
@ -20,12 +20,22 @@ import org.bukkit.permissions.PermissionAttachment;
|
|||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Portal {
|
||||
|
||||
/**
|
||||
* Only used for Folia
|
||||
*/
|
||||
public static final ReentrantReadWriteLock joinCooldownLock = new ReentrantReadWriteLock();
|
||||
public static HashMap<String, Long> joinCooldown = new HashMap<String, Long>();
|
||||
|
||||
|
||||
/**
|
||||
* Only used for Folia
|
||||
*/
|
||||
public static final ReentrantReadWriteLock cooldownLock = new ReentrantReadWriteLock();
|
||||
public static HashMap<String, HashMap<String, Long>> cooldown = new HashMap<String, HashMap<String, Long>>();
|
||||
// Config values
|
||||
public static boolean portalsActive = false;
|
||||
|
@ -467,7 +477,10 @@ public class Portal {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
Portal.joinCooldownLock.readLock().lock();
|
||||
Long joinCD = joinCooldown.get(player.getName());
|
||||
Portal.joinCooldownLock.readLock().unlock();
|
||||
if (joinCD != null) {
|
||||
int diff = (int) ((System.currentTimeMillis() - joinCD) / 1000);
|
||||
if (diff < joinCooldownDelay) {
|
||||
|
@ -478,9 +491,12 @@ public class Portal {
|
|||
throwPlayerBack(player);
|
||||
return false;
|
||||
}
|
||||
Portal.joinCooldownLock.writeLock().lock();
|
||||
joinCooldown.remove(player.getName());
|
||||
Portal.joinCooldownLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
cooldownLock.writeLock().lock();
|
||||
HashMap<String, Long> cds = cooldown.get(player.getName());
|
||||
if (cds != null) {
|
||||
if (cds.get(portal.getName()) != null) {
|
||||
|
@ -507,6 +523,7 @@ public class Portal {
|
|||
}
|
||||
cds.put(portal.getName(), System.currentTimeMillis());
|
||||
cooldown.put(player.getName(), cds);
|
||||
cooldownLock.writeLock().unlock();
|
||||
|
||||
boolean showFailMessage = !portal.hasArg("command.1");
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.sekwah.advancedportals.bukkit.util;
|
||||
|
||||
import com.sekwah.advancedportals.bukkit.AdvancedPortalsPlugin;
|
||||
import com.sekwah.advancedportals.bukkit.listeners.Listeners;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This is to stop jars such as spigot complaining about folia imports
|
||||
*/
|
||||
public class FoliaHandler {
|
||||
public static void repeatingTask(AdvancedPortalsPlugin plugin, Runnable runnable, int seconds) {
|
||||
plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, (task) -> {
|
||||
runnable.run();
|
||||
}, seconds, seconds, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.sekwah.advancedportals.bukkit.util;
|
||||
|
||||
public class ForkDetector {
|
||||
|
||||
public static boolean isSpigot() {
|
||||
try {
|
||||
Class.forName("org.spigotmc.SpigotConfig");
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isPaper() {
|
||||
try {
|
||||
Class.forName("com.destroystokyo.paper.PaperConfig");
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isFolia() {
|
||||
try {
|
||||
Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,8 @@ version: 0.9.3
|
|||
author: sekwah41
|
||||
description: An advanced portals plugin for bukkit.
|
||||
api-version: 1.13
|
||||
folia-supported: true
|
||||
website: https://advancedportals.sekwah.com/
|
||||
|
||||
softdepend:
|
||||
- WorldEdit
|
||||
|
|
Loading…
Reference in New Issue