Compare commits

...

12 Commits

Author SHA1 Message Date
Gabriel Tofvesson
7c9bf21ced Implement sign editing 2021-07-24 21:38:06 +02:00
Gabriel Tofvesson
f4cb5f58e5 Integrate new Quality-of-Life commands 2021-07-24 00:04:02 +02:00
Gabriel Tofvesson
3fde89e1ca Implement new Quality-of-Life commands 2021-07-23 23:55:34 +02:00
Gabriel Tofvesson
5701a20cc8 Fix re-render boundary checks 2021-07-07 20:55:34 +02:00
Gabriel Tofvesson
97e17cba3f Update list of capitator targets 2021-07-06 21:57:30 +02:00
Gabriel Tofvesson
8acef93d7d Implement minimum food level option for capitator functionality 2021-07-03 03:22:14 +02:00
Gabriel Tofvesson
353f97a5e8 Implement configurable hunger penalty for capitator axe 2021-07-03 03:08:36 +02:00
Gabriel Tofvesson
a3f0fcf5aa Implement /search result ordering by distance 2021-06-30 00:04:05 +02:00
Gabriel Tofvesson
cb10fa6720 Move deployment script to gist: https://gist.github.com/GabrielTofvesson/eb093c1434f5541bd9984e7baac4d65f 2021-06-29 21:04:45 +02:00
Gabriel Tofvesson
1c2d966791 Fix configuration typo 2021-06-29 16:01:22 +02:00
Gabriel Tofvesson
092aa33303 Update version number 2021-06-27 21:58:20 +02:00
Gabriel Tofvesson
a740b34e4e Implement ItemStack finder command 2021-06-27 21:57:55 +02:00
13 changed files with 347 additions and 310 deletions

View File

@ -9,4 +9,6 @@ search:
searchRadiusY: 8
searchRadiusZ: 8
ghostClick: true
capitator: true
capitator: true
capitatorHungerPerBlock: 0.03125
capitatorMinHunger: 3

View File

@ -1,5 +1,5 @@
name: InventoryTweaks
version: 1.4.0
version: 1.4.1
author: IKEA_Jesus
main: dev.w1zzrd.invtweaks.InvTweaksPlugin
api-version: 1.13
@ -18,6 +18,10 @@ commands:
description: Search for a given item in all nearby inventories
usage: /<command> {item type}
permission: invtweaks.search
find:
description: Show all chests that contain a given item
usage: /<command> {item type}
permission: invtweaks.find
capitator:
description: Toggle tree capitation for an axe in your main hand
usage: /<command>
@ -25,4 +29,12 @@ commands:
chestname:
description: Add a floating nametag to a chest
usage: /<command> {name}
permission: invtweaks.spawnfake
permission: invtweaks.spawnfake
growup:
description: Age baby animals
usage: /<command> [radius]
permission: invtweaks.growup
rewool:
description: Put the wool back on sheep
usage: /<command> [radius]
permission: invtweaks.rewool

View File

