Optimizations in the ability backend.

This commit is contained in:
thetrouper
2025-05-20 09:51:40 -05:00
parent 6ab32029f9
commit e43b3a3889
71 changed files with 315 additions and 192 deletions

0
.gitignore vendored Normal file → Executable file
View File

5
build.gradle Normal file → Executable file
View File

@@ -17,10 +17,15 @@ repositories {
name = "sonatype" name = "sonatype"
url = "https://oss.sonatype.org/content/groups/public/" url = "https://oss.sonatype.org/content/groups/public/"
} }
maven {
name = "sk89q-repo"
url = "https://maven.enginehub.org/repo/"
}
} }
dependencies { dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.5-R0.1-SNAPSHOT") compileOnly("io.papermc.paper:paper-api:1.21.5-R0.1-SNAPSHOT")
compileOnly 'com.sk89q.worldguard:worldguard-bukkit:7.0.14-SNAPSHOT'
} }
tasks { tasks {

0
gradle.properties Normal file → Executable file
View File

0
gradle/wrapper/gradle-wrapper.jar vendored Normal file → Executable file
View File

0
gradle/wrapper/gradle-wrapper.properties vendored Normal file → Executable file
View File

0
gradlew vendored Normal file → Executable file
View File

0
gradlew.bat vendored Normal file → Executable file
View File

0
settings.gradle Normal file → Executable file
View File

7
src/main/java/me/trouper/trimserver/TrimServer.java Normal file → Executable file
View File

@@ -12,12 +12,6 @@ public final class TrimServer extends JavaPlugin {
@Override @Override
public void onLoad() { public void onLoad() {
//getLogger().info("Setting PacketEvents API");
//PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
//
//getLogger().info("Loading PacketEvents");
//PacketEvents.getAPI().load();
getLogger().info("Instantiating Plugin"); getLogger().info("Instantiating Plugin");
instance = this; instance = this;
} }
@@ -39,7 +33,6 @@ public final class TrimServer extends JavaPlugin {
getLogger().info("Saved all IO files."); getLogger().info("Saved all IO files.");
manager.io.saveAll(); manager.io.saveAll();
getLogger().info("Saved all IO files."); getLogger().info("Saved all IO files.");
//PacketEvents.getAPI().terminate();
} }
public static TrimServer getInstance() { public static TrimServer getInstance() {

0
src/main/java/me/trouper/trimserver/data/IO.java Normal file → Executable file
View File

View File

View File

4
src/main/java/me/trouper/trimserver/server/Main.java Normal file → Executable file
View File

@@ -48,6 +48,10 @@ public interface Main {
default void error(CommandSender player, String message, Object... args) { default void error(CommandSender player, String message, Object... args) {
Text.sendMessage(Text.Pallet.ERROR, player, message, args); Text.sendMessage(Text.Pallet.ERROR, player, message, args);
} }
default void warning(CommandSender player, String message, Object... args) {
Text.sendMessage(Text.Pallet.WARNING, player, message, args);
}
default void checkPre(boolean check, String msg, Object... args) { default void checkPre(boolean check, String msg, Object... args) {
if (!check) { if (!check) {

View File

@@ -7,6 +7,7 @@ import me.trouper.trimserver.server.commands.TrustCommand;
import me.trouper.trimserver.server.events.JoinEvent; import me.trouper.trimserver.server.events.JoinEvent;
import me.trouper.trimserver.server.events.SwapHandsEvent; import me.trouper.trimserver.server.events.SwapHandsEvent;
import me.trouper.trimserver.server.events.TrustEvents; import me.trouper.trimserver.server.events.TrustEvents;
import me.trouper.trimserver.server.flags.AbilityFlag;
import me.trouper.trimserver.server.systems.TrustBackend; import me.trouper.trimserver.server.systems.TrustBackend;
import me.trouper.trimserver.server.systems.AbilityBackend; import me.trouper.trimserver.server.systems.AbilityBackend;
import me.trouper.trimserver.server.systems.abilities.trims.*; import me.trouper.trimserver.server.systems.abilities.trims.*;
@@ -16,11 +17,13 @@ public class Manager {
public IO io; public IO io;
public AbilityBackend abilityBackend; public AbilityBackend abilityBackend;
public TrustBackend trustBackend; public TrustBackend trustBackend;
public AbilityFlag abilityFlag;
public Manager() { public Manager() {
io = new IO(); io = new IO();
abilityBackend = new AbilityBackend(); abilityBackend = new AbilityBackend();
trustBackend = new TrustBackend(); trustBackend = new TrustBackend();
abilityFlag = new AbilityFlag();
} }
@@ -30,7 +33,11 @@ public class Manager {
registerAbilities(); registerAbilities();
registerEvents(); registerEvents();
registerCommands(); registerCommands();
BlockDisplayRaytracer.cleanup(); BlockDisplayRaytracer.cleanup();
abilityBackend.startTickingBars();
abilityFlag.register();
} }
private void registerCommands() { private void registerCommands() {

View File

@@ -162,8 +162,8 @@ public class AdminCommand implements QuickCommand {
armor.customName(Text.color("&eTesting Armor").decoration(TextDecoration.ITALIC,false)); armor.customName(Text.color("&eTesting Armor").decoration(TextDecoration.ITALIC,false));
armor.lore(List.of( armor.lore(List.of(
Text.color("&8&l| &7%s Trim".formatted(Text.formatEnum(main.man().abilityBackend.getValidPattern(trimPattern)))).decoration(TextDecoration.ITALIC,false), Text.color("&8&l| &7%s Trim".formatted(Text.formatEnum(AbilityBackend.ValidPattern.validate(trimPattern)))).decoration(TextDecoration.ITALIC,false),
Text.color("&8&l| &7%s".formatted(Text.formatEnum(main.man().abilityBackend.getValidMaterial(trimMaterial)))).decoration(TextDecoration.ITALIC,false), Text.color("&8&l| &7%s".formatted(Text.formatEnum(AbilityBackend.ValidMaterial.validate(trimMaterial)))).decoration(TextDecoration.ITALIC,false),
Text.color("&8&l| &7Won't Break").decoration(TextDecoration.ITALIC,false), Text.color("&8&l| &7Won't Break").decoration(TextDecoration.ITALIC,false),
Text.color("&8&l| &7Vanishes on death").decoration(TextDecoration.ITALIC,false), Text.color("&8&l| &7Vanishes on death").decoration(TextDecoration.ITALIC,false),
Text.color("&8&l| &7This armor is for testing purposes &c&lONLY&7!").decoration(TextDecoration.ITALIC,false) Text.color("&8&l| &7This armor is for testing purposes &c&lONLY&7!").decoration(TextDecoration.ITALIC,false)

View File

@@ -1,6 +1,7 @@
package me.trouper.trimserver.server.commands; package me.trouper.trimserver.server.commands;
import me.trouper.trimserver.TrimServer; import me.trouper.trimserver.TrimServer;
import me.trouper.trimserver.server.systems.AbilityBackend;
import me.trouper.trimserver.utils.Text; import me.trouper.trimserver.utils.Text;
import me.trouper.trimserver.utils.commands.Args; import me.trouper.trimserver.utils.commands.Args;
import me.trouper.trimserver.utils.commands.CommandRegistry; import me.trouper.trimserver.utils.commands.CommandRegistry;
@@ -8,7 +9,6 @@ import me.trouper.trimserver.utils.commands.QuickCommand;
import me.trouper.trimserver.utils.commands.completions.CompletionBuilder; import me.trouper.trimserver.utils.commands.completions.CompletionBuilder;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.inventory.meta.trim.TrimPattern;
@CommandRegistry(value = "info") @CommandRegistry(value = "info")
public class InfoCommand implements QuickCommand { public class InfoCommand implements QuickCommand {
@@ -19,8 +19,8 @@ public class InfoCommand implements QuickCommand {
} }
String query = args.get(0).toString(); String query = args.get(0).toString();
commandSender.sendMessage( commandSender.sendMessage(
Text.color(Text.formatArgs(Text.Pallet.INFO,"-------- Info for {0} trim. --------",query)).appendNewline() Text.color(Text.formatArgsLegacy(Text.Pallet.INFO,"-------- Info for {0} trim. --------",query)).appendNewline()
.append(main.man().abilityBackend.formatAbilityInfo(main.man().abilityBackend.getTrimPattern(query)) .append(main.man().abilityBackend.formatAbilityInfo(AbilityBackend.ValidPattern.validate(query))
)); ));
} }

View File

View File

View File

View File

@@ -10,7 +10,7 @@ public class SwapHandsEvent implements QuickListener, Main {
@EventHandler @EventHandler
public void onSwap(PlayerSwapHandItemsEvent e) { public void onSwap(PlayerSwapHandItemsEvent e) {
Player p = e.getPlayer(); Player p = e.getPlayer();
if (!main.man().abilityBackend.checkAndTriggerAbility(p)) return; if (!main.man().abilityBackend.triggerAbility(p)) return;
e.setCancelled(true); e.setCancelled(true);
} }
} }

View File

View File

@@ -0,0 +1,33 @@
package me.trouper.trimserver.server.flags;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.flags.*;
import com.sk89q.worldguard.protection.flags.registry.FlagConflictException;
import com.sk89q.worldguard.protection.flags.registry.FlagRegistry;
import com.sk89q.worldguard.session.handler.FlagValueChangeHandler;
import me.trouper.trimserver.TrimServer;
import org.jetbrains.annotations.Nullable;
public class AbilityFlag {
public static final String FLAG_NAME = "trim-ability";
public static StateFlag TRIM_ABILITY_FLAG;
public void register() {
FlagRegistry registry = WorldGuard.getInstance().getFlagRegistry();;
try {
StateFlag flag = new StateFlag(FLAG_NAME,true);
registry.register(flag);
TRIM_ABILITY_FLAG = flag;
} catch (FlagConflictException e) {
Flag<?> existing = registry.get(FLAG_NAME);
if (existing instanceof StateFlag) {
TRIM_ABILITY_FLAG = (StateFlag) existing;
} else {
TrimServer.getInstance().getLogger().warning("Flag conflict detected!");
e.printStackTrace();
}
}
}
}

View File

@@ -1,14 +1,25 @@
package me.trouper.trimserver.server.systems; package me.trouper.trimserver.server.systems;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.flags.Flags;
import com.sk89q.worldguard.protection.regions.RegionContainer;
import com.sk89q.worldguard.protection.regions.RegionQuery;
import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.keys.TrimPatternKeys;
import me.trouper.trimserver.server.Main; import me.trouper.trimserver.server.Main;
import me.trouper.trimserver.server.flags.AbilityFlag;
import me.trouper.trimserver.server.systems.abilities.MaterialInfo; import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
import me.trouper.trimserver.server.systems.abilities.AbstractAbility; import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
import me.trouper.trimserver.utils.Text; import me.trouper.trimserver.utils.Text;
import me.trouper.trimserver.utils.misc.Cooldown; import me.trouper.trimserver.utils.misc.Cooldown;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@@ -22,13 +33,10 @@ import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
public class AbilityBackend implements Main { public class AbilityBackend implements Main {
// Map to store registered abilities by their pattern
public final Map<TrimPattern, AbstractAbility> registeredAbilities = new HashMap<>(); public final Map<TrimPattern, AbstractAbility> registeredAbilities = new HashMap<>();
// Record for cooldown tracking key (Player UUID, Pattern, Material)
private record AbilityCooldown(UUID who, TrimPattern pattern, TrimMaterial material) {}; private record AbilityCooldown(UUID who, TrimPattern pattern, TrimMaterial material) {};
// Cooldown manager instance
private final Cooldown<AbilityCooldown> onCooldown = new Cooldown<>(); private final Cooldown<AbilityCooldown> onCooldown = new Cooldown<>();
/** /**
@@ -76,6 +84,13 @@ public class AbilityBackend implements Main {
return registeredAbilities.get(pattern); return registeredAbilities.get(pattern);
} }
public AbstractAbility getAbility(Player player) {
return getArmorInfo(player).getAbility();
}
public TrimMaterial getMaterial(Player player) {
return getArmorInfo(player).getValidMaterial().getCanonical();
}
/** /**
* Retrieves the TrimAbility for a player based on their full armor set's trim. * Retrieves the TrimAbility for a player based on their full armor set's trim.
@@ -85,60 +100,49 @@ public class AbilityBackend implements Main {
* @return The TrimAbility instance corresponding to the player's trim, or null if * @return The TrimAbility instance corresponding to the player's trim, or null if
* they don't have a valid full set trim or the pattern isn't registered. * they don't have a valid full set trim or the pattern isn't registered.
*/ */
public AbstractAbility getAbility(Player player) { public ArmorInfo getArmorInfo(Player player) {
PlayerInventory inventory = player.getInventory(); PlayerInventory inventory = player.getInventory();
ItemStack helmet = inventory.getHelmet(); ItemStack helmet = inventory.getHelmet();
ItemStack chestplate = inventory.getChestplate(); ItemStack chestplate = inventory.getChestplate();
ItemStack leggings = inventory.getLeggings(); ItemStack leggings = inventory.getLeggings();
ItemStack boots = inventory.getBoots(); ItemStack boots = inventory.getBoots();
// 1. Check if all armor slots are filled with *some* armor
if (helmet == null || helmet.getType() == Material.AIR || if (helmet == null || helmet.getType() == Material.AIR ||
chestplate == null || chestplate.getType() == Material.AIR || chestplate == null || chestplate.getType() == Material.AIR ||
leggings == null || leggings.getType() == Material.AIR || leggings == null || leggings.getType() == Material.AIR ||
boots == null || boots.getType() == Material.AIR) { boots == null || boots.getType() == Material.AIR) {
return null; // Not wearing a full set return null;
} }
ItemStack[] armorPieces = {helmet, chestplate, leggings, boots}; ItemStack[] armorPieces = {helmet, chestplate, leggings, boots};
TrimPattern requiredPattern = null; TrimPattern requiredPattern = null;
TrimMaterial requiredMaterial = null; TrimMaterial requiredMaterial = null;
// 2. Iterate through armor pieces to check for matching trims
for (int i = 0; i < armorPieces.length; i++) { for (int i = 0; i < armorPieces.length; i++) {
ItemStack piece = armorPieces[i]; ItemStack piece = armorPieces[i];
if (!(piece.getItemMeta() instanceof ArmorMeta meta)) { if (!(piece.getItemMeta() instanceof ArmorMeta meta)) return null;
return null; // Should have ArmorMeta if it's armor
}
ArmorTrim trim = meta.getTrim(); ArmorTrim trim = meta.getTrim();
if (trim == null) { if (trim == null) return null;
return null; // Needs a trim
}
TrimPattern currentPattern = trim.getPattern(); TrimPattern currentPattern = trim.getPattern();
TrimMaterial currentMaterial = trim.getMaterial(); TrimMaterial currentMaterial = trim.getMaterial();
if (i == 0) { if (i == 0) {
// First piece sets the requirement
requiredPattern = currentPattern; requiredPattern = currentPattern;
requiredMaterial = currentMaterial; requiredMaterial = currentMaterial;
} else { } else if (!currentPattern.equals(requiredPattern) || !currentMaterial.equals(requiredMaterial)) return null;
// Subsequent pieces must match the first one
if (!currentPattern.equals(requiredPattern) || !currentMaterial.equals(requiredMaterial)) {
return null; // Mismatch found
}
}
} }
// 3. If we reach here, all pieces match! Return the ability if registered. if (requiredPattern == null || requiredMaterial == null) return null;
if (requiredPattern != null && requiredMaterial != null) {
return registeredAbilities.get(requiredPattern); AbstractAbility ability = registeredAbilities.get(requiredPattern);
} if (ability == null) return null;
return null; // Should technically not be reached if logic is sound
return new ArmorInfo(true,registeredAbilities.get(requiredPattern),ValidMaterial.validate(requiredMaterial),ValidPattern.validate(requiredPattern),ValidArmorType.validate(helmet.getType()));
} }
/** /**
* Checks the player's equipped armor. If they are wearing a full set * Checks the player's equipped armor. If they are wearing a full set
* with the same trim pattern and material, it finds the corresponding * with the same trim pattern and material, it finds the corresponding
@@ -148,102 +152,79 @@ public class AbilityBackend implements Main {
* @param player The player whose armor should be checked. * @param player The player whose armor should be checked.
* @return true if an ability was successfully triggered, false otherwise. * @return true if an ability was successfully triggered, false otherwise.
*/ */
public boolean checkAndTriggerAbility(Player player) { public boolean triggerAbility(Player player) {
PlayerInventory inventory = player.getInventory(); ArmorInfo info = getArmorInfo(player);
ItemStack helmet = inventory.getHelmet();
ItemStack chestplate = inventory.getChestplate(); if (info == null || info.validPattern == null || info.validMaterial == null) return false;
ItemStack leggings = inventory.getLeggings();
ItemStack boots = inventory.getBoots(); AbstractAbility ability = registeredAbilities.get(info.getValidPattern().getCanonical());
if (ability == null) return false;
MaterialInfo materialInfo = ability.getAbilityInfo(info.getValidMaterial().getCanonical());
// 1. Check if all armor slots are filled with *some* armor int cooldownTicks = (materialInfo != null) ? materialInfo.cooldownTicks() : 0;
if (helmet == null || helmet.getType() == Material.AIR ||
chestplate == null || chestplate.getType() == Material.AIR || AbilityCooldown cooldownKey = new AbilityCooldown(player.getUniqueId(), info.getValidPattern().getCanonical(), info.getValidMaterial().getCanonical());
leggings == null || leggings.getType() == Material.AIR ||
boots == null || boots.getType() == Material.AIR) { if (onCooldown.isOnCooldown(cooldownKey)) return false;
return false; // Not wearing a full set
try {
boolean applyCooldown = abilityAllowed(player, player.getLocation()) && ability.dispatchAbility(info.getValidMaterial().getCanonical(), player);
if (cooldownTicks > 0 && applyCooldown) onCooldown.addCooldown(cooldownKey, (long) cooldownTicks * 50L);
return true;
} catch (Exception e) {
main.getPlugin().getLogger().log(Level.SEVERE, "Error executing trim ability for " + player.getName() + " (Pattern: " + ability.getPatternName() + ", Material: " + main.getRegistryAccess().getRegistry(RegistryKey.TRIM_MATERIAL).getKey(info.getValidMaterial().getCanonical()).getKey() + ")", e);
return false;
} }
}
ItemStack[] armorPieces = {helmet, chestplate, leggings, boots}; /**
TrimPattern requiredPattern = null; * Begins ticking player's action bars.
TrimMaterial requiredMaterial = null; */
public void startTickingBars() {
// 2. Iterate through armor pieces to check for matching trims Bukkit.getScheduler().runTaskTimer(main.getPlugin(),(task)->{
for (int i = 0; i < armorPieces.length; i++) { for (Player player : Bukkit.getServer().getOnlinePlayers()) {
ItemStack piece = armorPieces[i]; tickActionBar(player);
if (!(piece.getItemMeta() instanceof ArmorMeta meta)) {
return false; // Should have ArmorMeta if it's armor
} }
},0,20);
}
ArmorTrim trim = meta.getTrim(); /**
* Plays the cooldown and info action bar for a player.
* @param player the player to send actionbar to.
*/
public void tickActionBar(Player player) {
AbstractAbility ability = getAbility(player);
TrimMaterial material = getMaterial(player);
if (ability == null || material == null) return;
TrimPattern pattern = ability.getPattern();
MaterialInfo materialInfo = ability.getAbilityInfo(material);
AbilityCooldown cooldownKey = new AbilityCooldown(player.getUniqueId(), pattern, material);
int cooldownTicks = (materialInfo != null) ? materialInfo.cooldownTicks() : 0;
Component bar = Component.text("");
if (onCooldown.isOnCooldown(cooldownKey)) {
long remainingMillis = onCooldown.getCooldown(cooldownKey);
long totalDurationMillis = (long) cooldownTicks * 50L;
long elapsedMillis = totalDurationMillis - remainingMillis;
elapsedMillis = Math.max(0, elapsedMillis);
elapsedMillis = Math.min(totalDurationMillis, elapsedMillis);
if (trim == null) { bar = bar.append(Component.text("Ability Recharging: ", NamedTextColor.WHITE))
return false; // Needs a trim .append(Text.color(Text.generateProgressBar(20, (int)totalDurationMillis, (int)elapsedMillis)));
} } else {
bar = bar.append(Component.text("Ability Ready", NamedTextColor.namedColor(0x00FFAA)));
TrimPattern currentPattern = trim.getPattern();
TrimMaterial currentMaterial = trim.getMaterial();
if (i == 0) {
// First piece sets the requirement
requiredPattern = currentPattern;
requiredMaterial = currentMaterial;
} else {
// Subsequent pieces must match the first one
if (!currentPattern.equals(requiredPattern) || !currentMaterial.equals(requiredMaterial)) {
return false; // Mismatch found
}
}
} }
// 3. If we reach here, all pieces match! Find and trigger the ability. bar = bar.append(Text.formatArgs(Text.Pallet.INFO," /info {0} for usage",Text.formatEnum(ValidPattern.validate(pattern))));
if (requiredPattern != null && requiredMaterial != null) {
AbstractAbility ability = registeredAbilities.get(requiredPattern); player.sendActionBar(bar);
if (ability != null) {
// Get ability info for cooldown using the ability instance's method
MaterialInfo materialInfo = ability.getAbilityInfo(requiredMaterial);
// If there's no info or cooldown is 0, proceed immediately
int cooldownTicks = (materialInfo != null) ? materialInfo.cooldownTicks() : 0;
AbilityCooldown cooldownKey = new AbilityCooldown(player.getUniqueId(), requiredPattern, requiredMaterial);
if (onCooldown.isOnCooldown(cooldownKey)) {
// Calculate remaining time for progress bar if needed
long remainingMillis = onCooldown.getCooldown(cooldownKey);
// Example progress bar logic - adjust duration calculation as needed
long totalDurationMillis = (long) cooldownTicks * 50L; // Assuming 1 tick = 50ms
long elapsedMillis = totalDurationMillis - remainingMillis; // Calculate elapsed for the bar
// Ensure elapsed is not negative or greater than total
elapsedMillis = Math.max(0, elapsedMillis);
elapsedMillis = Math.min(totalDurationMillis, elapsedMillis);
player.sendActionBar(Component.text("Ability Recharging: ", NamedTextColor.WHITE)
.append(Text.color(Text.generateProgressBar(20, (int)totalDurationMillis, (int)elapsedMillis))));
return false; // Still on cooldown
}
try {
// Dispatch ability and check if it was successful
boolean applyCooldown = ability.dispatchAbility(requiredMaterial, player);
// Add cooldown if cooldownTicks is > 0 and the ability execution indicated we should apply cooldown
if (cooldownTicks > 0 && applyCooldown) {
onCooldown.addCooldown(cooldownKey, (long) cooldownTicks * 50L); // Cooldown expects milliseconds
}
return true; // Ability trigger attempt completed
} catch (Exception e) {
main.getPlugin().getLogger().log(Level.SEVERE, "Error executing trim ability for " + player.getName() + " (Pattern: " + ability.getPatternName() + ", Material: " + main.getRegistryAccess().getRegistry(RegistryKey.TRIM_MATERIAL).getKey(requiredMaterial).getKey() + ")", e);
return false; // Error occurred
}
} else {
return false; // No ability registered for this pattern
}
}
return false;
} }
/** /**
@@ -256,7 +237,7 @@ public class AbilityBackend implements Main {
public Component formatAbilityInfo(TrimPattern pattern) { public Component formatAbilityInfo(TrimPattern pattern) {
AbstractAbility ability = registeredAbilities.get(pattern); AbstractAbility ability = registeredAbilities.get(pattern);
if (ability != null) { if (ability != null) {
return ability.formatAbilityInfo(); // Call the method on the ability instance return ability.formatAbilityInfo();
} else { } else {
return Component.text("--- ", NamedTextColor.YELLOW) return Component.text("--- ", NamedTextColor.YELLOW)
.append(Component.text("No Ability Registered", NamedTextColor.YELLOW)) .append(Component.text("No Ability Registered", NamedTextColor.YELLOW))
@@ -285,34 +266,26 @@ public class AbilityBackend implements Main {
return false; return false;
} }
public TrimPattern getTrimPattern(String name) { /**
name = name.toUpperCase(); * Checks the ability flag at the useLocation
return ValidPattern.valueOf(name).getCanonical(); * @param player the player to check
} * @param useLocation the Bukkit location to check at
* @return
*/
public boolean abilityAllowed(Player player, Location useLocation) {
LocalPlayer user = WorldGuardPlugin.inst().wrapPlayer(player);
public ValidPattern getValidPattern(TrimPattern pattern) { RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer();
for (ValidPattern value : ValidPattern.values()) { RegionQuery query = container.createQuery();
if (!value.getCanonical().equals(pattern)) continue;
return value; main.man();
if (!query.testState(BukkitAdapter.adapt(useLocation),user, AbilityFlag.TRIM_ABILITY_FLAG)) {
main.warning(player,"You cannot use this ability here!");
return false;
} }
return null; return true;
} }
public TrimMaterial getTrimMaterial(String name) {
name = name.toUpperCase();
return ValidMaterial.valueOf(name).getCanonical();
}
public ValidMaterial getValidMaterial(TrimMaterial material) {
for (ValidMaterial value : ValidMaterial.values()) {
if (!value.getCanonical().equals(material)) continue;
return value;
}
return null;
}
public enum ValidMaterial { public enum ValidMaterial {
AMETHYST(TrimMaterial.AMETHYST), AMETHYST(TrimMaterial.AMETHYST),
COPPER(TrimMaterial.COPPER), COPPER(TrimMaterial.COPPER),
@@ -335,6 +308,19 @@ public class AbilityBackend implements Main {
public TrimMaterial getCanonical() { public TrimMaterial getCanonical() {
return canonical; return canonical;
} }
public static ValidMaterial validate(TrimMaterial material) {
for (ValidMaterial value : ValidMaterial.values()) {
if (!value.getCanonical().equals(material)) continue;
return value;
}
return null;
}
public static TrimMaterial validate(String name) {
name = name.toUpperCase();
return ValidMaterial.valueOf(name).getCanonical();
}
} }
public enum ValidPattern { public enum ValidPattern {
@@ -366,6 +352,19 @@ public class AbilityBackend implements Main {
public TrimPattern getCanonical() { public TrimPattern getCanonical() {
return canonical; return canonical;
} }
public static ValidPattern validate(TrimPattern pattern) {
for (ValidPattern value : ValidPattern.values()) {
if (!value.getCanonical().equals(pattern)) continue;
return value;
}
return null;
}
public static TrimPattern validate(String name) {
name = name.toUpperCase();
return ValidPattern.valueOf(name).getCanonical();
}
} }
public enum ValidArmorType { public enum ValidArmorType {
@@ -403,5 +402,69 @@ public class AbilityBackend implements Main {
public Material getBoots() { public Material getBoots() {
return boots; return boots;
} }
public static ValidArmorType validate(Material armor) {
for (ValidArmorType value : values()) {
if (value.helmet.equals(armor) || value.chestplate.equals(armor) || value.leggings.equals(armor) || value.boots.equals(armor)) return value;
}
return null;
}
} }
public class ArmorInfo {
private boolean abilityValid;
private AbstractAbility ability;
private ValidMaterial validMaterial;
private ValidPattern validPattern;
private ValidArmorType armorType;
public ArmorInfo(boolean abilityValid, AbstractAbility ability, ValidMaterial validMaterial, ValidPattern validPattern, ValidArmorType armorType) {
this.abilityValid = abilityValid;
this.ability = ability;
this.validMaterial = validMaterial;
this.validPattern = validPattern;
this.armorType = armorType;
}
public boolean isAbilityValid() {
return abilityValid;
}
public void setAbilityValid(boolean abilityValid) {
this.abilityValid = abilityValid;
}
public AbstractAbility getAbility() {
return ability;
}
public void setAbility(AbstractAbility ability) {
this.ability = ability;
}
public ValidMaterial getValidMaterial() {
return validMaterial;
}
public void setValidMaterial(ValidMaterial validMaterial) {
this.validMaterial = validMaterial;
}
public ValidPattern getValidPattern() {
return validPattern;
}
public void setValidPattern(ValidPattern validPattern) {
this.validPattern = validPattern;
}
public ValidArmorType getArmorType() {
return armorType;
}
public void setArmorType(ValidArmorType armorType) {
this.armorType = armorType;
}
}
} }

View File

View File

@@ -47,58 +47,51 @@ public abstract class AbstractAbility implements Main, QuickListener {
} }
// --- Default Ability Methods for Each Material --- // --- Default Ability Methods for Each Material ---
// Subclasses should override these methods to implement specific abilities.
// Apply @MaterialInfo to these methods in subclasses where implemented.
// Return true if the ability was successfully executed and should apply cooldown.
// Return false if the ability failed or otherwise shouldn't trigger cooldown.
/** Called when the player has a full set of this pattern with Amethyst trim. */ /** Called when the player has a full set of this pattern with Amethyst trim. */
@MaterialInfo(name = "Default Amethyst Ability", description = "A basic ability.") @MaterialInfo(name = "Default Amethyst Ability", description = "A basic ability.")
public boolean amethystAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean amethystAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Copper trim. */ /** Called when the player has a full set of this pattern with Copper trim. */
@MaterialInfo(name = "Default Copper Ability", description = "A basic ability.") @MaterialInfo(name = "Default Copper Ability", description = "A basic ability.")
public boolean copperAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean copperAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Diamond trim. */ /** Called when the player has a full set of this pattern with Diamond trim. */
@MaterialInfo(name = "Default Diamond Ability", description = "A basic ability.") @MaterialInfo(name = "Default Diamond Ability", description = "A basic ability.")
public boolean diamondAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean diamondAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Emerald trim. */ /** Called when the player has a full set of this pattern with Emerald trim. */
@MaterialInfo(name = "Default Emerald Ability", description = "A basic ability.") @MaterialInfo(name = "Default Emerald Ability", description = "A basic ability.")
public boolean emeraldAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean emeraldAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Gold trim. */ /** Called when the player has a full set of this pattern with Gold trim. */
@MaterialInfo(name = "Default Gold Ability", description = "A basic ability.") @MaterialInfo(name = "Default Gold Ability", description = "A basic ability.")
public boolean goldAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean goldAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Iron trim. */ /** Called when the player has a full set of this pattern with Iron trim. */
@MaterialInfo(name = "Default Iron Ability", description = "A basic ability.") @MaterialInfo(name = "Default Iron Ability", description = "A basic ability.")
public boolean ironAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean ironAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Lapis trim. */ /** Called when the player has a full set of this pattern with Lapis trim. */
@MaterialInfo(name = "Default Lapis Ability", description = "A basic ability.") @MaterialInfo(name = "Default Lapis Ability", description = "A basic ability.")
public boolean lapisAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean lapisAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Netherite trim. */ /** Called when the player has a full set of this pattern with Netherite trim. */
@MaterialInfo(name = "Default Netherite Ability", description = "A basic ability.") @MaterialInfo(name = "Default Netherite Ability", description = "A basic ability.")
public boolean netheriteAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean netheriteAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Quartz trim. */ /** Called when the player has a full set of this pattern with Quartz trim. */
@MaterialInfo(name = "Default Quartz Ability", description = "A basic ability.") @MaterialInfo(name = "Default Quartz Ability", description = "A basic ability.")
public boolean quartzAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean quartzAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Redstone trim. */ /** Called when the player has a full set of this pattern with Redstone trim. */
@MaterialInfo(name = "Default Redstone Ability", description = "A basic ability.") @MaterialInfo(name = "Default Redstone Ability", description = "A basic ability.")
public boolean redstoneAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean redstoneAbility(Player player) { return true; }
/** Called when the player has a full set of this pattern with Resin trim. */ /** Called when the player has a full set of this pattern with Resin trim. */
@MaterialInfo(name = "Default Resin Ability", description = "A basic ability.") @MaterialInfo(name = "Default Resin Ability", description = "A basic ability.")
public boolean resinAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility public boolean resinAbility(Player player) { return true; }
// --- Helper Method to Dispatch Based on Material ---
// This is called by the Manager after confirming the player has a matching set.
/** /**
* Internal dispatcher. Calls the appropriate material-specific ability method. * Internal dispatcher. Calls the appropriate material-specific ability method.
* Should only be called by the TrimAbilityManager after validation and cooldown check. * Should only be called by the TrimAbilityManager after validation and cooldown check.
@@ -138,16 +131,13 @@ public abstract class AbstractAbility implements Main, QuickListener {
*/ */
public MaterialInfo getAbilityInfo(TrimMaterial material) { public MaterialInfo getAbilityInfo(TrimMaterial material) {
String methodName = getMethodNameForMaterial(material); String methodName = getMethodNameForMaterial(material);
if (methodName == null) { if (methodName == null) return null;
return null; // Unknown material mapping
}
try { try {
Method abilityMethod = this.getClass().getMethod(methodName, Player.class); Method abilityMethod = this.getClass().getMethod(methodName, Player.class);
return abilityMethod.isAnnotationPresent(MaterialInfo.class) ? return abilityMethod.isAnnotationPresent(MaterialInfo.class) ?
abilityMethod.getAnnotation(MaterialInfo.class) : null; abilityMethod.getAnnotation(MaterialInfo.class) : null;
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
// Method not found - this should not happen for the default methods,
// but might if a subclass removes one or getMethodNameForMaterial is wrong.
return null; return null;
} catch (Exception e) { } catch (Exception e) {
main.getPlugin().getLogger().severe("Unexpected error getting annotation for method " + methodName + " in " + this.getClass().getSimpleName() + ": " + e.getMessage()); main.getPlugin().getLogger().severe("Unexpected error getting annotation for method " + methodName + " in " + this.getClass().getSimpleName() + ": " + e.getMessage());
@@ -184,7 +174,6 @@ public abstract class AbstractAbility implements Main, QuickListener {
PatternInfo patternInfo = getPatternInfo(); PatternInfo patternInfo = getPatternInfo();
Component message = Component.empty().appendNewline(); Component message = Component.empty().appendNewline();
// Add overall pattern info
if (patternInfo != null && patternInfo.description() != null && !patternInfo.description().isEmpty()) { if (patternInfo != null && patternInfo.description() != null && !patternInfo.description().isEmpty()) {
message = message.append(Text.color("&7%s".formatted(patternInfo.description()))).appendNewline().appendNewline(); message = message.append(Text.color("&7%s".formatted(patternInfo.description()))).appendNewline().appendNewline();
@@ -192,7 +181,6 @@ public abstract class AbstractAbility implements Main, QuickListener {
message = message.append(Text.color("&7&m---&e %s &7&m---".formatted(patternInfo.name()))).appendNewline(); message = message.append(Text.color("&7&m---&e %s &7&m---".formatted(patternInfo.name()))).appendNewline();
// Map descriptions to lists of material names and their cooldowns
Map<String, List<Map.Entry<String, Integer>>> groupedInfo = new LinkedHashMap<>(); Map<String, List<Map.Entry<String, Integer>>> groupedInfo = new LinkedHashMap<>();
List<TrimMaterial> materials = access.getRegistry(RegistryKey.TRIM_MATERIAL).stream().toList(); List<TrimMaterial> materials = access.getRegistry(RegistryKey.TRIM_MATERIAL).stream().toList();
@@ -212,15 +200,12 @@ public abstract class AbstractAbility implements Main, QuickListener {
if (groupedInfo.isEmpty()) { if (groupedInfo.isEmpty()) {
message = message.append(Text.color("&7(No specific material abilities defined for this pattern)")).appendNewline(); message = message.append(Text.color("&7(No specific material abilities defined for this pattern)")).appendNewline();
} else { } else {
// Iterate through the grouped descriptions and their materials
for (Map.Entry<String, List<Map.Entry<String, Integer>>> entry : groupedInfo.entrySet()) { for (Map.Entry<String, List<Map.Entry<String, Integer>>> entry : groupedInfo.entrySet()) {
String description = entry.getKey(); String description = entry.getKey();
List<Map.Entry<String, Integer>> materialEntries = entry.getValue(); List<Map.Entry<String, Integer>> materialEntries = entry.getValue();
// Append the description
message = message.append(Text.color("&7%s&f:".formatted(description))).appendNewline(); message = message.append(Text.color("&7%s&f:".formatted(description))).appendNewline();
// Append the list of materials with this description
for (Map.Entry<String, Integer> matEntry : materialEntries) { for (Map.Entry<String, Integer> matEntry : materialEntries) {
String matName = matEntry.getKey(); String matName = matEntry.getKey();
int cooldownSeconds = matEntry.getValue(); int cooldownSeconds = matEntry.getValue();

View File

@@ -35,6 +35,8 @@ public class EyeAbility extends AbstractAbility {
task.cancel(); task.cancel();
return; return;
} }
new SoundPlayer(player.getLocation(), Sound.BLOCK_BEACON_POWER_SELECT, 1, 2).playWithin(30);
Location headLocation = player.getEyeLocation().clone(); Location headLocation = player.getEyeLocation().clone();
Vector eyeCenter = headLocation.getDirection(); Vector eyeCenter = headLocation.getDirection();
Vector leftRight = new Vector(-eyeCenter.getZ(), 0, eyeCenter.getX()).normalize().multiply(0.2); Vector leftRight = new Vector(-eyeCenter.getZ(), 0, eyeCenter.getX()).normalize().multiply(0.2);
@@ -61,7 +63,7 @@ public class EyeAbility extends AbstractAbility {
List<Entity> targets = point.getNearbyEntities(owner,5,true,0.5, target -> target instanceof LivingEntity && !target.isDead() && !main.man().trustBackend.trusts(owner, (LivingEntity) target)); List<Entity> targets = point.getNearbyEntities(owner,5,true,0.5, target -> target instanceof LivingEntity && !target.isDead() && !main.man().trustBackend.trusts(owner, (LivingEntity) target));
targets.forEach(entity -> { targets.forEach(entity -> {
if (!(entity instanceof LivingEntity liv) || shaper.activeShellTasks.containsKey(liv.getUniqueId())) return; if (!(entity instanceof LivingEntity liv) || shaper.activeShellTasks.containsKey(liv.getUniqueId())) return;
hissSound.playWithin(10); hissSound.playWithin(30);
int tick = liv.getNoDamageTicks(); int tick = liv.getNoDamageTicks();
int maxTick = liv.getMaximumNoDamageTicks(); int maxTick = liv.getMaximumNoDamageTicks();
@@ -73,8 +75,13 @@ public class EyeAbility extends AbstractAbility {
liv.setFireTicks(20); liv.setFireTicks(20);
liv.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS,20,1,true,false,false)); liv.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS,20,1,true,false,false));
liv.getWorld().spawnParticle(Particle.LAVA, point.getLoc(), 1, 0.5, 0.5, 0.5, 0); liv.getWorld().spawnParticle(Particle.CRIMSON_SPORE, point.getLoc(), 1, 0.5, 0.5, 0.5, 0);
liv.getWorld().spawnParticle(Particle.BLOCK, point.getLoc(), 1, 0.5, 0.5, 0.5, 0,Material.RED_WOOL.createBlockData());
}); });
if (!point.getBlock().isPassable()) {
new SoundPlayer(point.getLoc(), Sound.BLOCK_LAVA_POP, 1, 1).playWithin(30);
point.getWorld().spawnParticle(Particle.LAVA, point.getLoc(), 1, 0.5, 0.5, 0.5, 0);
}
return !point.getBlock().isPassable() || !targets.isEmpty(); return !point.getBlock().isPassable() || !targets.isEmpty();
}).getLoc(); }).getLoc();
} }

View File

@@ -57,7 +57,6 @@ public class SilenceAbility extends AbstractAbility {
targets.forEach(target -> { targets.forEach(target -> {
PlayerUtils.dealTrueDamage((LivingEntity) target, DamageSource.builder(DamageType.SONIC_BOOM).withDirectEntity(player).build(), 10); PlayerUtils.dealTrueDamage((LivingEntity) target, DamageSource.builder(DamageType.SONIC_BOOM).withDirectEntity(player).build(), 10);
}); });
Verbose.send("Traced warden beam");
return !targets.isEmpty(); return !targets.isEmpty();
},(point,blockHit) -> { },(point,blockHit) -> {
Verbose.send("Block was hit at %s. Return Value !Passable: %s",blockHit.getLocation(),!blockHit.isPassable()); Verbose.send("Block was hit at %s. Return Value !Passable: %s",blockHit.getLocation(),!blockHit.isPassable());

View File

@@ -30,6 +30,9 @@ public class TideAbility extends AbstractAbility {
public void spawnTidalWave(Player caster) { public void spawnTidalWave(Player caster) {
AbstractAbility shaperInstance = main.man().abilityBackend.getAbility(TrimPattern.SHAPER); AbstractAbility shaperInstance = main.man().abilityBackend.getAbility(TrimPattern.SHAPER);
ShaperAbility shaper = (ShaperAbility) shaperInstance; ShaperAbility shaper = (ShaperAbility) shaperInstance;
new SoundPlayer(caster.getLocation(), Sound.WEATHER_RAIN_ABOVE, 1, 2F).playWithin(30);
new SoundPlayer(caster.getLocation(), Sound.ENTITY_PLAYER_SPLASH_HIGH_SPEED, 1, 1F).playWithin(30);
Vector direction = caster.getLocation().getDirection(); Vector direction = caster.getLocation().getDirection();
DisplayUtils.waveFan(caster.getLocation().add(0, 2,0),10,direction,90,0.1, point->{ DisplayUtils.waveFan(caster.getLocation().add(0, 2,0),10,direction,90,0.1, point->{

View File

View File

@@ -1,12 +1,16 @@
package me.trouper.trimserver.utils; package me.trouper.trimserver.utils;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.flags.Flags;
import com.sk89q.worldguard.protection.regions.RegionContainer;
import com.sk89q.worldguard.protection.regions.RegionQuery;
import me.trouper.trimserver.server.Main; import me.trouper.trimserver.server.Main;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.damage.DamageSource; import org.bukkit.damage.DamageSource;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class PlayerUtils implements Main { public class PlayerUtils implements Main {
@@ -80,6 +84,7 @@ public class PlayerUtils implements Main {
} }
public static void dealTrueDamage(LivingEntity target, DamageSource source, double amount) { public static void dealTrueDamage(LivingEntity target, DamageSource source, double amount) {
if (source.getDirectEntity() instanceof Player a && target instanceof Player t && !combatAllowed(t,a)) return;
double newHealth = target.getHealth() - amount; double newHealth = target.getHealth() - amount;
target.damage(1, source); target.damage(1, source);
if (newHealth <= 0) { if (newHealth <= 0) {
@@ -88,4 +93,18 @@ public class PlayerUtils implements Main {
target.setHealth(newHealth); target.setHealth(newHealth);
} }
} }
public static boolean combatAllowed(Player v, Player a) {
LocalPlayer victim = WorldGuardPlugin.inst().wrapPlayer(v);
LocalPlayer attacker = WorldGuardPlugin.inst().wrapPlayer(a);
RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer();
RegionQuery query = container.createQuery();
if (!query.testState(victim.getLocation(),victim, Flags.PVP) || !query.testState(attacker.getLocation(),attacker, Flags.PVP)) {
main.warning(a,"You cannot attack players protected by regions.");
return false;
}
return true;
}
} }

View File

View File

12
src/main/java/me/trouper/trimserver/utils/Text.java Normal file → Executable file
View File

@@ -41,7 +41,7 @@ public class Text implements Main {
} }
public static void sendMessage(Pallet pallet, CommandSender sender, String text, Object... args) { public static void sendMessage(Pallet pallet, CommandSender sender, String text, Object... args) {
text = formatArgs(pallet, text, args); text = formatArgsLegacy(pallet, text, args);
sendMessage(sender, text); sendMessage(sender, text);
if (sender instanceof Player p) p.playSound(p.getLocation(),pallet.sound.sound, SoundCategory.VOICE,10f,pallet.sound.pitch); if (sender instanceof Player p) p.playSound(p.getLocation(),pallet.sound.sound, SoundCategory.VOICE,10f,pallet.sound.pitch);
} }
@@ -52,7 +52,7 @@ public class Text implements Main {
} }
public static Component getMessage(Pallet pallet, String text, Object... args) { public static Component getMessage(Pallet pallet, String text, Object... args) {
return getMessage(formatArgs(pallet, text, args)); return getMessage(formatArgsLegacy(pallet, text, args));
} }
public static Component getMessage(String text) { public static Component getMessage(String text) {
@@ -63,7 +63,11 @@ public class Text implements Main {
} }
} }
public static String formatArgs(Pallet pallet, String format, Object... args) { public static String formatArgsLegacy(Pallet pallet, String format, Object... args) {
return LegacyComponentSerializer.legacyAmpersand().serialize(formatArgs(pallet,format,args));
}
public static Component formatArgs(Pallet pallet, String format, Object... args) {
Component message = Component.empty(); Component message = Component.empty();
Pattern pattern = Pattern.compile("\\{(\\d+)}"); Pattern pattern = Pattern.compile("\\{(\\d+)}");
Matcher matcher = pattern.matcher(format); Matcher matcher = pattern.matcher(format);
@@ -93,7 +97,7 @@ public class Text implements Main {
message = message.append(Component.text(suffix).color(pallet.mainText)); message = message.append(Component.text(suffix).color(pallet.mainText));
} }
return LegacyComponentSerializer.legacyAmpersand().serialize(message); return message;
} }
public static Component formatFancyMessage(String text) { public static Component formatFancyMessage(String text) {

0
src/main/java/me/trouper/trimserver/utils/Verbose.java Normal file → Executable file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

1
src/main/resources/plugin.yml Normal file → Executable file
View File

@@ -5,6 +5,7 @@ api-version: '1.21'
prefix: TrimServer prefix: TrimServer
load: STARTUP load: STARTUP
authors: [ obvWolf ] authors: [ obvWolf ]
depend: [ WorldGuard ]
description: Armor trims give you abilities description: Armor trims give you abilities
commands: commands:
trims: trims: