diff --git a/.gitignore b/.gitignore index 37ce5881cc..4a4c09c7a5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dependency-reduced-pom.xml # Temporary/etc. *.bak *.exe +.DS_Store diff --git a/README.md b/README.md index 3752da2ca9..adf885b898 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ The Denizen Scripting Language - Spigot Impl An implementation of the Denizen Scripting Language for Spigot servers, with strong Citizens interlinks to emphasize the power of using Denizen with NPCs! -**Version 1.3.0**: Compatible with Spigot 1.17.1, 1.18.2, 1.19.4, and 1.20.4! +**Version 1.3.1**: Compatible with Spigot 1.17.1, 1.18.2, 1.19.4, 1.20.6, and 1.21.11! **Learn about Denizen from the Beginner's guide:** https://guide.denizenscript.com/guides/background/index.html @@ -45,7 +45,7 @@ An implementation of the Denizen Scripting Language for Spigot servers, with str com.denizenscript denizen - 1.3.0-SNAPSHOT + 1.3.1-SNAPSHOT jar provided diff --git a/dist/pom.xml b/dist/pom.xml index 5baf74c6b5..25100bb2e5 100644 --- a/dist/pom.xml +++ b/dist/pom.xml @@ -5,7 +5,7 @@ com.denizenscript denizen-parent - 1.3.0-SNAPSHOT + 1.3.1-SNAPSHOT 4.0.0 @@ -54,6 +54,12 @@ ${project.parent.version} compile + + com.denizenscript + denizen-v1_21 + ${project.parent.version} + compile + com.denizenscript denizen-paper @@ -131,6 +137,12 @@ ** + + net.kyori:adventure-nbt + + ** + + @@ -153,6 +165,14 @@ org.apache com.denizenscript.shaded.org.apache + + net.kyori.option + com.denizenscript.shaded.net.kyori.option + + + net.kyori.adventure.nbt + com.denizenscript.shaded.net.adventure.nbt + diff --git a/paper/pom.xml b/paper/pom.xml index 4c0f10bb56..7d3b6b0869 100644 --- a/paper/pom.xml +++ b/paper/pom.xml @@ -6,7 +6,7 @@ com.denizenscript denizen-paper - 1.3.0-SNAPSHOT + 1.3.1-SNAPSHOT @@ -25,13 +25,13 @@ io.papermc.paper paper-api - 1.20.4-R0.1-SNAPSHOT + 1.21.11-R0.1-SNAPSHOT provided net.citizensnpcs citizens-main - 2.0.33-SNAPSHOT + 2.0.41-SNAPSHOT jar provided diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/PaperEventHelpers.java b/paper/src/main/java/com/denizenscript/denizen/paper/PaperEventHelpers.java index bda20425d7..d8a0773ccf 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/PaperEventHelpers.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/PaperEventHelpers.java @@ -2,20 +2,29 @@ import com.denizenscript.denizen.objects.InventoryTag; import com.denizenscript.denizen.scripts.containers.core.InventoryScriptContainer; +import com.denizenscript.denizen.utilities.VanillaTagHelper; +import com.denizenscript.denizen.utilities.inventory.InventoryViewUtil; import com.denizenscript.denizencore.objects.core.ScriptTag; import com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent; +import io.papermc.paper.event.server.ServerResourcesReloadedEvent; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; public class PaperEventHelpers implements Listener { @EventHandler public void onRecipeBookClick(PlayerRecipeBookClickEvent event) { - InventoryTag inventory = InventoryTag.mirrorBukkitInventory(event.getPlayer().getOpenInventory().getTopInventory()); + InventoryTag inventory = InventoryTag.mirrorBukkitInventory(InventoryViewUtil.getTopInventory(event.getPlayer().getOpenInventory())); if (inventory.getIdHolder() instanceof ScriptTag) { if (((InventoryScriptContainer) ((ScriptTag) inventory.getIdHolder()).getContainer()).gui) { event.setCancelled(true); } } } + + @EventHandler(priority = EventPriority.LOWEST) + public void onServerResourcesReloaded(ServerResourcesReloadedEvent event) { + VanillaTagHelper.loadTagsCache(); + } } diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/PaperModule.java b/paper/src/main/java/com/denizenscript/denizen/paper/PaperModule.java index 2877526769..305f8e41ce 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/PaperModule.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/PaperModule.java @@ -6,6 +6,7 @@ import com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat; import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.paper.datacomponents.ComponentAdaptersRegistry; import com.denizenscript.denizen.paper.events.*; import com.denizenscript.denizen.paper.properties.*; import com.denizenscript.denizen.paper.tags.PaperTagBase; @@ -18,7 +19,6 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.chat.ComponentSerializer; import org.bukkit.Bukkit; public class PaperModule { @@ -30,9 +30,12 @@ public static void init() { // Events ScriptEvent.registerScriptEvent(AnvilBlockDamagedScriptEvent.class); ScriptEvent.registerScriptEvent(AreaEnterExitScriptEventPaperImpl.class); - ScriptEvent.registerScriptEvent(BellRingScriptEvent.class); + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) { + ScriptEvent.registerScriptEvent(BellRingScriptEvent.class); + } ScriptEvent.registerScriptEvent(BlockPreDispenseScriptEvent.class); ScriptEvent.registerScriptEvent(CreeperIgnitesScriptEvent.class); + ScriptEvent.registerScriptEvent(DragonEggFormScriptEvent.class); ScriptEvent.registerScriptEvent(EntityAddToWorldScriptEvent.class); ScriptEvent.registerScriptEvent(EntityKnocksbackEntityScriptEvent.class); ScriptEvent.registerScriptEvent(EntityLoadCrossbowScriptEvent.class); @@ -45,7 +48,11 @@ public static void init() { ScriptEvent.registerScriptEvent(ExperienceOrbMergeScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerAbsorbsExperienceScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerBeaconEffectScriptEvent.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) { + ScriptEvent.registerScriptEvent(PlayerChangesFramedItemScriptEvent.class); + } ScriptEvent.registerScriptEvent(PlayerChoosesArrowScriptEvent.class); + ScriptEvent.registerScriptEvent(PlayerChunkUnloadScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerClicksFakeEntityScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerClicksInRecipeBookScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerClientOptionsChangeScriptEvent.class); @@ -53,22 +60,31 @@ public static void init() { ScriptEvent.registerScriptEvent(PlayerDeepSleepScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerElytraBoostScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerEquipsArmorScriptEvent.class); + ScriptEvent.registerScriptEvent(PlayerGrantedAdvancementCriterionScriptEvent.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { ScriptEvent.registerScriptEvent(PlayerInventorySlotChangeScriptEvent.class); } ScriptEvent.registerScriptEvent(PlayerItemTakesDamageScriptEventPaperImpl.class); ScriptEvent.registerScriptEvent(PlayerJumpsScriptEventPaperImpl.class); - ScriptEvent.registerScriptEvent(PlayerGrantedAdvancementCriterionScriptEvent.class); + ScriptEvent.registerScriptEvent(PlayerLecternPageChangeScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerLoomPatternSelectScriptEvent.class); + ScriptEvent.registerScriptEvent(PlayerNameEntityScriptEvent.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { ScriptEvent.registerScriptEvent(PlayerOpenSignScriptEvent.class); } ScriptEvent.registerScriptEvent(PlayerPreparesGrindstoneCraftScriptEvent.class); + ScriptEvent.registerScriptEvent(PlayerQuitsScriptEventPaperImpl.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) { ScriptEvent.registerScriptEvent(PlayerRaiseLowerItemScriptEventPaperImpl.class); } + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + ScriptEvent.registerScriptEvent(PlayerReceivesLinksScriptEvent.class); + } ScriptEvent.registerScriptEvent(PlayerSelectsStonecutterRecipeScriptEvent.class); - ScriptEvent.registerScriptEvent(PlayerLecternPageChangeScriptEvent.class); + ScriptEvent.registerScriptEvent(PlayerSetSpawnScriptEvent.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { + ScriptEvent.registerScriptEvent(PlayerShieldDisableScriptEvent.class); + } ScriptEvent.registerScriptEvent(PlayerSpectatesEntityScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerStopsSpectatingScriptEvent.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { @@ -76,16 +92,17 @@ public static void init() { } ScriptEvent.registerScriptEvent(PlayerTradesWithMerchantScriptEvent.class); ScriptEvent.registerScriptEvent(PreEntitySpawnScriptEvent.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { + ScriptEvent.registerScriptEvent(PrePlayerAttackEntityScriptEvent.class); + } ScriptEvent.registerScriptEvent(ProjectileCollideScriptEvent.class); ScriptEvent.registerScriptEvent(ServerListPingScriptEventPaperImpl.class); ScriptEvent.registerScriptEvent(ServerResourcesReloadedScriptEvent.class); ScriptEvent.registerScriptEvent(SkeletonHorseTrapScriptEvent.class); + ScriptEvent.registerScriptEvent(TargetBlockHitScriptEvent.class); if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) { ScriptEvent.registerScriptEvent(TNTPrimesScriptEvent.class); } - if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { - ScriptEvent.registerScriptEvent(PrePlayerAttackEntityScriptEvent.class); - } ScriptEvent.registerScriptEvent(UnknownCommandScriptEvent.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { ScriptEvent.registerScriptEvent(WardenChangesAngerLevelScriptEvent.class); @@ -97,8 +114,9 @@ public static void init() { if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) { PropertyParser.registerProperty(EntityAutoExpire.class, EntityTag.class); } - PropertyParser.registerProperty(EntityCanTick.class, EntityTag.class); + PropertyParser.registerProperty(EntityBodyStingers.class, EntityTag.class); PropertyParser.registerProperty(EntityCarryingEgg.class, EntityTag.class); + PropertyParser.registerProperty(EntityCanTick.class, EntityTag.class); PropertyParser.registerProperty(EntityDrinkingPotion.class, EntityTag.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { PropertyParser.registerProperty(EntityEggLayTime.class, EntityTag.class); @@ -113,6 +131,12 @@ public static void init() { PropertyParser.registerProperty(EntityWitherInvulnerable.class, EntityTag.class); PropertyParser.registerProperty(ItemArmorStand.class, ItemTag.class); + // Components system + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + PropertyParser.registerProperty(ItemRemovedComponents.class, ItemTag.class); + ComponentAdaptersRegistry.register(); + } + // Paper object extensions PaperElementExtensions.register(); PaperEntityExtensions.register(); @@ -145,7 +169,7 @@ public static String stringifyComponent(Component component) { if (component == null) { return null; } - return FormattedTextHelper.stringify(ComponentSerializer.parse(componentToJson(component))); + return FormattedTextHelper.stringify(FormattedTextHelper.parseJson(componentToJson(component))); } public static Component jsonToComponent(String json) { diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/ComponentAdaptersRegistry.java b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/ComponentAdaptersRegistry.java new file mode 100644 index 0000000000..ad691abf2b --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/ComponentAdaptersRegistry.java @@ -0,0 +1,9 @@ +package com.denizenscript.denizen.paper.datacomponents; + +public class ComponentAdaptersRegistry { + + public static void register() { + DataComponentAdapter.register(new FoodAdapter()); + DataComponentAdapter.register(new GliderAdapter()); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/DataComponentAdapter.java b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/DataComponentAdapter.java new file mode 100644 index 0000000000..82349b5660 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/DataComponentAdapter.java @@ -0,0 +1,227 @@ +package com.denizenscript.denizen.paper.datacomponents; + +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.properties.item.ItemComponentsPatch; +import com.denizenscript.denizen.objects.properties.item.ItemProperty; +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.objects.core.MapTag; +import com.denizenscript.denizencore.objects.properties.PropertyParser; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import io.papermc.paper.datacomponent.DataComponentType; +import org.bukkit.Registry; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +public abstract class DataComponentAdapter { + + // <--[language] + // @name Item Components + // @group Minecraft Logic + // @description + // Minecraft item components (see <@link url https://minecraft.wiki/w/Data_component_format>) are managed as follows: + // Each item type has a default set of component values; a food item will have food components by default, a tool item will have tool components by default, etc. + // Different items can override their type's default components, either by setting values that weren't there previously (e.g. making an inedible item edible), or by removing values that are there by default (e.g. making a shield item that can't block). + // Items' overrides can later be reset, making them use their type's default values again. + // + // In Denizen, different item components are represented by item properties. + // These properties allow both setting a component override on an item, and clearing/resetting it by providing no input. + // Item properties' name will generally match their respective item component's name, but not always! + // Due to this, features that take item component names as input (such as <@link tag ItemTag.is_overridden>) accept both Minecraft component names and Denizen property names. + // + // Here is an example of applying all of this in a script: + // + // # We define a default apple item + // - define apple + // # We remove the apple's "food" component, making eating it restore no food points (it is still consumable due to the "consumable" component). + // - adjust def:apple remove_component:food + // # This check will pass, as the apple's "food" component is overridden to have no value. + // - if <[apple].is_overridden[food]>: + // - narrate "The apple has a modified food component! It will behave differently to a normal apple." + // # We reset the apple item's food component by adjusting with no value, making it a normal apple. + // - adjust def:apple food: + // + // --> + + public static final Map COMPONENTS_BY_PROPERTY = new HashMap<>(); + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + + public static DataComponentType getComponentType(String name) { + String nameLower = CoreUtilities.toLowerCase(name); + DataComponentType componentType = Registry.DATA_COMPONENT_TYPE.get(Utilities.parseNamespacedKey(nameLower)); + if (componentType == null) { + componentType = COMPONENTS_BY_PROPERTY.get(nameLower); + } + return componentType; + } + + public static void register(DataComponentAdapter adapter) { + DataComponentAdapter.Property.currentlyRegisteringComponentAdapter = adapter; + PropertyParser.registerPropertyGetter( + item -> !item.getItemStack().isEmpty() ? adapter.new Property(item) : null, + ItemTag.class, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, DataComponentAdapter.Property.class); + DataComponentAdapter.Property.currentlyRegisteringComponentAdapter = null; + String componentName = adapter.componentType.key().asMinimalString(); + ItemComponentsPatch.registerHandledComponent(componentName); + if (!adapter.name.equals(componentName)) { + COMPONENTS_BY_PROPERTY.put(adapter.name, adapter.componentType); + } + } + + static { + + // <--[tag] + // @attribute ]> + // @returns ElementTag(Boolean) + // @description + // Returns whether an item has a specific item component type overridden, see <@link language Item Components>. + // --> + ItemTag.tagProcessor.registerTag(ElementTag.class, ElementTag.class, "is_overridden", (attribute, object, param) -> { + DataComponentType componentType = getComponentType(param.asString()); + if (componentType == null) { + attribute.echoError("Invalid type specified, must be a valid item component type or property name."); + return null; + } + return new ElementTag(object.getItemStack().isDataOverridden(componentType)); + }); + } + + public final C componentType; + public final Class denizenType; + public final String name; + + public DataComponentAdapter(C componentType, Class denizenType, String name) { + this.componentType = componentType; + this.denizenType = denizenType; + this.name = name; + } + + public abstract D getValue(ItemStack item); + + public abstract void setValue(ItemStack item, D value, Mechanism mechanism); + + public boolean isDefaultValue(D value) { + return false; + } + + public static abstract class NonValued extends DataComponentAdapter { + + public NonValued(DataComponentType.NonValued componentType, String name) { + super(componentType, ElementTag.class, name); + } + + @Override + public ElementTag getValue(ItemStack item) { + return new ElementTag(item.hasData(componentType)); + } + + @Override + public void setValue(ItemStack item, ElementTag value, Mechanism mechanism) { + if (!mechanism.requireBoolean()) { + return; + } + if (value.asBoolean()) { + item.setData(componentType); + } + else { + item.unsetData(componentType); + } + } + + // Overridden and false = removed, managed by ItemTag.removed_components + @Override + public boolean isDefaultValue(ElementTag value) { + return !value.asBoolean(); + } + } + + public static abstract class Valued extends DataComponentAdapter> { + + public static void setIfValid(Consumer setter, MapTag data, String key, Class objectType, Predicate checker, Function converter, String type, Mechanism mechanism) { + D value = data.getObjectAs(key, objectType, mechanism.context); + if (value == null) { + return; + } + T converted; + if ((checker != null && !checker.test(value)) || (converted = converter.apply(value)) == null) { + mechanism.echoError("Invalid '" + key + "' specified: must be a " + type + '.'); + return; + } + setter.accept(converted); + } + + public Valued(Class denizenType, DataComponentType.Valued