@ -41,7 +41,10 @@ public final class InvTweaksPlugin extends JavaPlugin {
private MagnetCommandExecutor magnetCommandExecutor;
private SearchCommandExecutor searchCommandExecutor;
private NamedChestCommand namedChestCommandExecutor;
private FindCommandExecutor findCommandExecutor;
private CapitatorCommand capitatorCommand;
private GrowUpCommand growUpCommand;
private ReWoolCommand reWoolCommand;
private PersistentData data;
private NamedChestManager chestManager;
private EnchantmentRegistryEntry<CapitatorEnchantment> capitatorEnchantment = null;
@ -120,9 +123,14 @@ public final class InvTweaksPlugin extends JavaPlugin {
pluginManager.registerEvents(new SortListener(), this);
pluginManager.registerEvents(new MagnetismListener(magnetCommandExecutor), this);
pluginManager.registerEvents(new TabCompletionListener(), this);
pluginManager.registerEvents(new TreeCapitatorListener(activateCapitator ? capitatorEnchantment.getEnchantment() : null), this);
pluginManager.registerEvents(new TreeCapitatorListener(
activateCapitator ? capitatorEnchantment.getEnchantment() : null,
getConfig().getDouble("capitatorHungerPerBlock"),
getConfig().getInt("capitatorMinHunger")
), this);
pluginManager.registerEvents(new PlayerMoveRenderListener(chestManager), this);
pluginManager.registerEvents(new ChestBreakListener(chestManager), this);
pluginManager.registerEvents(new SignEditListener(), this);
}
/**
@ -143,6 +151,9 @@ public final class InvTweaksPlugin extends JavaPlugin {
magnetCommandExecutor = new MagnetCommandExecutor(this, "magnet", getPersistentData());
searchCommandExecutor = new SearchCommandExecutor(this, "search");
namedChestCommandExecutor = new NamedChestCommand(chestManager);
findCommandExecutor = new FindCommandExecutor(this);
growUpCommand = new GrowUpCommand();
reWoolCommand = new ReWoolCommand();
if (activateCapitator)
capitatorCommand = new CapitatorCommand(capitatorEnchantment.getEnchantment());
@ -152,6 +163,9 @@ public final class InvTweaksPlugin extends JavaPlugin {
Objects.requireNonNull(getCommand("magnet")).setExecutor(magnetCommandExecutor);
Objects.requireNonNull(getCommand("search")).setExecutor(searchCommandExecutor);
Objects.requireNonNull(getCommand("chestname")).setExecutor(namedChestCommandExecutor);
Objects.requireNonNull(getCommand("find")).setExecutor(findCommandExecutor);
Objects.requireNonNull(getCommand("growup")).setExecutor(growUpCommand);
Objects.requireNonNull(getCommand("rewool")).setExecutor(reWoolCommand);
if (activateCapitator)
Objects.requireNonNull(getCommand("capitator")).setExecutor(capitatorCommand);
@ -164,6 +178,7 @@ public final class InvTweaksPlugin extends JavaPlugin {
magnetCommandExecutor.onDisable();
capitatorCommand = null;
findCommandExecutor = null;
namedChestCommandExecutor = null;
searchCommandExecutor = null;
magnetCommandExecutor = null;

View File

@ -0,0 +1,43 @@
package dev.w1zzrd.invtweaks.command;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import java.util.stream.Stream;
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.errorMessage;
public final class Commands {
private Commands() { throw new UnsupportedOperationException("Functional class"); }
public static double getCommandRadius(final CommandSender sender, final String[] args, final int radiusIndex) {
try {
if (args.length > radiusIndex)
return Double.parseDouble(args[radiusIndex]);
} catch (NumberFormatException e) {
sender.spigot().sendMessage(errorMessage(String.format("\"%s\" is not a number", args[radiusIndex])));
}
return Double.NaN;
}
public static <T extends Entity> Stream<T> getCommandEntityRadius(
final CommandSender sender,
final String[] args,
final int radiusIndex,
final Class<T> entityType
) {
final double radius = Commands.getCommandRadius(sender, args, 0);
if (Double.compare(radius, Double.NaN) == 0)
return null;
return
(radius >= 0 ?
((Player) sender).getNearbyEntities(radius, radius, radius).stream() :
Bukkit.getWorlds().stream().flatMap(it -> it.getEntities().stream()))
.filter(it -> entityType.isAssignableFrom(it.getClass()))
.map(it -> (T) it);
}
}

View File

@ -0,0 +1,98 @@
package dev.w1zzrd.invtweaks.command;
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Container;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static dev.w1zzrd.invtweaks.listener.TabCompletionListener.getMaterialMatching;
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
import static dev.w1zzrd.spigot.wizcompat.packet.EntityCreator.*;
public final class FindCommandExecutor implements CommandExecutor {
private static final int SEARCH_RADIUS = 3;
private static final long DESPAWN_WAIT = 20 * 10;
private final Plugin plugin;
public FindCommandExecutor(final Plugin plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
Material targetMaterial;
if (assertTrue(sender instanceof Player, "Command can only be run by players", sender) ||
assertTrue(args.length == 1, "Exactly one argument is expected", sender) ||
assertTrue((targetMaterial = getMaterialMatching(args[0])) != null, String.format("Unknown item/block: %s", args[0]), sender))
return true;
final Player player = (Player) sender;
final List<BlockState> matches = searchChunks(
player.getLocation().getChunk(),
SEARCH_RADIUS,
Material.CHEST, Material.TRAPPED_CHEST, Material.SHULKER_BOX
);
final List<Integer> entities = matches.stream()
.filter(it -> Arrays.stream(((Container)it).getSnapshotInventory().getContents()).filter(Objects::nonNull).map(ItemStack::getType).anyMatch(targetMaterial::equals))
.map(state -> spawnMarker(player, state.getLocation().add(0.5, 0.2, 0.5)))
.collect(Collectors.toList());
if (assertTrue(entities.size() > 0, "No containers neardby contain that item/block", sender))
return true;
Bukkit.getScheduler().runTaskLater(plugin, () -> {
entities.forEach(it -> sendEntityDespawnPacket(player, it));
}, DESPAWN_WAIT);
return true;
}
private static int spawnMarker(final Player target, final Location location) {
final Object entity = createFakeSlime(target);
setSlimeSize(entity, 1);
setEntityCollision(entity, false);
setEntityInvisible(entity, true);
setEntityInvulnerable(entity, true);
setEntityGlowing(entity, true);
setEntityLocation(entity, location.getX(), location.getY(), location.getZ(), 0f, 0f);
sendEntitySpawnPacket(target, entity);
sendEntityMetadataPacket(target, entity);
return getEntityID(entity);
}
private static List<BlockState> searchChunks(final Chunk middle, final int radius, final Material... targets) {
final int xMin = middle.getX() - radius;
final int xMax = middle.getX() + radius;
final int zMin = middle.getZ() - radius;
final int zMax = middle.getZ() + radius;
final World sourceWorld = middle.getWorld();
final List<Material> targetMaterials = Arrays.asList(targets);
final ArrayList<BlockState> collect = new ArrayList<>();
for (int x = xMin; x <= xMax; ++x)
for (int z = zMin; z <= zMax; ++z)
Arrays.stream(sourceWorld.getChunkAt(x, z).getTileEntities()).filter(it -> targetMaterials.contains(it.getType())).forEach(collect::add);
return collect;
}
}

View File

@ -0,0 +1,28 @@
package dev.w1zzrd.invtweaks.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.Breedable;
import org.bukkit.entity.Player;
import java.util.stream.Stream;
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
public class GrowUpCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (assertTrue(args.length == 0 || sender instanceof Player, "Only players can run this command", sender))
return true;
final Stream<Breedable> breedables = Commands.getCommandEntityRadius(sender, args, 0, Breedable.class);
if (breedables == null)
return true;
breedables.filter(it -> !it.getAgeLock() && !it.isAdult()).forEach(Ageable::setAdult);
return true;
}
}

View File

@ -0,0 +1,27 @@
package dev.w1zzrd.invtweaks.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.entity.Sheep;
import java.util.stream.Stream;
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
public class ReWoolCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (assertTrue(args.length == 0 || sender instanceof Player, "Only players can run this command", sender))
return true;
final Stream<Sheep> sheep = Commands.getCommandEntityRadius(sender, args, 0, Sheep.class);
if (sheep == null)
return true;
sheep.forEach(it -> it.setSheared(false));
return true;
}
}

View File

@ -3,10 +3,7 @@ package dev.w1zzrd.invtweaks.command;
import dev.w1zzrd.invtweaks.InvTweaksPlugin;
import dev.w1zzrd.invtweaks.serialization.SearchConfig;
import dev.w1zzrd.spigot.wizcompat.command.ConfigurableCommandExecutor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.*;
import org.bukkit.block.*;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
@ -17,10 +14,8 @@ import org.bukkit.plugin.Plugin;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.Comparator;
import java.util.logging.Logger;
import static dev.w1zzrd.invtweaks.listener.TabCompletionListener.getMaterialMatching;
@ -54,23 +49,23 @@ public class SearchCommandExecutor extends ConfigurableCommandExecutor<SearchCon
assertTrue((targetMaterial = getMaterialMatching(args[0])) != null, String.format(ERR_UNKNOWN, args[0]), sender)
) return true;
assert targetMaterial != null;
assert sender instanceof Player;
final Player player = (Player) sender;
final Location playerLocation = player.getLocation();
final SearchConfig config = getConfig();
final List<BlockState> matches = searchBlocks(
player.getLocation(),
player.getWorld(),
config.getSearchRadiusX(), config.getSearchRadiusY(), config.getSearchRadiusZ(),
Material.CHEST, Material.SHULKER_BOX
final List<BlockState> matches = searchChunks(
playerLocation.getChunk(),
config.getSearchRadiusX(),
Material.CHEST, Material.TRAPPED_CHEST, Material.SHULKER_BOX
);
// Ensure we found inventory-holding blocks
if (assertTrue(matches.size() != 0, ERR_NO_INVENTORIES, sender))
return true;
matches.sort(Comparator.comparingDouble(state -> state.getLocation().distanceSquared(playerLocation)));
final InventoryHolder result;
FIND_RESULT:
@ -152,7 +147,24 @@ public class SearchCommandExecutor extends ConfigurableCommandExecutor<SearchCon
return true;
}
private List<BlockState> searchBlocks(
private static List<BlockState> searchChunks(final Chunk middle, final int radius, final Material... targets) {
final int xMin = middle.getX() - radius;
final int xMax = middle.getX() + radius;
final int zMin = middle.getZ() - radius;
final int zMax = middle.getZ() + radius;
final World sourceWorld = middle.getWorld();
final List<Material> targetMaterials = Arrays.asList(targets);
final ArrayList<BlockState> collect = new ArrayList<>();
for (int x = xMin; x <= xMax; ++x)
for (int z = zMin; z <= zMax; ++z)
Arrays.stream(sourceWorld.getChunkAt(x, z).getTileEntities()).filter(it -> targetMaterials.contains(it.getType())).forEach(collect::add);
return collect;
}
private static List<BlockState> searchBlocks(
final Location centre,
final World world,
final int rx,

View File

@ -1,6 +1,7 @@
package dev.w1zzrd.invtweaks.feature;
import dev.w1zzrd.invtweaks.serialization.ChestNameConfig;
import dev.w1zzrd.spigot.wizcompat.packet.EntityCreator;
import dev.w1zzrd.spigot.wizcompat.serialization.PersistentData;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
@ -123,12 +124,13 @@ public final class NamedChestManager {
if (chunk == null)
return;
chunk.streamEntries().forEach(entry -> {
final Object entity = entry.getEntity(() -> null);
if (entity != null)
sendEntityDespawnPacket(target, getEntityID(entity));
});
sendEntityDespawnPackets(
target,
chunk.streamEntries()
.map(entry -> entry.getEntity(() -> null))
.filter(Objects::nonNull)
.mapToInt(EntityCreator::getEntityID).toArray()
);
});
}
@ -268,7 +270,7 @@ public final class NamedChestManager {
.filter(chunk -> {
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chestChunk = config.getChunkEntry(worldID, chunk.getRender().x(), chunk.getRender().z());
return chunk.getRender().x() < xMax ||
return chunk.getRender().x() > xMax ||
chunk.getRender().x() < xMin ||
chunk.getRender().z() > zMax ||
chunk.getRender().z() < zMin ||

View File

@ -0,0 +1,21 @@
package dev.w1zzrd.invtweaks.listener;
import dev.w1zzrd.spigot.wizcompat.packet.Players;
import org.bukkit.block.Sign;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.Objects;
public class SignEditListener implements Listener {
@EventHandler
public void onSignClick(final PlayerInteractEvent event) {
if(event.getAction() == Action.RIGHT_CLICK_BLOCK &&
Objects.requireNonNull(event.getClickedBlock()).getState() instanceof Sign &&
event.getPlayer().isSneaking()) { // Sneak-right-click to edit sign
Players.openSignEditor(event.getPlayer(), event.getClickedBlock().getLocation());
}
}
}

View File

@ -112,9 +112,16 @@ public class TabCompletionListener implements Listener {
public static Material getMaterialMatching(final String arg) {
final List<Material> mats = getAllMaterialsMatching(arg).collect(Collectors.toList());
if (mats.size() == 0)
return null;
if (mats.size() == 1)
return mats.get(0);
return mats.stream()
.filter(it -> multiNS ? arg.equals(it.getKey().toString()) : arg.equals(it.getKey().getKey()))
.findFirst().orElse(mats.size() == 1 ? mats.get(0) : null);
.filter(it -> multiNS || arg.contains(":") ? arg.equals(it.getKey().toString()) : arg.equals(it.getKey().getKey()))
.findFirst()
.orElse(null);
}
}

View File

@ -12,9 +12,12 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.*;
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.errorMessage;
import static org.bukkit.Material.*;
public class TreeCapitatorListener implements Listener {
@ -27,13 +30,38 @@ public class TreeCapitatorListener implements Listener {
JUNGLE_LOG,
DARK_OAK_LOG,
SPRUCE_LOG,
CRIMSON_STEM,
WARPED_STEM,
ACACIA_WOOD,
OAK_WOOD,
BIRCH_WOOD,
JUNGLE_WOOD,
DARK_OAK_WOOD,
SPRUCE_WOOD,
CRIMSON_STEM,
WARPED_STEM,
CRIMSON_HYPHAE,
WARPED_HYPHAE,
STRIPPED_ACACIA_LOG,
STRIPPED_OAK_LOG,
STRIPPED_BIRCH_LOG,
STRIPPED_JUNGLE_LOG,
STRIPPED_DARK_OAK_LOG,
STRIPPED_SPRUCE_LOG,
STRIPPED_CRIMSON_STEM,
STRIPPED_WARPED_STEM,
STRIPPED_CRIMSON_HYPHAE,
STRIPPED_WARPED_HYPHAE,
ACACIA_LEAVES,
OAK_LEAVES,
BIRCH_LEAVES,
JUNGLE_LEAVES,
DARK_OAK_LEAVES,
SPRUCE_LEAVES
SPRUCE_LEAVES,
NETHER_WART_BLOCK,
WARPED_WART_BLOCK,
SHROOMLIGHT
);
private static final List<Material> leaves = Arrays.asList(
@ -42,13 +70,20 @@ public class TreeCapitatorListener implements Listener {
BIRCH_LEAVES,
JUNGLE_LEAVES,
DARK_OAK_LEAVES,
SPRUCE_LEAVES
SPRUCE_LEAVES,
NETHER_WART_BLOCK,
WARPED_WART_BLOCK,
SHROOMLIGHT
);
private final Enchantment capitatorEnchantment;
private final double hungerPerBlock;
private final int minHunger;
public TreeCapitatorListener(final Enchantment capitatorEnchantment) {
public TreeCapitatorListener(final Enchantment capitatorEnchantment, final double hungerPerBlock, final int minHunger) {
this.capitatorEnchantment = capitatorEnchantment;
this.hungerPerBlock = hungerPerBlock;
this.minHunger = minHunger;
}
@EventHandler
@ -57,6 +92,12 @@ public class TreeCapitatorListener implements Listener {
if (event.isCancelled() || !handTool.containsEnchantment(capitatorEnchantment))
return;
// Check if capitator functionality is prevented by hunger
if (event.getPlayer().getFoodLevel() < minHunger) {
event.getPlayer().spigot().sendMessage(errorMessage("You are too tired to fell the tree"));
return;
}
if (handTool.getItemMeta() instanceof final Damageable tool) {
if (!leaves.contains(event.getBlock().getType()) && targetMaterials.contains(event.getBlock().getType())) {
int logBreakCount = 0;
@ -76,6 +117,11 @@ public class TreeCapitatorListener implements Listener {
event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f);
}
if (event.getPlayer().getGameMode() == GameMode.ADVENTURE || event.getPlayer().getGameMode() == GameMode.SURVIVAL) {
final int hunger = (int) Math.round(hungerPerBlock * logBreakCount);
event.getPlayer().setFoodLevel(Math.max(0, event.getPlayer().getFoodLevel() - hunger));
}
event.setCancelled(true);
}
}

View File

@ -1,276 +0,0 @@
#!/bin/bash
MIN_JAVA_VERSION=15
[ -z "$JAVA_PATH" ] && JAVA_PATH=$(which java)
[ -z "$PYTHON_PATH" ] && PYTHON_PATH=$(which python3)
[ -z "$SHELL" ] && SHELL=/bin/bash
SCRIPT_RUN_NAME="start.sh"
SCRIPT_DEBUG_NAME="start_debug.sh"
# JVM options graciously "borrowed" from the PaperMC Timings JVM Tuning page
# https://aikar.co/2018/07/02/tuning-the-jvm-g1gc-garbage-collector-flags-for-minecraft/
MC_AUTORUN_FLAGS="-XX:+UnlockExperimentalVMOptions -Xmx1G -Xms512M -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1"
MC_VERSION="1.16"
ENV_DIR="$(pwd)/server"
PAPER_JAR_FILE="paper.jar"
PLUGIN_PATH=""
UPDATE_PAPER=true
AUTORUN=""
EULA=false
DEPS=""
CLEAR_PLUGINS=0
print_help() {
echo -e "Paper test server deployment script\nArguments:"
echo " -v [VERSION] - Specify minecraft version to download"
echo " -p [PATH] - Path to plugin to debug"
echo " -d [PATH] - Path to directory where server will be deployed"
echo " -j [PATH] - Java executable path"
echo " --python [PATH] - Python3 executable path"
echo " --no-update - Don't download updates for Paper if jar file already exists"
echo " -n - Same as --no-update"
echo " -s [type] - Autorun server after deployment. Type is \"debug\" to start with remote debugging, else \"run\""
echo " -e - Accepts the EULA for the deployed server (user must accept it in a provided prompt)"
echo " --deps [PATH] - Path for plugin dependencies needed to load plugin"
echo " -c - Delete existing plugins directory on autorun"
echo " -h - Show this help prompt"
echo -e "Example:\n $0 -v 1.16 -p out/plugin.jar -s debug -e"
}
print_error() {
echo "[ERROR] $1" >&2
return 1
}
print_info() {
if [ $# -eq 2 ]
then
echo -e "$1" "[TEST_ENV] $2"
else
echo -e "[TEST_ENV] $1"
fi
return 1
}
SKIP=false
CUR=""
TARGET=""
for arg in "$@"
do
if $SKIP
then
eval "$TARGET"="$arg"
SKIP=false
continue
fi
CUR="$arg"
case "$arg" in
"-h")
print_help
exit
;;
"-v")
TARGET="MC_VERSION"
SKIP=true
;;
"-p")
TARGET="PLUGIN_PATH"
SKIP=true
;;
"-d")
TARGET="ENV_DIR"
SKIP=true
;;
"-j")
TARGET="JAVA_PATH"
SKIP=true
;;
"--python")
TARGET="PYTHON_PATH"
SKIP=true
;;
"-n" | "--no-update")
UPDATE_PAPER=false
;;
"-s")
TARGET="AUTORUN"
SKIP=true
;;
"-e")
EULA=true
;;
"--deps")
TARGET="DEPS"
SKIP=true
;;
"-c")
CLEAR_PLUGINS=1
;;
*)
print_error "Unknown argument: $arg"
exit
;;
esac
done
if $SKIP
then
print_error "Malformed parameter $CUR"
exit
fi
case "$AUTORUN" in
"run")
AUTORUN="$SCRIPT_RUN_NAME"
;;
"debug")
AUTORUN="$SCRIPT_DEBUG_NAME"
;;
*)
print_error "Unknown autorun argument \"$AUTORUN\""
exit
;;
esac
check_java_version() {
version=$("$JAVA_PATH" -version 2>&1 | head -n 1 | sed "s/java version \"\\([0-9]*\\)\\..*/\\1/g")
if [ "$version" -lt "$MIN_JAVA_VERSION" ]
then
print_error "Java version is too low! Minimum is $MIN_JAVA_VERSION (found $version)"
return 1
fi
print_info "Found Java version $version"
return 0
}
ensure_python3() {
if [ -z "$PYTHON_PATH" ]
then
print_error "Could not locate Python3 which is required for automatic server deployment: please install it"
return 1
fi
print_info "Found Python3 binary"
return 0
}
get_paper() {
print_info "Polling all versions of paper for MC $MC_VERSION"
link=$(curl -s "https://papermc.io/api/v2/projects/paper/version_group/$MC_VERSION/builds" | python3 -c "import sys, json; versions=json.load(sys.stdin); print('https://papermc.io/api/v2/projects/paper/versions/' + str(versions['builds'][-1]['version']) + '/builds/' + str(versions['builds'][-1]['build']) + '/downloads/' + str(versions['builds'][-1]['downloads']['application']['name']))")
if [ -f "new_$PAPER_JAR_FILE" ]
then
rm -f "new_$PAPER_JAR_FILE"
fi
print_info "Downloading from pulled link: $link"
result=$(curl -o "new_$PAPER_JAR_FILE" -s -w "%{http_code}" "$link")
if [ "$result" -eq 200 ] && [ -f "new_$PAPER_JAR_FILE" ]
then
rm -f "$PAPER_JAR_FILE"
mv "new_$PAPER_JAR_FILE" "$PAPER_JAR_FILE"
print_info "Downloaded latest version of paper"
return 1
else
print_info "Could not download new version of paper"
return 0
fi
}
check_java_version || exit
ensure_python3 || exit
if [ -d "$ENV_DIR/plugins" ] && [ $CLEAR_PLUGINS -eq 1 ]
then
print_info "Found existing plugins directory. Clearing it..."
rm -r "$ENV_DIR/plugins"
fi
mkdir -p "$ENV_DIR/plugins" || (print_error "Could not create server directory at $ENV_DIR" && exit)
if [ -n "$PLUGIN_PATH" ]
then
print_info "Copying plugin to server directory..."
cp "$PLUGIN_PATH" "$ENV_DIR/plugins" || (print_error "Could not copy targeted plugin to test environment" && exit)
fi
if [ -n "$DEPS" ]
then
if [ -d "$DEPS" ]
then
print_info "Copying dependencies..."
cp "$DEPS"/* "$ENV_DIR/plugins"
else
print_error "Dependencies directory does not exist!"
exit
fi
fi
cd "$ENV_DIR" || (print_error "Could not enter directory $ENV_DIR" && exit)
{ ! [ -f "$PAPER_JAR_FILE" ] || $UPDATE_PAPER; } && get_paper && { ! [ -f "$PAPER_JAR_FILE" ]; } && exit
[ -f "$PAPER_JAR_FILE" ] || exit
if ! [ -f "$SCRIPT_RUN_NAME" ]
then
echo -e "#!/bin/bash\njava $MC_AUTORUN_FLAGS -jar \"$PAPER_JAR_FILE\" nogui" > "$SCRIPT_RUN_NAME"
fi
if ! [ -f "$SCRIPT_DEBUG_NAME" ]
then
echo -e "#!/bin/bash\njava -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar \"$PAPER_JAR_FILE\" nogui" > "$SCRIPT_DEBUG_NAME"
fi
print_info "Test environment successfully prepared!"
# Disable case-sensitivity for conditional expressions
shopt -s nocaseglob
# No need to ask user to accept EULA if it is already accepted
if $EULA && ! { [ -f "eula.txt" ] && [[ "$(cat eula.txt)" =~ eula=true ]]; }
then
print_info -n "Do you accept the terms of the Minecraft End User License Agreement? (https://account.mojang.com/documents/minecraft_eula) [y/N]: "
read -r ACCEPTS
case "$ACCEPTS" in
"y"* | "Y"*)
echo "eula=true" > eula.txt
;;
*)
print_info "EULA was not accepted"
;;
esac
fi
# Re-enable case-sensitivity, just in case
shopt -u nocaseglob
if [ -n "$AUTORUN" ]
then
print_info "Autorun was specified. Attempting to kill active server processes..."
kill -9 $(fuser 25565/tcp | sed "s/.*\\s\\(.*\\)/\\1/g") > /dev/null 2>&1
print_info "Starting server..."
"$SHELL" "$AUTORUN"
fi