componentType, String name) { + super(componentType, denizenType, name); + } + + public abstract D toDenizen(P value); + + public abstract P fromDenizen(D value, Mechanism mechanism); + + @Override + public D getValue(ItemStack item) { + P data = item.getData(componentType); + return data != null ? toDenizen(data) : null; + } + + @Override + public void setValue(ItemStack item, D value, Mechanism mechanism) { + P converted = fromDenizen(value, mechanism); + if (converted != null) { + item.setData(componentType, converted); + } + } + } + + public class Property extends ItemProperty { + + private static DataComponentAdapter currentlyRegisteringComponentAdapter; + + public Property(ItemTag item) { + this.object = item; + } + + @Override + public D getPropertyValue() { + return getValue(getItemStack()); + } + + @Override + public D getPropertyValueNoDefault() { + if (!getItemStack().isDataOverridden(componentType)) { + return null; + } + return super.getPropertyValueNoDefault(); + } + + @Override + public boolean isDefaultValue(D value) { + return DataComponentAdapter.this.isDefaultValue(value); + } + + @Override + public void setPropertyValue(D value, Mechanism mechanism) { + if (value == null) { + getItemStack().resetData(componentType); + return; + } + setValue(getItemStack(), value, mechanism); + } + + @Override + public String getPropertyId() { + return name; + } + + public static void register() { + autoRegisterNullable(currentlyRegisteringComponentAdapter.name, DataComponentAdapter.Property.class, currentlyRegisteringComponentAdapter.denizenType, false); + } + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/FoodAdapter.java b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/FoodAdapter.java new file mode 100644 index 0000000000..4f6710f1fd --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/FoodAdapter.java @@ -0,0 +1,46 @@ +package com.denizenscript.denizen.paper.datacomponents; + +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.objects.core.MapTag; +import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.datacomponent.item.FoodProperties; + +public class FoodAdapter extends DataComponentAdapter.Valued { + + // <--[property] + // @object ItemTag + // @name food + // @input MapTag + // @description + // Controls an item's food <@link language Item Components>. + // The map includes keys: + // - "nutrition", ElementTag(Number) representing the amount of food points restored by this item. + // - "saturation", ElementTag(Decimal) representing the amount of saturation points restored by this item. + // - "can_always_eat", ElementTag(Boolean) controlling whether the item can always be eaten, even if the player isn't hungry. + // @mechanism + // Provide no input to reset the item to its default value. + // --> + + public FoodAdapter() { + super(MapTag.class, DataComponentTypes.FOOD, "food"); + } + + @Override + public MapTag toDenizen(FoodProperties value) { + MapTag foodData = new MapTag(); + foodData.putObject("nutrition", new ElementTag(value.nutrition())); + foodData.putObject("saturation", new ElementTag(value.saturation())); + foodData.putObject("can_always_eat", new ElementTag(value.canAlwaysEat())); + return foodData; + } + + @Override + public FoodProperties fromDenizen(MapTag value, Mechanism mechanism) { + FoodProperties.Builder builder = FoodProperties.food(); + setIfValid(builder::nutrition, value, "nutrition", ElementTag.class, ElementTag::isInt, ElementTag::asInt, "number", mechanism); + setIfValid(builder::saturation, value, "saturation", ElementTag.class, ElementTag::isFloat, ElementTag::asFloat, "decimal number", mechanism); + setIfValid(builder::canAlwaysEat, value, "can_always_eat", ElementTag.class, ElementTag::isBoolean, ElementTag::asBoolean, "boolean", mechanism); + return builder.build(); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/GliderAdapter.java b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/GliderAdapter.java new file mode 100644 index 0000000000..838f02ca72 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/GliderAdapter.java @@ -0,0 +1,20 @@ +package com.denizenscript.denizen.paper.datacomponents; + +import io.papermc.paper.datacomponent.DataComponentTypes; + +public class GliderAdapter extends DataComponentAdapter.NonValued { + + // <--[property] + // @object ItemTag + // @name glider + // @input ElementTag(Boolean) + // @description + // Controls whether an item can be used to glide when equipped (like elytras by default), see <@link language Item Components>. + // @mechanism + // Provide no input to reset the item to its default value. + // --> + + public GliderAdapter() { + super(DataComponentTypes.GLIDER, "glider"); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/AnvilBlockDamagedScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/AnvilBlockDamagedScriptEvent.java index 79af137dc8..effc8f9e27 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/AnvilBlockDamagedScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/AnvilBlockDamagedScriptEvent.java @@ -3,10 +3,10 @@ import com.denizenscript.denizen.events.BukkitScriptEvent; import com.denizenscript.denizen.objects.InventoryTag; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizen.utilities.inventory.InventoryViewUtil; import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; -import com.denizenscript.denizencore.utilities.CoreUtilities; import com.destroystokyo.paper.event.block.AnvilDamagedEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -42,6 +42,20 @@ public class AnvilBlockDamagedScriptEvent extends BukkitScriptEvent implements L public AnvilBlockDamagedScriptEvent() { registerCouldMatcher("anvil block damaged|breaks"); registerSwitches("state"); + this.registerOptionalDetermination("state", ElementTag.class, (evt, context, state) -> { + if (state.matchesEnum(AnvilDamagedEvent.DamageState.class)) { + evt.event.setDamageState(state.asEnum(AnvilDamagedEvent.DamageState.class)); + return true; + } + return false; + }); + this.registerOptionalDetermination("break", ElementTag.class, (evt, context, value) -> { + if (value.isBoolean()) { + evt.event.setBreaking(value.asBoolean()); + return true; + } + return false; + }); } public AnvilDamagedEvent event; @@ -62,36 +76,17 @@ public boolean matches(ScriptPath path) { @Override public ObjectTag getContext(String name) { - switch (name) { - case "state": return new ElementTag(event.getDamageState()); - case "inventory": return InventoryTag.mirrorBukkitInventory(event.getInventory()); - case "break": return new ElementTag(event.isBreaking()); - } - return super.getContext(name); - } - - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag) { - String lower = CoreUtilities.toLowerCase(determinationObj.toString()); - if (lower.startsWith("state:")) { - ElementTag stateElement = new ElementTag(lower.substring("state:".length())); - if (stateElement.matchesEnum(AnvilDamagedEvent.DamageState.class)) { - event.setDamageState(stateElement.asEnum(AnvilDamagedEvent.DamageState.class)); - return true; - } - } - else if (lower.startsWith("break:")) { - event.setBreaking(new ElementTag(lower.substring("break:".length())).asBoolean()); - return true; - } - } - return super.applyDetermination(path, determinationObj); + return switch (name) { + case "state" -> new ElementTag(event.getDamageState()); + case "inventory" -> InventoryTag.mirrorBukkitInventory(event.getInventory()); + case "break" -> new ElementTag(event.isBreaking()); + default -> super.getContext(name); + }; } @Override public ScriptEntryData getScriptEntryData() { - return new BukkitScriptEntryData(event.getView().getPlayer()); + return new BukkitScriptEntryData(InventoryViewUtil.getPlayer(event.getView())); } @EventHandler diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/BellRingScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/BellRingScriptEvent.java index b70112effa..40ef73ef82 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/BellRingScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/BellRingScriptEvent.java @@ -12,27 +12,6 @@ public class BellRingScriptEvent extends BukkitScriptEvent implements Listener { - // <--[event] - // @Events - // bell rings - // - // @Location true - // - // @Plugin Paper - // - // @Group Paper - // - // @Cancellable true - // - // @Triggers when a bell block rings. - // - // @Context - // returns the entity that rung the bell, if any. - // returns the location of the bell being rung. - // - // @Player when the ringing entity is a player. - // - // --> public BellRingScriptEvent() { registerCouldMatcher("bell rings"); @@ -58,7 +37,7 @@ public ScriptEntryData getScriptEntryData() { public ObjectTag getContext(String name) { switch (name) { case "entity": - return event.getEntity() == null ? null : new EntityTag(event.getEntity()); + return event.getEntity() == null ? null : new EntityTag(event.getEntity()).getDenizenObject(); case "location": return location; } return super.getContext(name); diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/DragonEggFormScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/DragonEggFormScriptEvent.java new file mode 100644 index 0000000000..944edf8062 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/DragonEggFormScriptEvent.java @@ -0,0 +1,77 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.objects.core.ListTag; +import io.papermc.paper.event.block.DragonEggFormEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class DragonEggFormScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // dragon egg forms + // + // @Plugin Paper + // + // @Group Paper + // + // @Location true + // + // @Triggers when the ender dragon is defeated and the dragon egg forms. + // + // @Context + // returns the EntityTag of the ender dragon right before it's removed. + // returns the LocationTag of the dragon egg. + // returns the LocationTag of the end portal. + // returns an ElementTag(Boolean) of whether the dragon has been previously killed. + // returns an ElementTag of the respawn phase. Valid values can be found at <@link url https://jd.papermc.io/paper/1.21.3/org/bukkit/boss/DragonBattle.RespawnPhase.html>. + // returns a ListTag(EntityTag) of the healing end crystals. + // returns a ListTag(EntityTag) of the respawn end crystals. + // + // --> + + public DragonEggFormScriptEvent() { + registerCouldMatcher("dragon egg forms"); + } + + public LocationTag location; + public EntityTag entity; + public DragonEggFormEvent event; + + @Override + public boolean matches(ScriptPath path) { + if (!runInCheck(path, location)) { + return false; + } + return super.matches(path); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "entity" -> entity; + case "location" -> location; + case "end_portal_location" -> new LocationTag(event.getDragonBattle().getEndPortalLocation()); + case "previously_killed" -> new ElementTag(event.getDragonBattle().hasBeenPreviouslyKilled()); + case "respawn_phase" -> new ElementTag(event.getDragonBattle().getRespawnPhase()); + case "healing_crystals" -> new ListTag(event.getDragonBattle().getHealingCrystals(), EntityTag::new); + case "respawn_crystals" -> new ListTag(event.getDragonBattle().getRespawnCrystals(), EntityTag::new); + default -> super.getContext(name); + }; + } + + @EventHandler + public void onDragonEggForms(DragonEggFormEvent event) { + location = new LocationTag(event.getBlock().getLocation()); + entity = new EntityTag(event.getDragonBattle().getEnderDragon()); + this.event = event; + EntityTag.rememberEntity(entity.getBukkitEntity()); + fire(event); + EntityTag.forgetEntity(entity.getBukkitEntity()); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/EntityKnocksbackEntityScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/EntityKnocksbackEntityScriptEvent.java index 713e8133f1..09ec78f7c8 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/EntityKnocksbackEntityScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/EntityKnocksbackEntityScriptEvent.java @@ -2,10 +2,11 @@ import com.denizenscript.denizen.events.BukkitScriptEvent; import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.ItemTag; import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.ObjectTag; -import com.denizenscript.denizen.objects.ItemTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; import com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent; import org.bukkit.event.EventHandler; @@ -19,6 +20,8 @@ public class EntityKnocksbackEntityScriptEvent extends BukkitScriptEvent impleme // // @Location true // + // @Warning this event may in some cases double-fire, requiring usage of the 'ratelimit' command (like 'ratelimit 1t') to prevent doubling actions. + // // @Switch with: to only process the event when the item used to cause damage (in the damager's hand) is a specified item. // // @Plugin Paper @@ -33,6 +36,7 @@ public class EntityKnocksbackEntityScriptEvent extends BukkitScriptEvent impleme // returns the EntityTag that was knocked back. // returns the EntityTag of the one who knocked. // returns the knockback applied as a vector. + // returns the cause of the knockback (only on MC 1.20+). Causes list: <@link url https://jd.papermc.io/paper/1.21.1/io/papermc/paper/event/entity/EntityKnockbackEvent.Cause.html> // // @Determine // LocationTag as a vector to change the acceleration applied. @@ -58,7 +62,7 @@ public EntityKnocksbackEntityScriptEvent() { public boolean matches(ScriptPath path) { String attacker = path.eventArgLowerAt(0); String target = path.eventArgLowerAt(3); - if (!hitBy.tryAdvancedMatcher(attacker) || (!entity.tryAdvancedMatcher(target))) { + if (!hitBy.tryAdvancedMatcher(attacker, path.context) || (!entity.tryAdvancedMatcher(target, path.context))) { return false; } if (!runInCheck(path, entity.getLocation())) { @@ -88,15 +92,13 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "entity": - return entity.getDenizenObject(); - case "damager": - return hitBy.getDenizenObject(); - case "acceleration": - return new LocationTag(event.getAcceleration()); - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "damager" -> hitBy.getDenizenObject(); + case "acceleration" -> new LocationTag(event.getAcceleration()); + case "cause" -> new ElementTag(event.getCause().name(), true); // TODO: once 1.20 is the minimum supported version, use the enum constructor + default -> super.getContext(name); + }; } @EventHandler diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/EntityLoadCrossbowScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/EntityLoadCrossbowScriptEvent.java index 34fadbc0b0..77eb0a94eb 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/EntityLoadCrossbowScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/EntityLoadCrossbowScriptEvent.java @@ -7,7 +7,6 @@ import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; -import com.denizenscript.denizencore.utilities.CoreUtilities; import io.papermc.paper.event.entity.EntityLoadCrossbowEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -48,9 +47,11 @@ public class EntityLoadCrossbowScriptEvent extends BukkitScriptEvent implements public EntityLoadCrossbowScriptEvent() { registerCouldMatcher(" loads crossbow"); registerSwitches("crossbow"); + this.registerTextDetermination("keep_item", (evt) -> { + evt.event.setConsumeItem(false); + }); } - public EntityLoadCrossbowEvent event; public EntityTag entity; @@ -70,31 +71,15 @@ public ScriptEntryData getScriptEntryData() { return new BukkitScriptEntryData(entity); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag) { - String lower = CoreUtilities.toLowerCase(determinationObj.toString()); - if (lower.equals("keep_item")) { - event.setConsumeItem(false); - return true; - } - } - return super.applyDetermination(path, determinationObj); - } - @Override public ObjectTag getContext(String name) { - switch (name) { - case "entity": - return entity.getDenizenObject(); - case "crossbow": - return new ItemTag(event.getCrossbow()); - case "hand": - return new ElementTag(event.getHand()); - case "consumes": - return new ElementTag(event.shouldConsumeItem()); - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "crossbow" -> new ItemTag(event.getCrossbow()); + case "hand" -> new ElementTag(event.getHand()); + case "consumes" -> new ElementTag(event.shouldConsumeItem()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerBeaconEffectScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerBeaconEffectScriptEvent.java index 6f204e60e6..99baed8d42 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerBeaconEffectScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerBeaconEffectScriptEvent.java @@ -31,7 +31,7 @@ public class PlayerBeaconEffectScriptEvent extends BukkitScriptEvent implements // // @Context // returns the LocationTag of the beacon applying an effect. - // returns a MapTag of the potion effect (in the same format as <@link tag EntityTag.effects_data>). + // returns a MapTag of the potion effect in <@link language Potion Effect Format>. // returns an ElementTag of the effect type. // returns an ElementTag(Boolean) of whether the beacon effect is the primary effect. // @@ -78,7 +78,7 @@ public ObjectTag getContext(String name) { return switch (name) { case "location" -> new LocationTag(event.getBlock().getLocation()); case "effect" -> new ElementTag(ItemPotion.effectToLegacyString(event.getEffect(), null)); - case "effect_data" -> ItemPotion.effectToMap(event.getEffect()); + case "effect_data" -> ItemPotion.effectToMap(event.getEffect(), true); case "effect_type" -> new ElementTag(event.getEffect().getType().getName()); case "is_primary" -> new ElementTag(event.isPrimary()); default -> super.getContext(name); diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChangesFramedItemScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChangesFramedItemScriptEvent.java new file mode 100644 index 0000000000..76745156e5 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChangesFramedItemScriptEvent.java @@ -0,0 +1,97 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class PlayerChangesFramedItemScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // player changes framed + // + // @Location true + // + // @Plugin Paper + // + // @Group Paper + // + // @Cancellable true + // + // @Triggers when a player interacts with an item frame by adding, removing, or rotating the item held in it. + // + // @Switch frame: to only process the event if the item frame entity being matches the input. + // @Switch action: to only process the event if the change matches the input. + // + // @Context + // returns the EntityTag of the item frame. + // returns the ItemTag of the item held in the item frame. + // returns the ElementTag of the action being performed, based on <@link url https://jd.papermc.io/paper/1.20/io/papermc/paper/event/player/PlayerItemFrameChangeEvent.ItemFrameChangeAction.html> + // + // @Determine + // "ITEM:" to change the item held by the item frame. If there is an item already in the frame, it will be replaced. To remove the item, set it to air. + // + // @Player Always. + // --> + + public PlayerChangesFramedItemScriptEvent() { + registerCouldMatcher("player changes framed "); + registerSwitches("frame", "action"); + this.registerDetermination("item", ItemTag.class, (evt, context, item) -> { + evt.event.setItemStack(item.getItemStack()); + }); + } + + public ItemTag item; + public EntityTag frame; + public ElementTag action; + public PlayerItemFrameChangeEvent event; + + @Override + public boolean matches(ScriptPath path) { + if (!path.tryArgObject(3, item)) { + return false; + } + if (!path.tryObjectSwitch("frame", frame)) { + return false; + } + if (!path.tryObjectSwitch("action", action)) { + return false; + } + if (!runInCheck(path, frame.getLocation())) { + return false; + } + return super.matches(path); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "frame" -> frame; + case "item" -> new ItemTag(event.getItemStack()); + case "action" -> action; + default -> super.getContext(name); + }; + } + + @Override + public ScriptEntryData getScriptEntryData() { + return new BukkitScriptEntryData(event.getPlayer()); + } + + @EventHandler + public void onPlayerChangesFramedItem(PlayerItemFrameChangeEvent event) { + item = new ItemTag(event.getItemStack()); + frame = new EntityTag(event.getItemFrame()); + action = new ElementTag(event.getAction()); + this.event = event; + fire(event); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChunkUnloadScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChunkUnloadScriptEvent.java new file mode 100644 index 0000000000..3199313aa8 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChunkUnloadScriptEvent.java @@ -0,0 +1,68 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.ChunkTag; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import io.papermc.paper.event.packet.PlayerChunkUnloadEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class PlayerChunkUnloadScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // player receives chunk unload + // + // @Group Paper + // + // @Location true + // + // @Plugin Paper + // + // @Warning This event will fire *extremely* rapidly and almost guarantees lag. Use with maximum caution. + // + // @Triggers when a Player receives a chunk unload packet. + // Should only be used for packet/clientside related stuff. Not intended for modifying server side. + // Generally prefer <@link event chunk unloads> in most cases. + // + // @Context + // returns a ChunkTag of the chunk being unloaded. + // + // @Player Always. + // --> + + public PlayerChunkUnloadScriptEvent() { + registerCouldMatcher("player receives chunk unload"); + } + + public PlayerChunkUnloadEvent event; + + @Override + public boolean matches(ScriptPath path) { + if (!runInCheck(path, event.getPlayer().getLocation())) { + return false; + } + return super.matches(path); + } + + @Override + public ScriptEntryData getScriptEntryData() { + return new BukkitScriptEntryData(event.getPlayer()); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "chunk" -> new ChunkTag(event.getChunk()); + default -> super.getContext(name); + }; + } + + @EventHandler + public void playerChunkUnloadEvent(PlayerChunkUnloadEvent event) { + this.event = event; + fire(event); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerElytraBoostScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerElytraBoostScriptEvent.java index 9063369f10..2a8300cc43 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerElytraBoostScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerElytraBoostScriptEvent.java @@ -8,7 +8,6 @@ import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; -import com.denizenscript.denizencore.utilities.CoreUtilities; import com.destroystokyo.paper.event.player.PlayerElytraBoostEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -47,6 +46,13 @@ public class PlayerElytraBoostScriptEvent extends BukkitScriptEvent implements L public PlayerElytraBoostScriptEvent() { registerCouldMatcher("player boosts elytra"); registerSwitches("with", "elytra"); + this.registerOptionalDetermination("keep", ElementTag.class, (evt, context, value) -> { + if (value.isBoolean()) { + evt.event.setShouldConsume(!value.asBoolean()); + return true; + } + return false; + }); } public PlayerElytraBoostEvent event; @@ -74,26 +80,12 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "item": - return firework; - case "entity": - return new EntityTag(event.getFirework()); - case "should_keep": - return new ElementTag(!event.shouldConsume()); - } - return super.getContext(name); - } - - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - String determination = CoreUtilities.toLowerCase(determinationObj.toString()); - if (determination.startsWith("keep:")) { - String value = determination.substring("keep:".length()); - event.setShouldConsume(!(new ElementTag(value).asBoolean())); - return true; - } - return super.applyDetermination(path, determinationObj); + return switch (name) { + case "item" -> firework; + case "entity" -> new EntityTag(event.getFirework()); + case "should_keep" -> new ElementTag(!event.shouldConsume()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerEquipsArmorScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerEquipsArmorScriptEvent.java index 204f0587c8..2c3b933852 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerEquipsArmorScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerEquipsArmorScriptEvent.java @@ -77,7 +77,7 @@ public boolean matches(ScriptPath path) { return false; } } - else if (!itemCompare.equals("armor") && !newItem.tryAdvancedMatcher(itemCompare)) { + else if (!itemCompare.equals("armor") && !newItem.tryAdvancedMatcher(itemCompare, path.context)) { return false; } } @@ -87,7 +87,7 @@ else if (!itemCompare.equals("armor") && !newItem.tryAdvancedMatcher(itemCompare return false; } } - else if (!itemCompare.equals("armor") && !oldItem.tryAdvancedMatcher(itemCompare)) { + else if (!itemCompare.equals("armor") && !oldItem.tryAdvancedMatcher(itemCompare, path.context)) { return false; } } diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLecternPageChangeScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLecternPageChangeScriptEvent.java index 7ffe51f311..c4a7a555a1 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLecternPageChangeScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLecternPageChangeScriptEvent.java @@ -7,7 +7,6 @@ import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; -import com.denizenscript.denizencore.utilities.CoreUtilities; import io.papermc.paper.event.player.PlayerLecternPageChangeEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -57,6 +56,13 @@ public class PlayerLecternPageChangeScriptEvent extends BukkitScriptEvent implem public PlayerLecternPageChangeScriptEvent() { registerCouldMatcher("player flips lectern page"); registerSwitches("book"); + this.registerOptionalDetermination("page", ElementTag.class, (evt, context, page) -> { + if (page.isInt()) { + evt.event.setNewPage(page.asInt() - 1); + return true; + } + return false; + }); } public PlayerLecternPageChangeEvent event; @@ -88,21 +94,6 @@ public ObjectTag getContext(String name) { default -> super.getContext(name); }; } - - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag) { - String lower = CoreUtilities.toLowerCase(determinationObj.toString()); - if (lower.startsWith("page:")) { - ElementTag value = new ElementTag(lower.substring("page:".length())); - if (value.isInt()) { - event.setNewPage(value.asInt() - 1); - return true; - } - } - } - return super.applyDetermination(path, determinationObj); - } @EventHandler public void onPlayerFlipsLecternPage(PlayerLecternPageChangeEvent event) { diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLoomPatternSelectScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLoomPatternSelectScriptEvent.java index abfdb486f5..ca27f1747f 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLoomPatternSelectScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLoomPatternSelectScriptEvent.java @@ -2,11 +2,11 @@ import com.denizenscript.denizen.events.BukkitScriptEvent; import com.denizenscript.denizen.objects.InventoryTag; +import com.denizenscript.denizen.utilities.Utilities; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; -import com.denizenscript.denizencore.utilities.CoreUtilities; import io.papermc.paper.event.player.PlayerLoomPatternSelectEvent; import org.bukkit.block.banner.PatternType; import org.bukkit.event.EventHandler; @@ -55,6 +55,13 @@ public class PlayerLoomPatternSelectScriptEvent extends BukkitScriptEvent implem public PlayerLoomPatternSelectScriptEvent() { registerCouldMatcher("player selects loom pattern"); registerSwitches("type"); + this.registerOptionalDetermination("pattern", ElementTag.class, (evt, context, pattern) -> { + if (Utilities.matchesEnumlike(pattern, PatternType.class)) { + evt.event.setPatternType(Utilities.elementToEnumlike(pattern, PatternType.class)); + return true; + } + return false; + }); } public PlayerLoomPatternSelectEvent event; @@ -64,7 +71,7 @@ public boolean matches(ScriptPath path) { if (!runInCheck(path, event.getLoomInventory().getLocation())) { return false; } - if (!path.tryObjectSwitch("type", new ElementTag(event.getPatternType()))) { + if (!path.tryObjectSwitch("type", Utilities.enumlikeToElement(event.getPatternType()))) { return false; } return super.matches(path); @@ -79,24 +86,11 @@ public ScriptEntryData getScriptEntryData() { public ObjectTag getContext(String name) { return switch (name) { case "loom" -> InventoryTag.mirrorBukkitInventory(event.getLoomInventory()); - case "pattern" -> new ElementTag(event.getPatternType()); + case "pattern" -> Utilities.enumlikeToElement(event.getPatternType()); default -> super.getContext(name); }; } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag) { - String lower = CoreUtilities.toLowerCase(determinationObj.toString()); - if (lower.startsWith("pattern:")) { - ElementTag value = new ElementTag(lower.substring("pattern:".length())); - event.setPatternType(value.asEnum(PatternType.class)); - return true; - } - } - return super.applyDetermination(path, determinationObj); - } - @EventHandler public void onPlayerSelectsLoomPattern(PlayerLoomPatternSelectEvent event) { this.event = event; diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNameEntityScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNameEntityScriptEvent.java new file mode 100644 index 0000000000..41d421bd78 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNameEntityScriptEvent.java @@ -0,0 +1,99 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.paper.PaperModule; +import com.denizenscript.denizen.utilities.PaperAPITools; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import io.papermc.paper.event.player.PlayerNameEntityEvent; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class PlayerNameEntityScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // player names + // + // @Location true + // + // @Plugin Paper + // + // @Group Paper + // + // @Cancellable true + // + // @Triggers when a player attempts to rename an entity with a name tag. + // + // @Context + // returns an EntityTag of the renamed entity. + // returns the old name of the entity, if any. + // returns the new name of the entity. + // returns whether this will cause the entity to persist through server restarts. + // + // @Determine + // "NAME:" to set a different name for the entity. + // "PERSISTENT:" to set whether the event will cause the entity to persist through restarts. NOTE: Entities may still persist for other reasons. To ensure they do not, use <@link mechanism EntityTag.force_no_persist>. + // + // @Player Always. + // + // --> + + public PlayerNameEntityScriptEvent() { + registerCouldMatcher("player names "); + this.registerOptionalDetermination("persistent", ElementTag.class, (evt, context, determination) -> { + if (determination.isBoolean()) { + evt.event.setPersistent(determination.asBoolean()); + return true; + } + return false; + }); + this.registerDetermination("name", ElementTag.class, (evt, context, determination) -> { + evt.event.setName(PaperModule.parseFormattedText(determination.toString(), ChatColor.WHITE)); + }); + } + + public PlayerNameEntityEvent event; + public EntityTag entity; + public ElementTag oldName; + + @Override + public boolean matches(ScriptPath path) { + if (!runInCheck(path, entity.getLocation())) { + return false; + } + if (!path.tryArgObject(2, entity)) { + return false; + } + return super.matches(path); + } + + @Override + public ScriptEntryData getScriptEntryData() { + return new BukkitScriptEntryData(event.getPlayer()); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "name" -> new ElementTag(PaperModule.stringifyComponent(event.getName()), true); + case "old_name" -> oldName; + case "persistent" -> new ElementTag(event.isPersistent()); + default -> super.getContext(name); + }; + } + + @EventHandler + public void playerNamesEntity(PlayerNameEntityEvent event) { + this.event = event; + entity = new EntityTag(event.getEntity()); + String name = PaperAPITools.instance.getCustomName(entity.getBukkitEntity()); + oldName = name == null ? null : new ElementTag(name, true); + fire(event); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerPreparesGrindstoneCraftScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerPreparesGrindstoneCraftScriptEvent.java index 00ec129dfd..f28063ba49 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerPreparesGrindstoneCraftScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerPreparesGrindstoneCraftScriptEvent.java @@ -4,9 +4,7 @@ import com.denizenscript.denizen.objects.*; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; import com.denizenscript.denizencore.objects.ObjectTag; -import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; -import com.denizenscript.denizencore.utilities.CoreUtilities; import com.destroystokyo.paper.event.inventory.PrepareResultEvent; import org.bukkit.entity.HumanEntity; import org.bukkit.event.EventHandler; @@ -48,6 +46,9 @@ public class PlayerPreparesGrindstoneCraftScriptEvent extends BukkitScriptEvent public PlayerPreparesGrindstoneCraftScriptEvent() { registerCouldMatcher("player prepares grindstone craft "); + this.registerDetermination("result", ItemTag.class, (evt, context, item) -> { + evt.event.setResult(item.getItemStack()); + }); } public PrepareResultEvent event; @@ -64,20 +65,6 @@ public boolean matches(ScriptPath path) { return super.matches(path); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag) { - String determination = determinationObj.toString(); - String lower = CoreUtilities.toLowerCase(determination); - if (lower.startsWith("result:")) { - ItemTag result = ItemTag.valueOf(determination.substring("result:".length()), path.container); - event.setResult(result.getItemStack()); - return true; - } - } - return super.applyDetermination(path, determinationObj); - } - @Override public ObjectTag getContext(String name) { return switch (name) { diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerQuitsScriptEventPaperImpl.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerQuitsScriptEventPaperImpl.java new file mode 100644 index 0000000000..a899af3781 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerQuitsScriptEventPaperImpl.java @@ -0,0 +1,38 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.player.PlayerQuitsScriptEvent; +import com.denizenscript.denizen.paper.PaperModule; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; +import net.md_5.bungee.api.ChatColor; + + +public class PlayerQuitsScriptEventPaperImpl extends PlayerQuitsScriptEvent { + + public PlayerQuitsScriptEventPaperImpl() { + registerSwitches("cause"); + this.registerTextDetermination("none", (evt) -> { + event.quitMessage(null); + }); + this.registerDetermination(null, ElementTag.class, (evt, context, determination) -> { + event.quitMessage(PaperModule.parseFormattedText(determination.toString(), ChatColor.WHITE)); + }); + } + + @Override + public boolean matches(ScriptPath path) { + if (!runGenericSwitchCheck(path, "cause", event.getReason().name())) { + return false; + } + return super.matches(path); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "message" -> new ElementTag(PaperModule.stringifyComponent(event.quitMessage())); + case "cause" -> new ElementTag(event.getReason()); + default -> super.getContext(name); + }; + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerReceivesLinksScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerReceivesLinksScriptEvent.java new file mode 100644 index 0000000000..90bd7d51e8 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerReceivesLinksScriptEvent.java @@ -0,0 +1,69 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.core.ListTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import io.papermc.paper.connection.PlayerConfigurationConnection; +import io.papermc.paper.connection.PlayerGameConnection; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerLinksSendEvent; + +public class PlayerReceivesLinksScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // player receives links + // + // @Group Paper + // + // @Plugin Paper + // + // @Triggers when a player receives a list of server links. + // + // @Determine + // "LINKS:" to set the links sent to the player. Each item in the list must be a MapTag in <@link language Server Links Format>. + // "ADD_LINKS:" to send additional links to the player. Each item in the list must be a MapTag in <@link language Server Links Format>. + // + // @Player Always. + // + // @Warning this may fire early in the player login process, during which the linked player is essentially an offline player. + // + // --> + + public PlayerLinksSendEvent event; + public PlayerTag player; + + public PlayerReceivesLinksScriptEvent() { + registerCouldMatcher("player receives links"); + this.registerDetermination("links", ListTag.class, (evt, context, value) -> { + Utilities.replaceServerLinks(evt.event.getLinks(), value, context); + }); + this.registerDetermination("add_links", ListTag.class, (evt, context, value) -> { + Utilities.fillServerLinks(evt.event.getLinks(), value, context); + }); + } + + @Override + public ScriptEntryData getScriptEntryData() { + return new BukkitScriptEntryData(player, null); + } + + @EventHandler + public void onPlayerLinksSend(PlayerLinksSendEvent event) { + if (event.getConnection() instanceof PlayerGameConnection gameConnection) { + player = new PlayerTag(gameConnection.getPlayer()); + } + else if (event.getConnection() instanceof PlayerConfigurationConnection configConnection) { + player = new PlayerTag(configConnection.getProfile().getId()); + } + else { + throw new IllegalStateException("Links send event fired with unknown connection type! " + event.getConnection() + " / " + event.getConnection().getClass().getName()); + } + this.event = event; + fire(event); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerSetSpawnScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerSetSpawnScriptEvent.java new file mode 100644 index 0000000000..7f68012369 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerSetSpawnScriptEvent.java @@ -0,0 +1,111 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.paper.PaperModule; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class PlayerSetSpawnScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // player sets spawn + // + // @Cancellable true + // + // @Location true + // + // @Plugin Paper + // + // @Group Paper + // + // @Triggers when a player's spawn point changes. + // + // @Switch cause: to only process when the cause for the event matches the input. + // + // @Context + // returns the reason the player's spawn point changed. A list of causes can be found at <@link url https://jd.papermc.io/paper/1.21.5/com/destroystokyo/paper/event/player/PlayerSetSpawnEvent.Cause.html>. + // returns whether this event will persist through source block (bed or respawn anchor) removal. + // returns a LocationTag of the new respawn location, if any. + // returns the notification message that is sent to the player. + // returns whether the player will be notified their spawn point changed. + // + // @Determine + // "FORCED:" to set whether the player's new spawnpoint will persist even if the bed or respawn anchor that triggered the event is removed. + // "MESSAGE:" to set the notification message that is sent to the player. + // "NOTIFY:" to set whether the player will be notified their spawnpoint changed. + // LocationTag to change the respawn location. + // + // @Player Always. + // + // --> + + public PlayerSetSpawnScriptEvent() { + registerCouldMatcher("player sets spawn"); + registerSwitches("cause"); + this.registerOptionalDetermination("forced", ElementTag.class, (evt, context, value) -> { + if (value.isBoolean()) { + evt.event.setForced(value.asBoolean()); + return true; + } + return false; + }); + this.registerDetermination("message", ElementTag.class, (evt, context, message) -> { + evt.event.setNotification(PaperModule.parseFormattedText(message.toString(), ChatColor.WHITE)); + }); + this.registerOptionalDetermination("notify", ElementTag.class, (evt, context, value) -> { + if (value.isBoolean()) { + evt.event.setNotifyPlayer(value.asBoolean()); + return true; + } + return false; + }); + this.registerDetermination(null, LocationTag.class, (evt, context, location) -> { + evt.event.setLocation(location); + evt.event.setForced(true); // required if the cause is a bed or respawn anchor + }); + } + + public PlayerSetSpawnEvent event; + + @Override + public boolean matches(ScriptPath path) { + if (!runInCheck(path, event.getLocation())) { + return false; + } + if (!runGenericSwitchCheck(path, "cause", event.getCause().toString())) { + return false; + } + return super.matches(path); + } + + @Override + public ScriptEntryData getScriptEntryData() { + return new BukkitScriptEntryData(event.getPlayer()); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "cause" -> new ElementTag(event.getCause()); + case "forced" -> new ElementTag(event.isForced()); + case "location" -> event.getLocation() != null ? new LocationTag(event.getLocation()) : null; + case "message" -> event.getNotification() != null ? new ElementTag(PaperModule.stringifyComponent(event.getNotification()), true) : null; + case "notify" -> new ElementTag(event.willNotifyPlayer()); + default -> super.getContext(name); + }; + } + + @EventHandler + public void onPlayerSetsSpawn(PlayerSetSpawnEvent event) { + this.event = event; + fire(event); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerShieldDisableScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerShieldDisableScriptEvent.java new file mode 100644 index 0000000000..89e1358e66 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerShieldDisableScriptEvent.java @@ -0,0 +1,79 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.DurationTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import io.papermc.paper.event.player.PlayerShieldDisableEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class PlayerShieldDisableScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // player shield disables + // + // @Plugin Paper + // + // @Group Paper + // + // @Location true + // + // @Cancellable true + // + // @Triggers When a players shield is disabled. + // + // @Context + // returns an EntityTag of the attacker who disabled the shield. + // returns a DurationTag of the cooldown the shield is disabled for. + // + // @Determine + // "COOLDOWN:" to change the cooldown. + // + // @Player Always. + // + // --> + + public PlayerShieldDisableScriptEvent() { + registerCouldMatcher("player shield disables"); + this.registerDetermination("cooldown", DurationTag.class, (evt, context, duration) -> { + evt.event.setCooldown(duration.getTicksAsInt()); + }); + } + + public PlayerShieldDisableEvent event; + public LocationTag location; + + @Override + public boolean matches(ScriptPath path) { + if (!runInCheck(path, location)) { + return false; + } + return super.matches(path); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "damager" -> new EntityTag(event.getDamager()).getDenizenObject(); + case "cooldown" -> new DurationTag((long) event.getCooldown()); + default -> super.getContext(name); + }; + } + + @Override + public ScriptEntryData getScriptEntryData() { + return new BukkitScriptEntryData(event.getPlayer()); + } + + @EventHandler + public void playerShieldDisableEvent(PlayerShieldDisableEvent event) { + location = new LocationTag(event.getPlayer().getLocation()); + this.event = event; + fire(event); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerTradesWithMerchantScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerTradesWithMerchantScriptEvent.java index 3dade8522d..f422eb9bc7 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerTradesWithMerchantScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerTradesWithMerchantScriptEvent.java @@ -45,6 +45,9 @@ public class PlayerTradesWithMerchantScriptEvent extends BukkitScriptEvent imple public PlayerTradesWithMerchantScriptEvent() { registerCouldMatcher("player trades with merchant"); registerSwitches("result"); + this.registerDetermination(null, TradeTag.class, (evt, context, trade) -> { + evt.event.setTrade(trade.getRecipe()); + }); } public PlayerPurchaseEvent event; @@ -60,15 +63,6 @@ public boolean matches(ScriptPath path) { return super.matches(path); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj.canBeType(TradeTag.class)) { - event.setTrade(determinationObj.asType(TradeTag.class, getTagContext(path)).getRecipe()); - return true; - } - return super.applyDetermination(path, determinationObj); - } - @Override public ScriptEntryData getScriptEntryData() { return new BukkitScriptEntryData(event.getPlayer()); @@ -76,13 +70,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("merchant") && event instanceof PlayerTradeEvent) { - return new EntityTag(((PlayerTradeEvent) event).getVillager()); - } - else if (name.equals("trade")) { - return new TradeTag(event.getTrade()).duplicate(); - } - return super.getContext(name); + return switch (name) { + case "merchant" -> event instanceof PlayerTradeEvent tradeEvent ? new EntityTag(tradeEvent.getVillager()) : null; + case "trade" -> new TradeTag(event.getTrade()).duplicate(); + default -> super.getContext(name); + }; } @EventHandler diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/ProjectileCollideScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/ProjectileCollideScriptEvent.java index 4474983ba4..2e32f403be 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/ProjectileCollideScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/ProjectileCollideScriptEvent.java @@ -26,7 +26,7 @@ public class ProjectileCollideScriptEvent extends BukkitScriptEvent implements L // // @Cancellable true // - // @Triggers when a projectile entity collides with an entity (before any damage calculations are done). + // @Triggers N/A - use <@link event projectile hits> with the 'entity' switch on versions above 1.19. // // @Context // returns the projectile that is colliding. @@ -35,7 +35,7 @@ public class ProjectileCollideScriptEvent extends BukkitScriptEvent implements L // @Player When the entity collided with is a player. // @NPC When the entity collided with is a NPC. // - // @deprecated Use <@link event projectile hits> with the 'entity' switch on versions above 1.19. + // @deprecated Use 'projectile hits' with the 'entity' switch on versions above 1.19. // // --> diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/ServerListPingScriptEventPaperImpl.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/ServerListPingScriptEventPaperImpl.java index 38c194a6da..6ea097a13d 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/ServerListPingScriptEventPaperImpl.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/ServerListPingScriptEventPaperImpl.java @@ -1,6 +1,9 @@ package com.denizenscript.denizen.paper.events; import com.denizenscript.denizen.events.server.ListPingScriptEvent; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; +import com.denizenscript.denizen.nms.abstracts.ProfileEditor; import com.denizenscript.denizen.objects.PlayerTag; import com.denizenscript.denizen.paper.PaperModule; import com.denizenscript.denizencore.objects.ObjectTag; @@ -26,41 +29,67 @@ public class ServerListPingScriptEventPaperImpl extends ListPingScriptEvent { public ServerListPingScriptEventPaperImpl() { this.registerOptionalDetermination("protocol_version", ElementTag.class, (evt, context, version) -> { if (version.isInt()) { - ((PaperServerListPingEvent) evt.event).setProtocolVersion(version.asInt()); + evt.getEvent().setProtocolVersion(version.asInt()); return true; } return false; }); this.registerDetermination("version_name", ElementTag.class, (evt, context, name) -> { - ((PaperServerListPingEvent) evt.event).setVersion(name.toString()); + evt.getEvent().setVersion(name.asString()); }); this.registerDetermination("exclude_players", ListTag.class, (evt, context, list) -> { HashSet exclusions = new HashSet<>(); for (PlayerTag player : list.filter(PlayerTag.class, context)) { exclusions.add(player.getUUID()); } - Iterator players = ((PaperServerListPingEvent) evt.event).iterator(); - while (players.hasNext()) { - if (exclusions.contains(players.next().getUniqueId())) { - players.remove(); + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) { + Iterator players = evt.getEvent().iterator(); + while (players.hasNext()) { + if (exclusions.contains(players.next().getUniqueId())) { + players.remove(); + } } + return; } + ListedPlayersEditor.excludeListedPlayers(evt.getEvent(), exclusions); }); this.registerOptionalDetermination("alternate_player_text", ListTag.class, (evt, context, text) -> { if (!CoreConfiguration.allowRestrictedActions) { Debug.echoError("Cannot use 'alternate_player_text' in list ping event: 'Allow restricted actions' is disabled in Denizen config.yml."); return false; } - ((PaperServerListPingEvent) evt.event).getPlayerSample().clear(); - for (String line : text) { - FakeProfile lineProf = new FakeProfile(); - lineProf.setName(line); - ((PaperServerListPingEvent) evt.event).getPlayerSample().add(lineProf); + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) { + evt.getEvent().getPlayerSample().clear(); + for (String line : text) { + FakeProfile lineProf = new FakeProfile(); + lineProf.setName(line); + evt.getEvent().getPlayerSample().add(lineProf); + } + return true; } + ListedPlayersEditor.setListedPlayerInfo(evt.getEvent(), text); return true; }); } + public PaperServerListPingEvent getEvent() { + return (PaperServerListPingEvent) event; + } + + // TODO: workaround for Java trying to load ListedPlayerInfo on old versions, remove once 1.20 is the minimum supported version + public static class ListedPlayersEditor { + public static void setListedPlayerInfo(PaperServerListPingEvent event, List lines) { + event.getListedPlayers().clear(); + for (String line : lines) { + event.getListedPlayers().add(new PaperServerListPingEvent.ListedPlayerInfo(line, ProfileEditor.NIL_UUID)); + } + } + + public static void excludeListedPlayers(PaperServerListPingEvent event, Set exclude) { + event.getListedPlayers().removeIf(listedPlayerInfo -> exclude.contains(listedPlayerInfo.id())); + } + } + public static class FakeProfile implements PlayerProfile { public String name; @Override public @Nullable String getName() { @@ -84,7 +113,7 @@ public static class FakeProfile implements PlayerProfile { @Override public void clearProperties() { } @Override public boolean isComplete() { return false; } @Override public @NotNull CompletableFuture update() { return null; } - @Override public org.bukkit.profile.@NotNull PlayerProfile clone() { return null; } + @Override public com.destroystokyo.paper.profile.@NotNull PlayerProfile clone() { return null; } @Override public boolean completeFromCache() { return false; } @Override public boolean completeFromCache(boolean b) { return false; } @Override public boolean completeFromCache(boolean b, boolean b1) { return false; } @@ -101,10 +130,10 @@ public void setMotd(String text) { @Override public ObjectTag getContext(String name) { return switch (name) { - case "motd" -> new ElementTag(PaperModule.stringifyComponent(event.motd())); - case "protocol_version" -> new ElementTag(((PaperServerListPingEvent) event).getProtocolVersion()); - case "version_name" -> new ElementTag(((PaperServerListPingEvent) event).getVersion()); - case "client_protocol_version" -> new ElementTag(((PaperServerListPingEvent) event).getClient().getProtocolVersion()); + case "motd" -> new ElementTag(PaperModule.stringifyComponent(event.motd()), true); + case "protocol_version" -> new ElementTag(getEvent().getProtocolVersion()); + case "version_name" -> new ElementTag(getEvent().getVersion(), true); + case "client_protocol_version" -> new ElementTag(getEvent().getClient().getProtocolVersion()); default -> super.getContext(name); }; } diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/TargetBlockHitScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/TargetBlockHitScriptEvent.java new file mode 100644 index 0000000000..332ac98dbf --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/TargetBlockHitScriptEvent.java @@ -0,0 +1,83 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import io.papermc.paper.event.block.TargetHitEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + + +public class TargetBlockHitScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // target block hit + // + // @Location true + // + // @Group Paper + // + // @Plugin Paper + // + // @Triggers when a target block is hit by a projectile such as an arrow. + // + // @Context + // returns an EntityTag of the projectile. + // returns a LocationTag of the block that was hit. + // returns a LocationTag vector of the hit normal (like '0,1,0' if the projectile hit the top of a block). + // returns an EntityTag of the entity that shot the projectile, if any. + // returns a ElementTag of the emitted redstone strength. + // + // @Player when the shooter is a player. + // + // @NPC when the shooter is a npc. + // --> + + public TargetBlockHitScriptEvent() { + registerCouldMatcher("target block hit"); + } + + public TargetHitEvent event; + public LocationTag hitBlock; + public EntityTag projectile; + public EntityTag shooter; + + @Override + public boolean matches(ScriptPath path) { + if (!runInCheck(path, hitBlock)) { + return false; + } + return super.matches(path); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "projectile" -> projectile.getDenizenObject(); + case "hit_block" -> hitBlock; + case "hit_face" -> event.getHitBlockFace() != null ? new LocationTag(event.getHitBlockFace().getDirection()) : null; + case "shooter" -> shooter != null ? shooter.getDenizenObject() : null; + case "strength" -> new ElementTag(event.getSignalStrength()); + default -> super.getContext(name); + }; + } + + @Override + public ScriptEntryData getScriptEntryData() { + return new BukkitScriptEntryData(shooter); + } + + @EventHandler + public void onProjectileHit(TargetHitEvent event) { + this.event = event; + projectile = new EntityTag(event.getEntity()); + hitBlock = new LocationTag(event.getHitBlock().getLocation()); + shooter = projectile.getShooter(); + fire(event); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/UnknownCommandScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/UnknownCommandScriptEvent.java index e6ed7000f9..c800cf62e0 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/UnknownCommandScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/UnknownCommandScriptEvent.java @@ -4,13 +4,14 @@ import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.paper.PaperModule; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; import com.denizenscript.denizencore.objects.ArgumentHelper; import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; -import com.denizenscript.denizencore.utilities.CoreUtilities; +import net.md_5.bungee.api.ChatColor; import org.bukkit.command.BlockCommandSender; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.CommandMinecart; @@ -53,6 +54,12 @@ public class UnknownCommandScriptEvent extends BukkitScriptEvent implements List public UnknownCommandScriptEvent() { registerCouldMatcher("command unknown"); + this.registerDetermination(null, ElementTag.class, (evt, context, text) -> { + evt.event.message(PaperModule.parseFormattedText(text.toString(), ChatColor.WHITE)); + }); + this.registerTextDetermination("none", (evt) -> { + evt.event.message(null); + }); } public UnknownCommandEvent event; @@ -60,53 +67,24 @@ public UnknownCommandScriptEvent() { public String rawArgs; public String sourceType; - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag) { - String determination = determinationObj.toString(); - if (CoreUtilities.equalsIgnoreCase(determination, "none")) { - event.setMessage(null); - } - else { - event.setMessage(determination); - } - return true; - } - return super.applyDetermination(path, determinationObj); - } - @Override public ScriptEntryData getScriptEntryData() { - return new BukkitScriptEntryData(event.getSender() instanceof Player ? new PlayerTag((Player) event.getSender()) : null, null); + return new BukkitScriptEntryData(event.getSender() instanceof Player player ? new PlayerTag(player) : null, null); } @Override public ObjectTag getContext(String name) { - if (name.equals("command")) { - return new ElementTag(command); - } - else if (name.equals("raw_args")) { - return new ElementTag(rawArgs); - } - else if (name.equals("args")) { - return new ListTag(Arrays.asList(ArgumentHelper.buildArgs(rawArgs, false))); - } - else if (name.equals("server")) { - return new ElementTag(sourceType.equals("server")); - } - else if (name.equals("source_type")) { - return new ElementTag(sourceType); - } - else if (name.equals("command_block_location") && sourceType.equals("command_block")) { - return new LocationTag(((BlockCommandSender) event.getSender()).getBlock().getLocation()); - } - else if (name.equals("command_minecart") && sourceType.equals("command_minecart")) { - return new EntityTag((CommandMinecart) event.getSender()); - } - else if (name.equals("message")) { - return new ElementTag(event.getMessage()); - } - return super.getContext(name); + return switch (name) { + case "command" -> new ElementTag(command, true); + case "raw_args" -> new ElementTag(rawArgs, true); + case "args" -> new ListTag(Arrays.asList(ArgumentHelper.buildArgs(rawArgs, false)), true); + case "server" -> new ElementTag(sourceType.equals("server")); + case "source_type" -> new ElementTag(sourceType, true); + case "command_block_location" -> sourceType.equals("command_block") ? new LocationTag(((BlockCommandSender) event.getSender()).getBlock().getLocation()) : null; + case "command_minecart" -> sourceType.equals("command_minecart") ? new EntityTag((CommandMinecart) event.getSender()) : null; + case "message" -> new ElementTag(PaperModule.stringifyComponent(event.message()), true); + default -> super.getContext(name); + }; } @EventHandler diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/WorldGameRuleChangeScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/WorldGameRuleChangeScriptEvent.java index 74f9555a86..64fc3dbe05 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/WorldGameRuleChangeScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/WorldGameRuleChangeScriptEvent.java @@ -7,10 +7,10 @@ import com.denizenscript.denizen.objects.PlayerTag; import com.denizenscript.denizen.objects.WorldTag; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizen.utilities.world.GameRuleReflect; import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; -import com.denizenscript.denizencore.utilities.CoreUtilities; import io.papermc.paper.event.world.WorldGameRuleChangeEvent; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; @@ -19,7 +19,6 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; - public class WorldGameRuleChangeScriptEvent extends BukkitScriptEvent implements Listener { // <--[event] @@ -52,6 +51,13 @@ public class WorldGameRuleChangeScriptEvent extends BukkitScriptEvent implements public WorldGameRuleChangeScriptEvent() { registerCouldMatcher("gamerule changes (in )"); registerSwitches("gamerule"); + this.registerOptionalDetermination("value", ElementTag.class, (evt, context, value) -> { + if (value.isBoolean() || value.isInt()) { + evt.event.setValue(value.toString()); + return true; + } + return false; + }); } public WorldGameRuleChangeEvent event; @@ -64,7 +70,7 @@ public boolean matches(ScriptPath path) { if (path.eventArgLowerAt(2).equals("in") && !path.tryArgObject(3, world)) { return false; } - if (!runGenericSwitchCheck(path, "gamerule", event.getGameRule().getName())) { + if (!runGenericSwitchCheck(path, "gamerule", GameRuleReflect.getName(event.getGameRule()))) { return false; } return super.matches(path); @@ -72,30 +78,15 @@ public boolean matches(ScriptPath path) { @Override public ObjectTag getContext(String name) { - switch (name) { - case "gamerule": return new ElementTag(event.getGameRule().getName()); - case "value": return new ElementTag(event.getValue()); - case "source_type": return getSourceType(); - case "command_block_location": return getCommandBlock(); - case "command_minecart": return getCommandMinecart(); - case "world": return world; - } - return super.getContext(name); - } - - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag) { - String lower = CoreUtilities.toLowerCase(determinationObj.toString()); - if (lower.startsWith("value:")) { - ElementTag value = new ElementTag(lower.substring("value:".length())); - if (value.isInt() || value.isBoolean()) { - event.setValue(value.toString()); - return true; - } - } - } - return super.applyDetermination(path, determinationObj); + return switch (name) { + case "gamerule" -> new ElementTag(GameRuleReflect.getName(event.getGameRule()), true); + case "value" -> new ElementTag(event.getValue(), true); + case "source_type" -> getSourceType(); + case "command_block_location" -> getCommandBlock(); + case "command_minecart" -> getCommandMinecart(); + case "world" -> world; + default -> super.getContext(name); + }; } @Override diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityBodyStingers.java b/paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityBodyStingers.java new file mode 100644 index 0000000000..209b08520f --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityBodyStingers.java @@ -0,0 +1,49 @@ +package com.denizenscript.denizen.paper.properties; + +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.properties.entity.EntityProperty; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; + +public class EntityBodyStingers extends EntityProperty { + + // <--[property] + // @object EntityTag + // @name body_stingers + // @input ElementTag(Number) + // @plugin Paper + // @description + // Controls the number of bee stingers stuck in an entity's body. + // Note: Bee stingers will only be visible for players or player-type npcs. + // --> + + public static boolean describes(EntityTag entity) { + return entity.isLivingEntity(); + } + + @Override + public boolean isDefaultValue(ElementTag value) { + return value.asInt() == 0; + } + + @Override + public ElementTag getPropertyValue() { + return new ElementTag(getLivingEntity().getBeeStingersInBody()); + } + + @Override + public String getPropertyId() { + return "body_stingers"; + } + + @Override + public void setPropertyValue(ElementTag param, Mechanism mechanism) { + if (mechanism.requireInteger()) { + getLivingEntity().setBeeStingersInBody(param.asInt()); + } + } + + public static void register() { + autoRegister("body_stingers", EntityBodyStingers.class, ElementTag.class, false); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityShouldBurn.java b/paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityShouldBurn.java index 93a216dcc8..32af74348e 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityShouldBurn.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityShouldBurn.java @@ -1,12 +1,12 @@ package com.denizenscript.denizen.paper.properties; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.objects.properties.entity.EntityProperty; import com.denizenscript.denizencore.objects.Mechanism; import com.denizenscript.denizencore.objects.core.ElementTag; -import org.bukkit.entity.Phantom; -import org.bukkit.entity.Skeleton; -import org.bukkit.entity.Zombie; +import org.bukkit.entity.*; public class EntityShouldBurn extends EntityProperty { @@ -16,13 +16,15 @@ public class EntityShouldBurn extends EntityProperty { // @input ElementTag(Boolean) // @plugin Paper // @description - // If the entity is a Zombie, Skeleton, or Phantom, controls whether it should burn in daylight. + // If the entity is a Zombie, Skeleton, Stray, Bogged, or Phantom, controls whether it should burn in daylight. // --> public static boolean describes(EntityTag entity) { return entity.getBukkitEntity() instanceof Zombie + || entity.getBukkitEntity() instanceof Phantom || entity.getBukkitEntity() instanceof Skeleton - || entity.getBukkitEntity() instanceof Phantom; + || entity.getBukkitEntity() instanceof Stray + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && entity.getBukkitEntity() instanceof Bogged); } @Override @@ -30,11 +32,17 @@ public ElementTag getPropertyValue() { if (getEntity() instanceof Zombie zombie) { return new ElementTag(zombie.shouldBurnInDay()); } + else if (getEntity() instanceof Phantom phantom) { + return new ElementTag(phantom.shouldBurnInDay()); + } else if (getEntity() instanceof Skeleton skeleton) { return new ElementTag(skeleton.shouldBurnInDay()); } - else { // phantom - return new ElementTag(as(Phantom.class).shouldBurnInDay()); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getEntity() instanceof Bogged bogged) { + return new ElementTag(bogged.shouldBurnInDay()); + } + else { // stray + return new ElementTag(as(Stray.class).shouldBurnInDay()); } } @@ -49,11 +57,17 @@ public void setPropertyValue(ElementTag param, Mechanism mechanism) { if (getEntity() instanceof Zombie zombie) { zombie.setShouldBurnInDay(param.asBoolean()); } + else if (getEntity() instanceof Phantom phantom) { + phantom.setShouldBurnInDay(param.asBoolean()); + } else if (getEntity() instanceof Skeleton skeleton) { skeleton.setShouldBurnInDay(param.asBoolean()); } - else { // phantom - as(Phantom.class).setShouldBurnInDay(param.asBoolean()); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getEntity() instanceof Bogged bogged) { + bogged.setShouldBurnInDay(param.asBoolean()); + } + else { // stray + as(Stray.class).setShouldBurnInDay(param.asBoolean()); } } } diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/properties/ItemRemovedComponents.java b/paper/src/main/java/com/denizenscript/denizen/paper/properties/ItemRemovedComponents.java new file mode 100644 index 0000000000..cd272bdda8 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/properties/ItemRemovedComponents.java @@ -0,0 +1,86 @@ +package com.denizenscript.denizen.paper.properties; + +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.properties.item.ItemProperty; +import com.denizenscript.denizen.paper.datacomponents.DataComponentAdapter; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.objects.core.ListTag; +import com.denizenscript.denizencore.objects.properties.PropertyParser; +import io.papermc.paper.datacomponent.DataComponentType; + +public class ItemRemovedComponents extends ItemProperty { + + // <--[property] + // @object ItemTag + // @name removed_components + // @input ListTag + // @description + // Controls the item components explicitly removed from an item. + // This can be used to remove item's default behavior, such as making consumable items non-consumable. + // Alternatively, use <@link mechanism ItemTag.remove_component> to remove a single component. + // See <@link language Item Components> for more information. + // --> + + public static boolean describes(ItemTag item) { + return !item.getItemStack().isEmpty(); + } + + public boolean isRemoved(DataComponentType componentType) { + return getItemStack().isDataOverridden(componentType) && !getItemStack().hasData(componentType); + } + + @Override + public ListTag getPropertyValue() { + return new ListTag(getMaterial().getDefaultDataTypes(), this::isRemoved, componentType -> new ElementTag(componentType.key().asMinimalString(), true)); + } + + @Override + public boolean isDefaultValue(ListTag value) { + return value.isEmpty(); + } + + @Override + public void setPropertyValue(ListTag value, Mechanism mechanism) { + for (DataComponentType componentType : getMaterial().getDefaultDataTypes()) { + if (isRemoved(componentType)) { + getItemStack().resetData(componentType); + } + } + for (String input : value) { + DataComponentType componentType = DataComponentAdapter.getComponentType(input); + if (componentType == null) { + mechanism.echoError("Invalid type to remove '" + input + "' specified: must be a valid property or item component name."); + continue; + } + getItemStack().unsetData(componentType); + } + } + + @Override + public String getPropertyId() { + return "removed_components"; + } + + public static void register() { + autoRegister("removed_components", ItemRemovedComponents.class, ListTag.class, false); + + // <--[mechanism] + // @object ItemTag + // @name remove_component + // @input ElementTag + // @description + // Removes the specified item component from the item, see <@link language Item Components> for more information. + // This can be used to remove item's default behavior, such as making consumable items non-consumable. + // See also <@link property ItemTag.removed_components>. + // --> + PropertyParser.registerMechanism(ItemRemovedComponents.class, ElementTag.class, "remove_component", (prop, mechanism, input) -> { + DataComponentType componentType = DataComponentAdapter.getComponentType(input.asString()); + if (componentType == null) { + mechanism.echoError("Invalid type to remove specified: must be a valid property or item component name."); + return; + } + prop.getItemStack().unsetData(componentType); + }); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperEntityExtensions.java b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperEntityExtensions.java index 1fb2c8ddaa..94bcb7f641 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperEntityExtensions.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperEntityExtensions.java @@ -7,6 +7,8 @@ import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.MapTag; +import io.papermc.paper.entity.Shearable; +import net.kyori.adventure.sound.Sound; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.ExperienceOrb; @@ -184,6 +186,47 @@ public static void register() { } object.getLivingEntity().damageItemStack(slot.asEnum(EquipmentSlot.class), amount.asInt()); }); + + // <--[mechanism] + // @object EntityTag + // @name shear + // @input None + // @Plugin paper + // @group paper + // @description + // Shears entities in the same way as a player can do using shears, including drops. + // If the entity is not ready to be sheared, there will be no drops but the sound will still play. + // + // This mech will: + // - Shear a sheep + // - harvest a bogged + // - harvest a mushroom cow (note: entity data will be lost as Minecraft will remove the entity and spawn an entirely new cow instead) + // - derp a snowman (i.e. remove the pumpkin) + // + // Optionally, specify a sound source to change the source of the sound. + // Valid sound sources can be found here: <@link url https://jd.advntr.dev/api/latest/net/kyori/adventure/sound/Sound.Source.html>. + // + // @example + // # Shears the entity you're looking at. + // - adjust shear + // + // --> + EntityTag.registerSpawnedOnlyMechanism("shear", false, (object, mechanism) -> { + if (!(object.getBukkitEntity() instanceof Shearable shearable)) { + return; + } + if (!mechanism.hasValue()) { + shearable.shear(); + return; + } + ElementTag input = mechanism.getValue(); + if (!mechanism.requireEnum(Sound.Source.class)) { + mechanism.echoError("Invalid sound source specified: " + input); + return; + } + Sound.Source source = input.asEnum(Sound.Source.class); + shearable.shear(source); + }); } } } diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperPlayerExtensions.java b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperPlayerExtensions.java index fba2df72d6..d0d3806724 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperPlayerExtensions.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperPlayerExtensions.java @@ -6,8 +6,12 @@ import com.denizenscript.denizen.objects.PlayerTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; +import com.denizenscript.denizencore.objects.core.MapTag; +import com.destroystokyo.paper.ClientOption; +import com.destroystokyo.paper.SkinParts; import net.kyori.adventure.util.TriState; import org.bukkit.Material; +import org.bukkit.entity.Player; public class PaperPlayerExtensions { @@ -26,6 +30,68 @@ public static void register() { return new ElementTag(object.getPlayerEntity().getAffectsSpawning()); }); + // <--[tag] + // @attribute + // @returns MapTag + // @group paper + // @Plugin Paper + // @description + // Returns the player's client options. + // The output map contains the following keys: + // - 'allow_server_listings' (ElementTag(Boolean)): whether the player allows server listings. Available only on MC 1.19+. + // - 'chat_colors_enabled' (ElementTag(Boolean)): whether the player has chat colors enabled. + // - 'chat_visibility' (ElementTag): the player's current chat visibility option. Possible output values are: FULL, SYSTEM, HIDDEN, and UNKNOWN. + // - 'locale' (ElementTag): the player's current locale. + // - 'main_hand' (ElementTag): the player's main hand, either LEFT or RIGHT. + // - 'skin_parts' (MapTag): which skin parts the player has enabled. The output map contains the following keys: + // - 'cape' (ElementTag(Boolean)): whether the player's cape is enabled. + // - 'hat' (ElementTag(Boolean)): whether the player's hat is enabled. + // - 'jacket' (ElementTag(Boolean)): whether the player's jacket is enabled. + // - 'left_sleeve' (ElementTag(Boolean)): whether the player's left sleeve is enabled. + // - 'right_sleeve' (ElementTag(Boolean)): whether the player's right sleeve is enabled. + // - 'left_pants' (ElementTag(Boolean)): whether the player's left pants is enabled. + // - 'right_pants' (ElementTag(Boolean)): whether the player's right pants is enabled. + // - 'text_filtering_enabled' (ElementTag(Boolean)): whether the player has text filtering enabled. Available only on MC 1.19+. + // - 'view_distance' (ElementTag(Number)): the player's current view distance. + // --> + PlayerTag.registerOnlineOnlyTag(MapTag.class, "client_options", (attribute, object) -> { + MapTag map = new MapTag(); + Player player = object.getPlayerEntity(); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { + map.putObject("allow_server_listings", new ElementTag(player.getClientOption(ClientOption.ALLOW_SERVER_LISTINGS))); + map.putObject("text_filtering_enabled", new ElementTag(player.getClientOption(ClientOption.TEXT_FILTERING_ENABLED))); + } + map.putObject("chat_colors_enabled", new ElementTag(player.getClientOption(ClientOption.CHAT_COLORS_ENABLED))); + map.putObject("chat_visibility", new ElementTag(player.getClientOption(ClientOption.CHAT_VISIBILITY))); + map.putObject("locale", new ElementTag(player.getClientOption(ClientOption.LOCALE))); + map.putObject("main_hand", new ElementTag(player.getClientOption(ClientOption.MAIN_HAND))); + MapTag skinParts = new MapTag(); + SkinParts parts = player.getClientOption(ClientOption.SKIN_PARTS); + skinParts.putObject("cape", new ElementTag(parts.hasCapeEnabled())); + skinParts.putObject("hat", new ElementTag(parts.hasHatsEnabled())); + skinParts.putObject("jacket", new ElementTag(parts.hasJacketEnabled())); + skinParts.putObject("left_sleeve", new ElementTag(parts.hasLeftSleeveEnabled())); + skinParts.putObject("right_sleeve", new ElementTag(parts.hasRightSleeveEnabled())); + skinParts.putObject("left_pants", new ElementTag(parts.hasLeftPantsEnabled())); + skinParts.putObject("right_pants", new ElementTag(parts.hasRightPantsEnabled())); + map.putObject("skin_parts", skinParts); + map.putObject("view_distance", new ElementTag(player.getClientOption(ClientOption.VIEW_DISTANCE))); + return map; + }); + + // <--[tag] + // @attribute + // @returns ElementTag(Number) + // @mechanism PlayerTag.view_distance + // @group paper + // @Plugin Paper + // @description + // Returns this player's view distance, or the view distance of the world they're in if unset. + // --> + PlayerTag.registerOnlineOnlyTag(ElementTag.class, "view_distance", (attribute, object) -> { + return new ElementTag(object.getPlayerEntity().getViewDistance()); + }); + // <--[mechanism] // @object PlayerTag // @name affects_monster_spawning @@ -83,6 +149,32 @@ public static void register() { } }); + // <--[mechanism] + // @object PlayerTag + // @name view_distance + // @input ElementTag(Number) + // @Plugin Paper + // @group paper + // @description + // Sets this player's view distance. Input must be a number between 2 and 32. + // This will be reset when a player rejoins. Provide no input to unset. + // @tags + // + // --> + PlayerTag.registerOnlineOnlyMechanism("view_distance", (object, mechanism) -> { + if (!mechanism.hasValue()) { + object.getPlayerEntity().setViewDistance(-1); + } + else if (mechanism.requireInteger()) { + int distance = mechanism.getValue().asInt(); + if (distance < 2 || distance > 32) { + mechanism.echoError("Invalid view distance '" + distance + "': must be between 2 and 32."); + return; + } + object.getPlayerEntity().setViewDistance(distance); + } + }); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { // <--[tag] diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperWorldExtensions.java b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperWorldExtensions.java index 2299f5864e..74c335a853 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperWorldExtensions.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperWorldExtensions.java @@ -1,8 +1,14 @@ package com.denizenscript.denizen.paper.properties; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizen.objects.WorldTag; import com.denizenscript.denizen.utilities.BukkitImplDeprecations; import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.objects.core.ListTag; +import org.bukkit.boss.DragonBattle; public class PaperWorldExtensions { @@ -23,6 +29,88 @@ public static void register() { return new ElementTag(world.getWorld().getNoTickViewDistance()); }); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { + + // <--[tag] + // @attribute + // @returns ElementTag(Number) + // @group paper + // @Plugin Paper + // @description + // Returns the number of end gateway portals. + // Only works if the world is an end world. + // --> + WorldTag.tagProcessor.registerTag(ElementTag.class, "gateway_count", (attribute, object) -> { + DragonBattle battle = object.getWorld().getEnderDragonBattle(); + if (battle == null) { + attribute.echoError("Provided world is not an end world!"); + return null; + } + return new ElementTag(battle.getGatewayCount()); + }); + + // <--[tag] + // @attribute + // @returns ListTag(EntityTag) + // @group paper + // @Plugin Paper + // @description + // Returns a ListTag of the healing end crystals located on top of the obsidian towers. + // Only works if the world is an end world. + // --> + WorldTag.tagProcessor.registerTag(ListTag.class, "healing_crystals", (attribute, object) -> { + DragonBattle battle = object.getWorld().getEnderDragonBattle(); + if (battle == null) { + attribute.echoError("Provided world is not an end world!"); + return null; + } + return new ListTag(battle.getHealingCrystals(), EntityTag::new); + }); + + // <--[tag] + // @attribute + // @returns ListTag(EntityTag) + // @group paper + // @Plugin Paper + // @description + // Returns a ListTag of the respawn end crystals located at the end exit portal. + // Only works if the world is an end world. + // --> + WorldTag.tagProcessor.registerTag(ListTag.class, "respawn_crystals", (attribute, object) -> { + DragonBattle battle = object.getWorld().getEnderDragonBattle(); + if (battle == null) { + attribute.echoError("Provided world is not an end world!"); + return null; + } + return new ListTag(battle.getRespawnCrystals(), EntityTag::new); + }); + + // <--[mechanism] + // @object WorldTag + // @name spawn_gateway + // @input LocationTag + // @group paper + // @Plugin Paper + // @description + // Spawns a new end gateway portal at the specified location. + // If no location is specified, tries to spawn a new end gateway using default game mechanics. + // Only works if the world is an end world. + // --> + WorldTag.tagProcessor.registerMechanism("spawn_gateway", false, (object, mechanism) -> { + DragonBattle battle = object.getWorld().getEnderDragonBattle(); + if (battle == null) { + mechanism.echoError("Cannot spawn gateway in non-end world!"); + return; + } + if (!mechanism.hasValue()) { + battle.spawnNewGateway(); + } + else if (mechanism.requireObject(LocationTag.class)) { + battle.spawnNewGateway(mechanism.valueAsType(LocationTag.class)); + } + }); + } + // <--[mechanism] // @object WorldTag // @name view_distance diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/tags/PaperTagBase.java b/paper/src/main/java/com/denizenscript/denizen/paper/tags/PaperTagBase.java index d5f4aef3f9..df7f418def 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/tags/PaperTagBase.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/tags/PaperTagBase.java @@ -2,29 +2,20 @@ import com.denizenscript.denizencore.objects.core.DurationTag; import com.denizenscript.denizencore.objects.core.ListTag; -import com.denizenscript.denizencore.tags.TagRunnable; -import com.denizenscript.denizencore.tags.Attribute; -import com.denizenscript.denizencore.tags.ReplaceableTagEvent; -import com.denizenscript.denizencore.tags.TagManager; +import com.denizenscript.denizencore.tags.*; import org.bukkit.Bukkit; -public class PaperTagBase { +public class PaperTagBase extends PseudoObjectTagBase { + + public static PaperTagBase instance; public PaperTagBase() { - TagManager.registerTagHandler(new TagRunnable.RootForm() { - @Override - public void run(ReplaceableTagEvent event) { - paperTag(event); - } - }, "paper"); + instance = this; + TagManager.registerStaticTagBaseHandler(PaperTagBase.class, "paper", (t) -> instance); } - public void paperTag(ReplaceableTagEvent event) { - if (!event.matches("paper") || event.replaced()) { - return; - } - - Attribute attribute = event.getAttributes().fulfill(1); + @Override + public void register() { // <--[tag] // @attribute @@ -34,13 +25,12 @@ public void paperTag(ReplaceableTagEvent event) { // Returns a sample of the server's last 5s of tick times as a list of durations. // On average, a tick should take 50ms or less for a stable 20tps. // --> - if (attribute.startsWith("tick_times")) { + tagProcessor.registerTag(ListTag.class, "tick_times", (attribute, object) -> { ListTag list = new ListTag(); for (long time : Bukkit.getServer().getTickTimes()) { list.addObject(new DurationTag(time / 1000000000D)); } - event.setReplacedObject(list.getObjectAttribute(attribute.fulfill(1))); - return; - } + return list; + }); } } diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/utilities/BlockTagsSetter.java b/paper/src/main/java/com/denizenscript/denizen/paper/utilities/BlockTagsSetter.java new file mode 100644 index 0000000000..0215cd070e --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/utilities/BlockTagsSetter.java @@ -0,0 +1,94 @@ +package com.denizenscript.denizen.paper.utilities; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizen.utilities.VanillaTagHelper; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.destroystokyo.paper.event.server.ServerTickEndEvent; +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.registry.tag.TagKey; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.BlockType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import java.lang.invoke.MethodHandle; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + +public class BlockTagsSetter implements Listener { + + public static final MethodHandle BOOTSTRAP_CONTEXT_CONSTRUCTOR; + + static { + try { + Class bootstrapContextImplClass = Class.forName("io.papermc.paper.plugin.bootstrap.PluginBootstrapContextImpl"); + BOOTSTRAP_CONTEXT_CONSTRUCTOR = ReflectionHelper.getConstructor(bootstrapContextImplClass, PluginMeta.class, Path.class, ComponentLogger.class, Path.class); + } + catch (Throwable e) { + throw new RuntimeException("Failed to initialize BlockTagsSetter", e); + } + } + + public static final BlockTagsSetter INSTANCE = new BlockTagsSetter(Denizen.getInstance()); + + public Map, Set>> modifiedTags = new HashMap<>(); + public boolean batchReloadNeeded; + + public BlockTagsSetter(Denizen plugin) { + Bukkit.getPluginManager().registerEvents(this, plugin); + try { + BootstrapContext fakeContext = (BootstrapContext) BOOTSTRAP_CONTEXT_CONSTRUCTOR.invoke(plugin.getPluginMeta(), plugin.getDataPath(), plugin.getComponentLogger(), plugin.getFile().toPath()); + fakeContext.getLifecycleManager().registerEventHandler(LifecycleEvents.TAGS.postFlatten(RegistryKey.BLOCK), event -> { + Map, Collection>> allTags = event.registrar().getAllTags(); + for (Map.Entry, Set>> entry : modifiedTags.entrySet()) { + TypedKey blockType = entry.getKey(); + Set> tags = entry.getValue(); + for (Map.Entry, Collection>> tagEntry : allTags.entrySet()) { + TagKey tagKey = tagEntry.getKey(); + Collection> values = tagEntry.getValue(); + if (values.contains(blockType) && !tags.contains(tagKey)) { + List> modifiedValues = new ArrayList<>(values); + modifiedValues.remove(blockType); + event.registrar().setTag(tagKey, modifiedValues); + } + } + for (TagKey tag : tags) { + event.registrar().addToTag(tag, List.of(blockType)); + } + } + }); + } + catch (Throwable e) { + Debug.echoError(e); + } + } + + @EventHandler + public void onServerTickEnd(ServerTickEndEvent event) { + if (batchReloadNeeded) { + batchReloadNeeded = false; + Bukkit.reloadData(); + } + } + + public void setTags(Material material, Set tags) { + TypedKey blockKey = TypedKey.create(RegistryKey.BLOCK, material.getKey()); + Set> tagKeys = tags.stream().map(tag -> TagKey.create(RegistryKey.BLOCK, tag)).collect(Collectors.toCollection(HashSet::new)); + Set> oldTagKeys = modifiedTags.put(blockKey, tagKeys); + if (tagKeys.equals(oldTagKeys)) { + return; + } + VanillaTagHelper.tagsByMaterial.put(material, tags.stream().map(Utilities::namespacedKeyToString).collect(Collectors.toCollection(HashSet::new))); + batchReloadNeeded = true; + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/utilities/PaperAPIToolsImpl.java b/paper/src/main/java/com/denizenscript/denizen/paper/utilities/PaperAPIToolsImpl.java index 76bc8a34c1..0b6d741ced 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/utilities/PaperAPIToolsImpl.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/utilities/PaperAPIToolsImpl.java @@ -11,6 +11,9 @@ import com.denizenscript.denizen.utilities.FormattedTextHelper; import com.denizenscript.denizen.utilities.PaperAPITools; import com.denizenscript.denizencore.DenizenCore; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.utilities.CoreUtilities; import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; @@ -18,24 +21,26 @@ import com.destroystokyo.paper.profile.ProfileProperty; import io.papermc.paper.entity.TeleportFlag; import io.papermc.paper.potion.PotionMix; +import io.papermc.paper.world.WeatheringCopperState; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.*; import org.bukkit.block.Sign; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.entity.TextDisplay; +import org.bukkit.entity.*; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionBrewer; import org.bukkit.scoreboard.Team; import org.bukkit.util.Consumer; +import java.net.URI; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -79,6 +84,12 @@ public String getCustomName(Entity entity) { return PaperModule.stringifyComponent(entity.customName()); } + @Override + public BaseComponent[] getCustomNameComponent(Entity entity) { + Component customName = entity.customName(); + return customName != null ? FormattedTextHelper.parseJson(PaperModule.componentToJson(customName)) : null; + } + @Override public void setPlayerListName(Player player, String name) { player.playerListName(PaperModule.parseFormattedText(name, ChatColor.WHITE)); @@ -155,8 +166,9 @@ public void teleport(Entity entity, Location loc, PlayerTeleportEvent.TeleportCa } } if (relativeTeleportFlags != null) { + // TODO: MC 1.21.3: Paper updated this API to work differently due to underlying Minecraft changes. for (TeleportCommand.Relative relativeTeleportFlag : relativeTeleportFlags) { - teleportFlags.add(TeleportFlag.Relative.values()[relativeTeleportFlag.ordinal()]); + teleportFlags.add(new ElementTag(relativeTeleportFlag.name()).asEnum(TeleportFlag.Relative.class)); } } entity.teleport(loc, cause, teleportFlags.toArray(new TeleportFlag[0])); @@ -170,11 +182,12 @@ public void registerBrewingRecipe(String keyName, ItemStack result, String input if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) { throw new UnsupportedOperationException(); } - RecipeChoice inputChoice = parseBrewingRecipeChoice(itemScriptContainer, input); + TagContext context = DenizenCore.implementation.getTagContext(itemScriptContainer); + RecipeChoice inputChoice = parseBrewingRecipeChoice(itemScriptContainer, input, context); if (inputChoice == null) { return; } - RecipeChoice ingredientChoice = parseBrewingRecipeChoice(itemScriptContainer, ingredient); + RecipeChoice ingredientChoice = parseBrewingRecipeChoice(itemScriptContainer, ingredient, context); if (ingredientChoice == null) { return; } @@ -195,10 +208,10 @@ public void clearBrewingRecipes() { } } - public static RecipeChoice parseBrewingRecipeChoice(ItemScriptContainer container, String choice) { + public static RecipeChoice parseBrewingRecipeChoice(ItemScriptContainer container, String choice, TagContext context) { if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && choice.startsWith("matcher:")) { String matcher = choice.substring("matcher:".length()); - return PotionMix.createPredicateChoice(item -> new ItemTag(item).tryAdvancedMatcher(matcher)); + return PotionMix.createPredicateChoice(item -> new ItemTag(item).tryAdvancedMatcher(matcher, context)); } boolean exact = true; if (choice.startsWith("material:")) { @@ -377,4 +390,45 @@ public String getClientBrand(Player player) { String clientBrand = player.getClientBrandName(); return clientBrand != null ? clientBrand : "unknown"; } + + @Override + public boolean canUseEquipmentSlot(LivingEntity entity, EquipmentSlot slot) { + return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) ? entity.canUseEquipmentSlot(slot) : super.canUseEquipmentSlot(entity, slot); + } + + @Override + public boolean hasCustomName(PotionMeta meta) { + return meta.hasCustomPotionName(); + } + + @Override + public void setMaterialTags(Material type, Set tags) { + if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + super.setMaterialTags(type, tags); + return; + } + BlockTagsSetter.INSTANCE.setTags(type, tags); + } + + @Override + public void addLink(ServerLinks links, String display, URI uri) { + links.addLink(PaperModule.parseFormattedText(display, ChatColor.WHITE), uri); + } + + @Override + public double[] getRecentTps() { + return Bukkit.getTPS(); + } + + @Override + public String getCopperGolemState(CopperGolem copperGolem) { + return copperGolem.getWeatheringState().name(); + } + + @Override + public void setCopperGolemState(ElementTag variant, CopperGolem copperGolem, Mechanism mechanism) { + if (mechanism.requireEnum(WeatheringCopperState.class)) { + copperGolem.setWeatheringState(variant.asEnum(WeatheringCopperState.class)); + } + } } diff --git a/plugin/pom.xml b/plugin/pom.xml index 53587ee87f..f1abe6f709 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -5,7 +5,7 @@ com.denizenscript denizen - 1.3.0-SNAPSHOT + 1.3.1-SNAPSHOT Denizen Scriptable Minecraft and Citizens2 @@ -16,13 +16,21 @@ CUSTOM + + + + everything + https://maven.citizensnpcs.co/repo + + + org.spigotmc spigot-api - 1.20.4-R0.1-SNAPSHOT + 1.21.11-R0.2-SNAPSHOT jar provided @@ -36,7 +44,7 @@ net.citizensnpcs citizens-main - 2.0.33-SNAPSHOT + 2.0.41-SNAPSHOT jar provided @@ -59,6 +67,11 @@ system ${project.basedir}/lib/Vault.jar + + net.kyori + adventure-nbt + 4.26.1 + it.unimi.dsi diff --git a/plugin/src/main/java/com/denizenscript/denizen/Denizen.java b/plugin/src/main/java/com/denizenscript/denizen/Denizen.java index 5a68b96155..9060b805b9 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/Denizen.java +++ b/plugin/src/main/java/com/denizenscript/denizen/Denizen.java @@ -5,6 +5,7 @@ import com.denizenscript.denizen.events.server.ServerPrestartScriptEvent; import com.denizenscript.denizen.events.server.ServerStartScriptEvent; import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.nms.interfaces.FakeArrow; import com.denizenscript.denizen.nms.interfaces.FakePlayer; import com.denizenscript.denizen.nms.interfaces.ItemProjectile; @@ -146,7 +147,10 @@ else if (javaVersion.startsWith("17")) { Debug.log("Running on fully supported Java 17."); } else if (javaVersion.startsWith("18") || javaVersion.startsWith("19")) { - getLogger().warning("Running unreliable future Java version. modern Minecraft versions are built for Java 17. Other Java versions are not guaranteed to function properly."); + getLogger().warning("Running unreliable Java version. modern Minecraft versions are built for Java 21 or 17. Other Java versions are not guaranteed to function properly."); + } + else if (javaVersion.startsWith("21")) { + Debug.log("Running on fully supported Java 21."); } else { Debug.log("Running on unrecognized (future?) Java version. May or may not work."); @@ -159,12 +163,26 @@ else if (javaVersion.startsWith("18") || javaVersion.startsWith("19")) { startedSuccessful = false; return; } - if (!NMSHandler.instance.isCorrectMappingsCode()) { - getLogger().warning("-------------------------------------"); - getLogger().warning("This build of Denizen was built for a different Spigot revision! This may potentially cause issues." - + " If you are experiencing trouble, update Denizen and Spigot both to latest builds!" - + " If this message appears with both Denizen and Spigot fully up-to-date, contact the Denizen team (via GitHub, Spigot, or Discord) to request an update be built."); - getLogger().warning("-------------------------------------"); + try { + if (Class.forName("com.destroystokyo.paper.PaperConfig") != null) { + supportsPaper = true; + } + } + catch (ClassNotFoundException ex) { + // Ignore. + } + catch (Throwable ex) { + Debug.echoError(ex); + } + if (!NMSHandler.instance.isExactServerVersionMatch()) { + String serverSoftware = supportsPaper ? "Paper" : "Spigot"; + getLogger().warning(""" + \n------------------------------------- + This build of Denizen was built for a different Minecraft version! This may potentially cause issues. + If you are experiencing trouble, update Denizen and both to latest builds! + If this message appears with both Denizen and fully up-to-date, contact the Denizen team (via Discord) to request an update be built. + -------------------------------------""".replace("", serverSoftware) + ); } triggerRegistry = new TriggerRegistry(); boolean citizensBork = false; @@ -202,17 +220,6 @@ else if (javaVersion.startsWith("18") || javaVersion.startsWith("19")) { catch (Exception e) { Debug.echoError(e); } - try { - if (Class.forName("com.destroystokyo.paper.PaperConfig") != null) { - supportsPaper = true; - } - } - catch (ClassNotFoundException ex) { - // Ignore. - } - catch (Throwable ex) { - Debug.echoError(ex); - } // bstats.org try { BStatsMetricsLite metrics = new BStatsMetricsLite(this); @@ -290,6 +297,9 @@ else if (javaVersion.startsWith("18") || javaVersion.startsWith("19")) { configOutput.close(); reloadConfig(); } + if (Settings.cache_legacySpigotNamesSupport) { + Debug.log("Legacy Spigot name support enabled. This may be unnecessary; see config.yml for more information."); + } } catch (Exception e) { Debug.echoError(e); @@ -338,7 +348,10 @@ else if (javaVersion.startsWith("18") || javaVersion.startsWith("19")) { Debug.echoError(e); } try { - new CommandEvents(); + // TODO: temporary patch, should switch to custom click events + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_20)) { + new CommandEvents(); + } if (Settings.cache_packetInterceptAutoInit) { NetworkInterceptHelper.enable(); } @@ -611,4 +624,9 @@ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { default -> null; }; } + + @Override + public File getFile() { + return super.getFile(); + } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/BukkitScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/BukkitScriptEvent.java index 601a52da4a..dd1a307ff7 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/BukkitScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/BukkitScriptEvent.java @@ -8,8 +8,10 @@ import com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper; import com.denizenscript.denizen.tags.BukkitTagContext; import com.denizenscript.denizen.utilities.NotedAreaTracker; +import com.denizenscript.denizen.utilities.Utilities; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; import com.denizenscript.denizen.utilities.inventory.SlotHelper; +import com.denizenscript.denizencore.events.ScriptEvent; import com.denizenscript.denizencore.flags.AbstractFlagTracker; import com.denizenscript.denizencore.flags.FlaggableObject; import com.denizenscript.denizencore.objects.ObjectTag; @@ -18,14 +20,13 @@ import com.denizenscript.denizencore.objects.notable.NoteManager; import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.utilities.CoreConfiguration; +import com.denizenscript.denizencore.utilities.CoreUtilities; import com.denizenscript.denizencore.utilities.Deprecations; import com.denizenscript.denizencore.utilities.debugging.Debug; -import com.denizenscript.denizencore.events.ScriptEvent; -import com.denizenscript.denizencore.utilities.CoreUtilities; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.*; +import org.bukkit.*; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Vehicle; import org.bukkit.event.*; import org.bukkit.event.inventory.InventoryType; import org.bukkit.plugin.EventExecutor; @@ -85,6 +86,66 @@ public abstract class BukkitScriptEvent extends ScriptEvent { // // --> + // <--[extension] + // @name Advanced Object Matching Extension + // @target_type language + // @target_name Advanced Object Matching + // @description + // Object types have their own special supported matchable inputs, refer to <@link language Advanced Object Matchables>. + // --> + + // <--[language] + // @name Script Event Switches + // @group Script Events + // @description + // Modern script events support the concept of 'switches'. + // A switch is a specification of additional requirements in an event line other than what's in the event label it. + // + // A switch consists of a name and a value input, and are can be added anywhere in an event line as "name:". + // For example, "on delta time secondly every:5:" is a valid event, where "delta time secondly" is the event itself, and "every:<#>" is a switch available to the event. + // + // A traditional Denizen event might look like "on damaged", + // where "" can be filled with "entity" or any entity type (like "player"). + // A switch-using event would instead take the format "on entity damaged" with switch "type:" + // meaning you can do "on entity damaged" for any entity, or "on entity damaged type:player:" for players specifically. + // This is both more efficient to process and more explicit in what's going on, however it is less clear/readable to the average user, so it is not often used. + // Some events may have switches for less-often specified data, and use the event line for other options. + // + // There are also some standard switches available to every script event, and some available to an entire category of script events. + // + // One switch available to every event is "server_flagged:", which requires that there be a server flag under the given name. + // For example, "on console output server_flagged:recording:" will only run the handler for console output when the "recording" flag is set on the server. + // This can also be used to require the server does NOT have a flag with "server_flagged:!" + // + // "chance:" is also a globally available switch. + // For example, "on player breaks diamond_ore chance:25:" will only fire on average one in every four times that a player breaks a diamond ore block. + // + // Events that have a player linked have the "flagged" and "permission" switches available. + // + // If the switch is specified, and an event doesn't have a linked player, the event will automatically fail to match. + // The "flagged:" switch will limit the event to only fire when the player has the flag with the specified name. + // It can be used like "on player breaks block flagged:nobreak:" (that would be used alongside "- flag player nobreak"). + // You can also use "flagged:!" to require the player does NOT have the flag, like "on player breaks block flagged:!griefbypass:" + // + // The "permission:" will limit the event to only fire when the player has the specified permission key. + // It can be used like "on player breaks block permission:denizen.my.perm:" + // For multiple flag or permission requirements, just list them separated by '|' pipes, like "flagged:a|b|c". This will require all named flags/permissions to be present, not just one. + // + // Events that have an NPC linked have the "assigned" switch available. + // If the switch is specified, and an event doesn't have a linked NPC, the event will automatically fail to match. + // The "assigned: