diff --git a/LICENSE.txt b/LICENSE.txt index 36ee398701..aa041d8e7d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019-2024 The Denizen Script Team +Copyright (c) 2019-2026 The Denizen Script Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index cd876a87ca..26fe7dd9f3 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.1**: Compatible with Spigot 1.17.1, 1.18.2, 1.19.4, 1.20.6, and 1.21.8! +**Version 1.3.3**: Compatible with Spigot 1.17.1, 1.18.2, 1.19.4, 1.20.6, 1.21.11, 26.1.2, and 26.2! **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.1-SNAPSHOT + 1.3.3-SNAPSHOT jar provided @@ -78,7 +78,7 @@ Copyright (C) 2013-2019 The Denizen Script Team, All Rights Reserved. The MIT License (MIT) -Copyright (c) 2019-2024 The Denizen Script Team +Copyright (c) 2019-2026 The Denizen Script Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/dist/pom.xml b/dist/pom.xml index 25100bb2e5..30c3e001e9 100644 --- a/dist/pom.xml +++ b/dist/pom.xml @@ -5,7 +5,7 @@ com.denizenscript denizen-parent - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT 4.0.0 @@ -60,6 +60,18 @@ ${project.parent.version} compile + + com.denizenscript + denizen-v26_1 + ${project.parent.version} + compile + + + com.denizenscript + denizen-v26_2 + ${project.parent.version} + compile + com.denizenscript denizen-paper diff --git a/paper/pom.xml b/paper/pom.xml index d426267ecc..ce0e1978cf 100644 --- a/paper/pom.xml +++ b/paper/pom.xml @@ -6,7 +6,7 @@ com.denizenscript denizen-paper - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT @@ -25,13 +25,13 @@ io.papermc.paper paper-api - 1.21.8-R0.1-SNAPSHOT + [26.2.build,) provided net.citizensnpcs citizens-main - 2.0.38-SNAPSHOT + 2.0.42-SNAPSHOT jar provided 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 58ff60ed66..14cce970a0 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; @@ -20,6 +21,9 @@ import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; +import java.util.ArrayList; +import java.util.List; + public class PaperModule { public static void init() { @@ -67,7 +71,7 @@ public static void init() { ScriptEvent.registerScriptEvent(PlayerJumpsScriptEventPaperImpl.class); ScriptEvent.registerScriptEvent(PlayerLecternPageChangeScriptEvent.class); ScriptEvent.registerScriptEvent(PlayerLoomPatternSelectScriptEvent.class); - ScriptEvent.registerScriptEvent(PlayerNameEntityScriptEvent.class); + ScriptEvent.registerScriptEvent(PlayerNamesEntityScriptEvent.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { ScriptEvent.registerScriptEvent(PlayerOpenSignScriptEvent.class); } @@ -76,6 +80,9 @@ public static void init() { 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(PlayerSetSpawnScriptEvent.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { @@ -100,6 +107,9 @@ public static void init() { ScriptEvent.registerScriptEvent(TNTPrimesScriptEvent.class); } ScriptEvent.registerScriptEvent(UnknownCommandScriptEvent.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + ScriptEvent.registerScriptEvent(VaultChangesStateScriptEvent.class); + } if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { ScriptEvent.registerScriptEvent(WardenChangesAngerLevelScriptEvent.class); } @@ -127,6 +137,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(); @@ -162,6 +178,14 @@ public static String stringifyComponent(Component component) { return FormattedTextHelper.stringify(FormattedTextHelper.parseJson(componentToJson(component))); } + public static List stringifyComponentList(List components) { + List result = new ArrayList<>(components.size()); + for (Component component : components) { + result.add(stringifyComponent(component)); + } + return result; + } + public static Component jsonToComponent(String json) { if (json == null) { return null; 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..2029b783e1 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/ComponentAdaptersRegistry.java @@ -0,0 +1,13 @@ +package com.denizenscript.denizen.paper.datacomponents; + +public class ComponentAdaptersRegistry { + + public static void register() { + DataComponentAdapter.register(new FoodAdapter()); + DataComponentAdapter.register(new GliderAdapter()); + DataComponentAdapter.register(new ItemModelAdapter()); + DataComponentAdapter.register(new MaxDurabilityAdapter()); + DataComponentAdapter.register(new MaxStackSizeAdapter()); + DataComponentAdapter.register(new RarityAdapter()); + } +} 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/datacomponents/ItemModelAdapter.java b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/ItemModelAdapter.java new file mode 100644 index 0000000000..f9eb15acbd --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/ItemModelAdapter.java @@ -0,0 +1,36 @@ +package com.denizenscript.denizen.paper.datacomponents; + +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import io.papermc.paper.datacomponent.DataComponentTypes; +import net.kyori.adventure.key.Key; + +public class ItemModelAdapter extends DataComponentAdapter.Valued { + + // <--[property] + // @object ItemTag + // @name item_model + // @input ElementTag + // @description + // Controls an item's model <@link language Item Components> in namespaced key format. + // The default namespace is "minecraft", so for example an input of "stone" becomes "minecraft:stone", and will set the item model to a stone block. + // This can also be used to display item models from your own custom resource packs. + // @mechanism + // Provide no input to reset the item to its default value. + // --> + + public ItemModelAdapter() { + super(ElementTag.class, DataComponentTypes.ITEM_MODEL, "item_model"); + } + + @Override + public ElementTag toDenizen(Key value) { + return new ElementTag(value.asMinimalString(), true); + } + + @Override + public Key fromDenizen(ElementTag value, Mechanism mechanism) { + return Utilities.parseNamespacedKey(value.asString()); + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/MaxDurabilityAdapter.java b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/MaxDurabilityAdapter.java new file mode 100644 index 0000000000..cc021bda08 --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/MaxDurabilityAdapter.java @@ -0,0 +1,34 @@ +package com.denizenscript.denizen.paper.datacomponents; + +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import io.papermc.paper.datacomponent.DataComponentTypes; + +public class MaxDurabilityAdapter extends DataComponentAdapter.Valued { + + // <--[property] + // @object ItemTag + // @name max_durability + // @input ElementTag(Number) + // @description + // Controls the maximum durability (number of uses) of this item. + // For use with <@link tag ItemTag.durability> and <@link mechanism ItemTag.durability>. + // See also <@link language Item Components>. + // @mechanism + // Provide no input to reset the item to its default value. + // --> + + public MaxDurabilityAdapter() { + super(ElementTag.class, DataComponentTypes.MAX_DAMAGE, "max_durability"); + } + + @Override + public ElementTag toDenizen(Integer value) { + return new ElementTag(value); + } + + @Override + public Integer fromDenizen(ElementTag value, Mechanism mechanism) { + return mechanism.requireInteger() ? value.asInt() : null; + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/MaxStackSizeAdapter.java b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/MaxStackSizeAdapter.java new file mode 100644 index 0000000000..c9365207dd --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/MaxStackSizeAdapter.java @@ -0,0 +1,32 @@ +package com.denizenscript.denizen.paper.datacomponents; + +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import io.papermc.paper.datacomponent.DataComponentTypes; + +public class MaxStackSizeAdapter extends DataComponentAdapter.Valued { + + // <--[property] + // @object ItemTag + // @name max_stack_size + // @input ElementTag(Number) + // @description + // Controls an item's max stack size <@link language Item Components>. + // @mechanism + // Provide no input to reset the item to its default value. + // --> + + public MaxStackSizeAdapter() { + super(ElementTag.class, DataComponentTypes.MAX_STACK_SIZE, "max_stack_size"); + } + + @Override + public ElementTag toDenizen(Integer value) { + return new ElementTag(value); + } + + @Override + public Integer fromDenizen(ElementTag value, Mechanism mechanism) { + return mechanism.requireInteger() ? value.asInt() : null; + } +} diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/RarityAdapter.java b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/RarityAdapter.java new file mode 100644 index 0000000000..9772fd51bf --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/RarityAdapter.java @@ -0,0 +1,34 @@ +package com.denizenscript.denizen.paper.datacomponents; + +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import io.papermc.paper.datacomponent.DataComponentTypes; +import org.bukkit.inventory.ItemRarity; + +public class RarityAdapter extends DataComponentAdapter.Valued { + + // <--[property] + // @object ItemTag + // @name rarity + // @input ElementTag + // @description + // Controls an item's rarity <@link language Item Components>. + // See <@link url https://jd.papermc.io/paper/org/bukkit/inventory/ItemRarity.html> for valid rarity values. + // @mechanism + // Provide no input to reset the item to its default value. + // --> + + public RarityAdapter() { + super(ElementTag.class, DataComponentTypes.RARITY, "rarity"); + } + + @Override + public ElementTag toDenizen(ItemRarity value) { + return new ElementTag(value); + } + + @Override + public ItemRarity fromDenizen(ElementTag value, Mechanism mechanism) { + return mechanism.requireEnum(ItemRarity.class) ? value.asEnum(ItemRarity.class) : null; + } +} 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 effc8f9e27..5f4268d66b 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 @@ -36,7 +36,7 @@ public class AnvilBlockDamagedScriptEvent extends BukkitScriptEvent implements L // // @Determine // "STATE:" to set the anvil's new damage state. - // "BREAK:" to set weather the anvil will break. + // "BREAK:" to set whether the anvil will break. // --> public AnvilBlockDamagedScriptEvent() { diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNameEntityScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNamesEntityScriptEvent.java similarity index 88% rename from paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNameEntityScriptEvent.java rename to paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNamesEntityScriptEvent.java index 41d421bd78..81627cd96c 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNameEntityScriptEvent.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNamesEntityScriptEvent.java @@ -13,7 +13,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -public class PlayerNameEntityScriptEvent extends BukkitScriptEvent implements Listener { +public class PlayerNamesEntityScriptEvent extends BukkitScriptEvent implements Listener { // <--[event] // @Events @@ -43,16 +43,16 @@ public class PlayerNameEntityScriptEvent extends BukkitScriptEvent implements Li // // --> - public PlayerNameEntityScriptEvent() { + public PlayerNamesEntityScriptEvent() { registerCouldMatcher("player names "); - this.registerOptionalDetermination("persistent", ElementTag.class, (evt, context, determination) -> { + 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) -> { + this.registerDetermination("name", ElementTag.class, (evt, context, determination) -> { evt.event.setName(PaperModule.parseFormattedText(determination.toString(), ChatColor.WHITE)); }); } 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/ServerListPingScriptEventPaperImpl.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/ServerListPingScriptEventPaperImpl.java index 6ea097a13d..af4af93b2b 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 @@ -15,6 +15,7 @@ import com.destroystokyo.paper.profile.PlayerProfile; import com.destroystokyo.paper.profile.ProfileProperty; import net.md_5.bungee.api.ChatColor; +import org.apache.commons.lang3.mutable.MutableInt; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.profile.PlayerTextures; @@ -70,6 +71,17 @@ public ServerListPingScriptEventPaperImpl() { ListedPlayersEditor.setListedPlayerInfo(evt.getEvent(), text); return true; }); + this.registerOptionalDetermination("player_count", ElementTag.class, (evt, context, input) -> { + if (!CoreConfiguration.allowRestrictedActions) { + Debug.echoError("Cannot use 'player_count' in list ping event: 'Allow restricted actions' is disabled in Denizen config.yml."); + return false; + } + if (input.isInt()) { + evt.getEvent().setNumPlayers(input.asInt()); + return true; + } + return false; + }); } public PaperServerListPingEvent getEvent() { @@ -86,7 +98,18 @@ public static void setListedPlayerInfo(PaperServerListPingEvent event, List exclude) { - event.getListedPlayers().removeIf(listedPlayerInfo -> exclude.contains(listedPlayerInfo.id())); + int size = event.getListedPlayers().size(); + MutableInt counter = new MutableInt(); + event.getListedPlayers().removeIf(listedPlayerInfo -> { + if (exclude.contains(listedPlayerInfo.id())) { + counter.increment(); + return true; + } + return false; + }); + if (size == event.getNumPlayers()) { + event.setNumPlayers(event.getNumPlayers() - counter.intValue()); + } } } diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/events/VaultChangesStateScriptEvent.java b/paper/src/main/java/com/denizenscript/denizen/paper/events/VaultChangesStateScriptEvent.java new file mode 100644 index 0000000000..33100459ab --- /dev/null +++ b/paper/src/main/java/com/denizenscript/denizen/paper/events/VaultChangesStateScriptEvent.java @@ -0,0 +1,74 @@ +package com.denizenscript.denizen.paper.events; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +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.VaultChangeStateEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class VaultChangesStateScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // vault changes state + // + // @Plugin Paper + // + // @Group Block + // + // @Cancellable true + // + // @Location true + // + // @Triggers when a vault block's state changes. A list of states can be found at <@link url https://jd.papermc.io/paper/org/bukkit/block/data/type/Vault.State.html>. + // + // @Context + // returns the LocationTag of the vault block. + // returns the vault state before the change. + // returns the vault state after the change. + // + // @Player when the change is triggered by a player. + // + // --> + + public VaultChangesStateScriptEvent() { + registerCouldMatcher("vault changes state"); + } + + public LocationTag location; + public VaultChangeStateEvent event; + + @Override + public boolean matches(ScriptPath path) { + if (!runInCheck(path, location)) { + 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 "old_state" -> new ElementTag(event.getCurrentState()); + case "new_state" -> new ElementTag(event.getNewState()); + case "location" -> location; + default -> super.getContext(name); + }; + } + + @EventHandler + public void onVaultChangesStateEvent(VaultChangeStateEvent event) { + location = new LocationTag(event.getBlock().getLocation()); + this.event = event; + fire(event); + } +} 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 faf88722fd..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,6 +7,7 @@ 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; @@ -69,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); @@ -78,7 +79,7 @@ public boolean matches(ScriptPath path) { @Override public ObjectTag getContext(String name) { return switch (name) { - case "gamerule" -> new ElementTag(event.getGameRule().getName(), true); + 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(); 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/PaperElementExtensions.java b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperElementExtensions.java index 090124e434..86a512797c 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperElementExtensions.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperElementExtensions.java @@ -19,7 +19,7 @@ public static void register() { // @Plugin Paper // @group paper // @description - // Returns the element with all MiniMessage tags parsed, see <@link url https://docs.adventure.kyori.net/minimessage/format.html> for more information. + // Returns the element with all MiniMessage tags parsed, see <@link url https://docs.papermc.io/adventure/minimessage/format/> for more information. // This may be useful for reading data from external plugins, but should not be used in normal scripts. // --> ElementTag.tagProcessor.registerTag(ElementTag.class, "parse_minimessage", (attribute, object) -> { 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 94bcb7f641..bc2f6a5870 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 @@ -13,6 +13,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.ExperienceOrb; import org.bukkit.entity.Goat; +import org.bukkit.entity.Villager; import org.bukkit.inventory.EquipmentSlot; import java.util.UUID; @@ -187,6 +188,26 @@ public static void register() { object.getLivingEntity().damageItemStack(slot.asEnum(EquipmentSlot.class), amount.asInt()); }); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + + // <--[mechanism] + // @object EntityTag + // @name restock_trades + // @input None + // @Plugin Paper + // @group paper + // @description + // Restocks a villager's trades. + // Note: this mechanism will fire the <@link event villager replenishes trade> event for every trade the villager is offering. + // This mechanism also updates the villager's demand for offers, which may cause item trade prices to rise or fall. + // --> + EntityTag.registerSpawnedOnlyMechanism("restock_trades", false, (object, mechanism) -> { + if (object.getBukkitEntity() instanceof Villager villager) { + villager.restock(); + } + }); + } + // <--[mechanism] // @object EntityTag // @name shear diff --git a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperItemExtensions.java b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperItemExtensions.java index f2829a34fe..0dd4e809b4 100644 --- a/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperItemExtensions.java +++ b/paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperItemExtensions.java @@ -1,5 +1,7 @@ package com.denizenscript.denizen.paper.properties; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.ItemTag; import com.denizenscript.denizencore.objects.core.ElementTag; @@ -7,16 +9,10 @@ public class PaperItemExtensions { public static void register() { - // <--[tag] - // @attribute - // @returns ElementTag - // @group paper - // @Plugin Paper - // @description - // Returns the rarity of an item, as "common", "uncommon", "rare", or "epic". - // --> - ItemTag.tagProcessor.registerTag(ElementTag.class, "rarity", (attribute, item) -> { - return new ElementTag(item.getItemStack().getRarity()); - }); + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_20)) { + ItemTag.tagProcessor.registerTag(ElementTag.class, "rarity", (attribute, item) -> { + return new ElementTag(item.getItemStack().getRarity()); + }); + } } } 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 c468235626..2b648d75a1 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 @@ -6,11 +6,13 @@ import com.denizenscript.denizen.objects.ItemTag; import com.denizenscript.denizen.paper.PaperModule; import com.denizenscript.denizen.scripts.commands.entity.TeleportCommand; +import com.denizenscript.denizen.scripts.commands.world.SignCommand; import com.denizenscript.denizen.scripts.containers.core.ItemScriptContainer; import com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper; 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; @@ -20,17 +22,16 @@ 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.block.sign.Side; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -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; @@ -41,6 +42,7 @@ 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; @@ -101,18 +103,28 @@ public String getPlayerListName(Player player) { } @Override - public String[] getSignLines(Sign sign) { - String[] output = new String[4]; - int i = 0; - for (Component component : sign.lines()) { - output[i++] = PaperModule.stringifyComponent(component); - } - return output; + public List getSignLines(Sign sign) { + return PaperModule.stringifyComponentList(SignCommand.SIGN_SIDES_SUPPORTED ? sign.getSide(Side.FRONT).lines() : sign.lines()); + } + + @Override + public List getSignBackLines(Sign sign) { + return PaperModule.stringifyComponentList(sign.getSide(Side.BACK).lines()); } @Override public void setSignLine(Sign sign, int line, String text) { - sign.line(line, PaperModule.parseFormattedText(text == null ? "" : text, ChatColor.BLACK)); + if (SignCommand.SIGN_SIDES_SUPPORTED) { + sign.getSide(Side.FRONT).line(line, PaperModule.parseFormattedText(text == null ? "" : text, ChatColor.BLACK)); + } + else { + sign.line(line, PaperModule.parseFormattedText(text == null ? "" : text, ChatColor.BLACK)); + } + } + + @Override + public void setSignBackLine(Sign sign, int line, String text) { + sign.getSide(Side.BACK).line(line, PaperModule.parseFormattedText(text == null ? "" : text, ChatColor.BLACK)); } @Override @@ -409,4 +421,26 @@ public void setMaterialTags(Material type, Set tags) { } 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 50400c64ce..b9ba8f0938 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -5,7 +5,7 @@ com.denizenscript denizen - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT Denizen Scriptable Minecraft and Citizens2 @@ -30,7 +30,7 @@ org.spigotmc spigot-api - 1.21.8-R0.1-SNAPSHOT + 26.2-R0.1-SNAPSHOT jar provided @@ -44,7 +44,7 @@ net.citizensnpcs citizens-main - 2.0.38-SNAPSHOT + 2.0.42-SNAPSHOT jar provided @@ -70,7 +70,7 @@ net.kyori adventure-nbt - 4.23.0 + 4.26.1 diff --git a/plugin/src/main/java/com/denizenscript/denizen/Denizen.java b/plugin/src/main/java/com/denizenscript/denizen/Denizen.java index 9060b805b9..65ee2cf9aa 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/Denizen.java +++ b/plugin/src/main/java/com/denizenscript/denizen/Denizen.java @@ -147,22 +147,17 @@ 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 Java version. modern Minecraft versions are built for Java 21 or 17. Other Java versions are not guaranteed to function properly."); + getLogger().warning("Running unreliable Java version. modern Minecraft versions are built for Java 25, 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 if (javaVersion.startsWith("25")) { + Debug.log("Running on fully supported Java 25."); + } else { Debug.log("Running on unrecognized (future?) Java version. May or may not work."); } - if (!NMSHandler.initialize(this)) { - getLogger().warning("-------------------------------------"); - getLogger().warning("This build of Denizen is not compatible with this Spigot version! Deactivating Denizen!"); - getLogger().warning("-------------------------------------"); - getServer().getPluginManager().disablePlugin(this); - startedSuccessful = false; - return; - } try { if (Class.forName("com.destroystokyo.paper.PaperConfig") != null) { supportsPaper = true; @@ -174,6 +169,14 @@ else if (javaVersion.startsWith("21")) { catch (Throwable ex) { Debug.echoError(ex); } + if (!NMSHandler.initialize(this)) { + getLogger().warning("-------------------------------------"); + getLogger().warning("This build of Denizen is not compatible with this Spigot version! Deactivating Denizen!"); + getLogger().warning("-------------------------------------"); + getServer().getPluginManager().disablePlugin(this); + startedSuccessful = false; + return; + } if (!NMSHandler.instance.isExactServerVersionMatch()) { String serverSoftware = supportsPaper ? "Paper" : "Spigot"; getLogger().warning(""" diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java b/plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java index a3e71d4ec1..ea2764e4b1 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java @@ -80,14 +80,23 @@ public static void registerMainEvents() { ScriptEvent.registerScriptEvent(BlockShearEntityScriptEvent.class); ScriptEvent.registerScriptEvent(BlockSpreadsScriptEvent.class); ScriptEvent.registerScriptEvent(BrewingStandFueledScriptEvent.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { + ScriptEvent.registerScriptEvent(BrewingStartsScriptEvent.class); + } ScriptEvent.registerScriptEvent(BrewsScriptEvent.class); ScriptEvent.registerScriptEvent(CauldronLevelChangeScriptEvent.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + ScriptEvent.registerScriptEvent(CrafterCraftsScriptEvent.class); + } ScriptEvent.registerScriptEvent(DragonEggMovesScriptEvent.class); ScriptEvent.registerScriptEvent(FurnaceBurnsItemScriptEvent.class); ScriptEvent.registerScriptEvent(FurnaceStartsSmeltingScriptEvent.class); ScriptEvent.registerScriptEvent(LeafDecaysScriptEvent.class); ScriptEvent.registerScriptEvent(LiquidLevelChangeScriptEvent.class); ScriptEvent.registerScriptEvent(LiquidSpreadScriptEvent.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + ScriptEvent.registerScriptEvent(LootDispensesFromBlockScriptEvent.class); + } ScriptEvent.registerScriptEvent(MoistureChangeScriptEvent.class); ScriptEvent.registerScriptEvent(NoteBlockPlaysNoteScriptEvent.class); ScriptEvent.registerScriptEvent(PistonExtendsScriptEvent.class); @@ -95,11 +104,10 @@ public static void registerMainEvents() { ScriptEvent.registerScriptEvent(RedstoneScriptEvent.class); ScriptEvent.registerScriptEvent(SpongeAbsorbsScriptEvent.class); if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { - ScriptEvent.registerScriptEvent(BrewingStartsScriptEvent.class); ScriptEvent.registerScriptEvent(TNTPrimesScriptEvent.class); } if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { - ScriptEvent.registerScriptEvent(CrafterCraftsScriptEvent.class); + ScriptEvent.registerScriptEvent(VaultDisplaysItemScriptEvent.class); } // Entity events diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/BlockDispensesScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/BlockDispensesScriptEvent.java index 60e44ac303..3d287a092b 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/BlockDispensesScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/BlockDispensesScriptEvent.java @@ -22,7 +22,7 @@ public class BlockDispensesScriptEvent extends BukkitScriptEvent implements List // // @Cancellable true // - // @Triggers when a block dispenses an item. + // @Triggers when a block dispenses a single item. // // @Context // returns the LocationTag of the dispenser. diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/BrewingStandFueledScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/BrewingStandFueledScriptEvent.java index cc6732dceb..8c1a68708e 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/BrewingStandFueledScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/BrewingStandFueledScriptEvent.java @@ -3,6 +3,7 @@ import com.denizenscript.denizen.events.BukkitScriptEvent; import com.denizenscript.denizen.objects.ItemTag; import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.utilities.BukkitImplDeprecations; import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; import org.bukkit.event.EventHandler; @@ -31,13 +32,30 @@ public class BrewingStandFueledScriptEvent extends BukkitScriptEvent implements // // @Determine // "FUEL_POWER:" to set the fuel power level to be added. - // "CONSUMING" to indicate that the fuel item should be consumed. - // "NOT_CONSUMING" to indicate that the fuel item should not be consumed. + // "CONSUMING:" to indicate whether the fuel item should be consumed. // // --> public BrewingStandFueledScriptEvent() { registerCouldMatcher("brewing stand fueled (with )"); + this.registerOptionalDetermination("fuel_power", ElementTag.class, (evt, context, power) -> { + if (power.isInt()) { + evt.event.setFuelPower(power.asInt()); + return true; + } + return false; + }); + this.registerOptionalDetermination("consuming", ElementTag.class, (evt, context, value) -> { + if (value.isBoolean()) { + evt.event.setConsuming(value.asBoolean()); + return true; + } + return false; + }); + this.registerTextDetermination("not_consuming", (evt) -> { + BukkitImplDeprecations.brewingStandConsumeDetermination.warn(); + evt.event.setConsuming(false); + }); } public LocationTag location; @@ -55,35 +73,15 @@ public boolean matches(ScriptPath path) { return super.matches(path); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag element) { - String val = element.asString(); - if (val.startsWith("fuel_power:")) { - event.setFuelPower(Integer.parseInt(val.substring("fuel_power:".length()))); - return true; - } - else if (val.equalsIgnoreCase("consuming")) { - event.setConsuming(true); - return true; - } - else if (val.equalsIgnoreCase("not_consuming")) { - event.setConsuming(false); - return true; - } - } - return super.applyDetermination(path, determinationObj); - } - @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "item": return item; - case "fuel_power": return new ElementTag(event.getFuelPower()); - case "consuming": return new ElementTag(event.isConsuming()); - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "item" -> item; + case "fuel_power" -> new ElementTag(event.getFuelPower()); + case "consuming" -> new ElementTag(event.isConsuming()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/CauldronLevelChangeScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/CauldronLevelChangeScriptEvent.java index a6134f62c9..599cc491c1 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/CauldronLevelChangeScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/CauldronLevelChangeScriptEvent.java @@ -5,6 +5,9 @@ import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.Levelled; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.CauldronLevelChangeEvent; @@ -39,10 +42,37 @@ public class CauldronLevelChangeScriptEvent extends BukkitScriptEvent implements public CauldronLevelChangeScriptEvent() { registerCouldMatcher("cauldron level changes|raises|lowers"); registerSwitches("cause"); + this.registerOptionalDetermination(null, ElementTag.class, (evt, context, value) -> { + if (!value.isInt()) { + return false; + } + int level = value.asInt(); + BlockState cauldronState = evt.event.getNewState(); + if (level <= 0) { + cauldronState.setType(Material.CAULDRON); + return true; + } + if (level > 3) { + return false; + } + if (cauldronState.getType() != Material.WATER_CAULDRON && cauldronState.getType() != Material.LAVA_CAULDRON) { + cauldronState.setType(cauldronType); + } + if (cauldronState.getBlockData() instanceof Levelled levelled) { + levelled.setLevel(level); + cauldronState.setBlockData(levelled); + evt.newLevel = level; + return true; + } + return false; + }); } public LocationTag location; public CauldronLevelChangeEvent event; + public Material cauldronType; + public int oldLevel; + public int newLevel; @Override public boolean matches(ScriptPath path) { @@ -54,12 +84,12 @@ public boolean matches(ScriptPath path) { } String changeType = path.eventArgLowerAt(2); if (changeType.equals("raises")) { - if (event.getNewLevel() <= event.getOldLevel()) { + if (newLevel <= oldLevel) { return false; } } else if (changeType.equals("lowers")) { - if (event.getNewLevel() >= event.getOldLevel()) { + if (newLevel >= oldLevel) { return false; } } @@ -69,33 +99,24 @@ else if (!changeType.equals("changes")) { return super.matches(path); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag element && element.isInt()) { - event.setNewLevel(element.asInt()); - } - return super.applyDetermination(path, determinationObj); - } - @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "cause": return new ElementTag(event.getReason()); - case "old_level": return new ElementTag(event.getOldLevel()); - case "new_level": return new ElementTag(event.getNewLevel()); - case "entity": - if (event.getEntity() != null) { - return new EntityTag(event.getEntity()).getDenizenObject(); - } - break; - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "cause" -> new ElementTag(event.getReason()); + case "old_level" -> new ElementTag(oldLevel); + case "new_level" -> new ElementTag(newLevel); + case "entity" -> event.getEntity() != null ? new EntityTag(event.getEntity()).getDenizenObject() : null; + default -> super.getContext(name); + }; } @EventHandler public void onCauldronLevelChange(CauldronLevelChangeEvent event) { location = new LocationTag(event.getBlock().getLocation()); + cauldronType = event.getBlock().getType(); + oldLevel = event.getBlock().getBlockData() instanceof Levelled levelled ? levelled.getLevel() : 0; + newLevel = event.getNewState().getBlockData() instanceof Levelled levelled ? levelled.getLevel() : 0; this.event = event; fire(event); } diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceBurnsItemScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceBurnsItemScriptEvent.java index fe34ed02b7..82c69cdf00 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceBurnsItemScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceBurnsItemScriptEvent.java @@ -35,6 +35,17 @@ public class FurnaceBurnsItemScriptEvent extends BukkitScriptEvent implements Li public FurnaceBurnsItemScriptEvent() { registerCouldMatcher("furnace burns "); + this.registerOptionalDetermination(null, ObjectTag.class, (evt, context, time) -> { + if (time instanceof ElementTag elementTag && elementTag.isInt()) { // Backwards compatibility for non-duration tick input + evt.event.setBurnTime(elementTag.asInt()); + return true; + } + else if (time.canBeType(DurationTag.class)) { + evt.event.setBurnTime(time.asType(DurationTag.class, context).getTicksAsInt()); + return true; + } + return false; + }); } public ItemTag item; @@ -52,30 +63,17 @@ public boolean matches(ScriptPath path) { return super.matches(path); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag element && element.isInt()) { - event.setBurnTime(element.asInt()); - return true; - } - else if (determinationObj.canBeType(DurationTag.class)) { - event.setBurnTime(determinationObj.asType(DurationTag.class, getTagContext(path)).getTicksAsInt()); - return true; - } - return super.applyDetermination(path, determinationObj); - } - @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "item": return item; - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "item" -> item; + default -> super.getContext(name); + }; } @EventHandler - public void onBrews(FurnaceBurnEvent event) { + public void onFurnaceBurns(FurnaceBurnEvent event) { location = new LocationTag(event.getBlock().getLocation()); item = new ItemTag(event.getFuel()); this.event = event; diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceStartsSmeltingScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceStartsSmeltingScriptEvent.java index 0e4d82c441..498f5f4c72 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceStartsSmeltingScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceStartsSmeltingScriptEvent.java @@ -31,19 +31,13 @@ public class FurnaceStartsSmeltingScriptEvent extends BukkitScriptEvent implemen // @Determine // DurationTag to set the total cook time for the item being smelted. // - // @Example - // # Sets the total cook time of every item to always be 2 seconds. - // on furnace starts smelting item: - // - determine 2s - // - // @Example - // # Sets the total cook time of iron ore to be 2 seconds. - // on furnace starts smelting iron_ore: - // - determine 2s // --> public FurnaceStartsSmeltingScriptEvent() { registerCouldMatcher("furnace starts smelting "); + this.registerDetermination(null, DurationTag.class, (evt, context, time) -> { + evt.event.setTotalCookTime(time.getTicksAsInt()); + }); } public ItemTag item; @@ -61,24 +55,15 @@ public boolean matches(ScriptPath path) { return super.matches(path); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj.canBeType(DurationTag.class)) { - event.setTotalCookTime(determinationObj.asType(DurationTag.class, getTagContext(path)).getTicksAsInt()); - return true; - } - return super.applyDetermination(path, determinationObj); - } - @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "item": return item; - case "recipe_id": return new ElementTag(event.getRecipe().getKey().toString()); - case "total_cook_time": return new DurationTag((long) event.getTotalCookTime()); - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "item" -> item; + case "recipe_id" -> new ElementTag(event.getRecipe().getKey().toString(), true); + case "total_cook_time" -> new DurationTag((long) event.getTotalCookTime()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/LeafDecaysScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/LeafDecaysScriptEvent.java index 6d6a4993be..447af73882 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/LeafDecaysScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/LeafDecaysScriptEvent.java @@ -52,11 +52,11 @@ public boolean matches(ScriptPath path) { @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "material": return material; - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "material" -> material; + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/LiquidLevelChangeScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/LiquidLevelChangeScriptEvent.java index d3fdc67897..63d0e1e3c5 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/LiquidLevelChangeScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/LiquidLevelChangeScriptEvent.java @@ -62,12 +62,12 @@ public boolean matches(ScriptPath path) { @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "old_material": return old_material; - case "new_material": return new MaterialTag(event.getNewData()); - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "old_material" -> old_material; + case "new_material" -> new MaterialTag(event.getNewData()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/LootDispensesFromBlockScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/LootDispensesFromBlockScriptEvent.java new file mode 100644 index 0000000000..d8c2e4671f --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/LootDispensesFromBlockScriptEvent.java @@ -0,0 +1,89 @@ +package com.denizenscript.denizen.events.block; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.*; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ListTag; +import com.denizenscript.denizencore.scripts.ScriptEntryData; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockDispenseLootEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class LootDispensesFromBlockScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // loot dispenses from + // + // @Group Block + // + // @Location true + // + // @Cancellable true + // + // @Player when the loot dispensing is triggered by a player. + // + // @Triggers when a block dispenses loot containing multiple items. + // + // @Context + // returns a ListTag(ItemTag) of loot items. + // returns a LocationTag of the block that is dispensing the items. + // + // @Determine + // "LOOT:" to set the loot items being dispensed. + // + // --> + + public LootDispensesFromBlockScriptEvent() { + registerCouldMatcher("loot dispenses from "); + this.registerDetermination("loot", ListTag.class, (evt, context, input) -> { + List items = new ArrayList<>(input.size()); + for (ItemTag item : input.filter(ItemTag.class, context)) { + items.add(item.getItemStack()); + } + evt.event.setDispensedLoot(items); + }); + } + + public MaterialTag block; + public LocationTag location; + public BlockDispenseLootEvent event; + + @Override + public boolean matches(ScriptPath path) { + if (!path.tryArgObject(3, block)) { + return false; + } + if (!runInCheck(path, location)) { + 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 "loot" -> new ListTag(event.getDispensedLoot(), ItemTag::new); + case "location" -> location; + default -> super.getContext(name); + }; + } + + @EventHandler + public void onLootDispensesFromBlock(BlockDispenseLootEvent event) { + block = new MaterialTag(event.getBlock().getType()); + location = new LocationTag(event.getBlock().getLocation()); + this.event = event; + fire(event); + } +} diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/NoteBlockPlaysNoteScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/NoteBlockPlaysNoteScriptEvent.java index d0743dc37b..53ab43d02a 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/NoteBlockPlaysNoteScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/NoteBlockPlaysNoteScriptEvent.java @@ -62,55 +62,39 @@ public Sound getSound() { if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { return event.getInstrument().getSound(); } - switch (event.getInstrument()) { - case PIANO: - return Sound.BLOCK_NOTE_BLOCK_HARP; - case BASS_DRUM: - return Sound.BLOCK_NOTE_BLOCK_BASEDRUM; - case SNARE_DRUM: - return Sound.BLOCK_NOTE_BLOCK_SNARE; - case STICKS: - return Sound.BLOCK_NOTE_BLOCK_HAT; - case BASS_GUITAR: - return Sound.BLOCK_NOTE_BLOCK_BASS; - case FLUTE: - return Sound.BLOCK_NOTE_BLOCK_FLUTE; - case BELL: - return Sound.BLOCK_NOTE_BLOCK_BELL; - case GUITAR: - return Sound.BLOCK_NOTE_BLOCK_GUITAR; - case CHIME: - return Sound.BLOCK_NOTE_BLOCK_CHIME; - case XYLOPHONE: - return Sound.BLOCK_NOTE_BLOCK_XYLOPHONE; - case IRON_XYLOPHONE: - return Sound.BLOCK_NOTE_BLOCK_IRON_XYLOPHONE; - case COW_BELL: - return Sound.BLOCK_NOTE_BLOCK_COW_BELL; - case DIDGERIDOO: - return Sound.BLOCK_NOTE_BLOCK_DIDGERIDOO; - case BIT: - return Sound.BLOCK_NOTE_BLOCK_BIT; - case BANJO: - return Sound.BLOCK_NOTE_BLOCK_BANJO; - case PLING: - return Sound.BLOCK_NOTE_BLOCK_PLING; - } - return null; + return switch (event.getInstrument()) { + case PIANO -> Sound.BLOCK_NOTE_BLOCK_HARP; + case BASS_DRUM -> Sound.BLOCK_NOTE_BLOCK_BASEDRUM; + case SNARE_DRUM -> Sound.BLOCK_NOTE_BLOCK_SNARE; + case STICKS -> Sound.BLOCK_NOTE_BLOCK_HAT; + case BASS_GUITAR -> Sound.BLOCK_NOTE_BLOCK_BASS; + case FLUTE -> Sound.BLOCK_NOTE_BLOCK_FLUTE; + case BELL -> Sound.BLOCK_NOTE_BLOCK_BELL; + case GUITAR -> Sound.BLOCK_NOTE_BLOCK_GUITAR; + case CHIME -> Sound.BLOCK_NOTE_BLOCK_CHIME; + case XYLOPHONE -> Sound.BLOCK_NOTE_BLOCK_XYLOPHONE; + case IRON_XYLOPHONE -> Sound.BLOCK_NOTE_BLOCK_IRON_XYLOPHONE; + case COW_BELL -> Sound.BLOCK_NOTE_BLOCK_COW_BELL; + case DIDGERIDOO -> Sound.BLOCK_NOTE_BLOCK_DIDGERIDOO; + case BIT -> Sound.BLOCK_NOTE_BLOCK_BIT; + case BANJO -> Sound.BLOCK_NOTE_BLOCK_BANJO; + case PLING -> Sound.BLOCK_NOTE_BLOCK_PLING; + default -> null; + }; } @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "instrument": return new ElementTag(event.getInstrument()); - case "sound": return Utilities.enumLikeToLegacyElement(getSound()); - case "tone": return new ElementTag(event.getNote().getTone()); - case "octave": return new ElementTag(event.getNote().getOctave()); - case "sharp": return new ElementTag(event.getNote().isSharped()); - case "pitch": return new ElementTag(Math.pow(2.0, (double) (event.getNote().getId() - 12) / 12.0)); // based on minecraft source - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "instrument" -> new ElementTag(event.getInstrument()); + case "sound" -> Utilities.enumLikeToLegacyElement(getSound()); + case "tone" -> new ElementTag(event.getNote().getTone()); + case "octave" -> new ElementTag(event.getNote().getOctave()); + case "sharp" -> new ElementTag(event.getNote().isSharped()); + case "pitch" -> new ElementTag(Math.pow(2.0, (double) (event.getNote().getId() - 12) / 12.0)); // based on minecraft source + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/PistonExtendsScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/PistonExtendsScriptEvent.java index f544ce7200..eaf1dccd83 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/PistonExtendsScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/PistonExtendsScriptEvent.java @@ -6,7 +6,6 @@ import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; import com.denizenscript.denizencore.objects.ObjectTag; -import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPistonExtendEvent; @@ -59,22 +58,16 @@ public boolean matches(ScriptPath path) { @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "material": return material; - case "sticky": return new ElementTag(event.isSticky()); - case "direction": return new LocationTag(event.getDirection().getDirection()); - case "relative": return new LocationTag(event.getBlock().getRelative(event.getDirection()).getLocation()); // Silently deprecated - case "blocks": { - ListTag blocks = new ListTag(); - for (Block block : event.getBlocks()) { - blocks.addObject(new LocationTag(block.getLocation())); - } - return blocks; - } - case "length": return new ElementTag(event.getBlocks().size()); - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "material" -> material; + case "sticky" -> new ElementTag(event.isSticky()); + case "direction" -> new LocationTag(event.getDirection().getDirection()); + case "relative" -> new LocationTag(event.getBlock().getRelative(event.getDirection()).getLocation()); // Silently deprecated + case "blocks" -> new ListTag(event.getBlocks(), block -> new LocationTag(block.getLocation())); + case "length" -> new ElementTag(event.getBlocks().size()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/PistonRetractsScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/PistonRetractsScriptEvent.java index 88ae3d2a0a..f048b60963 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/PistonRetractsScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/PistonRetractsScriptEvent.java @@ -6,7 +6,6 @@ import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; import com.denizenscript.denizencore.objects.ObjectTag; -import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPistonRetractEvent; @@ -59,22 +58,16 @@ public boolean matches(ScriptPath path) { @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "material": return material; - case "sticky": return new ElementTag(event.isSticky()); - case "direction": return new LocationTag(event.getDirection().getDirection()); - case "relative": return new LocationTag(event.getBlock().getRelative(event.getDirection().getOppositeFace()).getLocation()); // Silently deprecated - case "blocks": { - ListTag blocks = new ListTag(); - for (Block block : event.getBlocks()) { - blocks.addObject(new LocationTag(block.getLocation())); - } - return blocks; - } - case "retract_location": return new LocationTag(event.getBlock().getRelative(event.getDirection().getOppositeFace(), 2).getLocation()); - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "material" -> material; + case "sticky" -> new ElementTag(event.isSticky()); + case "direction" -> new LocationTag(event.getDirection().getDirection()); + case "relative" -> new LocationTag(event.getBlock().getRelative(event.getDirection().getOppositeFace()).getLocation()); // Silently deprecated + case "blocks" -> new ListTag(event.getBlocks(), block -> new LocationTag(block.getLocation())); + case "retract_location" -> new LocationTag(event.getBlock().getRelative(event.getDirection().getOppositeFace(), 2).getLocation()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/RedstoneScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/RedstoneScriptEvent.java index be845be4ae..ccc981d329 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/block/RedstoneScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/RedstoneScriptEvent.java @@ -28,15 +28,21 @@ public class RedstoneScriptEvent extends BukkitScriptEvent implements Listener { // returns what the redstone power level is becoming. // // @Determine - // ElementTag (Number) set the current value to a specific value. + // ElementTag(Number) set the current value to a specific value. // // --> public RedstoneScriptEvent() { registerCouldMatcher("redstone recalculated"); + this.registerOptionalDetermination(null, ElementTag.class, (evt, context, power) -> { + if (power.isInt()) { + evt.event.setNewCurrent(power.asInt()); + return true; + } + return false; + }); } - public LocationTag location; public BlockRedstoneEvent event; @@ -48,23 +54,14 @@ public boolean matches(ScriptPath path) { return super.matches(path); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - if (determinationObj instanceof ElementTag element && element.isInt()) { - event.setNewCurrent(element.asInt()); - return true; - } - return super.applyDetermination(path, determinationObj); - } - @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": return location; - case "old_current": return new ElementTag(event.getOldCurrent()); - case "new_current": return new ElementTag(event.getNewCurrent()); - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "old_current" -> new ElementTag(event.getOldCurrent()); + case "new_current" -> new ElementTag(event.getNewCurrent()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/block/VaultDisplaysItemScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/block/VaultDisplaysItemScriptEvent.java new file mode 100644 index 0000000000..eaec0a9c93 --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/events/block/VaultDisplaysItemScriptEvent.java @@ -0,0 +1,72 @@ +package com.denizenscript.denizen.events.block; + +import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizencore.objects.ObjectTag; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.VaultDisplayItemEvent; + +public class VaultDisplaysItemScriptEvent extends BukkitScriptEvent implements Listener { + + // <--[event] + // @Events + // vault displays + // + // @Group Block + // + // @Location true + // + // @Cancellable true + // + // @Triggers when a vault block displays an item. + // + // @Context + // returns the LocationTag of the vault block. + // returns the ItemTag being displayed. + // + // @Determine + // "ITEM:" to set the item being displayed. + // + // --> + + public VaultDisplaysItemScriptEvent() { + registerCouldMatcher("vault displays "); + this.registerDetermination("item", ItemTag.class, (evt, context, input) -> { + evt.event.setDisplayItem(input.getItemStack()); + }); + } + + public LocationTag location; + public VaultDisplayItemEvent event; + public ItemTag item; + + @Override + public boolean matches(ScriptPath path) { + if (!runInCheck(path, location)) { + return false; + } + if (!path.tryArgObject(2, item)) { + return false; + } + return super.matches(path); + } + + @Override + public ObjectTag getContext(String name) { + return switch (name) { + case "item" -> new ItemTag(event.getDisplayItem()); + case "location" -> location; + default -> super.getContext(name); + }; + } + + @EventHandler + public void onVaultDisplayItemEvent(VaultDisplayItemEvent event) { + location = new LocationTag(event.getBlock().getLocation()); + item = new ItemTag(event.getDisplayItem()); + this.event = event; + fire(event); + } +} diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/AreaEnterExitScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/AreaEnterExitScriptEvent.java index f9fb2c4ba0..99aead67d9 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/AreaEnterExitScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/AreaEnterExitScriptEvent.java @@ -1,9 +1,10 @@ package com.denizenscript.denizen.events.entity; import com.denizenscript.denizen.events.BukkitScriptEvent; -import com.denizenscript.denizen.objects.*; +import com.denizenscript.denizen.objects.AreaContainmentObject; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizen.utilities.NotedAreaTracker; -import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; import com.denizenscript.denizencore.flags.AbstractFlagTracker; import com.denizenscript.denizencore.flags.FlaggableObject; @@ -13,6 +14,7 @@ import com.denizenscript.denizencore.objects.notable.NoteManager; import com.denizenscript.denizencore.scripts.ScriptEntryData; import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.Debug; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.event.Event; @@ -53,7 +55,6 @@ public AreaEnterExitScriptEvent() { registerCouldMatcher(" enters|exits "); } - public EntityTag currentEntity; public AreaContainmentObject area; public boolean isEntering; @@ -87,52 +88,48 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("area")) { - return area; - } - else if (name.equals("cause")) { - String cause; - if (currentEvent instanceof PlayerJoinEvent) { - cause = "JOIN"; - } - else if (currentEvent instanceof PlayerQuitEvent) { - cause = "QUIT"; - } - else if (currentEvent instanceof PlayerChangedWorldEvent) { - cause = "WORLD_CHANGE"; - } - else if (currentEvent instanceof PlayerTeleportEvent) { - cause = "TELEPORT"; - } - else if (currentEvent instanceof VehicleMoveEvent) { - cause = "VEHICLE"; - } - else if (currentEvent instanceof PlayerMoveEvent) { - cause = "WALK"; - } - else { - cause = "UNKNOWN"; - } - return new ElementTag(cause); - } - else if (name.equals("to") && to != null) { - return new LocationTag(to); - } - else if (name.equals("from")) { - if (currentEvent instanceof PlayerMoveEvent) { - return new LocationTag(((PlayerMoveEvent) currentEvent).getFrom()); - } - else if (currentEvent instanceof VehicleMoveEvent) { - return new LocationTag(((VehicleMoveEvent) currentEvent).getFrom()); + return switch (name) { + case "area" -> area; + case "cause" -> { + String cause; + if (currentEvent instanceof PlayerJoinEvent) { + cause = "JOIN"; + } + else if (currentEvent instanceof PlayerQuitEvent) { + cause = "QUIT"; + } + else if (currentEvent instanceof PlayerChangedWorldEvent) { + cause = "WORLD_CHANGE"; + } + else if (currentEvent instanceof PlayerTeleportEvent) { + cause = "TELEPORT"; + } + else if (currentEvent instanceof VehicleMoveEvent) { + cause = "VEHICLE"; + } + else if (currentEvent instanceof PlayerMoveEvent) { + cause = "WALK"; + } + else { + cause = "UNKNOWN"; + } + yield new ElementTag(cause, true); } - else { - return new LocationTag(currentEntity.getLocation()); + case "to" -> to != null ? new LocationTag(to) : null; + case "from" -> { + if (currentEvent instanceof PlayerMoveEvent playerMove) { + yield new LocationTag(playerMove.getFrom()); + } + else if (currentEvent instanceof VehicleMoveEvent vehicleMove) { + yield new LocationTag(vehicleMove.getFrom()); + } + else { + yield new LocationTag(currentEntity.getLocation()); + } } - } - else if (name.equals("entity")) { - return currentEntity.getDenizenObject(); - } - return super.getContext(name); + case "entity" -> currentEntity.getDenizenObject(); + default -> super.getContext(name); + }; } public void registerCorrectClass() { @@ -178,7 +175,7 @@ else if (!needsMatchers && (matcher instanceof ExactMatchHelper)) { } exactTracked = needsMatchers ? null : exacts.toArray(new String[0]); matchers = needsMatchers ? matchList.toArray(new MatchHelper[0]) : null; - flagTracked = flags.size() > 0 ? flags.toArray(new String[0]) : null; + flagTracked = !flags.isEmpty() ? flags.toArray(new String[0]) : null; registerCorrectClass(); } @@ -258,7 +255,7 @@ public void processNewPosition(EntityTag entity, Location pos, Event eventCause) if (doTrackAll || matchers != null || flagTracked != null) { if (pos != null) { NotedAreaTracker.forEachAreaThatContains(new LocationTag(pos), (a) -> { - if (a instanceof FlaggableObject && anyMatch(a.getNoteName(), (FlaggableObject) a)) { + if (a instanceof FlaggableObject flaggable && anyMatch(a.getNoteName(), flaggable)) { processSingle(a, entity, inAreas, pos, eventCause); } }); @@ -279,11 +276,11 @@ public void processNewPosition(EntityTag entity, Location pos, Event eventCause) else { for (String name : exactTracked) { Notable obj = NoteManager.getSavedObject(name); - if (!(obj instanceof AreaContainmentObject)) { + if (!(obj instanceof AreaContainmentObject areaObject)) { Debug.echoError("Invalid area enter/exit event area '" + name + "'"); continue; } - processSingle((AreaContainmentObject) obj, entity, inAreas, pos, eventCause); + processSingle(areaObject, entity, inAreas, pos, eventCause); } } if (inAreas != null && inAreas.isEmpty()) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/CreeperPoweredScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/CreeperPoweredScriptEvent.java index 3eaee2ea7a..5c81f08581 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/CreeperPoweredScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/CreeperPoweredScriptEvent.java @@ -53,16 +53,12 @@ public boolean matches(ScriptPath path) { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("lightning") && lightning != null) { - return lightning; - } - else if (name.equals("cause")) { - return cause; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity; + case "lightning" -> lightning; + case "cause" -> cause; + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityBreaksHangingScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityBreaksHangingScriptEvent.java index aa4a142ce0..3a9b087522 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityBreaksHangingScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityBreaksHangingScriptEvent.java @@ -88,17 +88,13 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "cause": - return cause; - case "breaker": - return breaker; - case "hanging": - return hanging; - case "location": - return location; - } - return super.getContext(name); + return switch (name) { + case "cause" -> cause; + case "breaker" -> breaker.getDenizenObject(); + case "hanging" -> hanging; + case "location" -> location; + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesBlockScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesBlockScriptEvent.java index 873ccd4de9..93b1641206 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesBlockScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesBlockScriptEvent.java @@ -42,8 +42,8 @@ public EntityChangesBlockScriptEvent() { public EntityTag entity; public LocationTag location; - public MaterialTag old_material; - public MaterialTag new_material; + public MaterialTag oldMaterial; + public MaterialTag newMaterial; public EntityChangeBlockEvent event; @Override @@ -52,7 +52,7 @@ public boolean matches(ScriptPath path) { if (!entity.tryAdvancedMatcher(entName, path.context)) { return false; } - if (!path.tryArgObject(2, old_material)) { + if (!path.tryArgObject(2, oldMaterial)) { return false; } if (path.eventArgLowerAt(3).equals("into")) { @@ -61,7 +61,7 @@ public boolean matches(ScriptPath path) { Debug.echoError("Invalid event material [" + getName() + "]: '" + path.event + "' for " + path.container.getName()); return false; } - else if (!new_material.tryAdvancedMatcher(mat2, path.context)) { + else if (!newMaterial.tryAdvancedMatcher(mat2, path.context)) { return false; } } @@ -78,25 +78,21 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "entity": - return entity; - case "location": - return location; - case "new_material": - return new_material; - case "old_material": - return old_material; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "location" -> location; + case "new_material" -> newMaterial; + case "old_material" -> oldMaterial; + default -> super.getContext(name); + }; } @EventHandler public void onEntityChangesBlock(EntityChangeBlockEvent event) { entity = new EntityTag(event.getEntity()); location = new LocationTag(event.getBlock().getLocation()); - old_material = new MaterialTag(location.getBlock()); - new_material = new MaterialTag(event.getTo()); + oldMaterial = new MaterialTag(location.getBlock()); + newMaterial = new MaterialTag(event.getTo()); this.event = event; fire(event); } diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesPoseScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesPoseScriptEvent.java index a543c74c62..ed6906b1e3 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesPoseScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesPoseScriptEvent.java @@ -68,15 +68,12 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "entity": - return entity; - case "old_pose": - return new ElementTag(oldPose); - case "new_pose": - return new ElementTag(event.getPose()); - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "old_pose" -> new ElementTag(oldPose); + case "new_pose" -> new ElementTag(event.getPose()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCombustsScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCombustsScriptEvent.java index 74f951aabf..4813091b89 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCombustsScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCombustsScriptEvent.java @@ -80,32 +80,24 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "entity": - return entity.getDenizenObject(); - case "duration": - return new DurationTag(event.getDuration()); - case "source": - if (event instanceof EntityCombustByEntityEvent) { - return new EntityTag(((EntityCombustByEntityEvent) event).getCombuster()).getDenizenObject(); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "duration" -> new DurationTag(event.getDuration()); + case "source" -> { + if (event instanceof EntityCombustByEntityEvent byEntityEvent) { + yield new EntityTag(byEntityEvent.getCombuster()).getDenizenObject(); } - else if (event instanceof EntityCombustByBlockEvent) { - Block combuster = ((EntityCombustByBlockEvent) event).getCombuster(); + else if (event instanceof EntityCombustByBlockEvent byBlockEvent) { + Block combuster = byBlockEvent.getCombuster(); if (combuster != null) { - return new LocationTag(combuster.getLocation()); + yield new LocationTag(combuster.getLocation()); } } - break; - case "source_type": - if (event instanceof EntityCombustByEntityEvent) { - return new ElementTag("ENTITY"); - } - else if (event instanceof EntityCombustByBlockEvent) { - return new ElementTag("LOCATION"); - } - return new ElementTag("NONE"); - } - return super.getContext(name); + yield null; + } + case "source_type" -> new ElementTag(event instanceof EntityCombustByEntityEvent ? "ENTITY" : (event instanceof EntityCombustByBlockEvent ? "LOCATION" : "NONE")); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCreatePortalScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCreatePortalScriptEvent.java index b07b613933..2a18c24c7c 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCreatePortalScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCreatePortalScriptEvent.java @@ -1,14 +1,13 @@ package com.denizenscript.denizen.events.entity; -import com.denizenscript.denizen.objects.EntityTag; -import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.objects.LocationTag; -import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; 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 org.bukkit.block.BlockState; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityCreatePortalEvent; @@ -61,19 +60,12 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "entity": - return entity; - case "portal_type": - return new ElementTag(event.getPortalType().toString()); - case "blocks": - ListTag blocks = new ListTag(); - for (BlockState block : event.getBlocks()) { - blocks.add(new LocationTag(block.getBlock().getLocation()).identifySimple()); - } - return blocks; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "portal_type" -> new ElementTag(event.getPortalType()); + case "blocks" -> new ListTag(event.getBlocks(), block -> new LocationTag(block.getLocation())); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDespawnScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDespawnScriptEvent.java index e39a215822..ab32db8e67 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDespawnScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDespawnScriptEvent.java @@ -63,12 +63,10 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("cause")) { - return cause; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity; + case "cause" -> cause; + default -> super.getContext(name); + }; } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDropsItemScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDropsItemScriptEvent.java index 21942ffb38..55184b440a 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDropsItemScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDropsItemScriptEvent.java @@ -68,17 +68,13 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "item": - return item; - case "entity": - return itemEntity; - case "dropped_by": - return dropper.getDenizenObject(); - case "location": - return location; - } - return super.getContext(name); + return switch (name) { + case "item" -> item; + case "entity" -> itemEntity; + case "dropped_by" -> dropper.getDenizenObject(); + case "location" -> location; + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersPortalScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersPortalScriptEvent.java index 40799e70a8..e08bb23c75 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersPortalScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersPortalScriptEvent.java @@ -59,13 +59,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("location")) { - return location; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "location" -> location; + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersVehicleScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersVehicleScriptEvent.java index 6fefc81e8f..dae5c17519 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersVehicleScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersVehicleScriptEvent.java @@ -79,13 +79,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("vehicle")) { - return vehicle.getDenizenObject(); - } - else if (name.equals("entity")) { - return entity.getDenizenObject(); - } - return super.getContext(name); + return switch (name) { + case "vehicle" -> vehicle.getDenizenObject(); + case "entity" -> entity.getDenizenObject(); + default -> super.getContext(name); + }; } @Override diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsPortalScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsPortalScriptEvent.java index 211b259437..6bcaf28b5e 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsPortalScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsPortalScriptEvent.java @@ -59,13 +59,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("location")) { - return location; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "location" -> location; + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsVehicleScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsVehicleScriptEvent.java index 01902181df..5af1f56a4a 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsVehicleScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsVehicleScriptEvent.java @@ -63,13 +63,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("vehicle")) { - return vehicle.getDenizenObject(); - } - else if (name.equals("entity")) { - return entity.getDenizenObject(); - } - return super.getContext(name); + return switch (name) { + case "vehicle" -> vehicle.getDenizenObject(); + case "entity" -> entity.getDenizenObject(); + default -> super.getContext(name); + }; } @Override diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExplosionPrimesScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExplosionPrimesScriptEvent.java index 930b99010f..d135e460b7 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExplosionPrimesScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExplosionPrimesScriptEvent.java @@ -2,9 +2,8 @@ import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.events.BukkitScriptEvent; -import com.denizenscript.denizencore.objects.Argument; +import com.denizenscript.denizen.utilities.BukkitImplDeprecations; import com.denizenscript.denizencore.objects.core.ElementTag; -import com.denizenscript.denizencore.objects.ArgumentHelper; import com.denizenscript.denizencore.objects.ObjectTag; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -25,13 +24,36 @@ public class EntityExplosionPrimesScriptEvent extends BukkitScriptEvent implemen // @Triggers when an entity decides to explode. // // @Context - // returns the EntityTag. - // returns an ElementTag of the explosion's radius. - // returns an ElementTag with a value of "true" if the explosion will create fire and "false" otherwise. + // returns an EntityTag of the exploding entity. + // returns the explosion's radius. + // returns whether the explosion will create fire. + // + // @Determine + // ElementTag(Decimal) to change the explosion radius. + // "FIRE:" to set whether the explosion will produce fire. // --> public EntityExplosionPrimesScriptEvent() { registerCouldMatcher(" explosion primes"); + this.registerOptionalDetermination(null, ElementTag.class, (evt, context, value) -> { + if (value.isFloat()) { + evt.event.setRadius(value.asFloat()); + return true; + } + if (value.isBoolean()) { + BukkitImplDeprecations.explosionPrimeDetermination.warn(); + evt.event.setFire(value.asBoolean()); + return true; + } + return false; + }); + this.registerOptionalDetermination("fire", ElementTag.class, (evt, context, value) -> { + if (value.isBoolean()) { + evt.event.setFire(value.asBoolean()); + return true; + } + return false; + }); } public EntityTag entity; @@ -48,31 +70,14 @@ public boolean matches(ScriptPath path) { return super.matches(path); } - @Override - public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { - String determination = determinationObj.toString(); - if (ArgumentHelper.matchesDouble(determination)) { - event.setRadius(Float.parseFloat(determination)); - return true; - } - if (Argument.valueOf(determination).matchesBoolean()) { - event.setFire(determination.equalsIgnoreCase("true")); - return true; - } - return super.applyDetermination(path, determinationObj); - } - @Override public ObjectTag getContext(String name) { - switch (name) { - case "entity": - return entity; - case "radius": - return new ElementTag(event.getRadius()); - case "fire": - return new ElementTag(event.getFire()); - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "radius" -> new ElementTag(event.getRadius()); + case "fire" -> new ElementTag(event.getFire()); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityFormsBlockScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityFormsBlockScriptEvent.java index b55d57fdc7..c282dbf1b1 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityFormsBlockScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityFormsBlockScriptEvent.java @@ -1,10 +1,10 @@ package com.denizenscript.denizen.events.entity; +import com.denizenscript.denizen.events.BukkitScriptEvent; import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizen.objects.MaterialTag; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; -import com.denizenscript.denizen.events.BukkitScriptEvent; import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; import org.bukkit.event.EventHandler; @@ -27,8 +27,8 @@ public class EntityFormsBlockScriptEvent extends BukkitScriptEvent implements Li // For example, when a snowman forms snow. // // @Context - // returns the LocationTag the block. - // returns the MaterialTag of the block. + // returns the LocationTag of the block. + // returns the MaterialTag of what the block will become. // returns the EntityTag that formed the block. // // --> @@ -63,21 +63,18 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "location": - return location; - case "material": - return material; - case "entity": - return entity; - } - return super.getContext(name); + return switch (name) { + case "location" -> location; + case "material" -> material; + case "entity" -> entity; + default -> super.getContext(name); + }; } @EventHandler public void onEntityFormsBlock(EntityBlockFormEvent event) { location = new LocationTag(event.getBlock().getLocation()); - material = new MaterialTag(event.getBlock()); + material = new MaterialTag(event.getNewState()); entity = new EntityTag(event.getEntity()); this.event = event; fire(event); diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGlideScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGlideScriptEvent.java index b88c3103d2..c139c128cc 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGlideScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGlideScriptEvent.java @@ -66,13 +66,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("state")) { - return new ElementTag(state); - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "state" -> new ElementTag(state); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGoesIntoBlockScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGoesIntoBlockScriptEvent.java index 957a336fd3..6acd83ccbf 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGoesIntoBlockScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGoesIntoBlockScriptEvent.java @@ -63,12 +63,12 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - switch (name) { - case "entity": return entity.getDenizenObject(); - case "location": return location; - case "material": return material; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "location" -> location; + case "material" -> material; + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityInteractScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityInteractScriptEvent.java index 404cf77b40..8c433ab55c 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityInteractScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityInteractScriptEvent.java @@ -61,13 +61,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("location")) { - return location; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "location" -> location; + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySpawnScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySpawnScriptEvent.java index 0ac74a8b35..6ca957840a 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySpawnScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySpawnScriptEvent.java @@ -1,11 +1,11 @@ package com.denizenscript.denizen.events.entity; +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.denizen.events.BukkitScriptEvent; -import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; import org.bukkit.entity.Entity; import org.bukkit.event.EventHandler; @@ -79,19 +79,13 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("location")) { - return location; - } - else if (name.equals("reason")) { - return reason; - } - else if (name.equals("spawner_location") && event instanceof SpawnerSpawnEvent) { - return new LocationTag(((SpawnerSpawnEvent) event).getSpawner().getLocation()); - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity; + case "location" -> location; + case "reason" -> reason; + case "spawner_location" -> event instanceof SpawnerSpawnEvent spawnerEvent ? new LocationTag(spawnerEvent.getSpawner().getLocation()) : null; + default -> super.getContext(name); + }; } @EventHandler @@ -99,8 +93,8 @@ public void onEntitySpawn(EntitySpawnEvent event) { Entity entity = event.getEntity(); this.entity = new EntityTag(entity); location = new LocationTag(event.getLocation()); - if (event instanceof CreatureSpawnEvent) { - CreatureSpawnEvent.SpawnReason creatureReason = ((CreatureSpawnEvent) event).getSpawnReason(); + if (event instanceof CreatureSpawnEvent creatureSpawnEvent) { + CreatureSpawnEvent.SpawnReason creatureReason = creatureSpawnEvent.getSpawnReason(); if (creatureReason == CreatureSpawnEvent.SpawnReason.SPAWNER) { return; // Let the SpawnerSpawnEvent happen and handle it instead } diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySwimScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySwimScriptEvent.java index f189083ad9..9f9199d0af 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySwimScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySwimScriptEvent.java @@ -66,13 +66,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("state")) { - return new ElementTag(state); - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity.getDenizenObject(); + case "state" -> new ElementTag(state); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityTamesScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityTamesScriptEvent.java index 10881babc4..8c287f3983 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityTamesScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityTamesScriptEvent.java @@ -19,8 +19,6 @@ public class EntityTamesScriptEvent extends BukkitScriptEvent implements Listene // player tames entity // player tames // - // @Regex ^on [^\s]+ (tames [^\s]+|tamed)$ - // // @Group Entity // // @Location true @@ -33,28 +31,19 @@ public class EntityTamesScriptEvent extends BukkitScriptEvent implements Listene // returns a EntityTag of the tamed entity. // returns a EntityTag of the owner. // - // @Player when a player tames an entity and using the 'players tames entity' event. + // @Player when a player is what tamed the entity. // // --> public EntityTamesScriptEvent() { + registerCouldMatcher(" tamed"); + registerCouldMatcher("player tames "); } public EntityTag entity; public EntityTag owner; public EntityTameEvent event; - @Override - public boolean couldMatch(ScriptPath path) { - if (!path.eventArgLowerAt(1).equals("tames") && !path.eventArgLowerAt(1).equals("tamed")) { - return false; - } - if (!couldMatchEntity(path.eventArgLowerAt(0))) { - return false; - } - return true; - } - @Override public boolean matches(ScriptPath path) { String cmd = path.eventArgLowerAt(1); @@ -76,13 +65,11 @@ public ScriptEntryData getScriptEntryData() { @Override public ObjectTag getContext(String name) { - if (name.equals("entity")) { - return entity; - } - else if (name.equals("owner")) { - return owner; - } - return super.getContext(name); + return switch (name) { + case "entity" -> entity; + case "owner" -> owner.getDenizenObject(); + default -> super.getContext(name); + }; } @EventHandler diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreparesEnchantScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreparesEnchantScriptEvent.java index af97ebd94b..f6a75c4125 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreparesEnchantScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreparesEnchantScriptEvent.java @@ -77,6 +77,10 @@ public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) { } for (int i = 0; i < offers.size(); i++) { MapTag map = MapTag.getMapFor(offers.getObject(i), getTagContext(path)); + if (map.isEmpty()) { + event.getOffers()[i] = null; + continue; + } event.getOffers()[i].setCost(map.getElement("cost").asInt()); EnchantmentTag enchantment = map.getObjectAs("enchantment_type", EnchantmentTag.class, getTagContext(path)); if (enchantment == null) { @@ -108,6 +112,10 @@ public ObjectTag getContext(String name) { case "offers": ListTag output = new ListTag(); for (EnchantmentOffer offer : event.getOffers()) { + if (offer == null) { + output.addObject(new MapTag()); + continue; + } MapTag map = new MapTag(); map.putObject("cost", new ElementTag(offer.getCost())); map.putObject("enchantment", new ElementTag(offer.getEnchantment().getKey().getKey())); diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRaiseLowerItemScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRaiseLowerItemScriptEvent.java index 0d17a6c1ed..c727a00188 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRaiseLowerItemScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRaiseLowerItemScriptEvent.java @@ -1,6 +1,8 @@ package com.denizenscript.denizen.events.player; import com.denizenscript.denizen.events.BukkitScriptEvent; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.ItemTag; import com.denizenscript.denizen.objects.PlayerTag; import com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData; @@ -9,19 +11,14 @@ import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntryData; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerItemHeldEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerSwapHandItemsEvent; +import org.bukkit.event.player.*; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; +import java.util.*; public class PlayerRaiseLowerItemScriptEvent extends BukkitScriptEvent implements Listener { @@ -29,7 +26,7 @@ public class PlayerRaiseLowerItemScriptEvent extends BukkitScriptEvent implement // @Events // player raises|lowers|toggles // - // @Synonyms player raises shield, player raises spyglass + // @Synonyms player raises shield, player raises spyglass, player raises spear // // @Group Player // @@ -55,6 +52,12 @@ public class PlayerRaiseLowerItemScriptEvent extends BukkitScriptEvent implement public static final EnumSet raisableItems = EnumSet.of(Material.SHIELD, Material.CROSSBOW, Material.BOW, Material.TRIDENT, Material.SPYGLASS); + static { + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + raisableItems.addAll(Tag.ITEMS_SPEARS.getValues()); + } + } + public PlayerRaiseLowerItemScriptEvent() { registerCouldMatcher("player raises|lowers|toggles "); registerSwitches("reason"); diff --git a/plugin/src/main/java/com/denizenscript/denizen/events/server/ListPingScriptEvent.java b/plugin/src/main/java/com/denizenscript/denizen/events/server/ListPingScriptEvent.java index 0e8e554d2c..5f704f67c8 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/events/server/ListPingScriptEvent.java +++ b/plugin/src/main/java/com/denizenscript/denizen/events/server/ListPingScriptEvent.java @@ -48,6 +48,7 @@ public class ListPingScriptEvent extends BukkitScriptEvent implements Listener { // "VERSION_NAME:" to change the server's version name (only on Paper). // "EXCLUDE_PLAYERS:" to exclude a set of players from showing in the player count or preview of online players (only on Paper). // "ALTERNATE_PLAYER_TEXT:" to set custom text for the player list section of the server status (only on Paper). (Requires "Allow restricted actions" in Denizen/config.yml). Usage of this to present lines that look like player names (but aren't) is forbidden. + // "PLAYER_COUNT:" to set the amount of players that are online (only on Paper). (Requires "Allow restricted actions" in Denizen/config.yml). Usage of this to display a higher number of players than are actually connected is forbidden. // "MOTD:" to change the MOTD that will show. // // --> diff --git a/plugin/src/main/java/com/denizenscript/denizen/nms/NMSVersion.java b/plugin/src/main/java/com/denizenscript/denizen/nms/NMSVersion.java index 8b75de4b81..22a7edece8 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/nms/NMSVersion.java +++ b/plugin/src/main/java/com/denizenscript/denizen/nms/NMSVersion.java @@ -7,7 +7,9 @@ public enum NMSVersion { v1_18("1.18"), v1_19("1.19"), v1_20("1.20"), - v1_21("1.21"); + v1_21("1.21"), + v26_1("26.1"), + v26_2("26.2"); final String minecraftVersion; diff --git a/plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/ImprovedOfflinePlayer.java b/plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/ImprovedOfflinePlayer.java index 6e6b834478..f031a63c31 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/ImprovedOfflinePlayer.java +++ b/plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/ImprovedOfflinePlayer.java @@ -204,7 +204,7 @@ public void setAbsorptionAmount(float input) { } public void setBedSpawnLocation(Location location) { - if (location == null && !compound.keySet().contains("SpawnDimension")) { + if (location == null && !compound.contains("SpawnDimension")) { return; } CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder().put(compound); diff --git a/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/EntityHelper.java b/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/EntityHelper.java index 0501930cd7..f18202f90e 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/EntityHelper.java +++ b/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/EntityHelper.java @@ -5,6 +5,7 @@ import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizen.objects.properties.entity.EntityState; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.MapTag; import net.kyori.adventure.nbt.CompoundBinaryTag; @@ -514,4 +515,12 @@ public void onEntityMount(EntityDismountEvent event) { public Class getExitsVehicleEventImpl() { // TODO: once 1.20 is the minimum supported version, implement in the ScriptEvent class as usual return EntityExitsVehicleScriptEventImpl.class; } + + public EntityState.ArmadilloState getArmadilloState(Armadillo entity) { + throw new UnsupportedOperationException(); + } + + public void setArmadilloState(Armadillo entity, EntityState.ArmadilloState state) { + throw new UnsupportedOperationException(); + } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/ItemHelper.java b/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/ItemHelper.java index 4ae68a5f7f..f14e1255d6 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/ItemHelper.java +++ b/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/ItemHelper.java @@ -85,8 +85,7 @@ public CompoundBinaryTag getEntityData(ItemStack item) { // TODO: once 1.20 is t public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) { // TODO: once 1.20 is the minimum supported version, remove default impl boolean shouldRemove = entityNbt == null || entityNbt.isEmpty(); CompoundBinaryTag nbt = getNbtData(item); - // TODO: adventure-nbt: contains - if (shouldRemove && !nbt.keySet().contains("EntityTag")) { + if (shouldRemove && !nbt.contains("EntityTag")) { return item; } if (shouldRemove) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/BiomeTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/BiomeTag.java index d0f02e6750..e8b2e53caa 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/BiomeTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/BiomeTag.java @@ -508,7 +508,7 @@ else if (attribute.startsWith("water", 2)) { }); } - public static final ObjectTagProcessor tagProcessor = new ObjectTagProcessor<>(); + public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor<>(); @Override public ObjectTag getObjectAttribute(Attribute attribute) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/ChunkTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/ChunkTag.java index d2b8ba213b..28819cb6cf 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/ChunkTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/ChunkTag.java @@ -1,14 +1,13 @@ package com.denizenscript.denizen.objects; +import com.denizenscript.denizen.nms.NMSHandler; import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.utilities.BukkitImplDeprecations; -import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizen.utilities.flags.DataPersistenceFlagTracker; import com.denizenscript.denizen.utilities.flags.LocationFlagSearchHelper; import com.denizenscript.denizencore.flags.AbstractFlagTracker; import com.denizenscript.denizencore.flags.FlaggableObject; import com.denizenscript.denizencore.objects.*; -import com.denizenscript.denizen.nms.NMSHandler; import com.denizenscript.denizencore.objects.core.DurationTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; @@ -16,6 +15,7 @@ import com.denizenscript.denizencore.tags.ObjectTagProcessor; import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.Debug; import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; import org.bukkit.Location; @@ -26,7 +26,8 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import java.util.*; +import java.util.ArrayList; +import java.util.List; public class ChunkTag implements ObjectTag, Adjustable, FlaggableObject { @@ -889,15 +890,21 @@ public void adjust(Mechanism mechanism) { // <--[mechanism] // @object ChunkTag // @name regenerate + // @deprecated functionality was removed from Spigot and Paper as of MC 1.21. // @input None // @description + // Deprecated on MC 1.21+. // Causes the chunk to be entirely deleted and reformed from the world's seed. - // At time of writing this method only works as expected on Paper, and will error on Spigot. // @example // - adjust regenerate // --> if (mechanism.matches("regenerate")) { - getBukkitWorld().regenerateChunk(getX(), getZ()); + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_20)) { + getBukkitWorld().regenerateChunk(getX(), getZ()); + } + else { + BukkitImplDeprecations.chunkRegeneration.warn(mechanism.context); + } } // <--[mechanism] diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java index 81a797a550..a31fef84f7 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java @@ -100,6 +100,7 @@ public class EntityTag implements ObjectTag, Adjustable, EntityFormObject, Flagg // "projectile" plaintext: matches for any projectile type (arrow, trident, fish hook, snowball, etc). // "hanging" plaintext: matches for any hanging type (painting, item_frame, etc). // "monster" plaintext: matches for any monster type (creepers, zombies, etc). + // "enemy" plaintext: matches for any hostile entities (zombies, shulkers, ect). // "animal" plaintext: matches for any animal type (pigs, cows, etc). // "mob" plaintext: matches for any mob type (creepers, pigs, etc). // "living" plaintext: matches for any living type (players, pigs, creepers, etc). @@ -610,6 +611,13 @@ public boolean isMonsterType() { return getBukkitEntity() instanceof Monster; } + public boolean isEnemyType() { + if (getBukkitEntity() == null && entity_type != null) { + return Enemy.class.isAssignableFrom(entity_type.getBukkitEntityType().getEntityClass()); + } + return getBukkitEntity() instanceof Enemy; + } + public boolean isMobType() { if (getBukkitEntity() == null && entity_type != null) { return Mob.class.isAssignableFrom(entity_type.getBukkitEntityType().getEntityClass()); @@ -2372,6 +2380,20 @@ else if (object.getBukkitEntity() instanceof Hanging hanging) { return new ElementTag(object.isMonsterType()); }); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { + + // <--[tag] + // @attribute + // @returns ElementTag(Boolean) + // @group data + // @description + // Returns whether the entity type is an enemy. See <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Enemy.html> + // --> + tagProcessor.registerTag(ElementTag.class, "is_enemy", (attribute, object) -> { + return new ElementTag(object.isEnemyType()); + }); + } + // <--[tag] // @attribute // @returns ElementTag(Boolean) @@ -3223,6 +3245,29 @@ else if (object.getBukkitEntity() instanceof Hanging hanging) { fox.setSleeping(input.asBoolean()); }); + // <--[mechanism] + // @object EntityTag + // @name detonate + // @input None + // @description + // If the entity is a firework, creeper, or wind charge, detonates it. + // --> + tagProcessor.registerMechanism("detonate", false, (object, mechanism) -> { + Entity entity = object.getBukkitEntity(); + if (entity instanceof Firework firework) { + firework.detonate(); + } + else if (entity instanceof Creeper creeper) { + creeper.explode(); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && entity instanceof AbstractWindCharge windCharge) { + windCharge.explode(); + } + else { + mechanism.echoError("Cannot detonate entity of type '" + object.getEntityType() + "'."); + } + }); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { // <--[mechanism] @@ -4130,28 +4175,6 @@ public void adjust(Mechanism mechanism) { getLivingEntity().setSwimming(mechanism.getValue().asBoolean()); } - // <--[mechanism] - // @object EntityTag - // @name detonate - // @input None - // @description - // If the entity is a firework, creeper, or wind charge, detonates it. - // --> - if (mechanism.matches("detonate")) { - if (getBukkitEntity() instanceof Firework firework) { - firework.detonate(); - } - else if (getBukkitEntity() instanceof Creeper creeper) { - creeper.explode(); - } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBukkitEntity() instanceof WindCharge windCharge) { - windCharge.explode(); - } - else { - Debug.echoError("Cannot detonate entity of type '" + getBukkitEntityType().name() + "'."); - } - } - // <--[mechanism] // @object EntityTag // @name ignite @@ -4586,6 +4609,8 @@ public final boolean trySpecialEntityMatcher(String text, boolean isNPC) { return isMobType(); case "animal": return isAnimalType(); + case "enemy": + return isEnemyType(); } return false; } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/ItemTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/ItemTag.java index f6143fa045..87b2d98b98 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/ItemTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/ItemTag.java @@ -856,12 +856,13 @@ public boolean advancedMatches(String matcher, TagContext context) { return BukkitScriptEvent.coreFlaggedCheck(matcher.substring("item_flagged:".length()), getFlagTracker()); } else if (matcherLow.startsWith("item_enchanted:")) { - String enchMatcher = matcher.substring("item_enchanted:".length()); + String enchMatcherStr = matcher.substring("item_enchanted:".length()); if (getBukkitMaterial().isAir() || !getItemMeta().hasEnchants()) { return false; } + ScriptEvent.MatchHelper enchMatcher = ScriptEvent.createMatcher(enchMatcherStr); for (Enchantment enchant : getItemMeta().getEnchants().keySet()) { - if (BukkitScriptEvent.runGenericCheck(enchMatcher, enchant.getKey().getKey())) { + if (enchMatcher.doesMatch(enchant.getKey().getKey()) || enchMatcher.doesMatch(enchant.getKey().toString())) { return true; } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java index e686ef7c92..eff8b59faf 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java @@ -11,6 +11,7 @@ import com.denizenscript.denizen.objects.properties.material.MaterialDirectional; import com.denizenscript.denizen.objects.properties.material.MaterialDistance; import com.denizenscript.denizen.objects.properties.material.MaterialHalf; +import com.denizenscript.denizen.scripts.commands.world.SignCommand; import com.denizenscript.denizen.scripts.commands.world.SwitchCommand; import com.denizenscript.denizen.utilities.*; import com.denizenscript.denizen.utilities.blocks.SpawnableHelper; @@ -40,6 +41,7 @@ import org.bukkit.block.banner.PatternType; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Directional; +import org.bukkit.block.sign.Side; import org.bukkit.block.structure.Mirror; import org.bukkit.block.structure.StructureRotation; import org.bukkit.block.structure.UsageMode; @@ -52,8 +54,8 @@ import org.bukkit.material.MaterialData; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; import org.bukkit.util.*; +import org.bukkit.util.Vector; import java.lang.ref.WeakReference; import java.util.*; @@ -1249,14 +1251,14 @@ public static void register() { // @group world // @description // Returns a list of lines on a sign. + // For MC 1.20+, this returns the contents on the front of the sign. + // To get the contents on the back, see <@link tag LocationTag.sign_back_contents>. // --> tagProcessor.registerTag(ListTag.class, "sign_contents", (attribute, object) -> { - if (object.getBlockStateForTag(attribute) instanceof Sign) { - return new ListTag(Arrays.asList(PaperAPITools.instance.getSignLines(((Sign) object.getBlockStateForTag(attribute))))); - } - else { - return null; + if (object.getBlockStateForTag(attribute) instanceof Sign sign) { + return new ListTag(PaperAPITools.instance.getSignLines(sign), true); } + return null; }); // <--[tag] @@ -4156,14 +4158,15 @@ else if (material.hasModernData() && material.getModernData() instanceof org.buk // @group world // @description // Returns whether the location is a Sign block that is glowing. + // For MC 1.20+, this returns the glowing status of the front of the sign. + // To get the glowing state of the back, see <@link tag LocationTag.sign_back_glowing>. // --> tagProcessor.registerTag(ElementTag.class, "sign_glowing", (attribute, object) -> { - BlockState state = object.getBlockStateForTag(attribute); - if (!(state instanceof Sign)) { + if (!(object.getBlockStateForTag(attribute) instanceof Sign sign)) { attribute.echoError("Location is not a valid Sign block."); return null; } - return new ElementTag(((Sign) state).isGlowingText()); + return new ElementTag(SignCommand.SIGN_SIDES_SUPPORTED ? sign.getSide(Side.FRONT).isGlowingText() : sign.isGlowingText()); }); // <--[tag] @@ -4172,16 +4175,17 @@ else if (material.hasModernData() && material.getModernData() instanceof org.buk // @mechanism LocationTag.sign_glow_color // @group world // @description - // Returns the name of the glow-color of the sign at the location. + // Returns the name of the glow color of the sign at the location. + // For MC 1.20+, this returns the color on the front of the sign. + // To get the color of the back, see <@link tag LocationTag.sign_back_glow_color>. // See also <@link tag LocationTag.sign_glowing> // --> tagProcessor.registerTag(ElementTag.class, "sign_glow_color", (attribute, object) -> { - BlockState state = object.getBlockStateForTag(attribute); - if (!(state instanceof Sign)) { + if (!(object.getBlockStateForTag(attribute) instanceof Sign sign)) { attribute.echoError("Location is not a valid Sign block."); return null; } - return new ElementTag(((Sign) state).getColor()); + return new ElementTag(SignCommand.SIGN_SIDES_SUPPORTED ? sign.getSide(Side.FRONT).getColor() : sign.getColor()); }); // <--[tag] @@ -4507,6 +4511,193 @@ else if (material.hasModernData() && material.getModernData() instanceof org.buk } return new ElementTag(chiseledBookshelf.getSlot(input.toVector()) + 1); }); + + // <--[tag] + // @attribute + // @returns ListTag + // @mechanism LocationTag.disabled_slots + // @group world + // @description + // Returns which slots in a crafter are disabled. + // The slots are arranged from left to right, top to bottom. + // --> + tagProcessor.registerTag(ListTag.class, "disabled_slots", (attribute, object) -> { + if (!(object.getBlockStateForTag(attribute) instanceof Crafter crafter)) { + attribute.echoError("The 'LocationTag.disabled_slots' tag can only be called on a crafter block."); + return null; + } + ListTag slots = new ListTag(); + for (int i = 0; i <= 8; i++) { + if (crafter.isSlotDisabled(i)) { + slots.addObject(new ElementTag(i + 1)); + } + } + return slots; + }); + + // <--[mechanism] + // @object LocationTag + // @name disabled_slots + // @input ListTag + // @description + // Sets which slots in a crafter are disabled. + // The slots are arranged from left to right, top to bottom. + // Provide no input to enable all slots. + // @tags + // + // @example + // # Disables the slots in the top left and middle right + // - adjustblock <[location]> disabled_slots:1|6 + // --> + tagProcessor.registerMechanism("disabled_slots", false, ListTag.class, (object, mechanism, input) -> { + if (!(object.getBlockState() instanceof Crafter crafter)) { + mechanism.echoError("The 'LocationTag.disabled_slots' mechanism can only be called on a crafter block."); + return; + } + for (int i = 0; i <= 8; i++) { + crafter.setSlotDisabled(i, false); + } + for (String slot : input) { + ElementTag element = new ElementTag(slot); + if (!element.isInt()) { + mechanism.echoError("Invalid slot '" + slot + "' specified: must be an integer."); + continue; + } + int value = element.asInt(); + if (value < 1 || value > 9) { + mechanism.echoError("Invalid slot '" + slot + "' specified: must between 1 and 9."); + continue; + } + crafter.setSlotDisabled(value - 1, true); + } + crafter.update(); + }); + + // <--[tag] + // @attribute + // @returns ListTag + // @mechanism LocationTag.sign_back_contents + // @group world + // @description + // Returns the contents on the back of a sign block. + // For the contents on the front, see <@link tag LocationTag.sign_contents>. + // --> + tagProcessor.registerTag(ListTag.class, "sign_back_contents", (attribute, object) -> { + if (object.getBlockStateForTag(attribute) instanceof Sign sign) { + return new ListTag(PaperAPITools.instance.getSignBackLines(sign), true); + } + return null; + }); + + // <--[mechanism] + // @object LocationTag + // @name sign_back_contents + // @input ListTag + // @description + // Sets the contents on the back of a sign block. + // To set the contents of the front, see <@link mechanism LocationTag.sign_contents>. + // @tags + // + // --> + tagProcessor.registerMechanism("sign_back_contents", false, ListTag.class, (object, mechanism, input) -> { + if (!(object.getBlockState() instanceof Sign sign)) { + mechanism.echoError("This mechanism is only valid for Sign blocks."); + return; + } + for (int i = 0; i < 4; i++) { + PaperAPITools.instance.setSignBackLine(sign, i, ""); + } + CoreUtilities.fixNewLinesToListSeparation(input); + if (input.size() > 4) { + mechanism.echoError("Sign can only hold four lines!"); + } + else { + for (int i = 0; i < input.size(); i++) { + PaperAPITools.instance.setSignBackLine(sign, i, input.get(i)); + } + } + sign.update(); + }); + + // <--[tag] + // @attribute + // @returns ElementTag(Boolean) + // @mechanism LocationTag.sign_back_glowing + // @group world + // @description + // Returns whether the back of a Sign block at this location is glowing. + // To get the glowing state of the front, see <@link tag LocationTag.sign_glowing>. + // --> + tagProcessor.registerTag(ElementTag.class, "sign_back_glowing", (attribute, object) -> { + if (!(object.getBlockStateForTag(attribute) instanceof Sign sign)) { + attribute.echoError("Location is not a valid Sign block."); + return null; + } + return new ElementTag(sign.getSide(Side.BACK).isGlowingText()); + }); + + // <--[mechanism] + // @object LocationTag + // @name sign_back_glowing + // @input ElementTag(Boolean) + // @description + // Changes whether the back of the sign at the location is glowing. + // To set the glowing state of the front, see <@link mechanism LocationTag.sign_glowing>. + // @tags + // + // + // --> + tagProcessor.registerMechanism("sign_back_glowing", false, ElementTag.class, (object, mechanism, input) -> { + if (!(object.getBlockState() instanceof Sign sign)) { + mechanism.echoError("This mechanism can only be called on Sign blocks."); + } + else if (mechanism.requireBoolean()) { + sign.getSide(Side.BACK).setGlowingText(input.asBoolean()); + sign.update(); + } + }); + + // <--[tag] + // @attribute + // @returns ElementTag + // @mechanism LocationTag.sign_back_glow_color + // @group world + // @description + // Returns the name of the glow color on the back of the sign at the location. + // To get the color of the front, see <@link tag LocationTag.sign_glow_color>. + // See also <@link tag LocationTag.sign_back_glowing>. + // --> + tagProcessor.registerTag(ElementTag.class, "sign_back_glow_color", (attribute, object) -> { + if (!(object.getBlockStateForTag(attribute) instanceof Sign sign)) { + attribute.echoError("Location is not a valid Sign block."); + return null; + } + return new ElementTag(sign.getSide(Side.BACK).getColor()); + }); + + // <--[mechanism] + // @object LocationTag + // @name sign_back_glow_color + // @input ElementTag + // @description + // Changes the glow color on the back of a sign. + // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>. + // Use <@link mechanism LocationTag.sign_back_glowing> to toggle whether the sign is glowing. + // If a sign is not glowing, this is equivalent to applying a chat color to the sign. + // To set the color of the front, see <@link mechanism LocationTag.sign_glow_color>. + // @tags + // + // + // --> + tagProcessor.registerMechanism("sign_back_glow_color", false, ElementTag.class, (object, mechanism, input) -> { + if (!(object.getBlockState() instanceof Sign sign)) { + mechanism.echoError("This mechanism can only be called on Sign blocks."); + } + else if (mechanism.requireEnum(DyeColor.class)) { + sign.getSide(Side.BACK).setColor(input.asEnum(DyeColor.class)); + sign.update(); + } + }); } // <--[mechanism] @@ -4574,9 +4765,101 @@ else if (mechanism.requireObject(EntityTag.class)) { mechanism.echoError("The 'LocationTag.page' mechanism can only be called on a lectern block."); } }); + + // <--[mechanism] + // @object LocationTag + // @name sign_contents + // @input ListTag + // @description + // Sets the contents of a sign block. + // For MC 1.20+, this sets the contents on the front of the sign. + // To set the contents of the back, see <@link mechanism LocationTag.sign_back_contents>. + // @tags + // + // --> + tagProcessor.registerMechanism("sign_contents", false, ListTag.class, (object, mechanism, value) -> { + if (!(object.getBlockState() instanceof Sign sign)) { + mechanism.echoError("This mechanism is only valid for Sign blocks."); + return; + } + for (int i = 0; i < 4; i++) { + PaperAPITools.instance.setSignLine(sign, i, ""); + } + CoreUtilities.fixNewLinesToListSeparation(value); + if (value.size() > 4) { + mechanism.echoError("Sign can only hold four lines!"); + } + else { + for (int i = 0; i < value.size(); i++) { + PaperAPITools.instance.setSignLine(sign, i, value.get(i)); + } + } + sign.update(); + }); + + // <--[mechanism] + // @object LocationTag + // @name sign_glowing + // @input ElementTag(Boolean) + // @description + // Changes whether the sign at the location is glowing. + // For MC 1.20+, this sets the glowing status on the front of the sign. + // To set the glowing status of the back, see <@link mechanism LocationTag.sign_back_glowing>. + // @tags + // + // + // --> + tagProcessor.registerMechanism("sign_glowing", false, ElementTag.class, (object, mechanism, input) -> { + if (!mechanism.requireBoolean()) { + return; + } + if (!(object.getBlockState() instanceof Sign sign)) { + mechanism.echoError("This mechanism can only be called on Sign blocks."); + return; + } + if (SignCommand.SIGN_SIDES_SUPPORTED) { + sign.getSide(Side.FRONT).setGlowingText(input.asBoolean()); + } + else { + sign.setGlowingText(input.asBoolean()); + } + sign.update(); + }); + + // <--[mechanism] + // @object LocationTag + // @name sign_glow_color + // @input ElementTag + // @description + // Changes the glow color of a sign. + // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>. + // If a sign is not glowing, this is equivalent to applying a chat color to the sign. + // Use <@link mechanism LocationTag.sign_glowing> to toggle whether the sign is glowing. + // For MC 1.20+, this sets the color on the front of the sign. + // To set the color of the back, see <@link mechanism LocationTag.sign_back_glow_color>. + // @tags + // + // + // --> + tagProcessor.registerMechanism("sign_glow_color", false, ElementTag.class, (object, mechanism, input) -> { + if (!mechanism.requireEnum(DyeColor.class)) { + return; + } + if (!(object.getBlockState() instanceof Sign sign)) { + mechanism.echoError("This mechanism can only be called on Sign blocks."); + return; + } + if (SignCommand.SIGN_SIDES_SUPPORTED) { + sign.getSide(Side.FRONT).setColor(input.asEnum(DyeColor.class)); + } + else { + sign.setColor(input.asEnum(DyeColor.class)); + } + sign.update(); + }); } - public static final ObjectTagProcessor tagProcessor = new ObjectTagProcessor<>(); + public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor<>(); @Override public ObjectTag getObjectAttribute(Attribute attribute) { @@ -4769,33 +5052,6 @@ public void adjust(Mechanism mechanism) { state.update(); } - // <--[mechanism] - // @object LocationTag - // @name sign_contents - // @input ListTag - // @description - // Sets the contents of a sign block. - // @tags - // - // --> - if (mechanism.matches("sign_contents") && getBlockState() instanceof Sign) { - Sign state = (Sign) getBlockState(); - for (int i = 0; i < 4; i++) { - PaperAPITools.instance.setSignLine(state, i, ""); - } - ListTag list = mechanism.valueAsType(ListTag.class); - CoreUtilities.fixNewLinesToListSeparation(list); - if (list.size() > 4) { - mechanism.echoError("Sign can only hold four lines!"); - } - else { - for (int i = 0; i < list.size(); i++) { - PaperAPITools.instance.setSignLine(state, i, list.get(i)); - } - } - state.update(); - } - // <--[mechanism] // @object LocationTag // @name skull_skin @@ -5442,53 +5698,6 @@ else if (state instanceof Dropper) { } } - // <--[mechanism] - // @object LocationTag - // @name sign_glowing - // @input ElementTag(Boolean) - // @description - // Changes whether the sign at the location is glowing. - // @tags - // - // - // --> - if (mechanism.matches("sign_glowing") && mechanism.requireBoolean()) { - BlockState state = getBlockState(); - if (!(state instanceof Sign)) { - mechanism.echoError("'sign_glowing' mechanism can only be called on Sign blocks."); - } - else { - Sign sign = (Sign) state; - sign.setGlowingText(mechanism.getValue().asBoolean()); - sign.update(); - } - } - - // <--[mechanism] - // @object LocationTag - // @name sign_glow_color - // @input ElementTag - // @description - // Changes the glow color of a sign. - // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>. - // If a sign is not glowing, this is equivalent to applying a chat color to the sign. - // Use <@link mechanism LocationTag.sign_glowing> to toggle whether the sign is glowing. - // @tags - // - // - // --> - if (mechanism.matches("sign_glow_color") && mechanism.requireEnum(DyeColor.class)) { - BlockState state = getBlockState(); - if (!(state instanceof Sign)) { - mechanism.echoError("'sign_glow_color' mechanism can only be called on Sign blocks."); - } - else { - Sign sign = (Sign) state; - sign.setColor(mechanism.getValue().asEnum(DyeColor.class)); - sign.update(); - } - } - // <--[mechanism] // @object LocationTag // @name structure_block_data diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java index a99fa9ec70..74e6bc5e56 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java @@ -501,7 +501,7 @@ public static void register() { // --> tagProcessor.registerTag(ElementTag.class, "nickname", (attribute, object) -> { return new ElementTag(object.getCitizen().hasTrait(NicknameTrait.class) ? object.getCitizen().getOrAddTrait(NicknameTrait.class) - .getNickname() : object.getName()); + .getNickname() : object.getName(), true); }); // Documented in EntityTag @@ -510,16 +510,16 @@ public static void register() { BukkitImplDeprecations.npcNicknameTag.warn(attribute.context); attribute.fulfill(1); return new ElementTag(object.getCitizen().hasTrait(NicknameTrait.class) ? object.getCitizen().getOrAddTrait(NicknameTrait.class) - .getNickname() : object.getName()); + .getNickname() : object.getName(), true); } - return new ElementTag(object.getName()); + return new ElementTag(object.getName(), true); }); // <--[tag] // @attribute // @returns ListTag // @description - // Returns a list of all of the NPC's traits. + // Returns a list of all the NPC's traits. // --> tagProcessor.registerTag(ListTag.class, "traits", (attribute, object) -> { List list = new ArrayList<>(); @@ -535,14 +535,9 @@ public static void register() { // @description // Returns whether the NPC has a specified trait. // --> - tagProcessor.registerTag(ElementTag.class, "has_trait", (attribute, object) -> { - if (attribute.hasParam()) { - Class trait = CitizensAPI.getTraitFactory().getTraitClass(attribute.getParam()); - if (trait != null) { - return new ElementTag(object.getCitizen().hasTrait(trait)); - } - } - return null; + tagProcessor.registerTag(ElementTag.class, ElementTag.class, "has_trait", (attribute, object, param) -> { + Class trait = CitizensAPI.getTraitFactory().getTraitClass(param.asString()); + return trait != null ? new ElementTag(object.getCitizen().hasTrait(trait)) : null; }); // <--[tag] @@ -561,15 +556,12 @@ public static void register() { // @description // Returns whether the NPC has a specified trigger. // --> - tagProcessor.registerTag(ElementTag.class, "has_trigger", (attribute, object) -> { - if (!attribute.hasParam()) { - return null; - } + tagProcessor.registerTag(ElementTag.class, ElementTag.class, "has_trigger", (attribute, object, param) -> { if (!object.getCitizen().hasTrait(TriggerTrait.class)) { return new ElementTag(false); } TriggerTrait trait = object.getCitizen().getOrAddTrait(TriggerTrait.class); - return new ElementTag(trait.hasTrigger(attribute.getParam())); + return new ElementTag(trait.hasTrigger(param.asString())); }); // <--[tag] @@ -579,7 +571,7 @@ public static void register() { // Returns whether the NPC has anchors assigned. // --> tagProcessor.registerTag(ElementTag.class, "has_anchors", (attribute, object) -> { - return (new ElementTag(object.getCitizen().getOrAddTrait(Anchors.class).getAnchors().size() > 0)); + return new ElementTag(!object.getCitizen().getOrAddTrait(Anchors.class).getAnchors().isEmpty()); }); // <--[tag] @@ -589,11 +581,7 @@ public static void register() { // Returns a list of anchor names currently assigned to the NPC. // --> tagProcessor.registerTag(ListTag.class, "list_anchors", (attribute, object) -> { - ListTag list = new ListTag(); - for (Anchor anchor : object.getCitizen().getOrAddTrait(Anchors.class).getAnchors()) { - list.add(anchor.getName()); - } - return list; + return new ListTag(object.getCitizen().getOrAddTrait(Anchors.class).getAnchors(), anchor -> new ElementTag(anchor.getName(), true)); }); // <--[tag] @@ -606,22 +594,18 @@ public static void register() { Anchors trait = object.getCitizen().getOrAddTrait(Anchors.class); if (attribute.hasParam()) { Anchor anchor = trait.getAnchor(attribute.getParam()); - if (anchor != null) { - return new LocationTag(anchor.getLocation()); - } - else { - attribute.echoError("NPC Anchor '" + attribute.getParam() + "' is not defined."); - return null; - } + if (anchor != null) { + return new LocationTag(anchor.getLocation()); + } + else { + attribute.echoError("NPC Anchor '" + attribute.getParam() + "' is not defined."); + return null; + } } else if (attribute.startsWith("list", 2)) { attribute.fulfill(1); BukkitImplDeprecations.npcAnchorListTag.warn(attribute.context); - ListTag list = new ListTag(); - for (Anchor anchor : trait.getAnchors()) { - list.add(anchor.getName()); - } - return list; + return new ListTag(trait.getAnchors(), anchor -> new ElementTag(anchor.getName(), true)); } else { attribute.echoError("npc.anchor[...] tag must have an input."); @@ -635,18 +619,14 @@ else if (attribute.startsWith("list", 2)) { // @description // Returns the specified constant from the NPC. // --> - tagProcessor.registerTag(ElementTag.class, "constant", (attribute, object) -> { - if (attribute.hasParam()) { - if (object.getCitizen().hasTrait(ConstantsTrait.class) - && object.getCitizen().getOrAddTrait(ConstantsTrait.class).getConstant(attribute.getParam()) != null) { - return new ElementTag(object.getCitizen().getOrAddTrait(ConstantsTrait.class) - .getConstant(attribute.getParam())); - } - else { - return null; - } + tagProcessor.registerTag(ElementTag.class, ElementTag.class, "constant", (attribute, object, param) -> { + if (object.getCitizen().hasTrait(ConstantsTrait.class) + && object.getCitizen().getOrAddTrait(ConstantsTrait.class).getConstant(param.asString()) != null) { + return new ElementTag(object.getCitizen().getOrAddTrait(ConstantsTrait.class).getConstant(param.asString()), true); + } + else { + return null; } - return null; }); // <--[tag] @@ -655,13 +635,8 @@ else if (attribute.startsWith("list", 2)) { // @description // Returns true if the NPC has the specified pose, otherwise returns false. // --> - tagProcessor.registerTag(ElementTag.class, "has_pose", (attribute, object) -> { - if (attribute.hasParam()) { - return new ElementTag(object.getCitizen().getOrAddTrait(Poses.class).hasPose(attribute.getParam())); - } - else { - return null; - } + tagProcessor.registerTag(ElementTag.class, ElementTag.class, "has_pose", (attribute, object, param) -> { + return new ElementTag(object.getCitizen().getOrAddTrait(Poses.class).hasPose(param.asString())); }); // <--[tag] @@ -671,14 +646,9 @@ else if (attribute.startsWith("list", 2)) { // Returns the pose as a LocationTag with x, y, and z set to 0, and the world set to the first // possible available world Bukkit knows about. // --> - tagProcessor.registerTag(LocationTag.class, "pose", (attribute, object) -> { - if (attribute.hasParam()) { - Pose pose = object.getCitizen().getOrAddTrait(Poses.class).getPose(attribute.getParam()); - return new LocationTag(org.bukkit.Bukkit.getWorlds().get(0), 0, 0, 0, pose.getYaw(), pose.getPitch()); - } - else { - return null; - } + tagProcessor.registerTag(LocationTag.class, ElementTag.class, "pose", (attribute, object, param) -> { + Pose pose = object.getCitizen().getOrAddTrait(Poses.class).getPose(param.asString()); + return new LocationTag(Bukkit.getWorlds().get(0), 0, 0, 0, pose.getYaw(), pose.getPitch()); }, "get_pose"); // <--[tag] @@ -716,11 +686,7 @@ else if (attribute.startsWith("list", 2)) { if (stands == null || stands.isEmpty()) { return null; } - ListTag output = new ListTag(); - for (Entity stand : stands) { - output.addObject(new EntityTag(stand).getDenizenObject()); - } - return output; + return new ListTag(stands, stand -> new EntityTag(stand).getDenizenObject()); }); // <--[tag] @@ -865,7 +831,7 @@ else if (attribute.startsWith("list", 2)) { if (skin.getSignature() != null) { sign = ";" + skin.getSignature(); } - return new ElementTag(tex + sign); + return new ElementTag(tex + sign, true); } return null; }); @@ -883,7 +849,7 @@ else if (attribute.startsWith("list", 2)) { return null; } SkinTrait skin = object.getCitizen().getOrAddTrait(SkinTrait.class); - return new ElementTag(skin.getSkinName() + "|" + skin.getTexture()); + return new ElementTag(skin.getSkinName() + "|" + skin.getTexture(), true); }); // <--[tag] @@ -895,7 +861,7 @@ else if (attribute.startsWith("list", 2)) { // --> tagProcessor.registerTag(ElementTag.class, "skin", (attribute, object) -> { if (object.getCitizen().hasTrait(SkinTrait.class)) { - return new ElementTag(object.getCitizen().getOrAddTrait(SkinTrait.class).getSkinName()); + return new ElementTag(object.getCitizen().getOrAddTrait(SkinTrait.class).getSkinName(), true); } return null; }); @@ -1044,13 +1010,7 @@ else if (attribute.startsWith("list", 2)) { return null; } else { - ListTag result = new ListTag(); - for (AssignmentScriptContainer container : citizen.getOrAddTrait(AssignmentTrait.class).containerCache) { - if (container != null) { - result.addObject(new ScriptTag(container)); - } - } - return result; + return new ListTag(citizen.getOrAddTrait(AssignmentTrait.class).containerCache, Objects::nonNull, ScriptTag::new); } }); @@ -1139,7 +1099,7 @@ else if (attribute.startsWith("list", 2)) { // Not related to Sentinel combat. // --> tagProcessor.registerTag(ElementTag.class, "attack_strategy", (attribute, object) -> { - return new ElementTag(object.getNavigator().getLocalParameters().attackStrategy().toString()); + return new ElementTag(object.getNavigator().getLocalParameters().attackStrategy().toString(), true); }); // <--[tag] @@ -1224,7 +1184,7 @@ else if (attribute.startsWith("list", 2)) { if (object.getNavigator().getTargetType() == null) { return null; } - return new ElementTag(object.getNavigator().getTargetType().toString()); + return new ElementTag(object.getNavigator().getTargetType().toString(), true); }); // <--[tag] @@ -1247,7 +1207,7 @@ else if (attribute.startsWith("list", 2)) { // Returns the name of the registry this NPC came from. // --> tagProcessor.registerTag(ElementTag.class, "registry_name", (attribute, object) -> { - return new ElementTag(object.getCitizen().getOwningRegistry().getName()); + return new ElementTag(object.getCitizen().getOwningRegistry().getName(), true); }); // <--[tag] @@ -1256,15 +1216,9 @@ else if (attribute.startsWith("list", 2)) { // @description // Returns the value of a Citizens NPC metadata key. // --> - tagProcessor.registerTag(ElementTag.class, "citizens_data", (attribute, object) -> { - if (!attribute.hasParam()) { - return null; - } - Object val = object.getCitizen().data().get(attribute.getParam()); - if (val == null) { - return null; - } - return new ElementTag(val.toString()); + tagProcessor.registerTag(ElementTag.class, ElementTag.class, "citizens_data", (attribute, object, param) -> { + Object val = object.getCitizen().data().get(param.asString()); + return val != null ? new ElementTag(val.toString(), true) : null; }); // <--[tag] @@ -1343,6 +1297,16 @@ else if (attribute.startsWith("list", 2)) { return null; }); + // <--[tag] + // @attribute ]> + // @returns ElementTag(Boolean) + // @description + // Returns whether an NPC can navigate to a specified location. + // --> + tagProcessor.registerTag(ElementTag.class, LocationTag.class, "can_navigate_to", (attribute, object, param) -> { + return new ElementTag(object.getCitizen().getNavigator().canNavigateTo(param)); + }); + // <--[mechanism] // @object NPCTag // @name hologram_lines @@ -1454,29 +1418,6 @@ else if (attribute.startsWith("list", 2)) { mechanism.echoError("Must set waypoint_provider to 'wander' before setting wander_yrange!"); } }); - } - - public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor<>(); - - @Override - public ObjectTag getObjectAttribute(Attribute attribute) { - return tagProcessor.getObjectAttribute(this, attribute); - } - - @Override - public ObjectTag getNextObjectTypeDown() { - if (getEntity() != null) { - return new EntityTag(this); - } - return new ElementTag(identify()); - } - - public void applyProperty(Mechanism mechanism) { - mechanism.echoError("Cannot apply properties to an NPC!"); - } - - @Override - public void adjust(Mechanism mechanism) { // TODO: For all the mechanism tags, add the @Mechanism link! @@ -1489,11 +1430,11 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("set_assignment") && mechanism.requireObject(ScriptTag.class)) { - AssignmentTrait trait = getCitizen().getOrAddTrait(AssignmentTrait.class); + tagProcessor.registerMechanism("set_assignment", false, ScriptTag.class, (object, mechanism, input) -> { + AssignmentTrait trait = object.getCitizen().getOrAddTrait(AssignmentTrait.class); trait.clearAssignments(null); - trait.addAssignmentScript((AssignmentScriptContainer) mechanism.valueAsType(ScriptTag.class).getContainer(), null); - } + trait.addAssignmentScript((AssignmentScriptContainer) input.getContainer(), null); + }); // <--[mechanism] // @object NPCTag @@ -1504,9 +1445,9 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("add_assignment") && mechanism.requireObject(ScriptTag.class)) { - getCitizen().getOrAddTrait(AssignmentTrait.class).addAssignmentScript((AssignmentScriptContainer) mechanism.valueAsType(ScriptTag.class).getContainer(), null); - } + tagProcessor.registerMechanism("add_assignment", false, ScriptTag.class, (object, mechanism, input) -> { + object.getCitizen().getOrAddTrait(AssignmentTrait.class).addAssignmentScript((AssignmentScriptContainer) input.getContainer(), null); + }); // <--[mechanism] // @object NPCTag @@ -1517,20 +1458,21 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("remove_assignment")) { - if (npc.hasTrait(AssignmentTrait.class)) { - if (mechanism.hasValue()) { - AssignmentTrait trait = getCitizen().getOrAddTrait(AssignmentTrait.class); - trait.removeAssignmentScript(mechanism.getValue().asString(), null); - trait.checkAutoRemove(); - } - else { - BukkitImplDeprecations.assignmentRemove.warn(mechanism.context); - getCitizen().getOrAddTrait(AssignmentTrait.class).clearAssignments(null); - npc.removeTrait(AssignmentTrait.class); - } + tagProcessor.registerMechanism("remove_assignment", false, (object, mechanism) -> { + if (!object.getCitizen().hasTrait(AssignmentTrait.class)) { + return; } - } + if (mechanism.hasValue()) { + AssignmentTrait trait = object.getCitizen().getOrAddTrait(AssignmentTrait.class); + trait.removeAssignmentScript(mechanism.getValue().asString(), null); + trait.checkAutoRemove(); + } + else { + BukkitImplDeprecations.assignmentRemove.warn(mechanism.context); + object.getCitizen().getOrAddTrait(AssignmentTrait.class).clearAssignments(null); + object.getCitizen().removeTrait(AssignmentTrait.class); + } + }); // <--[mechanism] // @object NPCTag @@ -1541,12 +1483,12 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("clear_assignments")) { - if (npc.hasTrait(AssignmentTrait.class)) { - getCitizen().getOrAddTrait(AssignmentTrait.class).clearAssignments(null); - npc.removeTrait(AssignmentTrait.class); + tagProcessor.registerMechanism("clear_assignments", false, (object, mechanism) -> { + if (object.getCitizen().hasTrait(AssignmentTrait.class)) { + object.getCitizen().getOrAddTrait(AssignmentTrait.class).clearAssignments(null); + object.getCitizen().removeTrait(AssignmentTrait.class); } - } + }); // <--[mechanism] // @object NPCTag @@ -1558,11 +1500,13 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("hologram_direction")) { // && mechanism.requireEnum(HologramTrait.HologramDirection.class) + tagProcessor.registerMechanism("hologram_direction", false, (object, mechanism) -> { + //if (mechanism.requireEnum(HologramTrait.HologramDirection.class)) { + //HologramTrait hologram = object.getCitizen().getOrAddTrait(HologramTrait.class); + //hologram.setDirection(HologramTrait.HologramDirection.valueOf(param.asString().toUpperCase())); + //} BukkitImplDeprecations.npcHologramDirection.warn(mechanism.context); - //HologramTrait hologram = getCitizen().getOrAddTrait(HologramTrait.class); - //hologram.setDirection(HologramTrait.HologramDirection.valueOf(mechanism.getValue().asString().toUpperCase())); - } + }); // <--[mechanism] // @object NPCTag @@ -1573,10 +1517,11 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("hologram_line_height") && mechanism.requireDouble()) { - HologramTrait hologram = getCitizen().getOrAddTrait(HologramTrait.class); - hologram.setLineHeight(mechanism.getValue().asDouble()); - } + tagProcessor.registerMechanism("hologram_line_height", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireDouble()) { + object.getCitizen().getOrAddTrait(HologramTrait.class).setLineHeight(input.asDouble()); + } + }); // <--[mechanism] // @object NPCTag @@ -1587,9 +1532,9 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("set_nickname")) { - getNicknameTrait().setNickname(mechanism.getValue().asString()); - } + tagProcessor.registerMechanism("set_nickname", false, ElementTag.class, (object, mechanism, input) -> { + object.getNicknameTrait().setNickname(input.asString()); + }); // <--[mechanism] // @object NPCTag @@ -1600,9 +1545,9 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("remove_nickname")) { - getNicknameTrait().removeNickname(); - } + tagProcessor.registerMechanism("remove_nickname", false, (object, mechanism) -> { + object.getNicknameTrait().removeNickname(); + }); // <--[mechanism] // @object NPCTag @@ -1613,9 +1558,9 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("set_entity_type") && mechanism.requireObject(EntityTag.class)) { - getCitizen().setBukkitEntityType(mechanism.valueAsType(EntityTag.class).getBukkitEntityType()); - } + tagProcessor.registerMechanism("set_entity_type", false, EntityTag.class, (object, mechanism, input) -> { + object.getCitizen().setBukkitEntityType(input.getBukkitEntityType()); + }); // <--[mechanism] // @object NPCTag @@ -1626,27 +1571,22 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("name") || mechanism.matches("set_name")) { - getCitizen().setName(mechanism.getValue().asString().length() > 256 ? mechanism.getValue().asString().substring(0, 256) : mechanism.getValue().asString()); - } + tagProcessor.registerMechanism("name", false, ElementTag.class, (object, mechanism, input) -> { + object.getCitizen().setName(input.asString().length() > 256 ? input.asString().substring(0, 256) : input.asString()); + }, "set_name"); // <--[mechanism] // @object NPCTag // @name owner // @input PlayerTag // @description - // Sets the owner of the NPC. + // Sets the owner of the NPC. Provide no input to set the server as the owner. // @tags // // --> - if (mechanism.matches("owner")) { - if (PlayerTag.matches(mechanism.getValue().asString())) { - getCitizen().getOrAddTrait(Owner.class).setOwner(mechanism.valueAsType(PlayerTag.class).getPlayerEntity()); - } - else { - getCitizen().getOrAddTrait(Owner.class).setOwner(mechanism.getValue().asString()); - } - } + tagProcessor.registerMechanism("owner", false, (object, mechanism) -> { + object.getCitizen().getOrAddTrait(Owner.class).setOwner(mechanism.getValue().canBeType(PlayerTag.class) ? mechanism.valueAsType(PlayerTag.class).getUUID() : null); + }); // <--[mechanism] // @object NPCTag @@ -1659,29 +1599,29 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("skin_blob")) { + tagProcessor.registerMechanism("skin_blob", false, (object, mechanism) -> { if (!mechanism.hasValue()) { - if (getCitizen().hasTrait(SkinTrait.class)) { - getCitizen().getOrAddTrait(SkinTrait.class).clearTexture(); - if (getCitizen().isSpawned()) { - getCitizen().despawn(DespawnReason.PENDING_RESPAWN); - getCitizen().spawn(getCitizen().getStoredLocation()); + if (object.getCitizen().hasTrait(SkinTrait.class)) { + object.getCitizen().getOrAddTrait(SkinTrait.class).clearTexture(); + if (object.getCitizen().isSpawned()) { + object.getCitizen().despawn(DespawnReason.PENDING_RESPAWN); + object.getCitizen().spawn(object.getCitizen().getStoredLocation()); } } } else { - SkinTrait skinTrait = getCitizen().getOrAddTrait(SkinTrait.class); + SkinTrait skinTrait = object.getCitizen().getOrAddTrait(SkinTrait.class); String[] dat = mechanism.getValue().asString().split(";"); if (dat.length < 2) { Debug.echoError("Invalid skin_blob input. Must specify texture;signature;name in full."); return; } skinTrait.setSkinPersistent(dat.length > 2 ? dat[2] : UUID.randomUUID().toString(), dat[1], dat[0]); - if (getCitizen().isSpawned() && getCitizen().getEntity() instanceof SkinnableEntity) { - ((SkinnableEntity) getCitizen().getEntity()).getSkinTracker().notifySkinChange(true); + if (object.getCitizen().isSpawned() && object.getCitizen().getEntity() instanceof SkinnableEntity skinnable) { + skinnable.getSkinTracker().notifySkinChange(true); } } - } + }); // <--[mechanism] // @object NPCTag @@ -1694,21 +1634,21 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("skin")) { + tagProcessor.registerMechanism("skin", false, (object, mechanism) -> { if (!mechanism.hasValue()) { - if (getCitizen().hasTrait(SkinTrait.class)) { - getCitizen().getOrAddTrait(SkinTrait.class).clearTexture(); + if (object.getCitizen().hasTrait(SkinTrait.class)) { + object.getCitizen().getOrAddTrait(SkinTrait.class).clearTexture(); } } else { - SkinTrait skinTrait = getCitizen().getOrAddTrait(SkinTrait.class); + SkinTrait skinTrait = object.getCitizen().getOrAddTrait(SkinTrait.class); skinTrait.setSkinName(mechanism.getValue().asString()); } - if (getCitizen().isSpawned()) { - getCitizen().despawn(DespawnReason.PENDING_RESPAWN); - getCitizen().spawn(getCitizen().getStoredLocation()); + if (object.getCitizen().isSpawned()) { + object.getCitizen().despawn(DespawnReason.PENDING_RESPAWN); + object.getCitizen().spawn(object.getCitizen().getStoredLocation()); } - } + }); // <--[mechanism] // @object NPCTag @@ -1720,9 +1660,11 @@ public void adjust(Mechanism mechanism) { // @tags // // --> - if (mechanism.matches("auto_update_skin") && mechanism.requireBoolean()) { - getCitizen().getOrAddTrait(SkinTrait.class).setShouldUpdateSkins(mechanism.getValue().asBoolean()); - } + tagProcessor.registerMechanism("auto_update_skin", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireBoolean()) { + object.getCitizen().getOrAddTrait(SkinTrait.class).setShouldUpdateSkins(input.asBoolean()); + } + }); // <--[mechanism] // @object NPCTag @@ -1731,10 +1673,10 @@ public void adjust(Mechanism mechanism) { // @description // Sets the item type of the item. // --> - if (mechanism.matches("item_type") && mechanism.requireObject(ItemTag.class)) { - ItemTag item = mechanism.valueAsType(ItemTag.class); - Material mat = item.getMaterial().getMaterial(); - Entity npcEntity = getEntity(); + tagProcessor.registerMechanism("item_type", false, ItemTag.class, (object, mechanism, input) -> { + Material mat = input.getBukkitMaterial(); + Entity npcEntity = object.getEntity(); + NPC citizen = object.getCitizen(); if (npcEntity instanceof Item droppedItem) { droppedItem.getItemStack().setType(mat); } @@ -1742,27 +1684,27 @@ else if (npcEntity instanceof ItemFrame itemFrame) { itemFrame.getItem().setType(mat); } else if (npcEntity instanceof FallingBlock) { - getCitizen().data().setPersistent(NPC.Metadata.ITEM_ID, mat.name()); - getCitizen().data().setPersistent(NPC.Metadata.ITEM_DATA, 0); + citizen.data().setPersistent(NPC.Metadata.ITEM_ID, mat.name()); + citizen.data().setPersistent(NPC.Metadata.ITEM_DATA, 0); } else { Debug.echoError("NPC is the not an item type!"); } - if (getCitizen().isSpawned()) { - getCitizen().despawn(); - getCitizen().spawn(getCitizen().getStoredLocation()); + if (citizen.isSpawned()) { + citizen.despawn(); + citizen.spawn(citizen.getStoredLocation()); } - } + }); - if (mechanism.matches("spawn")) { + tagProcessor.registerMechanism("spawn", false, (object, mechanism) -> { BukkitImplDeprecations.npcSpawnMechanism.warn(mechanism.context); if (mechanism.requireObject("Invalid LocationTag specified. Assuming last known NPC location.", LocationTag.class)) { - getCitizen().spawn(mechanism.valueAsType(LocationTag.class)); + object.getCitizen().spawn(mechanism.valueAsType(LocationTag.class)); } else { - getCitizen().spawn(getCitizen().getStoredLocation()); + object.getCitizen().spawn(object.getCitizen().getStoredLocation()); } - } + }); // <--[mechanism] // @object NPCTag @@ -1773,10 +1715,12 @@ else if (npcEntity instanceof FallingBlock) { // @tags // // --> - if (mechanism.matches("range") && mechanism.requireFloat()) { - getCitizen().getNavigator().getDefaultParameters().range(mechanism.getValue().asFloat()); - getCitizen().getNavigator().getLocalParameters().range(mechanism.getValue().asFloat()); - } + tagProcessor.registerMechanism("range", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireFloat()) { + object.getCitizen().getNavigator().getDefaultParameters().range(input.asFloat()); + object.getCitizen().getNavigator().getLocalParameters().range(input.asFloat()); + } + }); // <--[mechanism] // @object NPCTag @@ -1787,10 +1731,12 @@ else if (npcEntity instanceof FallingBlock) { // @tags // // --> - if (mechanism.matches("attack_range") && mechanism.requireFloat()) { - getCitizen().getNavigator().getDefaultParameters().attackRange(mechanism.getValue().asFloat()); - getCitizen().getNavigator().getLocalParameters().attackRange(mechanism.getValue().asFloat()); - } + tagProcessor.registerMechanism("attack_range", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireFloat()) { + object.getCitizen().getNavigator().getDefaultParameters().attackRange(input.asFloat()); + object.getCitizen().getNavigator().getLocalParameters().attackRange(input.asFloat()); + } + }); // <--[mechanism] // @object NPCTag @@ -1801,15 +1747,17 @@ else if (npcEntity instanceof FallingBlock) { // @tags // // --> - if (mechanism.matches("speed") && mechanism.requireFloat()) { - getCitizen().getNavigator().getDefaultParameters().speedModifier(mechanism.getValue().asFloat()); - getCitizen().getNavigator().getLocalParameters().speedModifier(mechanism.getValue().asFloat()); - } + tagProcessor.registerMechanism("speed", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireFloat()) { + object.getCitizen().getNavigator().getDefaultParameters().speedModifier(input.asFloat()); + object.getCitizen().getNavigator().getLocalParameters().speedModifier(input.asFloat()); + } + }); - if (mechanism.matches("despawn")) { + tagProcessor.registerMechanism("despawn", false, (object, mechanism) -> { BukkitImplDeprecations.npcDespawnMech.warn(mechanism.context); - getCitizen().despawn(DespawnReason.PLUGIN); - } + object.getCitizen().despawn(DespawnReason.PLUGIN); + }); // <--[mechanism] // @object NPCTag @@ -1820,18 +1768,18 @@ else if (npcEntity instanceof FallingBlock) { // @tags // // --> - if (mechanism.matches("set_sneaking") && mechanism.requireBoolean()) { - if (!getCitizen().hasTrait(SneakingTrait.class)) { - getCitizen().addTrait(SneakingTrait.class); + tagProcessor.registerMechanism("set_sneaking", false, ElementTag.class, (object, mechanism, input) -> { + if (!mechanism.requireBoolean()) { + return; } - SneakingTrait trait = getCitizen().getOrAddTrait(SneakingTrait.class); - if (trait.isSneaking() && !mechanism.getValue().asBoolean()) { + SneakingTrait trait = object.getCitizen().getOrAddTrait(SneakingTrait.class); + if (trait.isSneaking() && !input.asBoolean()) { trait.stand(); } - else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { + else if (!trait.isSneaking() && input.asBoolean()) { trait.sneak(); } - } + }); // <--[mechanism] // @object NPCTag @@ -1842,9 +1790,11 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if (mechanism.matches("set_protected") && mechanism.requireBoolean()) { - getCitizen().setProtected(mechanism.getValue().asBoolean()); - } + tagProcessor.registerMechanism("set_protected", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireBoolean()) { + object.getCitizen().setProtected(input.asBoolean()); + } + }); // <--[mechanism] // @object NPCTag @@ -1855,9 +1805,11 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if (mechanism.matches("lookclose") && mechanism.requireBoolean()) { - getLookCloseTrait().lookClose(mechanism.getValue().asBoolean()); - } + tagProcessor.registerMechanism("lookclose", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireBoolean()) { + object.getLookCloseTrait().lookClose(input.asBoolean()); + } + }); // <--[mechanism] // @object NPCTag @@ -1868,9 +1820,11 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if (mechanism.matches("controllable") && mechanism.requireBoolean()) { - getCitizen().getOrAddTrait(Controllable.class).setEnabled(mechanism.getValue().asBoolean()); - } + tagProcessor.registerMechanism("controllable", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireBoolean()) { + object.getCitizen().getOrAddTrait(Controllable.class).setEnabled(input.asBoolean()); + } + }); // <--[mechanism] // @object NPCTag @@ -1881,9 +1835,11 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if (mechanism.matches("targetable") && mechanism.requireBoolean()) { - getCitizen().getOrAddTrait(TargetableTrait.class).setTargetable(mechanism.getValue().asBoolean()); - } + tagProcessor.registerMechanism("targetable", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireBoolean()) { + object.getCitizen().getOrAddTrait(TargetableTrait.class).setTargetable(input.asBoolean()); + } + }); // <--[mechanism] // @object NPCTag @@ -1894,14 +1850,11 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if (mechanism.matches("teleport_on_stuck") && mechanism.requireBoolean()) { - if (mechanism.getValue().asBoolean()) { - getNavigator().getDefaultParameters().stuckAction(TeleportStuckAction.INSTANCE); + tagProcessor.registerMechanism("teleport_on_stuck", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireBoolean()) { + object.getNavigator().getDefaultParameters().stuckAction(input.asBoolean() ? TeleportStuckAction.INSTANCE : null); } - else { - getNavigator().getDefaultParameters().stuckAction(null); - } - } + }); // <--[mechanism] // @object NPCTag @@ -1912,10 +1865,12 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if ((mechanism.matches("distance_margin") || mechanism.matches("set_distance")) && mechanism.requireDouble()) { - getNavigator().getDefaultParameters().distanceMargin(mechanism.getValue().asDouble()); - getNavigator().getLocalParameters().distanceMargin(mechanism.getValue().asDouble()); - } + tagProcessor.registerMechanism("distance_margin", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireDouble()) { + object.getNavigator().getDefaultParameters().distanceMargin(input.asDouble()); + object.getNavigator().getLocalParameters().distanceMargin(input.asDouble()); + } + }, "set_distance"); // <--[mechanism] // @object NPCTag @@ -1926,10 +1881,12 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if (mechanism.matches("path_distance_margin") && mechanism.requireDouble()) { - getNavigator().getDefaultParameters().pathDistanceMargin(mechanism.getValue().asDouble()); - getNavigator().getLocalParameters().pathDistanceMargin(mechanism.getValue().asDouble()); - } + tagProcessor.registerMechanism("path_distance_margin", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireDouble()) { + object.getNavigator().getDefaultParameters().pathDistanceMargin(input.asDouble()); + object.getNavigator().getLocalParameters().pathDistanceMargin(input.asDouble()); + } + }); // <--[mechanism] // @object NPCTag @@ -1941,9 +1898,11 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if (mechanism.matches("use_new_finder") && mechanism.requireBoolean()) { - getNavigator().getDefaultParameters().useNewPathfinder(mechanism.getValue().asBoolean()); - } + tagProcessor.registerMechanism("use_new_finder", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireBoolean()) { + object.getNavigator().getDefaultParameters().useNewPathfinder(input.asBoolean()); + } + }); // <--[mechanism] // @object NPCTag @@ -1956,15 +1915,15 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // // --> - if (mechanism.matches("navigator_look_at")) { + tagProcessor.registerMechanism("navigator_look_at", false, (object, mechanism) -> { if (mechanism.hasValue() && mechanism.requireObject(LocationTag.class)) { final LocationTag loc = mechanism.valueAsType(LocationTag.class); - getNavigator().getLocalParameters().lookAtFunction((n) -> loc); + object.getNavigator().getLocalParameters().lookAtFunction((n) -> loc); } else { - getNavigator().getLocalParameters().lookAtFunction(null); + object.getNavigator().getLocalParameters().lookAtFunction(null); } - } + }); // <--[mechanism] // @object NPCTag @@ -1975,9 +1934,9 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // TODO // --> - if (mechanism.matches("name_visible")) { - getCitizen().data().setPersistent(NPC.Metadata.NAMEPLATE_VISIBLE, mechanism.getValue().asString()); - } + tagProcessor.registerMechanism("name_visible", false, ElementTag.class, (object, mechanism, input) -> { + object.getCitizen().data().setPersistent(NPC.Metadata.NAMEPLATE_VISIBLE, input.asString()); + }); // <--[mechanism] // @object NPCTag @@ -1988,9 +1947,11 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // TODO // --> - if (mechanism.matches("glow_color") && mechanism.requireEnum(ChatColor.class)) { - getCitizen().getOrAddTrait(ScoreboardTrait.class).setColor(ChatColor.valueOf(mechanism.getValue().asString().toUpperCase())); - } + tagProcessor.registerMechanism("glow_color", false, ElementTag.class, (object, mechanism, input) -> { + if (mechanism.requireEnum(ChatColor.class)) { + object.getCitizen().getOrAddTrait(ScoreboardTrait.class).setColor(input.asEnum(ChatColor.class)); + } + }); // <--[mechanism] // @object NPCTag @@ -2001,19 +1962,18 @@ else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) { // @tags // TODO // --> - if (mechanism.matches("clear_waypoints")) { - Waypoints wp = getCitizen().getOrAddTrait(Waypoints.class); - if ((wp.getCurrentProvider() instanceof WaypointProvider.EnumerableWaypointProvider)) { - ((List) ((WaypointProvider.EnumerableWaypointProvider) wp.getCurrentProvider()).waypoints()).clear(); + tagProcessor.registerMechanism("clear_waypoints", false, (object, mechanism) -> { + Waypoints wp = object.getCitizen().getOrAddTrait(Waypoints.class); + if (wp.getCurrentProvider() instanceof WaypointProvider.EnumerableWaypointProvider provider) { + ((List) provider.waypoints()).clear(); } - else if ((wp.getCurrentProvider() instanceof WanderWaypointProvider)) { - List locs = ((WanderWaypointProvider) wp.getCurrentProvider()).getRegionCentres(); + else if (wp.getCurrentProvider() instanceof WanderWaypointProvider provider) { + List locs = provider.getRegionCentres(); for (Location loc : locs) { locs.remove(loc); // Manual clear to ensure recalculation for the forwarding list } - } - } + }); // <--[mechanism] // @object NPCTag @@ -2024,22 +1984,39 @@ else if ((wp.getCurrentProvider() instanceof WanderWaypointProvider)) { // @tags // TODO // --> - if (mechanism.matches("add_waypoint") && mechanism.requireObject(LocationTag.class)) { - Location target = mechanism.valueAsType(LocationTag.class).clone(); - Waypoints wp = getCitizen().getOrAddTrait(Waypoints.class); - if ((wp.getCurrentProvider() instanceof LinearWaypointProvider)) { - ((LinearWaypointProvider) wp.getCurrentProvider()).addWaypoint(new Waypoint(target)); + tagProcessor.registerMechanism("add_waypoint", false, LocationTag.class, (object, mechanism, input) -> { + Waypoints wp = object.getCitizen().getOrAddTrait(Waypoints.class); + if (wp.getCurrentProvider() instanceof LinearWaypointProvider provider) { + provider.addWaypoint(new Waypoint(input)); } - else if ((wp.getCurrentProvider() instanceof WaypointProvider.EnumerableWaypointProvider)) { - ((List) ((WaypointProvider.EnumerableWaypointProvider) wp.getCurrentProvider()).waypoints()).add(new Waypoint(target)); + else if (wp.getCurrentProvider() instanceof WaypointProvider.EnumerableWaypointProvider provider) { + ((List) provider.waypoints()).add(new Waypoint(input)); } - else if ((wp.getCurrentProvider() instanceof WanderWaypointProvider)) { - ((WanderWaypointProvider) wp.getCurrentProvider()).getRegionCentres().add(target); + else if (wp.getCurrentProvider() instanceof WanderWaypointProvider provider) { + provider.getRegionCentres().add(input); } - } + }); + } - tagProcessor.processMechanism(this, mechanism); + public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor<>(); + + @Override + public ObjectTag getObjectAttribute(Attribute attribute) { + return tagProcessor.getObjectAttribute(this, attribute); + } + @Override + public ObjectTag getNextObjectTypeDown() { + return getEntity() != null ? new EntityTag(this) : new ElementTag(identify()); + } + + public void applyProperty(Mechanism mechanism) { + mechanism.echoError("Cannot apply properties to an NPC!"); + } + + @Override + public void adjust(Mechanism mechanism) { + tagProcessor.processMechanism(this, mechanism); // Pass along to EntityTag mechanism handler if not already handled. if (!mechanism.fulfilled()) { if (isSpawned()) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java index eae5750277..0649efa9ed 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java @@ -2679,6 +2679,33 @@ else if (mechanism.requireObject(LocationTag.class)) { object.getNBTEditor().setSpawnForced(input.asBoolean()); } }); + + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + + // <--[mechanism] + // @object PlayerTag + // @name links + // @input ListTag(MapTag) + // @description + // Sends the specified list of server links to the player. This will override existing links player has. + // Each item in the list must be a MapTag in <@link language Server Links Format>. + // Generally prefer <@link mechanism PlayerTag.add_links>. + // --> + registerOnlineOnlyMechanism("links", ListTag.class, (player, mechanism, input) -> { + player.getPlayerEntity().sendLinks(Utilities.replaceServerLinks(Bukkit.getServerLinks().copy(), input, mechanism.context)); + }); + + // <--[mechanism] + // @object PlayerTag + // @name add_links + // @input ListTag(MapTag) + // @description + // Adds the specified list of server links to the player. Each item in the list must be a MapTag in <@link language Server Links Format>. + // --> + registerOnlineOnlyMechanism("add_links", ListTag.class, (player, mechanism, input) -> { + player.getPlayerEntity().sendLinks(Utilities.fillServerLinks(Bukkit.getServerLinks().copy(), input, mechanism.context)); + }); + } } public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor<>(); diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java index 1106a9a84c..e6690210cc 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java @@ -6,6 +6,7 @@ import com.denizenscript.denizen.nms.abstracts.BiomeNMS; import com.denizenscript.denizen.utilities.BukkitImplDeprecations; import com.denizenscript.denizen.utilities.flags.WorldFlagHandler; +import com.denizenscript.denizen.utilities.world.GameRuleReflect; import com.denizenscript.denizencore.flags.AbstractFlagTracker; import com.denizenscript.denizencore.flags.FlaggableObject; import com.denizenscript.denizencore.objects.Adjustable; @@ -177,7 +178,7 @@ public T getGameRuleOrDefault(GameRule gameRule) { if (value == null) { value = world.getGameRuleDefault(gameRule); if (value == null) { - throw new IllegalStateException("World " + world_name + " contains no GameRule " + gameRule.getName()); + throw new IllegalStateException("World " + world_name + " contains no GameRule " + GameRuleReflect.getName(gameRule)); } } return value; @@ -852,16 +853,19 @@ else if (time >= 12500) { // @returns ElementTag // @description // Returns the current value of the specified gamerule in the world. - // Note that the name is case-sensitive... so "doFireTick" is correct, but "dofiretick" is not. // --> registerTag(ElementTag.class, "gamerule", (attribute, object) -> { if (!attribute.hasParam()) { attribute.echoError("The tag 'worldtag.gamerule[...]' must have an input value."); return null; } - GameRule rule = GameRule.getByName(attribute.getParam()); - Object result = object.getWorld().getGameRuleValue(rule); - return new ElementTag(result == null ? "null" : result.toString()); + GameRule rule = GameRuleReflect.getByName(attribute.getParam()); + if (rule == null) { + attribute.echoError("Invalid game rule specified: " + attribute.getParam() + '.'); + return null; + } + Object result = GameRuleReflect.getValue(object.getWorld(), rule); + return new ElementTag(String.valueOf(result), true); }); // <--[tag] @@ -872,10 +876,10 @@ else if (time >= 12500) { // --> registerTag(MapTag.class, "gamerule_map", (attribute, object) -> { MapTag map = new MapTag(); - for (GameRule rule : GameRule.values()) { - Object result = object.getWorld().getGameRuleValue(rule); + for (GameRule rule : GameRuleReflect.values()) { + Object result = GameRuleReflect.getValue(object.getWorld(), rule); if (result != null) { - map.putObject(rule.getName(), new ElementTag(result.toString())); + map.putObject(GameRuleReflect.getName(rule), new ElementTag(result.toString(), true)); } } return map; @@ -987,7 +991,7 @@ else if (time >= 12500) { // @description // Returns whether enough players are sleeping to prepare for the night to advance. // Typically used before checking <@link tag WorldTag.enough_deep_sleeping> - // By default, automatically checks the playersSleepingPercentage gamerule, + // By default, automatically checks the players_sleeping_percentage gamerule, // but this can optionally be overridden by specifying a percentage integer. // Any integer above 100 will always yield 'false'. Requires at least one player to be sleeping to return 'true'. // --> @@ -1008,7 +1012,7 @@ else if (time >= 12500) { // @description // Returns whether enough players have been in bed long enough for the night to advance (generally 100 ticks). // Loops through all online players, so is typically used after checking <@link tag WorldTag.enough_sleeping> - // By default, automatically checks the playersSleepingPercentage gamerule, + // By default, automatically checks the players_sleeping_percentage gamerule, // but this can optionally be overridden by specifying a percentage integer. // Any integer above 100 will always yield 'false'. Requires at least one player to be sleeping to return 'true'. // --> @@ -1092,7 +1096,12 @@ else if (time >= 12500) { mechanism.echoError("Provided world is not an end world!"); return; } - battle.initiateRespawn(); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { // workaround for upstream bug + battle.initiateRespawn(List.of()); + } + else { + battle.initiateRespawn(); + } }); // <--[mechanism] @@ -1509,7 +1518,7 @@ else if (time >= 12500) { // @input None // @description // Skips to the next day as if enough players slept through the night. - // NOTE: This ignores the doDaylightCycle gamerule! + // NOTE: This ignores the advance_time gamerule! // --> tagProcessor.registerMechanism("skip_night", false, (object, mechanism) -> { // general logic from NMS world tick @@ -1525,7 +1534,7 @@ else if (time >= 12500) { NMSHandler.worldHelper.wakeUpAllPlayers(world); } // minor change: prior to 1.18, hasStorm/isRaining was not checked - if (object.getGameRuleOrDefault(GameRule.DO_WEATHER_CYCLE) && world.hasStorm()) { + if (object.getGameRuleOrDefault(GameRuleReflect.WEATHER_CYCLE_GAMERULE) && world.hasStorm()) { NMSHandler.worldHelper.clearWeather(world); } }); diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/PropertyRegistry.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/PropertyRegistry.java index 477c8bb129..b4339b1773 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/PropertyRegistry.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/PropertyRegistry.java @@ -173,6 +173,9 @@ public static void registerMainProperties() { } PropertyParser.registerProperty(EntitySpeed.class, EntityTag.class); PropertyParser.registerProperty(EntitySpell.class, EntityTag.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { + PropertyParser.registerProperty(EntityState.class, EntityTag.class); + } if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { PropertyParser.registerProperty(EntityStepHeight.class, EntityTag.class); } @@ -260,6 +263,7 @@ public static void registerMainProperties() { PropertyParser.registerProperty(ItemScript.class, ItemTag.class); PropertyParser.registerProperty(ItemSignContents.class, ItemTag.class); // Special case handling in ItemComponentsPatch if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { + PropertyParser.registerProperty(ItemSignBackContents.class, ItemTag.class); // Special case handling in ItemComponentsPatch PropertyParser.registerProperty(ItemSignIsWaxed.class, ItemTag.class); // Special case handling in ItemComponentsPatch } registerItemProperty(ItemSkullskin.class, "profile"); @@ -296,6 +300,9 @@ public static void registerMainProperties() { PropertyParser.registerProperty(MaterialLightable.class, MaterialTag.class); PropertyParser.registerProperty(MaterialMode.class, MaterialTag.class); PropertyParser.registerProperty(MaterialNote.class, MaterialTag.class); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + PropertyParser.registerProperty(MaterialOminous.class, MaterialTag.class); + } PropertyParser.registerProperty(MaterialPersistent.class, MaterialTag.class); PropertyParser.registerProperty(MaterialPower.class, MaterialTag.class); PropertyParser.registerProperty(MaterialShape.class, MaterialTag.class); diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityColor.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityColor.java index 75fbf49b06..3e858feff3 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityColor.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityColor.java @@ -259,6 +259,9 @@ public String getColor(boolean includeDeprecated) { case CAT -> { Cat cat = as(Cat.class); // TODO once 1.21 is the minimum supported version, replace with direct registry-based handling + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + yield Utilities.namespacedKeyToString(cat.getCatType().getKey()) + "|" + cat.getCollarColor().name(); + } yield cat.getCatType() + "|" + cat.getCollarColor().name(); } case PANDA -> { diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityItem.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityItem.java index 36fa15c2f7..80d0cbc86f 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityItem.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityItem.java @@ -4,19 +4,11 @@ import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.objects.ItemTag; -import com.denizenscript.denizen.utilities.BukkitImplDeprecations; import com.denizenscript.denizencore.objects.Mechanism; -import com.denizenscript.denizencore.objects.ObjectTag; -import com.denizenscript.denizencore.objects.properties.Property; -import com.denizenscript.denizencore.objects.properties.PropertyParser; -import com.denizenscript.denizencore.tags.TagContext; import net.citizensnpcs.api.npc.NPC; -import org.bukkit.Material; -import org.bukkit.block.data.BlockData; import org.bukkit.entity.*; -import org.bukkit.inventory.ItemStack; -public class EntityItem implements Property { +public class EntityItem extends EntityProperty { // <--[property] // @object EntityTag @@ -30,118 +22,66 @@ public class EntityItem implements Property { // - an eye-of-ender's item, which is both displayed and dropped. // - a fireball's display item. // - an item display's display item. + // - an ominous item spawner's display item. // --> - public static boolean describes(ObjectTag object) { - if (!(object instanceof EntityTag)) { - return false; - } - Entity entity = ((EntityTag) object).getBukkitEntity(); + public static boolean describes(EntityTag object) { + Entity entity = object.getBukkitEntity(); return entity instanceof Item || entity instanceof Enderman || entity instanceof SizedFireball || entity instanceof ThrowableProjectile || entity instanceof EnderSignal - || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && entity instanceof ItemDisplay); - } - - public static EntityItem getFrom(ObjectTag entity) { - if (!describes(entity)) { - return null; - } - else { - return new EntityItem((EntityTag) entity); - } + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && entity instanceof ItemDisplay) + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && entity instanceof OminousItemSpawner); } - public static final String[] handledMechs = new String[] { - "item" - }; - - public EntityItem(EntityTag entity) { - item = entity; - } - - EntityTag item; - - public ItemTag getItem(boolean includeDeprecated, TagContext context) { - if (isDroppedItem()) { - return new ItemTag(getDroppedItem().getItemStack()); + @Override + public ItemTag getPropertyValue() { + if (getEntity() instanceof Item item) { + return new ItemTag(item.getItemStack()); } - else if (includeDeprecated && isEnderman()) { - BukkitImplDeprecations.entityItemEnderman.warn(context); - BlockData data = getEnderman().getCarriedBlock(); - if (data == null) { - return new ItemTag(Material.AIR); - } - return new ItemTag(data.getMaterial()); + else if (getEntity() instanceof SizedFireball fireball) { + return new ItemTag(fireball.getDisplayItem()); } - else if (isFireball()) { - return new ItemTag(getFireball().getDisplayItem()); + else if (getEntity() instanceof ThrowableProjectile projectile) { + return new ItemTag(projectile.getItem()); } - else if (isThrowableProjectile()) { - return new ItemTag(getThrowableProjectile().getItem()); + else if (getEntity() instanceof EnderSignal signal) { + return new ItemTag(signal.getItem()); } - else if (isEnderSignal()) { - return new ItemTag(getEnderSignal().getItem()); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getEntity() instanceof ItemDisplay itemDisplay) { + return new ItemTag(itemDisplay.getItemStack()); } - else if (isDisplay()) { // TODO: 1.19: when 1.19 is minimum, make a 'getDisplay' - return new ItemTag(((ItemDisplay) item.getBukkitEntity()).getItemStack()); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof OminousItemSpawner ominousItemSpawner) { + return new ItemTag(ominousItemSpawner.getItem()); } return null; } - public boolean isDroppedItem() { - return item.getBukkitEntity() instanceof Item; - } - - public boolean isEnderman() { - return item.getBukkitEntity() instanceof Enderman; - } - - public boolean isFireball() { - return item.getBukkitEntity() instanceof SizedFireball; - } - - public boolean isThrowableProjectile() { - return item.getBukkitEntity() instanceof ThrowableProjectile; - } - - public boolean isEnderSignal() { - return item.getBukkitEntity() instanceof EnderSignal; - } - - public boolean isDisplay() { - return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && item.getBukkitEntity() instanceof Display; - } - - public Item getDroppedItem() { - return (Item) item.getBukkitEntity(); - } - - public Enderman getEnderman() { - return (Enderman) item.getBukkitEntity(); - } - - public EnderSignal getEnderSignal() { - return (EnderSignal) item.getBukkitEntity(); - } - - public ThrowableProjectile getThrowableProjectile() { - return (ThrowableProjectile) item.getBukkitEntity(); - } - - public SizedFireball getFireball() { - return (SizedFireball) item.getBukkitEntity(); - } - @Override - public String getPropertyString() { - ItemTag item = getItem(false, null); - if (item.getBukkitMaterial() != Material.AIR) { - return item.identify(); + public void setPropertyValue(ItemTag item, Mechanism mechanism) { + if (object.isCitizensNPC()) { + object.getDenizenNPC().getCitizen().data().setPersistent(NPC.Metadata.ITEM_ID, item.getBukkitMaterial().name()); + } + if (getEntity() instanceof Item droppedItem) { + droppedItem.setItemStack(item.getItemStack()); + } + else if (getEntity() instanceof SizedFireball fireball) { + fireball.setDisplayItem(item.getItemStack()); + } + else if (getEntity() instanceof ThrowableProjectile projectile) { + projectile.setItem(item.getItemStack()); + } + else if (getEntity() instanceof EnderSignal signal) { + signal.setItem(item.getItemStack()); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getEntity() instanceof ItemDisplay itemDisplay) { + itemDisplay.setItemStack(item.getItemStack()); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof OminousItemSpawner ominousItemSpawner) { + ominousItemSpawner.setItem(item.getItemStack()); } - return null; } @Override @@ -150,37 +90,6 @@ public String getPropertyId() { } public static void register() { - PropertyParser.registerTag(EntityItem.class, ItemTag.class, "item", (attribute, object) -> { - return object.getItem(true, attribute.context); - }); - } - - @Override - public void adjust(Mechanism mechanism) { - if (mechanism.matches("item") && mechanism.requireObject(ItemTag.class)) { - ItemStack itemStack = mechanism.valueAsType(ItemTag.class).getItemStack(); - if (item.isCitizensNPC()) { - item.getDenizenNPC().getCitizen().data().setPersistent(NPC.Metadata.ITEM_ID, itemStack.getType().name()); - } - if (isDroppedItem()) { - getDroppedItem().setItemStack(itemStack); - } - else if (isEnderman()) { - BukkitImplDeprecations.entityItemEnderman.warn(mechanism.context); - getEnderman().setCarriedBlock(itemStack.getType().createBlockData()); - } - else if (isFireball()) { - getFireball().setDisplayItem(itemStack); - } - else if (isThrowableProjectile()) { - getThrowableProjectile().setItem(itemStack); - } - else if (isEnderSignal()) { - getEnderSignal().setItem(itemStack); - } - else if (isDisplay()) { // TODO: 1.19: when 1.19 is minimum, make a 'getDisplay' - ((ItemDisplay) item.getBukkitEntity()).setItemStack(itemStack); - } - } + autoRegister("item", EntityItem.class, ItemTag.class, false); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityProfession.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityProfession.java index 478f6e9f56..66adee6d3b 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityProfession.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityProfession.java @@ -2,117 +2,57 @@ import com.denizenscript.denizen.objects.EntityTag; import com.denizenscript.denizen.utilities.Utilities; -import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.Mechanism; -import com.denizenscript.denizencore.objects.ObjectTag; -import com.denizenscript.denizencore.objects.properties.Property; -import com.denizenscript.denizencore.tags.Attribute; -import com.denizenscript.denizencore.utilities.CoreUtilities; -import org.bukkit.entity.EntityType; +import com.denizenscript.denizencore.objects.core.ElementTag; import org.bukkit.entity.Villager; import org.bukkit.entity.ZombieVillager; -public class EntityProfession implements Property { +public class EntityProfession extends EntityProperty { - // TODO This technically has registries on all supported versions - public static boolean describes(ObjectTag entity) { - if (!(entity instanceof EntityTag)) { - return false; - } - return ((EntityTag) entity).getBukkitEntityType() == EntityType.VILLAGER - || ((EntityTag) entity).getBukkitEntityType() == EntityType.ZOMBIE_VILLAGER; + // <--[property] + // @object EntityTag + // @name profession + // @input ElementTag + // @description + // Controls the profession of a villager or zombie villager. + // For the list of possible professions, refer to <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Villager.Profession.html> + // --> + + public static boolean describes(EntityTag entity) { + return entity.getBukkitEntity() instanceof Villager + || entity.getBukkitEntity() instanceof ZombieVillager; } - public static EntityProfession getFrom(ObjectTag entity) { - if (!describes(entity)) { - return null; + @Override + public ElementTag getPropertyValue() { + if (getEntity() instanceof Villager villager) { + return Utilities.enumlikeToElement(villager.getProfession()); } - else { - return new EntityProfession((EntityTag) entity); + else if (getEntity() instanceof ZombieVillager zombieVillager) { + return Utilities.enumlikeToElement(zombieVillager.getVillagerProfession()); } + return null; } - public static final String[] handledTags = new String[] { - "profession" - }; - - public static final String[] handledMechs = new String[] { - "profession" - }; - - public EntityProfession(EntityTag entity) { - professional = entity; - } - - EntityTag professional; - - public Villager.Profession getProfession() { - if (professional.getBukkitEntityType() == EntityType.ZOMBIE_VILLAGER) { - return ((ZombieVillager) professional.getBukkitEntity()).getVillagerProfession(); + @Override + public void setPropertyValue(ElementTag value, Mechanism mechanism) { + if (!mechanism.requireEnum(Villager.Profession.class)) { + return; } - return ((Villager) professional.getBukkitEntity()).getProfession(); - } - - public void setProfession(Villager.Profession profession) { - if (professional.getBukkitEntityType() == EntityType.ZOMBIE_VILLAGER) { - ((ZombieVillager) professional.getBukkitEntity()).setVillagerProfession(profession); + if (getEntity() instanceof Villager villager) { + villager.setProfession(value.asEnum(Villager.Profession.class)); } - else { - ((Villager) professional.getBukkitEntity()).setProfession(profession); + else if (getEntity() instanceof ZombieVillager zombieVillager) { + zombieVillager.setVillagerProfession(value.asEnum(Villager.Profession.class)); } } - @Override - public String getPropertyString() { - return CoreUtilities.toLowerCase(String.valueOf(getProfession())); - } - @Override public String getPropertyId() { return "profession"; } - @Override - public ObjectTag getObjectAttribute(Attribute attribute) { - - if (attribute == null) { - return null; - } - - // <--[tag] - // @attribute - // @returns ElementTag - // @mechanism EntityTag.profession - // @group properties - // @description - // If the entity can have professions, returns the entity's profession. - // Currently, only Villager-type and infected zombie entities can have professions. - // For the list of possible professions, refer to <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Villager.Profession.html> - // --> - if (attribute.startsWith("profession")) { - return new ElementTag(String.valueOf(getProfession()), true) - .getObjectAttribute(attribute.fulfill(1)); - } - - return null; - } - - @Override - public void adjust(Mechanism mechanism) { - - // <--[mechanism] - // @object EntityTag - // @name profession - // @input ElementTag - // @description - // Changes the entity's profession. - // Currently, only Villager-type entities can have professions. - // For the list of possible professions, refer to <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Villager.Profession.html> - // @tags - // - // --> - if (mechanism.matches("profession") && Utilities.requireEnumlike(mechanism, Villager.Profession.class)) { - setProfession(Utilities.elementToEnumlike(mechanism.getValue(), Villager.Profession.class)); - } + public static void register() { + autoRegister("profession", EntityProfession.class, ElementTag.class, false); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySize.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySize.java index e821a24dbc..6c58c4fab9 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySize.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySize.java @@ -1,53 +1,68 @@ package com.denizenscript.denizen.objects.properties.entity; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizencore.objects.Mechanism; import com.denizenscript.denizencore.objects.core.ElementTag; -import com.denizenscript.denizencore.objects.ObjectTag; -import com.denizenscript.denizencore.objects.properties.Property; -import com.denizenscript.denizencore.objects.properties.PropertyParser; -import org.bukkit.entity.Phantom; -import org.bukkit.entity.PufferFish; -import org.bukkit.entity.Slime; - -public class EntitySize implements Property { - - public static boolean describes(ObjectTag entity) { - return entity instanceof EntityTag && - (((EntityTag) entity).getBukkitEntity() instanceof Slime - || ((EntityTag) entity).getBukkitEntity() instanceof Phantom - || ((EntityTag) entity).getBukkitEntity() instanceof PufferFish); +import org.bukkit.entity.*; + +public class EntitySize extends EntityProperty { + // TODO: once 26.2 is the minimum supported version, remove Slime usage in favor of AbstractCubeMob + + // <--[property] + // @object EntityTag + // @name size + // @input ElementTag + // @description + // Controls the size of an entity. + // Cube-type (slime, magma cube, sulfur cube) mob sizes are between 1 and 127. + // Phantom mob sizes are between 0 and 64. + // Pufferfish mob sizes are between 0 and 2. + // --> + + public static boolean describes(EntityTag entityTag) { + Entity entity = entityTag.getBukkitEntity(); + return entity instanceof Phantom + || entity instanceof PufferFish + || entity instanceof Slime + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v26_2) && entity instanceof AbstractCubeMob); } - public static EntitySize getFrom(ObjectTag entity) { - if (!describes(entity)) { - return null; - } - else { - return new EntitySize((EntityTag) entity); + @Override + public ElementTag getPropertyValue() { + if (getEntity() instanceof Phantom phantom) { + return new ElementTag(phantom.getSize()); } - } - - public EntitySize(EntityTag ent) { - entity = ent; - } - - EntityTag entity; - - public int getSize() { - if (isSlime()) { - return getSlime().getSize(); + else if (getEntity() instanceof PufferFish pufferfish) { + return new ElementTag(pufferfish.getPuffState()); } - else if (isPhantom()) { - return getPhantom().getSize(); + else if (getEntity() instanceof Slime slime) { + return new ElementTag(slime.getSize()); } - else { - return getPufferFish().getPuffState(); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v26_2) && getEntity() instanceof AbstractCubeMob cube) { + return new ElementTag(cube.getSize()); } + return null; } @Override - public String getPropertyString() { - return String.valueOf(getSize()); + public void setPropertyValue(ElementTag value, Mechanism mechanism) { + if (!mechanism.requireInteger()) { + return; + } + if (getEntity() instanceof Phantom phantom) { + phantom.setSize(value.asInt()); + } + else if (getEntity() instanceof PufferFish pufferfish) { + pufferfish.setPuffState(value.asInt()); + } + else if (getEntity() instanceof Slime slime) { + slime.setSize(value.asInt()); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v26_2) && getEntity() instanceof AbstractCubeMob cube) { + cube.setSize(value.asInt()); + } } @Override @@ -56,63 +71,6 @@ public String getPropertyId() { } public static void register() { - - // <--[tag] - // @attribute - // @returns ElementTag(Number) - // @mechanism EntityTag.size - // @group properties - // @description - // Returns the size of a slime-type entity or a Phantom (1-120). - // If the entity is a PufferFish it returns the puff state (0-3). - // --> - PropertyParser.registerTag(EntitySize.class, ElementTag.class, "size", (attribute, object) -> { - return new ElementTag(object.getSize()); - }); - - - // <--[mechanism] - // @object EntityTag - // @name size - // @input ElementTag(Number) - // @description - // Sets the size of a slime-type entity or a Phantom (1-120). - // If the entity is a PufferFish it sets the puff state (0-3). - // @tags - // - // --> - PropertyParser.registerMechanism(EntitySize.class, ElementTag.class, "size", (object, mechanism, input) -> { - if (mechanism.requireInteger()) { - if (object.isSlime()) { - object.getSlime().setSize(input.asInt()); - } - else if (object.isPhantom()) { - object.getPhantom().setSize(input.asInt()); - } - else { - object.getPufferFish().setPuffState(input.asInt()); - } - } - }); - } - - public boolean isSlime() { - return entity.getBukkitEntity() instanceof Slime; - } - - public boolean isPhantom() { - return entity.getBukkitEntity() instanceof Phantom; - } - - public Slime getSlime() { - return (Slime) entity.getBukkitEntity(); - } - - public Phantom getPhantom() { - return (Phantom) entity.getBukkitEntity(); - } - - public PufferFish getPufferFish() { - return (PufferFish) entity.getBukkitEntity(); + autoRegister("size", EntitySize.class, ElementTag.class, false); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityState.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityState.java new file mode 100644 index 0000000000..e68ce63ea6 --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityState.java @@ -0,0 +1,54 @@ +package com.denizenscript.denizen.objects.properties.entity; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import org.bukkit.entity.Armadillo; + +public class EntityState extends EntityProperty { + + // <--[property] + // @object EntityTag + // @name state + // @input ElementTag + // @description + // Controls the current state of an armadillo. + // Valid states are IDLE, ROLLING, SCARED, and UNROLLING. + // The entity may roll or unroll due to normal vanilla conditions. If this is not desired, disable <@link property EntityTag.has_ai>. + // --> + + public static boolean describes(EntityTag entity) { + return entity.getBukkitEntity() instanceof Armadillo; + } + + public enum ArmadilloState { + IDLE, ROLLING, SCARED, UNROLLING + } + + @Override + public boolean isDefaultValue(ElementTag val) { + return val.asEnum(ArmadilloState.class).equals(ArmadilloState.IDLE); + } + + @Override + public ElementTag getPropertyValue() { + return new ElementTag(NMSHandler.entityHelper.getArmadilloState(as(Armadillo.class))); + } + + @Override + public void setPropertyValue(ElementTag param, Mechanism mechanism) { + if (mechanism.requireEnum(ArmadilloState.class)) { + NMSHandler.entityHelper.setArmadilloState(as(Armadillo.class), param.asEnum(ArmadilloState.class)); + } + } + + @Override + public String getPropertyId() { + return "state"; + } + + public static void register() { + autoRegister("state", EntityState.class, ElementTag.class, false); + } +} diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVariant.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVariant.java index 4ded567281..5b50f1c822 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVariant.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVariant.java @@ -3,15 +3,13 @@ import com.denizenscript.denizen.nms.NMSHandler; import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.utilities.PaperAPITools; import com.denizenscript.denizen.utilities.Utilities; import com.denizenscript.denizencore.objects.Mechanism; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; -import org.bukkit.entity.Chicken; -import org.bukkit.entity.Cow; -import org.bukkit.entity.Pig; -import org.bukkit.entity.Wolf; +import org.bukkit.entity.*; import java.lang.invoke.MethodHandle; @@ -37,18 +35,23 @@ public class EntityVariant extends EntityProperty { // @name variant // @input ElementTag // @description - // Controls which variant a chicken, cow, pig, or wolf is. + // Controls which variant a chicken, copper golem, cow, pig, wolf, or zombie nautilus is. // A list of valid chicken variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Chicken.Variant.html>. + // A list of valid copper golem variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/CopperGolem.CopperWeatherState.html>. // A list of valid cow variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Cow.Variant.html>. // A list of valid pig variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Pig.Variant.html>. // A list of valid wolf variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Wolf.Variant.html>. + // A list of valid zombie nautilus variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/ZombieNautilus.Variant.html>. // --> - public static boolean describes(EntityTag entity) { - return entity.getBukkitEntity() instanceof Wolf - || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && entity.getBukkitEntity() instanceof Chicken) - || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && entity.getBukkitEntity() instanceof Cow) - || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && entity.getBukkitEntity() instanceof Pig); + public static boolean describes(EntityTag entityTag) { + Entity entity = entityTag.getBukkitEntity(); + return entity instanceof Wolf + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && (entity instanceof Chicken + || entity instanceof CopperGolem + || entity instanceof Cow + || entity instanceof Pig + || entity instanceof ZombieNautilus)); } @Override @@ -56,20 +59,28 @@ public ElementTag getPropertyValue() { if (getEntity() instanceof Wolf wolf) { return new ElementTag(Utilities.namespacedKeyToString(wolf.getVariant().getKey()), true); } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof Chicken chicken) { - return new ElementTag(Utilities.namespacedKeyToString(chicken.getVariant().getKey()), true); - } - else if (COW_GET_VARIANT != null && getEntity() instanceof Cow cow) { - try { - return new ElementTag(Utilities.namespacedKeyToString(((Cow.Variant) COW_GET_VARIANT.invoke(cow)).getKey()), true); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + if (getEntity() instanceof Chicken chicken) { + return new ElementTag(Utilities.namespacedKeyToString(chicken.getVariant().getKey()), true); } - catch (Throwable e) { - Debug.echoError(e); - return null; + else if (getEntity() instanceof CopperGolem copperGolem) { + return new ElementTag(PaperAPITools.instance.getCopperGolemState(copperGolem), true); + } + else if (COW_GET_VARIANT != null && getEntity() instanceof Cow cow) { + try { + return new ElementTag(Utilities.namespacedKeyToString(((Cow.Variant) COW_GET_VARIANT.invoke(cow)).getKey()), true); + } + catch (Throwable e) { + Debug.echoError(e); + return null; + } + } + else if (getEntity() instanceof Pig pig) { + return new ElementTag(Utilities.namespacedKeyToString(pig.getVariant().getKey()), true); + } + else if (getEntity() instanceof ZombieNautilus zombieNautilus) { + return new ElementTag(Utilities.namespacedKeyToString(zombieNautilus.getVariant().getKey()), true); } - } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof Pig pig) { - return new ElementTag(Utilities.namespacedKeyToString(pig.getVariant().getKey()), true); } return null; } @@ -82,27 +93,38 @@ public void setPropertyValue(ElementTag variant, Mechanism mechanism) { wolf.setVariant(wolfVariant); } } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof Chicken chicken) { - Chicken.Variant chickenVariant = Utilities.elementToRequiredEnumLike(variant, Chicken.Variant.class, mechanism); - if (chickenVariant != null) { - chicken.setVariant(chickenVariant); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + if (getEntity() instanceof Chicken chicken) { + Chicken.Variant chickenVariant = Utilities.elementToRequiredEnumLike(variant, Chicken.Variant.class, mechanism); + if (chickenVariant != null) { + chicken.setVariant(chickenVariant); + } } - } - else if (COW_SET_VARIANT != null && getEntity() instanceof Cow cow) { - Cow.Variant cowVariant = Utilities.elementToRequiredEnumLike(variant, Cow.Variant.class, mechanism); - if (cowVariant != null) { - try { - COW_SET_VARIANT.invoke(cow, cowVariant); + else if (getEntity() instanceof CopperGolem copperGolem) { + PaperAPITools.instance.setCopperGolemState(variant, copperGolem, mechanism); + } + else if (COW_SET_VARIANT != null && getEntity() instanceof Cow cow) { + Cow.Variant cowVariant = Utilities.elementToRequiredEnumLike(variant, Cow.Variant.class, mechanism); + if (cowVariant != null) { + try { + COW_SET_VARIANT.invoke(cow, cowVariant); + } + catch (Throwable e) { + Debug.echoError(e); + } } - catch (Throwable e) { - Debug.echoError(e); + } + else if (getEntity() instanceof Pig pig) { + Pig.Variant pigVariant = Utilities.elementToRequiredEnumLike(variant, Pig.Variant.class, mechanism); + if (pigVariant != null) { + pig.setVariant(pigVariant); } } - } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof Pig pig) { - Pig.Variant pigVariant = Utilities.elementToRequiredEnumLike(variant, Pig.Variant.class, mechanism); - if (pigVariant != null) { - pig.setVariant(pigVariant); + else if (getEntity() instanceof ZombieNautilus zombieNautilus) { + ZombieNautilus.Variant zombieNautilusVariant = Utilities.elementToRequiredEnumLike(variant, ZombieNautilus.Variant.class, mechanism); + if (zombieNautilusVariant != null) { + zombieNautilus.setVariant(zombieNautilusVariant); + } } } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemArmorPose.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemArmorPose.java index 306baff5cb..f6521e0f0c 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemArmorPose.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemArmorPose.java @@ -78,8 +78,7 @@ public void setPropertyValue(MapTag param, Mechanism mechanism) { if (entityNbt == null) { return; } - // TODO: adventure-nbt: contains - if (!(entityNbt.get("Pose") instanceof CompoundBinaryTag)) { + if (!entityNbt.contains("Pose", BinaryTagTypes.COMPOUND)) { return; } entityNbt = entityNbt.remove("Pose"); diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemCustomModel.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemCustomModel.java index c47897271e..e97bea7df7 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemCustomModel.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemCustomModel.java @@ -15,6 +15,7 @@ public class ItemCustomModel extends ItemProperty { // @description // Controls the custom model data ID number of the item. // Use with no input to remove the custom model data. + // Prefer <@link property ItemTag.item_model> on MC 1.21+. // See also <@link tag ItemTag.has_custom_model_data> // --> public static boolean describes(ItemTag item) { @@ -56,6 +57,7 @@ public static void register() { // @group properties // @description // Returns whether the item has a custom model data ID number set on it. + // Prefer <@link property ItemTag.item_model> on MC 1.21+. // See also <@link tag ItemTag.custom_model_data>. // --> PropertyParser.registerTag(ItemCustomModel.class, ElementTag.class, "has_custom_model_data", (attribute, prop) -> { diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemDurability.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemDurability.java index ddd46aa1fb..a8a8b3847d 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemDurability.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemDurability.java @@ -58,14 +58,7 @@ public ObjectTag getObjectAttribute(Attribute attribute) { .getObjectAttribute(attribute.fulfill(1)); } - // <--[tag] - // @attribute - // @returns ElementTag(Number) - // @group properties - // @description - // Returns the maximum durability (number of uses) of this item. - // For use with <@link tag ItemTag.durability> and <@link mechanism ItemTag.durability>. - // --> + // replaced by paper->MaxDurabilityAdapter if (attribute.startsWith("max_durability")) { return new ElementTag(item.getMaterial().getMaterial().getMaxDurability()) .getObjectAttribute(attribute.fulfill(1)); diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemRawNBT.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemRawNBT.java index 719addec44..578cc1526c 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemRawNBT.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemRawNBT.java @@ -153,11 +153,10 @@ public static CompoundBinaryTag compoundOrEmpty(CompoundBinaryTag compoundTag) { public static BinaryTag convertObjectToNbt(ObjectTag inputObject, TagContext context, String path) { if (inputObject.canBeType(MapTag.class)) { MapTag map = inputObject.asType(MapTag.class, context); - // TODO: adventure-nbt: builders initial size - Map result = new HashMap<>(map.size()); + CompoundBinaryTag.Builder resultBuilder = CompoundBinaryTag.builder(map.size()); for (Map.Entry entry : map.entrySet()) { try { - result.put(entry.getKey().str, convertObjectToNbt(entry.getValue(), context, path + "." + entry.getKey().str)); + resultBuilder.put(entry.getKey().str, convertObjectToNbt(entry.getValue(), context, path + "." + entry.getKey().str)); } catch (Exception ex) { Debug.echoError("Object NBT interpretation failed for key '" + path + "." + entry.getKey().str + "'."); @@ -165,15 +164,14 @@ public static BinaryTag convertObjectToNbt(ObjectTag inputObject, TagContext con return null; } } - return CompoundBinaryTag.from(result); + return resultBuilder.build(); } else if (!HAS_NBT_LIST_TYPES && inputObject.shouldBeType(ListTag.class)) { ListTag list = inputObject.asType(ListTag.class, context); - // TODO: adventure-nbt: builders initial size - List result = new ArrayList<>(list.size()); + ListBinaryTag.Builder resultBuilder = ListBinaryTag.heterogeneousListBinaryTag(list.size()); for (int i = 0; i < list.size(); i++) { try { - result.add(convertObjectToNbt(list.getObject(i), context, path + '[' + i + ']')); + resultBuilder.add(convertObjectToNbt(list.getObject(i), context, path + '[' + i + ']')); } catch (Exception ex) { Debug.echoError("Object NBT interpretation failed for list key '" + path + "' at index " + i + '.'); @@ -181,7 +179,7 @@ else if (!HAS_NBT_LIST_TYPES && inputObject.shouldBeType(ListTag.class)) { return null; } } - return ListBinaryTag.listBinaryTag(BinaryTagTypes.LIST_WILDCARD, result); + return resultBuilder.build(); } String input = inputObject.identify(); if (input.equals("end")) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignBackContents.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignBackContents.java new file mode 100644 index 0000000000..3371e133b0 --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignBackContents.java @@ -0,0 +1,70 @@ +package com.denizenscript.denizen.objects.properties.item; + +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.utilities.PaperAPITools; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ListTag; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import org.bukkit.block.Sign; +import org.bukkit.inventory.meta.BlockStateMeta; + +public class ItemSignBackContents extends ItemProperty { + + // <--[property] + // @object ItemTag + // @name sign_back_contents + // @input ListTag + // @description + // Controls the contents on the back of a sign item. + // For the front of the sign, see <@link property ItemTag.sign_contents>. + // --> + + public static boolean describes(ItemTag item) { + return item.getItemMeta() instanceof BlockStateMeta blockStateMeta + && blockStateMeta.getBlockState() instanceof Sign; + } + + @Override + public ListTag getPropertyValue() { + return new ListTag(PaperAPITools.instance.getSignBackLines((Sign) as(BlockStateMeta.class).getBlockState()), true); + } + + @Override + public boolean isDefaultValue(ListTag value) { + for (String line : value) { + if (!line.isEmpty()) { + return false; + } + } + return true; + } + + @Override + public void setPropertyValue(ListTag value, Mechanism mechanism) { + BlockStateMeta blockStateMeta = as(BlockStateMeta.class); + Sign sign = (Sign) blockStateMeta.getBlockState(); + for (int i = 0; i < 4; i++) { + PaperAPITools.instance.setSignBackLine(sign, i, ""); + } + CoreUtilities.fixNewLinesToListSeparation(value); + if (value.size() > 4) { + mechanism.echoError("Sign can only hold four lines!"); + } + else { + for (int i = 0; i < value.size(); i++) { + PaperAPITools.instance.setSignBackLine(sign, i, value.get(i)); + } + } + blockStateMeta.setBlockState(sign); + setItemMeta(blockStateMeta); + } + + @Override + public String getPropertyId() { + return "sign_back_contents"; + } + + public static void register() { + autoRegister("sign_back_contents", ItemSignBackContents.class, ListTag.class, false); + } +} diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignContents.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignContents.java index 9774eccaf2..c307452885 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignContents.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignContents.java @@ -1,84 +1,63 @@ package com.denizenscript.denizen.objects.properties.item; -import com.denizenscript.denizen.utilities.PaperAPITools; -import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.utilities.PaperAPITools; import com.denizenscript.denizencore.objects.Mechanism; import com.denizenscript.denizencore.objects.core.ListTag; -import com.denizenscript.denizencore.objects.ObjectTag; -import com.denizenscript.denizencore.objects.properties.Property; -import com.denizenscript.denizencore.tags.Attribute; import com.denizenscript.denizencore.utilities.CoreUtilities; import org.bukkit.block.Sign; import org.bukkit.inventory.meta.BlockStateMeta; -import java.util.Arrays; - -public class ItemSignContents implements Property { - - public static boolean describes(ObjectTag item) { - return item instanceof ItemTag - && ((ItemTag) item).getItemMeta() instanceof BlockStateMeta - && ((BlockStateMeta) ((ItemTag) item).getItemMeta()).getBlockState() instanceof Sign; +public class ItemSignContents extends ItemProperty { + + // <--[property] + // @object ItemTag + // @name sign_contents + // @input ListTag + // @description + // Controls the contents of a sign item. + // For MC 1.20+, this is the contents on the front of the sign. + // For the back of the sign, see <@link property ItemTag.sign_back_contents>. + // --> + + public static boolean describes(ItemTag item) { + return item.getItemMeta() instanceof BlockStateMeta blockStateMeta + && blockStateMeta.getBlockState() instanceof Sign; } - public static ItemSignContents getFrom(ObjectTag _item) { - if (!describes(_item)) { - return null; - } - else { - return new ItemSignContents((ItemTag) _item); - } - } - - public static final String[] handledTags = new String[] { - "sign_contents" - }; - - public static final String[] handledMechs = new String[] { - "sign_contents" - }; - - public ListTag getSignContents() { - return new ListTag(Arrays.asList(PaperAPITools.instance.getSignLines((Sign) ((BlockStateMeta) item.getItemMeta()).getBlockState())), true); - } - - public ItemSignContents(ItemTag _item) { - item = _item; + @Override + public ListTag getPropertyValue() { + return new ListTag(PaperAPITools.instance.getSignLines((Sign) as(BlockStateMeta.class).getBlockState()), true); } - ItemTag item; - @Override - public ObjectTag getObjectAttribute(Attribute attribute) { - - if (attribute == null) { - return null; - } - - // <--[tag] - // @attribute - // @returns ListTag - // @mechanism ItemTag.sign_contents - // @group properties - // @description - // Returns a list of lines on a sign item. - // --> - if (attribute.startsWith("sign_contents")) { - return getSignContents().getObjectAttribute(attribute.fulfill(1)); + public boolean isDefaultValue(ListTag value) { + for (String line : value) { + if (!line.isEmpty()) { + return false; + } } - - return null; + return true; } @Override - public String getPropertyString() { - for (String line : getSignContents()) { - if (line.length() > 0) { - return getSignContents().identify(); + public void setPropertyValue(ListTag value, Mechanism mechanism) { + BlockStateMeta blockStateMeta = as(BlockStateMeta.class); + Sign sign = (Sign) blockStateMeta.getBlockState(); + for (int i = 0; i < 4; i++) { + PaperAPITools.instance.setSignLine(sign, i, ""); + } + CoreUtilities.fixNewLinesToListSeparation(value); + if (value.size() > 4) { + mechanism.echoError("Sign can only hold four lines!"); + } + else { + for (int i = 0; i < value.size(); i++) { + PaperAPITools.instance.setSignLine(sign, i, value.get(i)); } } - return null; + blockStateMeta.setBlockState(sign); + setItemMeta(blockStateMeta); } @Override @@ -86,36 +65,7 @@ public String getPropertyId() { return "sign_contents"; } - @Override - public void adjust(Mechanism mechanism) { - - // <--[mechanism] - // @object ItemTag - // @name sign_contents - // @input ListTag - // @description - // Sets the contents of a sign item. - // @tags - // - // --> - if (mechanism.matches("sign_contents")) { - BlockStateMeta bsm = ((BlockStateMeta) item.getItemMeta()); - Sign sign = (Sign) bsm.getBlockState(); - for (int i = 0; i < 4; i++) { - PaperAPITools.instance.setSignLine(sign, i, ""); - } - ListTag list = mechanism.valueAsType(ListTag.class); - CoreUtilities.fixNewLinesToListSeparation(list); - if (list.size() > 4) { - Debug.echoError("Sign can only hold four lines!"); - } - else { - for (int i = 0; i < list.size(); i++) { - PaperAPITools.instance.setSignLine(sign, i, list.get(i)); - } - } - bsm.setBlockState(sign); - item.setItemMeta(bsm); - } + public static void register() { + autoRegister("sign_contents", ItemSignContents.class, ListTag.class, false); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialCount.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialCount.java index 398d1c5b27..dd69b8ef8a 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialCount.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialCount.java @@ -4,63 +4,98 @@ import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.MaterialTag; import com.denizenscript.denizencore.objects.Mechanism; -import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; -import com.denizenscript.denizencore.objects.properties.Property; import com.denizenscript.denizencore.objects.properties.PropertyParser; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.*; -public class MaterialCount implements Property { +public class MaterialCount extends MaterialProperty { + // TODO: once 1.21 is the minimum supported version, remove PinkPetals usage in favor of FlowerBed - public static boolean describes(ObjectTag material) { - if (!(material instanceof MaterialTag mat)) { - return false; - } - if (!mat.hasModernData()) { - return false; - } - BlockData data = mat.getModernData(); + // <--[property] + // @object MaterialTag + // @name count + // @input ElementTag(Number) + // @description + // Controls the amount of pickles in a Sea Pickle material, eggs in a Turtle Egg material, charges in a Respawn Anchor material, candles in a Candle material, flowers in a flower bed, or leaves in a leaf litter. + // --> + + public static boolean describes(MaterialTag material) { + BlockData data = material.getModernData(); return data instanceof SeaPickle || data instanceof TurtleEgg || data instanceof RespawnAnchor || data instanceof Candle - || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && data instanceof PinkPetals); + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && data instanceof PinkPetals) + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && (data instanceof FlowerBed + || data instanceof LeafLitter)); } - public static MaterialCount getFrom(ObjectTag _material) { - if (!describes(_material)) { - return null; + @Override + public ElementTag getPropertyValue() { + if (getBlockData() instanceof SeaPickle seaPickle) { + return new ElementTag(seaPickle.getPickles()); + } + else if (getBlockData() instanceof TurtleEgg turtleEgg) { + return new ElementTag(turtleEgg.getEggs()); + } + else if (getBlockData() instanceof RespawnAnchor respawnAnchor) { + return new ElementTag(respawnAnchor.getCharges()); } - else { - return new MaterialCount((MaterialTag) _material); + else if (getBlockData() instanceof Candle candle) { + return new ElementTag(candle.getCandles()); } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof PinkPetals pinkPetals) { + return new ElementTag(pinkPetals.getFlowerAmount()); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof FlowerBed flowerBed) { + return new ElementTag(flowerBed.getFlowerAmount()); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof LeafLitter leafLitter) { + return new ElementTag(leafLitter.getSegmentAmount()); + } + return null; } - public static final String[] handledMechs = new String[] { - "count", "pickle_count" - }; - - public MaterialCount(MaterialTag _material) { - material = _material; + @Override + public void setPropertyValue(ElementTag value, Mechanism mechanism) { + if (mechanism.requireInteger()) { + int count = value.asInt(); + if (count < getMin() || count > getMax()) { + mechanism.echoError("Material count mechanism value '" + count + "' is not valid. Must be between " + getMin() + " and " + getMax() + "."); + return; + } + if (getBlockData() instanceof SeaPickle seaPickle) { + seaPickle.setPickles(count); + } + else if (getBlockData() instanceof TurtleEgg turtleEgg) { + turtleEgg.setEggs(count); + } + else if (getBlockData() instanceof RespawnAnchor respawnAnchor) { + respawnAnchor.setCharges(count); + } + else if (getBlockData() instanceof Candle candle) { + candle.setCandles(count); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof PinkPetals pinkPetals) { + pinkPetals.setFlowerAmount(count); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof FlowerBed flowerBed) { + flowerBed.setFlowerAmount(count); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof LeafLitter leafLitter) { + leafLitter.setSegmentAmount(count); + } + } } - MaterialTag material; + @Override + public String getPropertyId() { + return "count"; + } public static void register() { - // <--[tag] - // @attribute - // @returns ElementTag(Number) - // @mechanism MaterialTag.count - // @group properties - // @description - // Returns the amount of pickles in a Sea Pickle material, eggs in a Turtle Egg material, charges in a Respawn Anchor material, candles in a Candle material, or petals in a Pink Petals material. - // --> - PropertyParser.registerStaticTag(MaterialCount.class, ElementTag.class, "count", (attribute, material) -> { - return new ElementTag(material.getCurrent()); - }, "pickle_count"); - // <--[tag] // @attribute // @returns ElementTag(Number) @@ -84,142 +119,57 @@ public static void register() { PropertyParser.registerStaticTag(MaterialCount.class, ElementTag.class, "count_min", (attribute, material) -> { return new ElementTag(material.getMin()); }, "pickle_min"); - } - - public boolean isSeaPickle() { - return material.getModernData() instanceof SeaPickle; - } - - public boolean isTurtleEgg() { - return material.getModernData() instanceof TurtleEgg; - } - public boolean isRespawnAnchor() { - return material.getModernData() instanceof RespawnAnchor; - } - - public boolean isCandle() { - return material.getModernData() instanceof Candle; - } - - public TurtleEgg getTurtleEgg() { - return (TurtleEgg) material.getModernData(); - } - - public SeaPickle getSeaPickle() { - return (SeaPickle) material.getModernData(); - } - - public RespawnAnchor getRespawnAnchor() { - return (RespawnAnchor) material.getModernData(); - } - - public Candle getCandle() { - return (Candle) material.getModernData(); - } - - public int getCurrent() { - if (isSeaPickle()) { - return getSeaPickle().getPickles(); - } - else if (isTurtleEgg()) { - return getTurtleEgg().getEggs(); - } - else if (isRespawnAnchor()) { - return getRespawnAnchor().getCharges(); - } - else if (isCandle()) { - return getCandle().getCandles(); - } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && material.getModernData() instanceof PinkPetals pinkPetals) { - return pinkPetals.getFlowerAmount(); - } - throw new UnsupportedOperationException(); + autoRegister("count", MaterialCount.class, ElementTag.class, false, "pickle_count"); } public int getMax() { - if (isSeaPickle()) { - return getSeaPickle().getMaximumPickles(); + if (getBlockData() instanceof SeaPickle seaPickle) { + return seaPickle.getMaximumPickles(); } - else if (isTurtleEgg()) { - return getTurtleEgg().getMaximumEggs(); + else if (getBlockData() instanceof TurtleEgg turtleEgg) { + return turtleEgg.getMaximumEggs(); } - else if (isRespawnAnchor()) { - return getRespawnAnchor().getMaximumCharges(); + else if (getBlockData() instanceof RespawnAnchor respawnAnchor) { + return respawnAnchor.getMaximumCharges(); } - else if (isCandle()) { - return getCandle().getMaximumCandles(); + else if (getBlockData() instanceof Candle candle) { + return candle.getMaximumCandles(); } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && material.getModernData() instanceof PinkPetals pinkPetals) { + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof PinkPetals pinkPetals) { return pinkPetals.getMaximumFlowerAmount(); } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof FlowerBed flowerBed) { + return flowerBed.getMaximumFlowerAmount(); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof LeafLitter leafLitter) { + return leafLitter.getMaximumSegmentAmount(); + } throw new UnsupportedOperationException(); } public int getMin() { - if (isSeaPickle()) { - return getSeaPickle().getMinimumPickles(); + if (getBlockData() instanceof SeaPickle seaPickle) { + return seaPickle.getMinimumPickles(); } - else if (isTurtleEgg()) { - return getTurtleEgg().getMinimumEggs(); + else if (getBlockData() instanceof TurtleEgg turtleEgg) { + return turtleEgg.getMinimumEggs(); } - else if (isRespawnAnchor()) { + else if (getBlockData() instanceof RespawnAnchor) { return 0; } - else if (isCandle()) { + else if (getBlockData() instanceof Candle) { return 1; } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && material.getModernData() instanceof PinkPetals) { + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof PinkPetals) { return 1; } - throw new UnsupportedOperationException(); - } - - @Override - public String getPropertyString() { - return String.valueOf(getCurrent()); - } - - @Override - public String getPropertyId() { - return "count"; - } - - @Override - public void adjust(Mechanism mechanism) { - - // <--[mechanism] - // @object MaterialTag - // @name count - // @input ElementTag(Number) - // @description - // Sets the amount of pickles in a Sea Pickle material, eggs in a Turtle Egg material, charges in a Respawn Anchor material, candles in a Candle material, or petals in a Pink Petals material. - // @tags - // - // - // - // --> - if ((mechanism.matches("count") || (mechanism.matches("pickle_count"))) && mechanism.requireInteger()) { - int count = mechanism.getValue().asInt(); - if (count < getMin() || count > getMax()) { - mechanism.echoError("Material count mechanism value '" + count + "' is not valid. Must be between " + getMin() + " and " + getMax() + "."); - return; - } - if (isSeaPickle()) { - getSeaPickle().setPickles(count); - } - else if (isTurtleEgg()) { - getTurtleEgg().setEggs(count); - } - else if (isRespawnAnchor()) { - getRespawnAnchor().setCharges(count); - } - else if (isCandle()) { - getCandle().setCandles(count); - } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && material.getModernData() instanceof PinkPetals pinkPetals) { - pinkPetals.setFlowerAmount(count); - } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof FlowerBed) { + return 1; } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof LeafLitter) { + return 1; + } + throw new UnsupportedOperationException(); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialLevel.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialLevel.java index 054573c513..08b3bff668 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialLevel.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialLevel.java @@ -9,10 +9,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Brushable; import org.bukkit.block.data.Levelled; -import org.bukkit.block.data.type.Beehive; -import org.bukkit.block.data.type.Cake; -import org.bukkit.block.data.type.Farmland; -import org.bukkit.block.data.type.Snow; +import org.bukkit.block.data.type.*; public class MaterialLevel extends MaterialProperty { @@ -32,6 +29,7 @@ public class MaterialLevel extends MaterialProperty { // For farmland, this is the moisture level. // For composters, this is the amount of compost. // For brushable blocks (also referred to as "suspicious blocks"), this is the level of dusting. 1.20+ only. + // For dried ghasts, this is the level of hydration. 1.21+ only. // See also <@link tag MaterialTag.maximum_level> and <@link tag MaterialTag.minimum_level>. // --> @@ -42,12 +40,34 @@ public static boolean describes(MaterialTag material) { || data instanceof Snow || data instanceof Farmland || data instanceof Beehive - || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && data instanceof Brushable); + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && data instanceof Brushable) + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && data instanceof DriedGhast); } @Override public ElementTag getPropertyValue() { - return new ElementTag(getCurrent()); + if (getBlockData() instanceof Cake cake) { + return new ElementTag(cake.getBites()); + } + else if (getBlockData() instanceof Snow snow) { + return new ElementTag(snow.getLayers()); + } + else if (getBlockData() instanceof Beehive beehive) { + return new ElementTag(beehive.getHoneyLevel()); + } + else if (getBlockData() instanceof Farmland farmland) { + return new ElementTag(farmland.getMoisture()); + } + else if (getBlockData() instanceof Levelled levelled) { + return new ElementTag(levelled.getLevel()); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getBlockData() instanceof Brushable brushable) { + return new ElementTag(brushable.getDusted()); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof DriedGhast driedGhast) { + return new ElementTag(driedGhast.getHydration()); + } + return null; } @Override @@ -65,7 +85,27 @@ public void setPropertyValue(ElementTag value, Mechanism mechanism) { mechanism.echoError("Level value '" + level + "' is not valid. Must be between " + getMin() + " and " + getMax() + " for material '" + getBlockData().getMaterial().name() + "'."); return; } - setCurrent(level); + if (getBlockData() instanceof Cake cake) { + cake.setBites(level); + } + else if (getBlockData() instanceof Snow snow) { + snow.setLayers(level); + } + else if (getBlockData() instanceof Beehive beehive) { + beehive.setHoneyLevel(level); + } + else if (getBlockData() instanceof Farmland farmland) { + farmland.setMoisture(level); + } + else if (getBlockData() instanceof Levelled levelled) { + levelled.setLevel(level); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getBlockData() instanceof Brushable brushable) { + brushable.setDusted(level); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof DriedGhast driedGhast) { + driedGhast.setHydration(level); + } } public static void register() { @@ -96,112 +136,35 @@ public static void register() { autoRegister("level", MaterialLevel.class, ElementTag.class, false); } - public Levelled getLevelled() { - return (Levelled) getBlockData(); - } - - public boolean isCake() { - return getBlockData() instanceof Cake; - } - - public Cake getCake() { - return (Cake) getBlockData(); - } - - public boolean isSnow() { - return getBlockData() instanceof Snow; - } - - public Snow getSnow() { - return (Snow) getBlockData(); - } - - public boolean isHive() { - return getBlockData() instanceof Beehive; - } - - public Beehive getHive() { - return (Beehive) getBlockData(); - } - - public boolean isFarmland() { - return getBlockData() instanceof Farmland; - } - - public Farmland getFarmland() { - return (Farmland) getBlockData(); - } - - public boolean isBrushable() { - return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getBlockData() instanceof Brushable; - } - - public int getCurrent() { - if (isCake()) { - return getCake().getBites(); - } - else if (isSnow()) { - return getSnow().getLayers(); - } - else if (isHive()) { - return getHive().getHoneyLevel(); - } - else if (isFarmland()) { - return getFarmland().getMoisture(); + public int getMax() { + if (getBlockData() instanceof Cake cake) { + return cake.getMaximumBites(); } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && isBrushable()) { - return ((Brushable) getBlockData()).getDusted(); + else if (getBlockData() instanceof Snow snow) { + return snow.getMaximumLayers(); } - return getLevelled().getLevel(); - } - - public int getMax() { - if (isCake()) { - return getCake().getMaximumBites(); + else if (getBlockData() instanceof Beehive beehive) { + return beehive.getMaximumHoneyLevel(); } - else if (isSnow()) { - return getSnow().getMaximumLayers(); + else if (getBlockData() instanceof Farmland farmland) { + return farmland.getMaximumMoisture(); } - else if (isHive()) { - return getHive().getMaximumHoneyLevel(); + else if (getBlockData() instanceof Levelled levelled) { + return levelled.getMaximumLevel(); } - else if (isFarmland()) { - return getFarmland().getMaximumMoisture(); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getBlockData() instanceof Brushable brushable) { + return brushable.getMaximumDusted(); } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && isBrushable()) { - return ((Brushable) getBlockData()).getMaximumDusted(); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof DriedGhast driedGhast) { + return driedGhast.getMaximumHydration(); } - return getLevelled().getMaximumLevel(); + throw new UnsupportedOperationException(); } public int getMin() { - if (isSnow()) { - return getSnow().getMinimumLayers(); + if (getBlockData() instanceof Snow snow) { + return snow.getMinimumLayers(); } return 0; } - - public void setCurrent(int level) { - if (isCake()) { - getCake().setBites(level); - return; - } - else if (isSnow()) { - getSnow().setLayers(level); - return; - } - else if (isHive()) { - getHive().setHoneyLevel(level); - return; - } - else if (isFarmland()) { - getFarmland().setMoisture(level); - return; - } - else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && isBrushable()) { - ((Brushable) getBlockData()).setDusted(level); - return; - } - getLevelled().setLevel(level); - } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialMode.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialMode.java index 3dfb61c2cc..68f720512a 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialMode.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialMode.java @@ -4,271 +4,175 @@ import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.MaterialTag; import com.denizenscript.denizencore.objects.Mechanism; -import com.denizenscript.denizencore.objects.ObjectTag; import com.denizenscript.denizencore.objects.core.ElementTag; -import com.denizenscript.denizencore.objects.properties.Property; -import com.denizenscript.denizencore.objects.properties.PropertyParser; -import com.denizenscript.denizencore.utilities.CoreUtilities; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.*; -public class MaterialMode implements Property { - - public static boolean describes(ObjectTag material) { - if (!(material instanceof MaterialTag)) { - return false; - } - MaterialTag mat = (MaterialTag) material; - if (!mat.hasModernData()) { - return false; - } - BlockData data = mat.getModernData(); - return data instanceof Comparator - || data instanceof PistonHead +public class MaterialMode extends MaterialProperty { + + // <--[property] + // @object MaterialTag + // @name mode + // @input ElementTag + // @description + // Controls a block's mode. + // For big_dripleafs, modes are FULL, NONE, PARTIAL, and UNSTABLE. + // For bubble_columns, modes are NORMAL and DRAG. + // For command_blocks, modes are CONDITIONAL and NORMAL. + // For comparators, modes are COMPARE and SUBTRACT. + // For creaking_hearts, modes are AWAKE, DORMANT, and UPROOTED. + // For daylight_detectors, modes are INVERTED and NORMAL. + // For potent_sulfur, modes are CONTINUOUS, DORMANT, DRY, ERUPTING, and WET. + // For piston_heads, modes are NORMAL and SHORT. + // For sculk_catalysts, modes are BLOOM and NORMAL. + // For sculk_sensors, modes are ACTIVE, COOLDOWN, and INACTIVE. + // For sculk_shriekers, modes are SHRIEKING and NORMAL. + // For structure_blocks, modes are CORNER, DATA, LOAD, and SAVE. + // For tripwires, modes are ARMED and DISARMED. + // For trial_spawners, modes are ACTIVE, COOLDOWN, EJECTING_REWARD, INACTIVE, WAITING_FOR_PLAYERS, and WAITING_FOR_REWARD_EJECTION. + // For vaults, modes are ACTIVE, EJECTING, INACTIVE, and UNLOCKING. + // --> + + public static boolean describes(MaterialTag material) { + BlockData data = material.getModernData(); + return data instanceof BigDripleaf || data instanceof BubbleColumn - || data instanceof StructureBlock - || data instanceof DaylightDetector || data instanceof CommandBlock + || data instanceof Comparator + || data instanceof DaylightDetector + || data instanceof PistonHead || data instanceof SculkSensor - || data instanceof BigDripleaf + || data instanceof StructureBlock || data instanceof Tripwire || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && (data instanceof SculkCatalyst - || data instanceof SculkShrieker)); + || data instanceof SculkShrieker)) + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && (data instanceof CreakingHeart + || data instanceof TrialSpawner + || data instanceof Vault)) + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v26_2) && data instanceof PotentSulfur); } - public static MaterialMode getFrom(ObjectTag _material) { - if (!describes(_material)) { - return null; + @Override + public ElementTag getPropertyValue() { + if (getBlockData() instanceof BigDripleaf bigDripleaf) { + return new ElementTag(bigDripleaf.getTilt()); } - else { - return new MaterialMode((MaterialTag) _material); + else if (getBlockData() instanceof BubbleColumn bubbleColumn) { + return new ElementTag(bubbleColumn.isDrag() ? "DRAG" : "NORMAL", true); } - } - - public static final String[] handledMechs = new String[] { - "mode" - }; - - public MaterialMode(MaterialTag _material) { - material = _material; - } - - public MaterialTag material; - - public static void register() { - - // <--[tag] - // @attribute - // @returns ElementTag - // @mechanism MaterialTag.mode - // @group properties - // @description - // Returns a block's mode. - // For comparators, output is COMPARE or SUBTRACT. - // For piston_heads, output is NORMAL or SHORT. - // For bubble_columns, output is NORMAL or DRAG. - // For structure_blocks, output is CORNER, DATA, LOAD, or SAVE. - // For sculk_sensors, output is ACTIVE, COOLDOWN, or INACTIVE. - // For daylight_detectors, output is INVERTED or NORMAL. - // For command_blocks, output is CONDITIONAL or NORMAL. - // For big_dripleafs, output is FULL, NONE, PARTIAL, or UNSTABLE. - // For sculk_catalysts, output is BLOOM or NORMAL. - // For sculk_shriekers, output is SHRIEKING or NORMAL. - // For tripwires, output is ARMED or DISARMED. - // --> - PropertyParser.registerStaticTag(MaterialMode.class, ElementTag.class, "mode", (attribute, material) -> { - return new ElementTag(material.getPropertyString()); - }); - } - - public boolean isComparator() { - return material.getModernData() instanceof Comparator; - } - - public boolean isPistonHead() { - return material.getModernData() instanceof PistonHead; - } - - public boolean isBubbleColumn() { - return material.getModernData() instanceof BubbleColumn; - } - - public boolean isStructureBlock() { - return material.getModernData() instanceof StructureBlock; - } - - public boolean isDaylightDetector() { - return material.getModernData() instanceof DaylightDetector; - } - - public boolean isCommandBlock() { - return material.getModernData() instanceof CommandBlock; - } - - public boolean isSculkSensor() { - return material.getModernData() instanceof SculkSensor; - } - - public boolean isBigDripleaf() { - return material.getModernData() instanceof BigDripleaf; - } - - public boolean isTripwire() { - return material.getModernData() instanceof Tripwire; - } - - public boolean isSculkCatalyst() { - return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && material.getModernData() instanceof SculkCatalyst; - } - - public boolean isSculkShrieker() { - return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && material.getModernData() instanceof SculkShrieker; - } - - public Comparator getComparator() { - return (Comparator) material.getModernData(); - } - - public PistonHead getPistonHead() { - return (PistonHead) material.getModernData(); - } - - public BubbleColumn getBubbleColumn() { - return (BubbleColumn) material.getModernData(); - } - - public StructureBlock getStructureBlock() { - return (StructureBlock) material.getModernData(); - } - - public DaylightDetector getDaylightDetector() { - return (DaylightDetector) material.getModernData(); - } - - public CommandBlock getCommandBlock() { - return (CommandBlock) material.getModernData(); - } - - public SculkSensor getSculkSensor() { - return (SculkSensor) material.getModernData(); - } - - public BigDripleaf getBigDripleaf() { - return (BigDripleaf) material.getModernData(); - } - - public Tripwire getTripwire() { - return (Tripwire) material.getModernData(); - } - - /*public SculkCatalyst getSculkCatalyst() { // TODO: 1.19 - return (SculkCatalyst) material.getModernData(); - } - - public SculkShrieker getSculkShrieker() { - return (SculkShrieker) material.getModernData(); - }*/ - - @Override - public String getPropertyString() { - if (isComparator()) { - return getComparator().getMode().name(); + else if (getBlockData() instanceof CommandBlock cmdBlock) { + return new ElementTag(cmdBlock.isConditional() ? "CONDITIONAL" : "NORMAL", true); } - else if (isBubbleColumn()) { - return getBubbleColumn().isDrag() ? "DRAG" : "NORMAL"; + else if (getBlockData() instanceof Comparator comparator) { + return new ElementTag(comparator.getMode()); } - else if (isPistonHead()) { - return getPistonHead().isShort() ? "SHORT" : "NORMAL"; + else if (getBlockData() instanceof DaylightDetector daylightDetector) { + return new ElementTag(daylightDetector.isInverted() ? "INVERTED" : "NORMAL", true); } - else if (isStructureBlock()) { - return getStructureBlock().getMode().name(); + else if (getBlockData() instanceof PistonHead pistonHead) { + return new ElementTag(pistonHead.isShort() ? "SHORT" : "NORMAL", true); } - else if (isDaylightDetector()) { - return getDaylightDetector().isInverted() ? "INVERTED" : "NORMAL"; + else if (getBlockData() instanceof SculkSensor sculkSensor) { + return new ElementTag(sculkSensor.getPhase()); } - else if (isCommandBlock()) { - return getCommandBlock().isConditional() ? "CONDITIONAL" : "NORMAL"; + else if (getBlockData() instanceof StructureBlock structureBlock) { + return new ElementTag(structureBlock.getMode()); } - else if (isSculkSensor()) { - return getSculkSensor().getPhase().name(); + else if (getBlockData() instanceof Tripwire tripwire) { + return new ElementTag(tripwire.isDisarmed() ? "DISARMED" : "ARMED", true); } - else if (isBigDripleaf()) { - return getBigDripleaf().getTilt().name(); + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof SculkCatalyst sculkCatalyst) { + return new ElementTag(sculkCatalyst.isBloom() ? "BLOOM" : "NORMAL", true); } - else if (isTripwire()) { - return getTripwire().isDisarmed() ? "DISARMED" : "ARMED"; + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof SculkShrieker sculkShrieker) { + return new ElementTag(sculkShrieker.isShrieking() ? "SHRIEKING" : "NORMAL", true); } - else if (isSculkCatalyst()) { - return ((SculkCatalyst) material.getModernData()).isBloom() ? "BLOOM" : "NORMAL"; // TODO: 1.19 + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof CreakingHeart creakingHeart) { + return new ElementTag(creakingHeart.getCreakingHeartState().name(), true); // TODO: once 1.21 is the minimum supported version, use the enum constructor } - else if (isSculkShrieker()) { - return ((SculkShrieker) material.getModernData()).isShrieking() ? "SHRIEKING" : "NORMAL"; // TODO: 1.19 + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof TrialSpawner trialSpawner) { + return new ElementTag(trialSpawner.getTrialSpawnerState().name(), true); // TODO: once 1.21 is the minimum supported version, use the enum constructor } - return null; // Unreachable - } - - @Override - public String getPropertyId() { - return "mode"; + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof Vault vault) { + return new ElementTag(vault.getVaultState().name(), true); // TODO: once 1.21 is the minimum supported version, use the enum constructor + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v26_2) && getBlockData() instanceof PotentSulfur potentSulfur) { + return new ElementTag(potentSulfur.getPotentSulfurState().name(), true); // TODO: once 26.2 is the minimum supported version, use the enum constructor + } + return null; } @Override - public void adjust(Mechanism mechanism) { - - // <--[mechanism] - // @object MaterialTag - // @name mode - // @input ElementTag - // @description - // Set a block's mode. - // For comparators, input is COMPARE or SUBTRACT. - // For piston_heads, input is NORMAL or SHORT. - // For bubble_columns, input is NORMAL or DRAG. - // For structure_blocks, input is CORNER, DATA, LOAD, or SAVE. - // For sculk_sensors, input is ACTIVE, COOLDOWN, or INACTIVE. - // For daylight_detectors, input is INVERTED or NORMAL. - // For command_blocks, input is CONDITIONAL or NORMAL. - // For big_dripleafs, input is FULL, NONE, PARTIAL, or UNSTABLE. - // For sculk_catalysts, input is BLOOM or NORMAL. - // For sculk_shriekers, input is SHRIEKING or NORMAL. - // For tripwires, input is ARMED or DISARMED. - // @tags - // - // --> - if (mechanism.matches("mode")) { - if (isComparator() && mechanism.requireEnum(Comparator.Mode.class)) { - getComparator().setMode(Comparator.Mode.valueOf(mechanism.getValue().asString().toUpperCase())); - } - else if (isBubbleColumn()) { - getBubbleColumn().setDrag(CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), "drag")); + public void setPropertyValue(ElementTag value, Mechanism mechanism) { + if (getBlockData() instanceof BigDripleaf bigDripleaf) { + if (mechanism.requireEnum(BigDripleaf.Tilt.class)) { + bigDripleaf.setTilt(value.asEnum(BigDripleaf.Tilt.class)); } - else if (isPistonHead()) { - getPistonHead().setShort(CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), "short")); - } - else if (isStructureBlock() && mechanism.requireEnum(StructureBlock.Mode.class)) { - getStructureBlock().setMode(StructureBlock.Mode.valueOf(mechanism.getValue().asString().toUpperCase())); - } - else if (isDaylightDetector()) { - getDaylightDetector().setInverted(CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), "inverted")); + } + else if (getBlockData() instanceof BubbleColumn bubbleColumn) { + bubbleColumn.setDrag(value.asLowerString().equals("drag")); + } + else if (getBlockData() instanceof CommandBlock cmdBlock) { + cmdBlock.setConditional(value.asLowerString().equals("conditional")); + } + else if (getBlockData() instanceof Comparator comparator) { + if (mechanism.requireEnum(Comparator.Mode.class)) { + comparator.setMode(value.asEnum(Comparator.Mode.class)); } - else if (isCommandBlock()) { - getCommandBlock().setConditional(CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), "conditional")); + } + else if (getBlockData() instanceof DaylightDetector daylightDetector) { + daylightDetector.setInverted(value.asLowerString().equals("inverted")); + } + else if (getBlockData() instanceof PistonHead pistonHead) { + pistonHead.setShort(value.asLowerString().equals("short")); + } + else if (getBlockData() instanceof SculkSensor sculkSensor) { + if (mechanism.requireEnum(SculkSensor.Phase.class)) { + sculkSensor.setPhase(value.asEnum(SculkSensor.Phase.class)); } - else if (isSculkSensor() && mechanism.requireEnum(SculkSensor.Phase.class)) { - getSculkSensor().setPhase(SculkSensor.Phase.valueOf(mechanism.getValue().asString().toUpperCase())); + } + else if (getBlockData() instanceof StructureBlock structureBlock) { + if (mechanism.requireEnum(StructureBlock.Mode.class)) { + structureBlock.setMode(value.asEnum(StructureBlock.Mode.class)); } - else if (isBigDripleaf() && mechanism.requireEnum(BigDripleaf.Tilt.class)) { - getBigDripleaf().setTilt(BigDripleaf.Tilt.valueOf(mechanism.getValue().asString().toUpperCase())); + } + else if (getBlockData() instanceof Tripwire tripwire) { + tripwire.setDisarmed(value.asLowerString().equals("disarmed")); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof SculkCatalyst sculkCatalyst) { + sculkCatalyst.setBloom(value.asLowerString().equals("bloom")); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof SculkShrieker sculkShrieker) { + sculkShrieker.setShrieking(value.asLowerString().equals("shrieking")); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof CreakingHeart creakingHeart) { + if (mechanism.requireEnum(CreakingHeart.State.class)) { + creakingHeart.setCreakingHeartState(value.asEnum(CreakingHeart.State.class)); } - else if (isTripwire()) { - getTripwire().setDisarmed(CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), "disarmed")); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof TrialSpawner trialSpawner) { + if (mechanism.requireEnum(TrialSpawner.State.class)) { + trialSpawner.setTrialSpawnerState(value.asEnum(TrialSpawner.State.class)); } - else if (isSculkCatalyst()) { - ((SculkCatalyst) material.getModernData()).setBloom(CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), "bloom")); // TODO: 1.19 + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof Vault vault) { + if (mechanism.requireEnum(Vault.State.class)) { + vault.setVaultState(value.asEnum(Vault.State.class)); } - else if (isSculkShrieker()) { - ((SculkShrieker) material.getModernData()).setShrieking(CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), "shrieking")); // TODO: 1.19 + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v26_2) && getBlockData() instanceof PotentSulfur potentSulfur) { + if (mechanism.requireEnum(PotentSulfur.State.class)) { + potentSulfur.setPotentSulfurState(value.asEnum(PotentSulfur.State.class)); } } } + + @Override + public String getPropertyId() { + return "mode"; + } + + public static void register() { + autoRegister("mode", MaterialMode.class, ElementTag.class, false); + } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialOminous.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialOminous.java new file mode 100644 index 0000000000..5f40b316e0 --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialOminous.java @@ -0,0 +1,53 @@ +package com.denizenscript.denizen.objects.properties.material; + +import com.denizenscript.denizen.objects.MaterialTag; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; +import org.bukkit.block.data.type.TrialSpawner; +import org.bukkit.block.data.type.Vault; + +public class MaterialOminous extends MaterialProperty { + + // <--[property] + // @object MaterialTag + // @name ominous + // @input ElementTag(Boolean) + // @description + // Controls whether a trial spawner or vault is in ominous mode. + // --> + + public static boolean describes(MaterialTag material) { + return material.getModernData() instanceof TrialSpawner + || material.getModernData() instanceof Vault; + } + + @Override + public ElementTag getPropertyValue() { + if (getBlockData() instanceof TrialSpawner trialSpawner) { + return new ElementTag(trialSpawner.isOminous()); + } + return new ElementTag(as(Vault.class).isOminous()); + } + + @Override + public void setPropertyValue(ElementTag value, Mechanism mechanism) { + if (!mechanism.requireBoolean()) { + return; + } + if (getBlockData() instanceof TrialSpawner trialSpawner) { + trialSpawner.setOminous(value.asBoolean()); + } + else { + as(Vault.class).setOminous(value.asBoolean()); + } + } + + @Override + public String getPropertyId() { + return "ominous"; + } + + public static void register() { + autoRegister("ominous", MaterialOminous.class, ElementTag.class, false); + } +} diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialProperty.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialProperty.java index 4cbb516800..2f6046bbe2 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialProperty.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialProperty.java @@ -17,4 +17,8 @@ public MaterialProperty(MaterialTag material) { public BlockData getBlockData() { return object.getModernData(); } + + public T as(Class dataType) { + return (T) getBlockData(); + } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialSides.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialSides.java index 7da3aeb2a3..137b5671b7 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialSides.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialSides.java @@ -1,118 +1,110 @@ package com.denizenscript.denizen.objects.properties.material; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.MaterialTag; 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.ListTag; -import com.denizenscript.denizencore.objects.properties.Property; -import com.denizenscript.denizencore.objects.properties.PropertyParser; import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.DebugInternals; import org.bukkit.block.BlockFace; -import org.bukkit.block.data.*; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.MossyCarpet; import org.bukkit.block.data.type.RedstoneWire; import org.bukkit.block.data.type.Wall; -public class MaterialSides implements Property { - - public static boolean describes(ObjectTag material) { - if (!(material instanceof MaterialTag)) { - return false; - } - MaterialTag mat = (MaterialTag) material; - if (!mat.hasModernData()) { - return false; - } - BlockData data = mat.getModernData(); - if (!(data instanceof Wall) && !(data instanceof RedstoneWire)) { - return false; - } - return true; - } - - public static MaterialSides getFrom(ObjectTag _material) { - if (!describes(_material)) { - return null; - } - else { - return new MaterialSides((MaterialTag) _material); - } - } - - public static final String[] handledMechs = new String[] { - "sides", "heights" - }; - - public MaterialSides(MaterialTag _material) { - material = _material; - } - - MaterialTag material; - - public static void register() { - - // <--[tag] - // @attribute - // @returns ListTag - // @mechanism MaterialTag.heights - // @group properties - // @deprecated Use 'sides' - // @description - // Deprecated in favor of <@link tag MaterialTag.sides> - // --> - // <--[tag] - // @attribute - // @returns ListTag - // @mechanism MaterialTag.sides - // @group properties - // @description - // Returns the list of heights for a wall block, or connections for a redstone wire, in order North|East|South|West|Vertical. - // For wall blocks: For n/e/s/w, can be "tall", "low", or "none". For vertical, can be "tall" or "none". - // For redstone wires: For n/e/s/w, can be "none", "side", or "up". No vertical. - // --> - PropertyParser.registerStaticTag(MaterialSides.class, ListTag.class, "sides", (attribute, material) -> { - return material.getSidesList(); - }, "heights"); - } - - public boolean isWall() { - return material.getModernData() instanceof Wall; - } - - public Wall getWall() { - return (Wall) material.getModernData(); +import java.util.function.BiConsumer; + +public class MaterialSides extends MaterialProperty { + + // <--[property] + // @object MaterialTag + // @name sides + // @input ListTag + // @description + // Controls the heights for a wall block or mossy carpet, or connections for a redstone wire, in order North|East|South|West|Vertical. + // For wall blocks: For n/e/s/w, can be "tall", "low", or "none". For vertical, can be "tall" or "none". + // For redstone wires: For n/e/s/w, can be "none", "side", or "up". No vertical. + // For mossy carpets: For n/e/s/w, can be "tall", "low", or "none". Vertical controls the bottom, and can either be "bottom" or "none". + // --> + + public static boolean describes(MaterialTag material) { + BlockData data = material.getModernData(); + return data instanceof Wall + || data instanceof RedstoneWire + || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && data instanceof MossyCarpet); } - public boolean isWire() { - return material.getModernData() instanceof RedstoneWire; - } - - public RedstoneWire getWire() { - return (RedstoneWire) material.getModernData(); - } - - public ListTag getSidesList() { + @Override + public ListTag getPropertyValue() { ListTag list = new ListTag(5); - if (isWall()) { - Wall wall = getWall(); + if (getBlockData() instanceof Wall wall) { list.add(wall.getHeight(BlockFace.NORTH).name()); list.add(wall.getHeight(BlockFace.EAST).name()); list.add(wall.getHeight(BlockFace.SOUTH).name()); list.add(wall.getHeight(BlockFace.WEST).name()); list.add(wall.isUp() ? "TALL" : "NONE"); } - else if (isWire()) { - RedstoneWire wire = getWire(); + else if (getBlockData() instanceof RedstoneWire wire) { list.add(wire.getFace(BlockFace.NORTH).name()); list.add(wire.getFace(BlockFace.EAST).name()); list.add(wire.getFace(BlockFace.SOUTH).name()); list.add(wire.getFace(BlockFace.WEST).name()); } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof MossyCarpet carpet) { + list.add(carpet.getHeight(BlockFace.NORTH).name()); + list.add(carpet.getHeight(BlockFace.EAST).name()); + list.add(carpet.getHeight(BlockFace.SOUTH).name()); + list.add(carpet.getHeight(BlockFace.WEST).name()); + list.add(carpet.isBottom() ? "BOTTOM" : "NONE"); + } return list; } @Override - public String getPropertyString() { - return getSidesList().identify(); + public void setPropertyValue(ListTag list, Mechanism mechanism) { + if (getBlockData() instanceof Wall wall) { + if (list.size() != 5) { + mechanism.echoError("Invalid sides list, size must be 5."); + return; + } + setSide(wall::setHeight, Wall.Height.class, BlockFace.NORTH, list, 0, mechanism); + setSide(wall::setHeight, Wall.Height.class, BlockFace.EAST, list, 1, mechanism); + setSide(wall::setHeight, Wall.Height.class, BlockFace.SOUTH, list, 2, mechanism); + setSide(wall::setHeight, Wall.Height.class, BlockFace.WEST, list, 3, mechanism); + wall.setUp(CoreUtilities.equalsIgnoreCase(list.get(4), "tall")); + } + else if (getBlockData() instanceof RedstoneWire wire) { + if (list.size() != 4) { + mechanism.echoError("Invalid sides list, size must be 4."); + return; + } + setSide(wire::setFace, RedstoneWire.Connection.class, BlockFace.NORTH, list, 0, mechanism); + setSide(wire::setFace, RedstoneWire.Connection.class, BlockFace.EAST, list, 1, mechanism); + setSide(wire::setFace, RedstoneWire.Connection.class, BlockFace.SOUTH, list, 2, mechanism); + setSide(wire::setFace, RedstoneWire.Connection.class, BlockFace.WEST, list, 3, mechanism); + } + else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof MossyCarpet carpet) { + if (list.size() != 5) { + mechanism.echoError("Invalid sides list, size must be 5."); + return; + } + setSide(carpet::setHeight, MossyCarpet.Height.class, BlockFace.NORTH, list, 0, mechanism); + setSide(carpet::setHeight, MossyCarpet.Height.class, BlockFace.EAST, list, 1, mechanism); + setSide(carpet::setHeight, MossyCarpet.Height.class, BlockFace.SOUTH, list, 2, mechanism); + setSide(carpet::setHeight, MossyCarpet.Height.class, BlockFace.WEST, list, 3, mechanism); + carpet.setBottom(CoreUtilities.equalsIgnoreCase(list.get(4), "bottom")); + } + } + + public static > void setSide(BiConsumer consumer, Class type, BlockFace face, ListTag list, int index, Mechanism mechanism) { + T value = new ElementTag(list.get(index)).asEnum(type); + if (value == null) { + mechanism.echoError("'" + list.get(index) + "' is not a valid " + DebugInternals.getClassNameOpti(type) + "."); + return; + } + consumer.accept(face, value); } @Override @@ -120,55 +112,28 @@ public String getPropertyId() { return "sides"; } - @Override - public void adjust(Mechanism mechanism) { + // <--[tag] + // @attribute + // @returns ListTag + // @mechanism MaterialTag.heights + // @group properties + // @deprecated use 'sides' + // @description + // Deprecated in favor of <@link property MaterialTag.sides> + // --> + + // <--[mechanism] + // @object MaterialTag + // @name heights + // @input ElementTag + // @deprecated use 'sides' + // @description + // Deprecated in favor of <@link property MaterialTag.sides> + // @tags + // + // --> - // <--[mechanism] - // @object MaterialTag - // @name heights - // @input ElementTag - // @deprecated Use 'sides' - // @description - // Deprecated in favor of <@link mechanism MaterialTag.sides> - // @tags - // - // --> - // <--[mechanism] - // @object MaterialTag - // @name sides - // @input ElementTag - // @description - // Sets the list of heights for a wall block, or connections for a redstone wire, in order North|East|South|West|Vertical. - // For wall blocks: For n/e/s/w, can be "tall", "low", or "none". For vertical, can be "tall" or "none". - // For redstone wires: For n/e/s/w, can be "none", "side", or "up". No vertical. - // @tags - // - // --> - if ((mechanism.matches("sides") || mechanism.matches("heights")) && mechanism.requireObject(ListTag.class)) { - ListTag list = mechanism.valueAsType(ListTag.class); - if (isWall()) { - if (list.size() != 5) { - mechanism.echoError("Invalid sides list, size must be 5."); - return; - } - Wall wall = getWall(); - wall.setHeight(BlockFace.NORTH, Wall.Height.valueOf(list.get(0).toUpperCase())); - wall.setHeight(BlockFace.EAST, Wall.Height.valueOf(list.get(1).toUpperCase())); - wall.setHeight(BlockFace.SOUTH, Wall.Height.valueOf(list.get(2).toUpperCase())); - wall.setHeight(BlockFace.WEST, Wall.Height.valueOf(list.get(3).toUpperCase())); - wall.setUp(CoreUtilities.toLowerCase(list.get(4)).equals("tall")); - } - else if (isWire()) { - if (list.size() != 4) { - mechanism.echoError("Invalid sides list, size must be 4."); - return; - } - RedstoneWire wire = getWire(); - wire.setFace(BlockFace.NORTH, RedstoneWire.Connection.valueOf(list.get(0).toUpperCase())); - wire.setFace(BlockFace.EAST, RedstoneWire.Connection.valueOf(list.get(1).toUpperCase())); - wire.setFace(BlockFace.SOUTH, RedstoneWire.Connection.valueOf(list.get(2).toUpperCase())); - wire.setFace(BlockFace.WEST, RedstoneWire.Connection.valueOf(list.get(3).toUpperCase())); - } - } + public static void register() { + autoRegister("sides", MaterialSides.class, ListTag.class, false, "heights"); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/BukkitCommandRegistry.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/BukkitCommandRegistry.java index 63be0127f2..6e06ceaef5 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/BukkitCommandRegistry.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/BukkitCommandRegistry.java @@ -68,9 +68,9 @@ public static void registerCitizensCommands() { registerCommand(FishCommand.class); registerCommand(LookcloseCommand.class); registerCommand(PauseCommand.class); - registerCommand(PauseCommand.ResumeCommand.class); registerCommand(PoseCommand.class); registerCommand(PushableCommand.class); + registerCommand(ResumeCommand.class); registerCommand(SitCommand.class); registerCommand(SleepCommand.class); registerCommand(StandCommand.class); diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/InventoryCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/InventoryCommand.java index 1bac8fa481..fe00ec1e52 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/InventoryCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/InventoryCommand.java @@ -442,7 +442,8 @@ else if (destination.getIdHolder() instanceof EntityTag entity && entity.getLivi } ItemTag toAdjust = new ItemTag(destination.getInventory().getItem(slotId)); Argument mechanismArgument = new Argument(dataAction); - toAdjust.safeAdjust(new Mechanism(mechanismArgument.getPrefix().getValue(), mechanismArgument.object, scriptEntry.getContext())); + boolean hasValue = mechanismArgument.hasPrefix(); + toAdjust.safeAdjust(new Mechanism(hasValue ? mechanismArgument.getPrefix().getValue() : mechanismArgument.getValue(), hasValue ? mechanismArgument.object : null, scriptEntry.getContext())); NMSHandler.itemHelper.setInventoryItem(destination.getInventory(), toAdjust.getItemStack(), slotId); break; case FLAG: diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/MapCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/MapCommand.java index aec090b8ce..bf48201523 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/MapCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/MapCommand.java @@ -98,7 +98,7 @@ public static void autoExecute(ScriptEntry scriptEntry, @ArgName("resize") boolean resize, @ArgName("script") @ArgPrefixed @ArgDefaultNull ScriptTag script, @ArgName("dot") @ArgPrefixed @ArgDefaultNull ColorTag dot, - @ArgName("radius") @ArgPrefixed @ArgDefaultText("-1") int radius, + @ArgName("radius") @ArgPrefixed @ArgDefaultText("1") int radius, @ArgName("x") @ArgPrefixed @ArgDefaultText("0") double x, @ArgName("y") @ArgPrefixed @ArgDefaultText("0") double y, @ArgName("width") @ArgPrefixed @ArgDefaultText("-1") int width, diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/PauseCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/PauseCommand.java index 7b33411c51..682be06d73 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/PauseCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/PauseCommand.java @@ -2,12 +2,13 @@ import com.denizenscript.denizen.Denizen; import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException; +import com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultNull; +import com.denizenscript.denizencore.scripts.commands.generator.ArgLinear; +import com.denizenscript.denizencore.scripts.commands.generator.ArgName; import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizen.objects.NPCTag; -import com.denizenscript.denizencore.exceptions.InvalidArgumentsException; -import com.denizenscript.denizencore.objects.Argument; import com.denizenscript.denizencore.objects.core.DurationTag; -import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntry; import com.denizenscript.denizencore.scripts.commands.AbstractCommand; import net.citizensnpcs.trait.waypoint.Waypoints; @@ -17,19 +18,12 @@ public class PauseCommand extends AbstractCommand { - public static class ResumeCommand extends PauseCommand { - - public ResumeCommand() { - setName("resume"); - setSyntax("resume [waypoints/activity] ()"); - } - } - public PauseCommand() { setName("pause"); setSyntax("pause [waypoints/activity] ()"); setRequiredArguments(1, 2); isProcedural = false; + autoCompile(); } // <--[command] @@ -68,101 +62,53 @@ public PauseCommand() { // - resume waypoints // --> - // <--[command] - // @Name Resume - // @Syntax resume [waypoints/activity] () - // @Required 1 - // @Plugin Citizens - // @Short Resumes an NPC's waypoint navigation or goal activity temporarily or indefinitely. - // @Group npc - // - // @Description - // The resume command resumes an NPC's waypoint navigation or goal activity temporarily or indefinitely. - // This works along side <@link command pause>. - // See the documentation of the pause command for more details. - // - // @Tags - // - // - // @Usage - // Use to pause an NPC's waypoint navigation and then resume it. - // - pause waypoints - // - resume waypoints - // --> + public static final Map durations = new HashMap<>(); - private Map durations = new HashMap<>(); + public enum Type { ACTIVITY, WAYPOINTS, NAVIGATION } - enum PauseType {ACTIVITY, WAYPOINTS, NAVIGATION} - - @Override - public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException { - for (Argument arg : scriptEntry) { - if (arg.matchesArgumentType(DurationTag.class) - && !scriptEntry.hasObject("duration")) { - scriptEntry.addObject("duration", arg.asType(DurationTag.class)); - } - else if (!scriptEntry.hasObject("pause_type") - && arg.matchesEnum(PauseType.class)) { - scriptEntry.addObject("pause_type", arg.asElement()); - } - else { - arg.reportUnhandled(); - } - } - if (!scriptEntry.hasObject("pause_type")) { - throw new InvalidArgumentsException("Must specify a pause type!"); - } + public static void autoExecute(ScriptEntry scriptEntry, + @ArgName("action") Type type, + @ArgName("duration") @ArgLinear @ArgDefaultNull DurationTag duration) { + executeToggle(scriptEntry, type, duration, true); } - @Override - public void execute(ScriptEntry scriptEntry) { - DurationTag duration = scriptEntry.getObjectTag("duration"); - ElementTag pauseTypeElement = scriptEntry.getElement("pause_type"); - PauseType pauseType = PauseType.valueOf(pauseTypeElement.asString().toUpperCase()); - if (scriptEntry.dbCallShouldDebug()) { - Debug.report(scriptEntry, getName(), duration, pauseTypeElement); - } - NPCTag npc = null; - if (Utilities.getEntryNPC(scriptEntry) != null) { - npc = Utilities.getEntryNPC(scriptEntry); + public static void executeToggle(ScriptEntry scriptEntry, Type type, DurationTag duration, boolean pause) { + if (!Utilities.entryHasNPC(scriptEntry)) { + throw new InvalidArgumentsRuntimeException("Need to provide an NPC"); } - pause(npc, pauseType, !scriptEntry.getCommandName().equalsIgnoreCase("RESUME")); + NPCData data = new NPCData(Utilities.getEntryNPC(scriptEntry), type); + toggle(data, pause); if (duration != null) { - if (durations.containsKey(npc.getCitizen().getId() + pauseType.name())) { + if (durations.containsKey(data)) { try { - Denizen.getInstance().getServer().getScheduler().cancelTask(durations.get(npc.getCitizen().getId() + pauseType.name())); + Denizen.getInstance().getServer().getScheduler().cancelTask(durations.get(data)); } catch (Exception e) { Debug.echoError(scriptEntry, "There was an error pausing that!"); Debug.echoError(scriptEntry, e); } } - Debug.echoDebug(scriptEntry, "Running delayed task: Unpause " + pauseType); - final NPCTag theNpc = npc; - final ScriptEntry se = scriptEntry; - durations.put(npc.getId() + pauseType.name(), Denizen.getInstance() + durations.put(data, Denizen.getInstance() .getServer().getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> { - Debug.echoDebug(se, "Running delayed task: Pausing " + pauseType); - pause(theNpc, pauseType, false); - + Debug.echoDebug(scriptEntry, "Running delayed task: " + (!pause ? "Pausing" : "Resuming") + " " + type); + toggle(data, !pause); }, duration.getTicks())); } } - public void pause(NPCTag denizen, PauseType pauseType, boolean pause) { - switch (pauseType) { - case WAYPOINTS: - denizen.getCitizen().getOrAddTrait(Waypoints.class).getCurrentProvider().setPaused(pause); + public static void toggle(NPCData data, boolean pause) { + switch (data.type) { + case WAYPOINTS -> { + data.npc.getCitizen().getOrAddTrait(Waypoints.class).getCurrentProvider().setPaused(pause); if (pause) { - denizen.getNavigator().cancelNavigation(); + data.npc.getNavigator().cancelNavigation(); } - return; - case ACTIVITY: - denizen.getCitizen().getDefaultGoalController().setPaused(pause); - return; - case NAVIGATION: - // TODO: Finish this + } + case ACTIVITY -> data.npc.getCitizen().getDefaultBehaviorController().setPaused(pause); + case NAVIGATION -> { /* TODO IMPLEMENT */ } } } + + public record NPCData(NPCTag npc, Type type) { } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/ResumeCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/ResumeCommand.java new file mode 100644 index 0000000000..0bc01e013e --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/ResumeCommand.java @@ -0,0 +1,47 @@ +package com.denizenscript.denizen.scripts.commands.npc; + +import com.denizenscript.denizencore.objects.core.DurationTag; +import com.denizenscript.denizencore.scripts.ScriptEntry; +import com.denizenscript.denizencore.scripts.commands.AbstractCommand; +import com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultNull; +import com.denizenscript.denizencore.scripts.commands.generator.ArgLinear; +import com.denizenscript.denizencore.scripts.commands.generator.ArgName; + +public class ResumeCommand extends AbstractCommand { + + public ResumeCommand() { + setName("resume"); + setSyntax("resume [waypoints/activity] ()"); + setRequiredArguments(1, 2); + isProcedural = false; + autoCompile(); + } + + // <--[command] + // @Name Resume + // @Syntax resume [waypoints/activity] () + // @Required 1 + // @Plugin Citizens + // @Short Resumes an NPC's waypoint navigation or goal activity temporarily or indefinitely. + // @Group npc + // + // @Description + // The resume command resumes an NPC's waypoint navigation or goal activity temporarily or indefinitely. + // This works along side <@link command pause>. + // See the documentation of the pause command for more details. + // + // @Tags + // + // + // @Usage + // Use to pause an NPC's waypoint navigation and then resume it. + // - pause waypoints + // - resume waypoints + // --> + + public static void autoExecute(ScriptEntry scriptEntry, + @ArgName("action") PauseCommand.Type type, + @ArgName("duration") @ArgLinear @ArgDefaultNull DurationTag duration) { + PauseCommand.executeToggle(scriptEntry, type, duration, false); + } +} diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ChatCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ChatCommand.java index 552a8c2de2..8fcb71877f 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ChatCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ChatCommand.java @@ -18,6 +18,7 @@ import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.tags.TagManager; import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.ai.speech.Talkable; import net.citizensnpcs.api.ai.speech.TalkableEntity; import net.citizensnpcs.api.ai.speech.event.NPCSpeechEvent; @@ -137,7 +138,7 @@ public static void autoExecute(ScriptEntry scriptEntry, public static void speak(DenizenSpeechContext speechContext) { Entity talker = speechContext.getTalker().getEntity(); if (EntityTag.isCitizensNPC(talker)) { - NPCSpeechEvent event = new NPCSpeechEvent(speechContext); + NPCSpeechEvent event = new NPCSpeechEvent(CitizensAPI.getNPCRegistry().getNPC(talker), speechContext); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { return; diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/DebugBlockCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/DebugBlockCommand.java index 5d8312f97e..defcfd3b8f 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/DebugBlockCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/DebugBlockCommand.java @@ -5,15 +5,15 @@ import com.denizenscript.denizen.objects.PlayerTag; import com.denizenscript.denizen.utilities.BukkitImplDeprecations; import com.denizenscript.denizen.utilities.Utilities; -import com.denizenscript.denizencore.objects.core.ColorTag; -import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizencore.exceptions.InvalidArgumentsException; import com.denizenscript.denizencore.objects.Argument; +import com.denizenscript.denizencore.objects.core.ColorTag; import com.denizenscript.denizencore.objects.core.DurationTag; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; import com.denizenscript.denizencore.scripts.ScriptEntry; import com.denizenscript.denizencore.scripts.commands.AbstractCommand; +import com.denizenscript.denizencore.utilities.debugging.Debug; import java.util.Collections; import java.util.List; @@ -43,6 +43,12 @@ public DebugBlockCommand() { // // If arguments are unspecified, the default color is white, the default player is the linked player, the default name is none, and the default duration is 10 seconds. // + // Note that on MC 1.21+ this has limitations (within Minecraft itself), namely: + // - the color is always green. + // - the duration is always 10 seconds. + // - the name can only be a LocationTag and defaults to the debug block's location if unspecified. + // - debug blocks can't be cleared. + // // @Tags // None // diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TitleCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TitleCommand.java index b503b62aa7..0e85ea309f 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TitleCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TitleCommand.java @@ -2,19 +2,16 @@ import com.denizenscript.denizen.tags.BukkitTagContext; import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizencore.scripts.commands.generator.*; +import com.denizenscript.denizencore.tags.ParseableTag; import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizen.nms.NMSHandler; import com.denizenscript.denizen.objects.PlayerTag; -import com.denizenscript.denizencore.exceptions.InvalidArgumentsException; -import com.denizenscript.denizencore.objects.*; import com.denizenscript.denizencore.objects.core.DurationTag; -import com.denizenscript.denizencore.objects.core.ElementTag; -import com.denizenscript.denizencore.objects.core.ListTag; import com.denizenscript.denizencore.scripts.ScriptEntry; import com.denizenscript.denizencore.scripts.commands.AbstractCommand; import com.denizenscript.denizencore.tags.TagManager; -import java.util.Collections; import java.util.List; public class TitleCommand extends AbstractCommand { @@ -23,8 +20,9 @@ public TitleCommand() { setName("title"); setSyntax("title (title:) (subtitle:) (fade_in:/{1s}) (stay:/{3s}) (fade_out:/{1s}) (targets:|...) (per_player)"); setRequiredArguments(1, 7); - setParseArgs(false); + addRemappedPrefixes("targets", "target"); isProcedural = false; + autoCompile(); } // <--[command] @@ -58,83 +56,45 @@ public TitleCommand() { // - title "title:Tatooine" "subtitle:What a desolate place this is." // --> - @Override - public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException { - for (Argument arg : ArgumentHelper.interpret(scriptEntry, scriptEntry.getOriginalArguments())) { - if (arg.matchesPrefix("title")) { - scriptEntry.addObject("title", arg.asElement()); - } - else if (arg.matchesPrefix("subtitle")) { - scriptEntry.addObject("subtitle", arg.asElement()); - } - else if (arg.matchesPrefix("fade_in")) { - String argStr = TagManager.tag(arg.getValue(), scriptEntry.getContext()); - scriptEntry.addObject("fade_in", DurationTag.valueOf(argStr, scriptEntry.context)); - } - else if (arg.matchesPrefix("stay")) { - String argStr = TagManager.tag(arg.getValue(), scriptEntry.getContext()); - scriptEntry.addObject("stay", DurationTag.valueOf(argStr, scriptEntry.context)); - } - else if (arg.matchesPrefix("fade_out")) { - String argStr = TagManager.tag(arg.getValue(), scriptEntry.getContext()); - scriptEntry.addObject("fade_out", DurationTag.valueOf(argStr, scriptEntry.context)); - } - else if (arg.matchesPrefix("targets", "target")) { - scriptEntry.addObject("targets", ListTag.getListFor(TagManager.tagObject(arg.getValue(), scriptEntry.getContext()), scriptEntry.getContext()).filter(PlayerTag.class, scriptEntry)); + public static void autoExecute(ScriptEntry scriptEntry, + @ArgName("title") @ArgPrefixed @ArgUnparsed @ArgDefaultNull String strTitle, + @ArgName("subtitle") @ArgPrefixed @ArgUnparsed @ArgDefaultNull String strSubTitle, + @ArgName("fade_in") @ArgPrefixed @ArgDefaultText("1s") DurationTag fadeIn, + @ArgName("stay") @ArgPrefixed @ArgDefaultText("3s") DurationTag stay, + @ArgName("fade_out") @ArgPrefixed @ArgDefaultText("1s") DurationTag fadeOut, + @ArgName("targets") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List players, + @ArgName("per_player") boolean perPlayer) { + if (strTitle == null && strSubTitle == null) { + Debug.echoError("Must have a title or subtitle!"); + return; + } + if (players == null) { + if (!Utilities.entryHasPlayer(scriptEntry)) { + Debug.echoError("Must specify target(s)."); + return; } - else if (!scriptEntry.hasObject("per_player") - && arg.matches("per_player")) { - scriptEntry.addObject("per_player", new ElementTag(true)); + players = List.of(Utilities.getEntryPlayer(scriptEntry)); + } + BukkitTagContext context = perPlayer ? new BukkitTagContext((BukkitTagContext) scriptEntry.getContext()) : (BukkitTagContext) scriptEntry.getContext(); + ParseableTag parseableTitle = TagManager.parseTextToTag(strTitle, context); + ParseableTag parseableSubTitle = TagManager.parseTextToTag(strSubTitle, context); + String parsedTitle = perPlayer ? null : parse(parseableTitle, context); + String parsedSubTitle = perPlayer ? null : parse(parseableSubTitle, context); + for (PlayerTag player : players) { + if (!player.isOnline()) { + Debug.echoDebug(scriptEntry, "Player is offline, can't send title to them. Skipping."); + continue; } - else { - arg.reportUnhandled(); + if (perPlayer) { + context.player = player; + parsedTitle = parse(parseableTitle, context); + parsedSubTitle = parse(parseableSubTitle, context); } + NMSHandler.packetHelper.showTitle(player.getPlayerEntity(), parsedTitle, parsedSubTitle, fadeIn.getTicksAsInt(), stay.getTicksAsInt(), fadeOut.getTicksAsInt()); } - if (!scriptEntry.hasObject("title") && !scriptEntry.hasObject("subtitle")) { - throw new InvalidArgumentsException("Must have a title or subtitle!"); - } - scriptEntry.defaultObject("fade_in", new DurationTag(1)).defaultObject("stay", new DurationTag(3)) - .defaultObject("fade_out", new DurationTag(1)) - .defaultObject("targets", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry))) - .defaultObject("subtitle", new ElementTag("")).defaultObject("title", new ElementTag("")); } - @Override - public void execute(ScriptEntry scriptEntry) { - String title = scriptEntry.getElement("title").asString(); - String subtitle = scriptEntry.getElement("subtitle").asString(); - DurationTag fade_in = scriptEntry.getObjectTag("fade_in"); - DurationTag stay = scriptEntry.getObjectTag("stay"); - DurationTag fade_out = scriptEntry.getObjectTag("fade_out"); - List targets = (List) scriptEntry.getObject("targets"); - ElementTag perPlayerObj = scriptEntry.getElement("per_player"); - boolean perPlayer = perPlayerObj != null && perPlayerObj.asBoolean(); - BukkitTagContext context = (BukkitTagContext) scriptEntry.getContext(); - if (!perPlayer) { - title = TagManager.tag(title, context); - subtitle = TagManager.tag(subtitle, context); - } - if (scriptEntry.dbCallShouldDebug()) { - Debug.report(scriptEntry, getName(), db("title", title), db("subtitle", subtitle), fade_in, stay, fade_out, db("targets", targets), perPlayerObj); - } - for (PlayerTag player : targets) { - if (player != null) { - if (!player.isOnline()) { - Debug.echoDebug(scriptEntry, "Player is offline, can't send title to them. Skipping."); - continue; - } - String personalTitle = title; - String personalSubtitle = subtitle; - if (perPlayer) { - context.player = player; - personalTitle = TagManager.tag(personalTitle, context); - personalSubtitle = TagManager.tag(personalSubtitle, context); - } - NMSHandler.packetHelper.showTitle(player.getPlayerEntity(), personalTitle, personalSubtitle, fade_in.getTicksAsInt(), stay.getTicksAsInt(), fade_out.getTicksAsInt()); - } - else { - Debug.echoError("Sent title to non-existent player!?"); - } - } + public static String parse(ParseableTag tag, BukkitTagContext context) { + return tag == null ? "" : tag.parse(context).toString(); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CopyBlockCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CopyBlockCommand.java index 05e42edccb..b0f939b657 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CopyBlockCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CopyBlockCommand.java @@ -1,17 +1,18 @@ package com.denizenscript.denizen.scripts.commands.world; +import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizen.utilities.PaperAPITools; import com.denizenscript.denizen.utilities.blocks.FullBlockData; -import com.denizenscript.denizencore.utilities.debugging.Debug; -import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizencore.exceptions.InvalidArgumentsException; import com.denizenscript.denizencore.objects.Argument; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntry; import com.denizenscript.denizencore.scripts.commands.AbstractCommand; +import com.denizenscript.denizencore.utilities.debugging.Debug; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.*; +import org.bukkit.block.sign.Side; import org.bukkit.inventory.InventoryHolder; import java.util.ArrayList; @@ -110,6 +111,12 @@ else if (sourceState instanceof Sign) { for (String line : ((Sign) sourceState).getLines()) { PaperAPITools.instance.setSignLine(((Sign) updateState), n++, line); } + if (SignCommand.SIGN_SIDES_SUPPORTED) { + n = 0; + for (String line : ((Sign) sourceState).getSide(Side.BACK).getLines()) { + PaperAPITools.instance.setSignBackLine(((Sign) updateState), n++, line); + } + } } else if (sourceState instanceof Skull) { ((Skull) updateState).setSkullType(((Skull) sourceState).getSkullType()); diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/GameRuleCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/GameRuleCommand.java index 4a2e65b188..3efb74c13d 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/GameRuleCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/GameRuleCommand.java @@ -1,17 +1,18 @@ package com.denizenscript.denizen.scripts.commands.world; -import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizen.objects.WorldTag; +import com.denizenscript.denizen.utilities.world.GameRuleReflect; import com.denizenscript.denizencore.exceptions.InvalidArgumentsException; import com.denizenscript.denizencore.objects.Argument; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntry; import com.denizenscript.denizencore.scripts.commands.AbstractCommand; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.denizenscript.denizencore.utilities.debugging.DebugInternals; import org.bukkit.Bukkit; +import org.bukkit.GameRule; import org.bukkit.generator.WorldInfo; -import java.util.stream.Collectors; - public class GameRuleCommand extends AbstractCommand { public GameRuleCommand() { @@ -31,24 +32,25 @@ public GameRuleCommand() { // // @Description // Sets a gamerule on the world. A list of valid gamerules can be found here: <@link url https://minecraft.wiki/w/Game_rule> - // Note: Be careful, gamerules are CASE SENSITIVE. // // @Tags // ]> // // @Usage // Use to disable fire spreading in world "Adventure". - // - gamerule Adventure doFireTick false + // - gamerule Adventure fire_spread_radius_around_player 0 // // @Usage // Use to avoid mobs from destroying blocks (creepers, endermen...) and picking items up (zombies, skeletons...) in world "Adventure". - // - gamerule Adventure mobGriefing false + // - gamerule Adventure mob_griefing false // --> @Override public void addCustomTabCompletions(TabCompletionsBuilder tab) { - tab.add(Bukkit.getWorlds().get(0).getGameRules()); - tab.add(Bukkit.getWorlds().stream().map(WorldInfo::getName).collect(Collectors.toSet())); + for (GameRule gameRule : GameRuleReflect.values()) { + tab.add(GameRuleReflect.getName(gameRule)); + } + tab.add(Bukkit.getWorlds().stream().map(WorldInfo::getName).toList()); } @Override @@ -82,12 +84,37 @@ else if (!scriptEntry.hasObject("value")) { @Override public void execute(ScriptEntry scriptEntry) { WorldTag world = scriptEntry.getObjectTag("world"); - ElementTag gamerule = scriptEntry.getElement("gamerule"); - ElementTag value = scriptEntry.getElement("value"); + ElementTag gameRuleInput = scriptEntry.getElement("gamerule"); + ElementTag valueInput = scriptEntry.getElement("value"); if (scriptEntry.dbCallShouldDebug()) { - Debug.report(scriptEntry, getName(), world, gamerule, value); + Debug.report(scriptEntry, getName(), world, gameRuleInput, valueInput); + } + GameRule gameRule = GameRuleReflect.getByName(gameRuleInput.asString()); + if (gameRule == null) { + Debug.echoError("Invalid game rule specified: " + gameRuleInput.asString() + '.'); + return; + } + Class gameRuleType = GameRuleReflect.getType(gameRule); + Object convertedValue; + if (gameRuleType == Integer.class) { + if (!valueInput.isInt()) { + Debug.echoError("Invalid value specified: must be a number."); + return; + } + convertedValue = valueInput.asInt(); + } + else if (gameRuleType == Boolean.class) { + if (!valueInput.isBoolean()) { + Debug.echoError("Invalid value specified: must be a boolean."); + return; + } + convertedValue = valueInput.asBoolean(); + } + else { + Debug.echoError("Unrecognized game rule type '" + DebugInternals.getFullClassNameOpti(gameRuleType) + "'! Please report this to the developers."); + return; } - if (!world.getWorld().setGameRuleValue(gamerule.asString(), value.asString())) { + if (!world.getWorld().setGameRule(gameRule, convertedValue)) { Debug.echoError(scriptEntry, "Invalid gamerule!"); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SignCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SignCommand.java index 5623bdc060..2904ef64ce 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SignCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SignCommand.java @@ -1,16 +1,18 @@ package com.denizenscript.denizen.scripts.commands.world; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; +import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizen.objects.MaterialTag; import com.denizenscript.denizen.objects.properties.material.MaterialDirectional; import com.denizenscript.denizen.utilities.Utilities; -import com.denizenscript.denizencore.utilities.debugging.Debug; -import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizencore.exceptions.InvalidArgumentsException; import com.denizenscript.denizencore.objects.Argument; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; import com.denizenscript.denizencore.scripts.ScriptEntry; import com.denizenscript.denizencore.scripts.commands.AbstractCommand; +import com.denizenscript.denizencore.utilities.debugging.Debug; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -19,6 +21,8 @@ public class SignCommand extends AbstractCommand { + public static final boolean SIGN_SIDES_SUPPORTED = NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20); + public SignCommand() { setName("sign"); setSyntax("sign (type:{automatic}/sign_post/wall_sign) (material:) [|...] [] (direction:north/east/south/west)"); diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/ItemScriptHelper.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/ItemScriptHelper.java index 2a416d20b0..c57b5f43cf 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/ItemScriptHelper.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/ItemScriptHelper.java @@ -60,14 +60,17 @@ public static void removeDenizenRecipes() { Iterator recipeIterator = Bukkit.recipeIterator(); ArrayList keys = new ArrayList<>(); while (recipeIterator.hasNext()) { - if (recipeIterator.next() instanceof Keyed keyed && keyed.getKey().getNamespace().equals("denizen")) { - if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { - keys.add(keyed.getKey()); - } - else { - recipeIterator.remove(); + try { + if (recipeIterator.next() instanceof Keyed keyed && keyed.getKey().getNamespace().equals("denizen")) { + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + keys.add(keyed.getKey()); + } + else { + recipeIterator.remove(); + } } } + catch (AbstractMethodError ignored) {} // TODO: 26.1: work around Spigot bug } if (!keys.isEmpty()) { NMSHandler.itemHelper.removeRecipes(keys); diff --git a/plugin/src/main/java/com/denizenscript/denizen/tags/core/ServerTagBase.java b/plugin/src/main/java/com/denizenscript/denizen/tags/core/ServerTagBase.java index f8bda93241..08122dc15d 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/tags/core/ServerTagBase.java +++ b/plugin/src/main/java/com/denizenscript/denizen/tags/core/ServerTagBase.java @@ -14,6 +14,7 @@ import com.denizenscript.denizen.utilities.*; import com.denizenscript.denizen.utilities.depends.Depends; import com.denizenscript.denizen.utilities.inventory.SlotHelper; +import com.denizenscript.denizen.utilities.world.GameRuleReflect; import com.denizenscript.denizencore.DenizenCore; import com.denizenscript.denizencore.events.ScriptEvent; import com.denizenscript.denizencore.objects.Mechanism; @@ -639,11 +640,7 @@ else if (param.shouldBeType(EntityTag.class)) { // Returns a list of all available gamerules on the server. // --> tagProcessor.registerStaticTag(ListTag.class, "gamerules", (attribute, object) -> { - ListTag gamerules = new ListTag(); - for (GameRule rule : GameRule.values()) { - gamerules.add(rule.getName()); - } - return gamerules; + return new ListTag(Arrays.asList(GameRuleReflect.values()), gameRule -> new ElementTag(GameRuleReflect.getName(gameRule), true)); }); // <--[tag] @@ -1123,6 +1120,16 @@ else if (param.shouldBeType(EntityTag.class)) { return new ElementTag(Denizen.versionTag); }); + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the name of the Bukkit platform, such as "Paper". + // --> + tagProcessor.registerStaticTag(ElementTag.class, "bukkit_name", (attribute, object) -> { + return new ElementTag(Bukkit.getName()); + }); + // <--[tag] // @attribute // @returns ElementTag @@ -1660,7 +1667,7 @@ else if (nameLow.startsWith(matchInput) && (newMatch.isOnline() == matchPlayer.i // --> tagProcessor.registerTag(ListTag.class, "recent_tps", (attribute, object) -> { ListTag recentTPS = new ListTag(3); - for (double tps : NMSHandler.instance.getRecentTps()) { + for (double tps : PaperAPITools.instance.getRecentTps()) { recentTPS.addObject(new ElementTag(tps)); } return recentTPS; @@ -2025,6 +2032,32 @@ else if (nameLow.startsWith(matchInput) && (newMatch.isOnline() == matchPlayer.i } }); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { + + // <--[mechanism] + // @object server + // @name links + // @input ListTag(MapTag) + // @description + // Sets the default server links. Each item in the list must be a MapTag in <@link language Server Links Format>. + // Generally prefer <@link mechanism server.add_links>. + // --> + tagProcessor.registerMechanism("links", false, ListTag.class, (object, mechanism, input) -> { + Utilities.replaceServerLinks(Bukkit.getServerLinks(), input, mechanism.context); + }); + + // <--[mechanism] + // @object server + // @name add_links + // @input ListTag(MapTag) + // @description + // Adds links to the default server links. Each item in the list must be a MapTag in <@link language Server Links Format>. + // --> + tagProcessor.registerMechanism("add_links", false, ListTag.class, (object, mechanism, input) -> { + Utilities.fillServerLinks(Bukkit.getServerLinks(), input, mechanism.context); + }); + } + // <--[mechanism] // @object server // @name default_colors diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/BukkitImplDeprecations.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/BukkitImplDeprecations.java index 8c862c1172..19cd68ef9d 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/BukkitImplDeprecations.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/BukkitImplDeprecations.java @@ -230,11 +230,6 @@ public class BukkitImplDeprecations { public static Warning itemEnchantmentsLegacy = new Warning("itemEnchantmentsLegacy", "The tag 'ItemTag.enchantments' is deprecated: use enchantment_map, or enchantment_types."); public static Warning echantmentTagUpdate = new Warning("echantmentTagUpdate", "Several legacy enchantment-related tags are deprecated in favor of using EnchantmentTag."); - // Added 2022/01/30, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15. - // 2023-year-end commonality: #29 - // Safe to remove now. - public static Warning entityItemEnderman = new Warning("entityItemEnderman", "The property 'entity.item' for endermen has been replaced by 'entity.material' due to usage of block materials."); - // Added 2021/06/19, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15. public static Warning entityMapTraceTag = new Warning("entityMapTraceTag", "The tag 'EntityTag.map_trace' is deprecated in favor of EntityTag.trace_framed_map"); @@ -267,6 +262,9 @@ public class BukkitImplDeprecations { // Added 2025/07/10 public static Warning entityKnockback = new Warning("entityKnockback", "The 'EntityTag.knockback' property is deprecated. You should adjust the knockback enchantment on the weapon itself."); + // Added 2025/10/24 + public static Warning explosionPrimeDetermination = new Warning("explosionPrimeDetermination", "The determination to control fire in the ' explosion primes' event is now formatted as 'FIRE:'."); + // ==================== SLOW deprecations ==================== // These aren't spammed, but will show up repeatedly until fixed. Server owners will probably notice them. @@ -485,11 +483,17 @@ public class BukkitImplDeprecations { // Added 2025/08/23 public static Warning advancementBackgroundFormat = new FutureWarning("advancementBackgroundFormat", "The 'background:' input in the advancement command no longer uses the 'textures/' path or '.png' suffix, so for example 'minecraft:textures/gui/advancements/backgrounds/stone.png' would be 'minecraft:gui/advancements/backgrounds/stone'."); + // Added 2025/09/07 + public static Warning brewingStandConsumeDetermination = new FutureWarning("brewingStandConsumeDetermination", "The 'consuming' and 'not_consuming' determinations in the 'brewing stand fueled' event have been deprecated in favor of 'CONSUMING:'."); + // Added 2025/09/22 public static Warning playerSteerEntityEvent = new FutureWarning("playerSteerEntityEvent", "The 'player steers ' event is deprecated in favor of the 'player input' event in MC 1.21+."); // ==================== PAST deprecations of things that are already gone but still have a warning left behind ==================== + // Removed upstream 2025/02/15 + public static Warning chunkRegeneration = new StrongWarning("chunkRegeneration", "'ChunkTag.regenerate' is deprecated: support for chunk regeneration has been removed upstream."); + // Removed upstream 2023/10/29 without warning. public static Warning npcHologramDirection = new StrongWarning("npcHologramDirection", "NPCTag's 'hologram_direction' is deprecated: it was removed from Citizens. Ask in the Citizens channel on the Discord if you need it."); diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/FormattedTextHelper.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/FormattedTextHelper.java index 67392b5fa9..f253ece61c 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/FormattedTextHelper.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/FormattedTextHelper.java @@ -19,6 +19,8 @@ import net.md_5.bungee.chat.ComponentSerializer; import net.md_5.bungee.chat.VersionedComponentSerializer; +import java.net.URI; +import java.net.URISyntaxException; import java.util.List; public class FormattedTextHelper { @@ -798,13 +800,18 @@ else if (code == 'o' || code == 'O') { started = i + 1; } else if (i + "https://a.".length() < chars.length && chars[i] == 'h' && chars[i + 1] == 't' && chars[i + 2] == 't' && chars[i + 3] == 'p') { - String subStr = str.substring(i, i + "https://a.".length()); - if (subStr.startsWith("https://") || subStr.startsWith("http://")) { - int nextSpace = CoreUtilities.indexOfAny(str, i, ' ', '\t', '\n', ChatColor.COLOR_CHAR); - if (nextSpace == -1) { - nextSpace = str.length(); + if (str.startsWith("https://", i) || str.startsWith("http://", i)) { + int urlEnd = indexOfUrlEnd(chars, i); + if (urlEnd - i < "https://a.".length()) { + continue; + } + String url = str.substring(i, urlEnd); + try { + new URI(url); + } + catch (URISyntaxException ignored) { + continue; } - String url = str.substring(i, nextSpace); nextText.setText(nextText.getText() + str.substring(started, i)); base.addExtra(nextText); lastText = nextText; @@ -813,8 +820,8 @@ else if (i + "https://a.".length() < chars.length && chars[i] == 'h' && chars[i TextComponent clickableText = new TextComponent(url); clickableText.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)); lastText.addExtra(clickableText); - i = nextSpace - 1; - started = nextSpace; + i = urlEnd - 1; + started = urlEnd; continue; } } @@ -826,6 +833,17 @@ else if (i + "https://a.".length() < chars.length && chars[i] == 'h' && chars[i return new BaseComponent[] { cleanBase && !optimize ? root : base }; } + public static final AsciiMatcher URL_VALID = new AsciiMatcher(AsciiMatcher.LETTERS_LOWER + AsciiMatcher.LETTERS_UPPER + AsciiMatcher.DIGITS + "-._~:/?#@!$&'()*+,;=%"); + + public static int indexOfUrlEnd(char[] chars, int start) { + for (int i = start; i < chars.length; i++) { + if (!URL_VALID.isMatch(chars[i])) { + return i; + } + } + return chars.length; + } + public static int indexOfLastColorBlockStart(String text) { int result = text.lastIndexOf(ChatColor.COLOR_CHAR + "["); if (result == -1 || text.indexOf(']', result + 2) != -1) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java index e649c9aec1..7413b4dbdf 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java @@ -20,7 +20,7 @@ public static boolean colorIsApplicable(EntityType type) { // TODO Frog variants technically have registries on all supported versions public static String getColor(Entity entity, boolean includeDeprecated) { if (entity instanceof Frog frog) { - return String.valueOf(frog.getVariant()); + return Utilities.keyedToString(frog.getVariant()); } else if (entity instanceof Boat boat) { if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/PaperAPITools.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/PaperAPITools.java index 2625cc5ced..1c516f02cd 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/PaperAPITools.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/PaperAPITools.java @@ -3,18 +3,19 @@ import com.denizenscript.denizen.nms.NMSHandler; import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.scripts.commands.entity.TeleportCommand; +import com.denizenscript.denizen.scripts.commands.world.SignCommand; import com.denizenscript.denizen.scripts.containers.core.ItemScriptContainer; import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.*; import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -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; @@ -25,6 +26,8 @@ import org.bukkit.util.Consumer; import java.lang.invoke.MethodHandle; +import java.net.URI; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.function.Predicate; @@ -84,12 +87,25 @@ public String getPlayerListName(Player player) { return player.getPlayerListName(); } - public String[] getSignLines(Sign sign) { - return sign.getLines(); + public List getSignLines(Sign sign) { + return Arrays.asList(SignCommand.SIGN_SIDES_SUPPORTED ? sign.getSide(Side.FRONT).getLines() : sign.getLines()); + } + + public List getSignBackLines(Sign sign) { + return Arrays.asList(sign.getSide(Side.BACK).getLines()); } public void setSignLine(Sign sign, int line, String text) { - sign.setLine(line, text == null ? "" : text); + if (SignCommand.SIGN_SIDES_SUPPORTED) { + sign.getSide(Side.FRONT).setLine(line, text == null ? "" : text); + } + else { + sign.setLine(line, text == null ? "" : text); + } + } + + public void setSignBackLine(Sign sign, int line, String text) { + sign.getSide(Side.BACK).setLine(line, text == null ? "" : text); } public void sendResourcePack(Player player, String url, String hash, boolean forced, String prompt) { @@ -233,4 +249,22 @@ public boolean hasCustomName(PotionMeta meta) { public void setMaterialTags(Material type, Set tags) { NMSHandler.blockHelper.setVanillaTags(type, tags); } + + public void addLink(ServerLinks links, String display, URI uri) { + links.addLink(display, uri); + } + + public double[] getRecentTps() { + return NMSHandler.instance.getRecentTps(); + } + + public String getCopperGolemState(CopperGolem copperGolem) { + return copperGolem.getWeatherState().name(); + } + + public void setCopperGolemState(ElementTag variant, CopperGolem copperGolem, Mechanism mechanism) { + if (mechanism.requireEnum(CopperGolem.CopperWeatherState.class)) { + copperGolem.setWeatherState(variant.asEnum(CopperGolem.CopperWeatherState.class)); + } + } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/Utilities.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/Utilities.java index 1487700384..7f3923bcea 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/Utilities.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/Utilities.java @@ -13,8 +13,10 @@ 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.core.MapTag; import com.denizenscript.denizencore.objects.core.ScriptTag; import com.denizenscript.denizencore.scripts.ScriptEntry; +import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.tags.TagManager; import com.denizenscript.denizencore.utilities.AsciiMatcher; import com.denizenscript.denizencore.utilities.CoreConfiguration; @@ -38,6 +40,8 @@ import java.io.File; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.*; /** @@ -56,6 +60,11 @@ public static NamespacedKey parseNamespacedKey(String input) { } } + // Needed due to bytecode differences between versions (e.g. enum to interface changes) + public static String keyedToString(Keyed keyed) { + return namespacedKeyToString(keyed.getKey()); + } + public static String namespacedKeyToString(NamespacedKey key) { return key.getNamespace().equals(NamespacedKey.MINECRAFT) ? key.getKey() : key.toString(); } @@ -563,11 +572,11 @@ public static ListTag listLegacyTypes(Class type) { } public static ElementTag enumlikeToElement(Object val) { - if (val instanceof Enum) { - return new ElementTag(((Enum) val).name()); + if (val instanceof Enum enumVal) { + return new ElementTag(enumVal); } - if (val instanceof Keyed) { - return new ElementTag(namespacedKeyToString(((Keyed) val).getKey()), true); + if (val instanceof Keyed keyed) { + return new ElementTag(keyedToString(keyed), true); } return new ElementTag(val.toString()); } @@ -645,4 +654,53 @@ public static boolean requireEnumlike(Mechanism mechanism, Class type) { } return true; } + + // <--[language] + // @name Server Links Format + // @group Minecraft Logic + // @description + // Server links are represented in Denizen as <@link ObjectType MapTag>s with the following keys: + // - link: The address of the link, required. + // And one of: + // - type: The type of the link, valid types are listed at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/ServerLinks.Type.html>. + // - display: The display name of the link. + // --> + + public static ServerLinks replaceServerLinks(ServerLinks serverLinks, ListTag list, TagContext context) { + serverLinks.getLinks().forEach(serverLinks::removeLink); + return fillServerLinks(serverLinks, list, context); + } + + public static ServerLinks fillServerLinks(ServerLinks serverLinks, ListTag list, TagContext context) { + for (MapTag map : list.filter(MapTag.class, context)) { + if (!map.containsKey("link")) { + Debug.echoError("Invalid server links map '" + map + "': missing 'link' key!"); + continue; + } + URI uri; + String strUri = map.getElement("link").asString(); + try { + uri = new URI(strUri); + } + catch (URISyntaxException e) { + Debug.echoError("Invalid server links map '" + map + "': invalid 'link' value '" + strUri + "'."); + continue; + } + if (map.containsKey("display")) { + PaperAPITools.instance.addLink(serverLinks, map.getElement("display").asString(), uri); + } + else if (map.containsKey("type")) { + ServerLinks.Type type = map.getElement("type").asEnum(ServerLinks.Type.class); + if (type == null) { + Debug.echoError("Invalid server links map '" + map + "': invalid 'type' value '" + map.getElement("type") + "'."); + continue; + } + serverLinks.addLink(type, uri); + } + else { + Debug.echoError("Invalid server links map '" + map + "': must have either a 'display' or 'type' key!"); + } + } + return serverLinks; + } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/ChunkCoordinate.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/ChunkCoordinate.java index 17ac185ff9..229e2aa1e4 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/ChunkCoordinate.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/ChunkCoordinate.java @@ -7,6 +7,10 @@ public class ChunkCoordinate { + public static int blockToChunkCoord(int coordinate) { + return coordinate >> 4; + } + public final int x; public final int z; @@ -20,8 +24,8 @@ public ChunkCoordinate(int _x, int _z, String _worldName) { } public ChunkCoordinate(Location location) { - x = location.getBlockX() >> 4; - z = location.getBlockZ() >> 4; + x = blockToChunkCoord(location.getBlockX()); + z = blockToChunkCoord(location.getBlockZ()); worldName = location.getWorld().getName(); } diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/FakeBlock.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/FakeBlock.java index 5efa823896..4f7dd0cabe 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/FakeBlock.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/FakeBlock.java @@ -8,6 +8,8 @@ import com.denizenscript.denizen.objects.PlayerTag; import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; import com.denizenscript.denizencore.objects.core.DurationTag; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.scheduler.BukkitRunnable; @@ -24,7 +26,7 @@ public static class FakeBlockMap { public Map byLocation = new HashMap<>(); - public Map> byChunk = new HashMap<>(); + public Map>> byChunk = new HashMap<>(); public FakeBlock getOrAdd(PlayerTag player, LocationTag location) { location = new LocationTag(location.getBlockX(), location.getBlockY(), location.getBlockZ(), location.getWorldName()); @@ -34,19 +36,27 @@ public FakeBlock getOrAdd(PlayerTag player, LocationTag location) { } block = new FakeBlock(player, location); byLocation.put(location, block); - List chunkBlocks = byChunk.computeIfAbsent(block.chunkCoord, k -> new ArrayList<>()); + List chunkBlocks = byChunk.computeIfAbsent(block.chunkCoord, k -> new Int2ObjectOpenHashMap<>()).computeIfAbsent(block.sectionY, k -> new ArrayList<>()); chunkBlocks.add(block); return block; } public void remove(FakeBlock block) { if (byLocation.remove(block.location) != null) { - List chunkBlocks = byChunk.get(block.chunkCoord); - if (chunkBlocks != null) { - chunkBlocks.remove(block); - if (chunkBlocks.isEmpty()) { - byChunk.remove(block.chunkCoord); - } + Int2ObjectMap> chunkBlocks = byChunk.get(block.chunkCoord); + if (chunkBlocks == null) { + return; + } + List sectionBlocks = chunkBlocks.get(block.sectionY); + if (sectionBlocks == null) { + return; + } + sectionBlocks.remove(block); + if (sectionBlocks.isEmpty()) { + chunkBlocks.remove(block.sectionY); + } + if (chunkBlocks.isEmpty()) { + byChunk.remove(block.chunkCoord); } } } @@ -67,19 +77,29 @@ public static List getFakeBlocksFor(UUID id, ChunkCoordinate chunkCoo if (map == null) { return null; } - return map.byChunk.get(chunkCoord); + Int2ObjectMap> chunkMap = map.byChunk.get(chunkCoord); + if (chunkMap == null) { + return null; + } + List blocks = new ArrayList<>(); + chunkMap.values().forEach(blocks::addAll); + return blocks; } public final PlayerTag player; public final LocationTag location; public final ChunkCoordinate chunkCoord; + public final int sectionY; public MaterialTag material; public BukkitTask currentTask = null; + public int lastBlockLight = -1; + public int lastSkyLight = -1; private FakeBlock(PlayerTag player, LocationTag location) { this.player = player; this.location = location; this.chunkCoord = new ChunkCoordinate(location); + this.sectionY = ChunkCoordinate.blockToChunkCoord(location.getBlockY()); } public static void showFakeBlockTo(List players, LocationTag location, MaterialTag material, DurationTag duration, boolean sendNow) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpongeSchematicHelper.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpongeSchematicHelper.java index 1740d8d6de..be27810e69 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpongeSchematicHelper.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpongeSchematicHelper.java @@ -63,8 +63,7 @@ public static CuboidBlockSet fromSpongeStream(InputStream is) { throw new Exception("Tag 'Schematic' does not exist or is not first!"); } CompoundBinaryTag schematic = rootTag.getValue(); - // TODO: adventure-nbt: compound tag contains - if (schematic.keySet().contains("DenizenEntities")) { + if (schematic.contains("DenizenEntities")) { String entities = stringifyTag(schematic.get("DenizenEntities")); cbs.entities = ListTag.valueOf(entities, CoreUtilities.errorButNoDebugContext); } @@ -74,7 +73,7 @@ public static CuboidBlockSet fromSpongeStream(InputStream is) { int originX = 0; int originY = 0; int originZ = 0; - if (schematic.keySet().contains("DenizenOffset")) { + if (schematic.contains("DenizenOffset")) { // Note: "Offset" contains complete nonsense from WE, so just don't touch it. int[] offsetArr = getChildTag(schematic, "DenizenOffset", BinaryTagTypes.INT_ARRAY).value(); originX = offsetArr[0]; @@ -122,7 +121,7 @@ public static CuboidBlockSet fromSpongeStream(InputStream is) { } } Map tileEntitiesMap = new HashMap<>(); - if (schematic.keySet().contains("BlockEntities")) { + if (schematic.contains("BlockEntities")) { ListBinaryTag tileEntities = getChildTag(schematic, "BlockEntities", BinaryTagTypes.LIST); for (BinaryTag tag : tileEntities) { if (!(tag instanceof CompoundBinaryTag compoundTag)) { @@ -165,7 +164,7 @@ public static CuboidBlockSet fromSpongeStream(InputStream is) { cbs.blocks[cbsIndex] = block; index++; } - if (schematic.keySet().contains("DenizenFlags")) { + if (schematic.contains("DenizenFlags")) { CompoundBinaryTag flags = getChildTag(schematic, "DenizenFlags", BinaryTagTypes.COMPOUND); for (Map.Entry flagData : flags) { int flagIndex = Integer.parseInt(flagData.getKey()); diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/StatsRecord.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/StatsRecord.java index c4614d3711..3de0ccb75a 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/StatsRecord.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/StatsRecord.java @@ -30,16 +30,13 @@ public void gather() { String deprecations = String.join("\n", Deprecations.firedRecently.keySet()); Deprecations.firedRecently.clear(); String mcVersion = Bukkit.getVersion(); - int firstDash = mcVersion.indexOf('-'); - int secondDash = firstDash == -1 ? -1 : mcVersion.indexOf('-', firstDash + 1); int mcPart = mcVersion.indexOf("(MC: "); int endPart = mcPart == -1 ? -1 : mcVersion.indexOf(")", mcPart); - String platform = secondDash == -1 ? "" : mcVersion.substring(firstDash + 1, secondDash); mcVersion = (endPart == -1) ? "" : mcVersion.substring(mcPart + "(MC: ".length(), endPart); content = "postid=pluginstats&plugin=Denizen" + "&differentiator=" + differentiator + "&pl_plugin_version=" + URLEncoder.encode(denizenVersion) - + "&pl_platform=" + URLEncoder.encode(platform) + + "&pl_platform=" + URLEncoder.encode(Bukkit.getName()) + "&pl_minecraft_version=" + URLEncoder.encode(mcVersion) + "&pl_player_count=" + Bukkit.getOnlinePlayers().size() + "&pl_script_count=" + ScriptRegistry.scriptContainers.size() diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/nbt/CustomNBT.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/nbt/CustomNBT.java index f537851002..96b26865cd 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/nbt/CustomNBT.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/nbt/CustomNBT.java @@ -43,7 +43,7 @@ public static List getNBTMaterials(ItemStack itemStack, String key) { } CompoundBinaryTag compoundTag = NMSHandler.itemHelper.getNbtData(itemStack); List materials = new ArrayList<>(); - if (compoundTag.keySet().contains(key)) { + if (compoundTag.contains(key)) { ListBinaryTag temp = compoundTag.getList(key, BinaryTagTypes.STRING); for (BinaryTag tag : temp) { materials.add(Material.matchMaterial(((StringBinaryTag) tag).value())); @@ -114,7 +114,7 @@ public static boolean hasCustomNBT(ItemStack itemStack, String key, String basek if (customData == null) { return false; } - return customData.getCompound(basekey).keySet().contains(CoreUtilities.toLowerCase(key)); + return customData.getCompound(basekey).contains(CoreUtilities.toLowerCase(key)); } public static String getCustomNBT(ItemStack itemStack, String key, String basekey) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/packets/NetworkInterceptCodeGen.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/packets/NetworkInterceptCodeGen.java index 96d5ef3055..9e01916f1c 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/packets/NetworkInterceptCodeGen.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/packets/NetworkInterceptCodeGen.java @@ -1,10 +1,13 @@ package com.denizenscript.denizen.utilities.packets; import com.denizenscript.denizen.nms.NMSHandler; -import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizencore.utilities.codegen.CodeGenUtil; import com.denizenscript.denizencore.utilities.codegen.MethodGenerator; -import org.objectweb.asm.*; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -12,6 +15,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; public class NetworkInterceptCodeGen { @@ -54,18 +59,46 @@ public static void generateClass(Class denClass, Class abstractClass, Clas + ", Public=" + Modifier.isPublic(modifier) + ", final=" + Modifier.isFinal(modifier)); } MethodGenerator gen = MethodGenerator.generateMethod(className, cw, Opcodes.ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method)); - gen.loadThis(); - gen.loadInstanceField(Type.getInternalName(abstractClass), "oldListener", Type.getDescriptor(nmsClass)); + List locals = new ArrayList<>(); int id = 1; for (Class type : method.getParameterTypes()) { if (NMSHandler.debugPackets) { Debug.log("Var " + id + " is type " + type.getName()); } - MethodGenerator.Local local = gen.addLocal("arg_" + (id++), type); - gen.loadLocal(local); + locals.add(gen.addLocal("arg_" + (id++), type)); } + Label returnLabel = new Label(); + gen.loadThis(); + gen.loadInstanceField(Type.getInternalName(abstractClass), "oldListener", Type.getDescriptor(nmsClass)); + gen.jumpIfNullTo(returnLabel); + gen.loadThis(); + gen.loadInstanceField(Type.getInternalName(abstractClass), "oldListener", Type.getDescriptor(nmsClass)); + locals.forEach(gen::loadLocal); gen.invokeVirtual(method); - gen.returnValue(method.getReturnType()); + Class returnType = method.getReturnType(); + gen.returnValue(returnType); + + gen.advanceAndLabel(returnLabel); + if (returnType != Void.TYPE) { + if (returnType.isPrimitive()) { + if (returnType == Long.TYPE) { + gen.mv.visitInsn(Opcodes.LCONST_0); + } + else if (returnType == Float.TYPE) { + gen.mv.visitInsn(Opcodes.FCONST_0); + } + else if (returnType == Double.TYPE) { + gen.mv.visitInsn(Opcodes.DCONST_0); + } + else { + gen.mv.visitInsn(Opcodes.ICONST_0); + } + } + else { + gen.loadNull(); + } + } + gen.returnValue(returnType); gen.end(); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/world/GameRuleReflect.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/world/GameRuleReflect.java new file mode 100644 index 0000000000..62b09b4dd3 --- /dev/null +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/world/GameRuleReflect.java @@ -0,0 +1,74 @@ +package com.denizenscript.denizen.utilities.world; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import org.bukkit.GameRule; +import org.bukkit.Keyed; +import org.bukkit.World; + +import java.lang.invoke.MethodHandle; + +public class GameRuleReflect { + + private static final MethodHandle GAMERULE_VALUES = ReflectionHelper.getMethodHandle(GameRule.class, "values"); + private static final MethodHandle GAMERULE_GET_BY_NAME = ReflectionHelper.getMethodHandle(GameRule.class, "getByName", String.class); + private static final MethodHandle GAMERULE_GET_NAME = ReflectionHelper.getMethodHandle(GameRule.class, "getName"); + private static final MethodHandle GAMERULE_GET_TYPE = ReflectionHelper.getMethodHandle(GameRule.class, "getType"); + + public static final boolean MODERN_GAMERULE_NAMING = NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21); + public static final GameRule WEATHER_CYCLE_GAMERULE = getByName(MODERN_GAMERULE_NAMING ? "advance_weather" : "doWeatherCycle"); + + public static GameRule[] values() { + try { + return (GameRule[]) GAMERULE_VALUES.invokeExact(); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static GameRule getByName(String name) { + try { + return (GameRule) GAMERULE_GET_BY_NAME.invokeExact(name); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static String getName(GameRule gameRule) { + if (MODERN_GAMERULE_NAMING) { + return Utilities.keyedToString(gameRule); + } + try { + return (String) GAMERULE_GET_NAME.invokeExact(gameRule); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static Class getType(GameRule gameRule) { + try { + return (Class) GAMERULE_GET_TYPE.invokeExact(gameRule); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + // TODO 1.21.11: certain gamemodes (e.g. max_minecart_speed) behave badly for some reason + public static T getValue(World world, GameRule gameRule) { + try { + return world.getGameRuleValue(gameRule); + } + catch (IllegalArgumentException e) { + if (e.getMessage().equals("Tried to access invalid game rule")) { + return null; + } + throw e; + } + } +} diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/world/PathFinder.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/world/PathFinder.java index 6a2acd30e5..4bb78544c6 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/world/PathFinder.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/world/PathFinder.java @@ -2,6 +2,7 @@ import com.denizenscript.denizen.objects.LocationTag; import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.citizensnpcs.api.ai.NavigatorParameters; import net.citizensnpcs.api.astar.AStarMachine; import net.citizensnpcs.api.astar.pathfinder.*; import org.bukkit.Location; @@ -19,7 +20,7 @@ public class PathFinder { public static List getPath(Location start, Location dest) { VectorGoal goal = new VectorGoal(dest, 1); - Path plan = (Path) ASTAR.runFully(goal, new VectorNode(goal, start, new ChunkBlockSource(start, 100), new MinecraftBlockExaminer()), 50000); + Path plan = (Path) ASTAR.runFully(goal, new VectorNode(goal, start, new ChunkBlockSource(start, 100), new NavigatorParameters().examiner(new MinecraftBlockExaminer())), 50000); if (plan == null) { Debug.verboseLog("PathFinder: No path found from " + start + " to " + dest + " (return null)"); return new ArrayList<>(); diff --git a/pom.xml b/pom.xml index a5c28e2ada..1320cdd25b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,18 +6,20 @@ com.denizenscript denizen-parent pom - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT UTF-8 Unknown CUSTOM - 1.3.1 + 1.3.3 plugin paper + v26_2 + v26_1 v1_21 v1_20 v1_19 diff --git a/v1_17/pom.xml b/v1_17/pom.xml index 36caa23f16..29e4b05219 100644 --- a/v1_17/pom.xml +++ b/v1_17/pom.xml @@ -6,7 +6,7 @@ com.denizenscript denizen-v1_17 - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT diff --git a/v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/NBTAdapter.java b/v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/NBTAdapter.java index 01124e496a..4d335032cf 100644 --- a/v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/NBTAdapter.java +++ b/v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/NBTAdapter.java @@ -103,10 +103,9 @@ else if (nmsTag instanceof EndTag) { } public static ListBinaryTag toAPI(ListTag nmsListTag) { - // TODO: adventure-nbt: builder initial capacity - ListBinaryTag.Builder builder = ListBinaryTag.builder(); - for (Tag nmsEntry : nmsListTag) { - builder.add(toAPI(nmsEntry)); + ListBinaryTag.Builder builder = ListBinaryTag.builder(nmsListTag.size()); + for (Tag nmsValue : nmsListTag) { + builder.add(toAPI(nmsValue)); } return builder.build(); } @@ -120,11 +119,11 @@ public static ListTag toNMS(ListBinaryTag listTag) { } public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) { - Map tags = new HashMap<>(nmsCompoundTag.size()); + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size()); for (String key : nmsCompoundTag.getAllKeys()) { - tags.put(key, toAPI(nmsCompoundTag.get(key))); + builder.put(key, toAPI(nmsCompoundTag.get(key))); } - return CompoundBinaryTag.from(tags); + return builder.build(); } public static CompoundTag toNMS(CompoundBinaryTag compoundTag) { diff --git a/v1_18/pom.xml b/v1_18/pom.xml index bda527e371..574ee03cbb 100644 --- a/v1_18/pom.xml +++ b/v1_18/pom.xml @@ -6,7 +6,7 @@ com.denizenscript denizen-v1_18 - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT diff --git a/v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/NBTAdapter.java b/v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/NBTAdapter.java index 7fb6cc6c87..5ee7e4c17e 100644 --- a/v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/NBTAdapter.java +++ b/v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/NBTAdapter.java @@ -103,10 +103,9 @@ else if (nmsTag instanceof EndTag) { } public static ListBinaryTag toAPI(ListTag nmsListTag) { - // TODO: adventure-nbt: builder initial capacity - ListBinaryTag.Builder builder = ListBinaryTag.builder(); - for (Tag nmsEntry : nmsListTag) { - builder.add(toAPI(nmsEntry)); + ListBinaryTag.Builder builder = ListBinaryTag.builder(nmsListTag.size()); + for (Tag nmsValue : nmsListTag) { + builder.add(toAPI(nmsValue)); } return builder.build(); } @@ -120,11 +119,11 @@ public static ListTag toNMS(ListBinaryTag listTag) { } public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) { - Map tags = new HashMap<>(nmsCompoundTag.size()); + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size()); for (String key : nmsCompoundTag.getAllKeys()) { - tags.put(key, toAPI(nmsCompoundTag.get(key))); + builder.put(key, toAPI(nmsCompoundTag.get(key))); } - return CompoundBinaryTag.from(tags); + return builder.build(); } public static CompoundTag toNMS(CompoundBinaryTag compoundTag) { diff --git a/v1_19/pom.xml b/v1_19/pom.xml index 71d0daf7c3..53df243ebb 100644 --- a/v1_19/pom.xml +++ b/v1_19/pom.xml @@ -6,7 +6,7 @@ com.denizenscript denizen-v1_19 - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT diff --git a/v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/NBTAdapter.java b/v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/NBTAdapter.java index 0486d9beba..470d0cba60 100644 --- a/v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/NBTAdapter.java +++ b/v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/NBTAdapter.java @@ -103,10 +103,9 @@ else if (nmsTag instanceof EndTag) { } public static ListBinaryTag toAPI(ListTag nmsListTag) { - // TODO: adventure-nbt: builder initial capacity - ListBinaryTag.Builder builder = ListBinaryTag.builder(); - for (Tag nmsEntry : nmsListTag) { - builder.add(toAPI(nmsEntry)); + ListBinaryTag.Builder builder = ListBinaryTag.builder(nmsListTag.size()); + for (Tag nmsValue : nmsListTag) { + builder.add(toAPI(nmsValue)); } return builder.build(); } @@ -120,11 +119,11 @@ public static ListTag toNMS(ListBinaryTag listTag) { } public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) { - Map tags = new HashMap<>(nmsCompoundTag.size()); + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size()); for (String key : nmsCompoundTag.getAllKeys()) { - tags.put(key, toAPI(nmsCompoundTag.get(key))); + builder.put(key, toAPI(nmsCompoundTag.get(key))); } - return CompoundBinaryTag.from(tags); + return builder.build(); } public static CompoundTag toNMS(CompoundBinaryTag compoundTag) { diff --git a/v1_20/pom.xml b/v1_20/pom.xml index 50b8cebb62..d3bc7a89cc 100644 --- a/v1_20/pom.xml +++ b/v1_20/pom.xml @@ -6,7 +6,7 @@ com.denizenscript denizen-v1_20 - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT diff --git a/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/EntityHelperImpl.java b/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/EntityHelperImpl.java index 58fb9ba367..e4ce519430 100644 --- a/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/EntityHelperImpl.java +++ b/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/EntityHelperImpl.java @@ -6,6 +6,7 @@ import com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo; import com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl; import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.properties.entity.EntityState; import com.denizenscript.denizen.utilities.Utilities; import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; import com.denizenscript.denizencore.objects.ObjectTag; @@ -43,6 +44,7 @@ import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.navigation.PathNavigation; +import net.minecraft.world.entity.animal.armadillo.Armadillo; import net.minecraft.world.entity.item.FallingBlockEntity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.PrimedTnt; @@ -848,4 +850,26 @@ public void modifyRawNBT(Entity entity, CompoundBinaryTag tag) { nmsEntity.load(nmsMergedTag); nmsEntity.setUUID(uuid); } + + @Override + public EntityState.ArmadilloState getArmadilloState(org.bukkit.entity.Armadillo entity) { + Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle(); + return switch (armadillo.getState()) { + case IDLE -> EntityState.ArmadilloState.IDLE; + case ROLLING -> EntityState.ArmadilloState.ROLLING; + case SCARED -> EntityState.ArmadilloState.SCARED; + case UNROLLING -> EntityState.ArmadilloState.UNROLLING; + }; + } + + @Override + public void setArmadilloState(org.bukkit.entity.Armadillo entity, EntityState.ArmadilloState state) { + Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle(); + armadillo.switchToState(switch (state) { + case IDLE -> Armadillo.ArmadilloState.IDLE; + case ROLLING -> Armadillo.ArmadilloState.ROLLING; + case SCARED -> Armadillo.ArmadilloState.SCARED; + case UNROLLING -> Armadillo.ArmadilloState.UNROLLING; + }); + } } diff --git a/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/ItemHelperImpl.java b/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/ItemHelperImpl.java index cc304f59b4..bbe86f9fb8 100644 --- a/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/ItemHelperImpl.java +++ b/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/ItemHelperImpl.java @@ -315,8 +315,7 @@ public CompoundBinaryTag getEntityData(ItemStack item) { @Override public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) { CompoundTag nmsEntityNbt = EMPTY_TAG; - // TODO: adventure-nbt: contains - if (entityNbt != null && !entityNbt.isEmpty() && (!entityNbt.keySet().contains("id") || entityNbt.size() > 1)) { + if (entityNbt != null && !entityNbt.isEmpty() && (!entityNbt.contains("id") || entityNbt.size() > 1)) { nmsEntityNbt = NBTAdapter.toNMS(entityNbt); nmsEntityNbt.putString("id", entityType.getKey().toString()); } diff --git a/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/NBTAdapter.java b/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/NBTAdapter.java index c284a1c859..32664fb8d3 100644 --- a/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/NBTAdapter.java +++ b/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/NBTAdapter.java @@ -105,10 +105,9 @@ else if (nmsTag instanceof EndTag) { } public static ListBinaryTag toAPI(ListTag nmsListTag) { - // TODO: adventure-nbt: builder initial capacity - ListBinaryTag.Builder builder = ListBinaryTag.builder(); - for (Tag nmsEntry : nmsListTag) { - builder.add(toAPI(nmsEntry)); + ListBinaryTag.Builder builder = ListBinaryTag.builder(nmsListTag.size()); + for (Tag nmsValue : nmsListTag) { + builder.add(toAPI(nmsValue)); } return builder.build(); } @@ -122,11 +121,11 @@ public static ListTag toNMS(ListBinaryTag listTag) { } public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) { - Map tags = new HashMap<>(nmsCompoundTag.size()); + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size()); for (String key : nmsCompoundTag.getAllKeys()) { - tags.put(key, toAPI(nmsCompoundTag.get(key))); + builder.put(key, toAPI(nmsCompoundTag.get(key))); } - return CompoundBinaryTag.from(tags); + return builder.build(); } public static CompoundTag toNMS(CompoundBinaryTag compoundTag) { diff --git a/v1_21/pom.xml b/v1_21/pom.xml index 70c9f2ca0d..d5e04a0315 100644 --- a/v1_21/pom.xml +++ b/v1_21/pom.xml @@ -6,7 +6,7 @@ com.denizenscript denizen-v1_21 - 1.3.1-SNAPSHOT + 1.3.3-SNAPSHOT @@ -19,13 +19,13 @@ org.spigotmc spigot-api - 1.21.8-R0.1-SNAPSHOT + 1.21.11-R0.2-SNAPSHOT provided org.spigotmc spigot - 1.21.8-R0.1-SNAPSHOT + 1.21.11-R0.2-SNAPSHOT remapped-mojang provided @@ -45,9 +45,9 @@ remap-obf - org.spigotmc:minecraft-server:1.21.8-R0.1-SNAPSHOT:txt:maps-mojang + org.spigotmc:minecraft-server:1.21.11-R0.2-SNAPSHOT:txt:maps-mojang true - org.spigotmc:spigot:1.21.8-R0.1-SNAPSHOT:jar:remapped-mojang + org.spigotmc:spigot:1.21.11-R0.2-SNAPSHOT:jar:remapped-mojang true remapped-obf @@ -60,8 +60,8 @@ remap-spigot ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar - org.spigotmc:minecraft-server:1.21.8-R0.1-SNAPSHOT:csrg:maps-spigot - org.spigotmc:spigot:1.21.8-R0.1-SNAPSHOT:jar:remapped-obf + org.spigotmc:minecraft-server:1.21.11-R0.2-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.11-R0.2-SNAPSHOT:jar:remapped-obf diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/Handler.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/Handler.java index 1cd431f486..01ffc5fac9 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/Handler.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/Handler.java @@ -31,7 +31,6 @@ import com.google.common.collect.Iterables; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; -import com.mojang.authlib.yggdrasil.ProfileResult; import com.mojang.serialization.DynamicOps; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.md_5.bungee.api.ChatColor; @@ -46,7 +45,7 @@ import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.StringTag; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.ProblemReporter; @@ -68,19 +67,19 @@ import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.boss.BossBar; -import org.bukkit.craftbukkit.v1_21_R5.CraftRegistry; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_21_R5.boss.CraftBossBar; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryCustom; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; -import org.bukkit.craftbukkit.v1_21_R5.legacy.FieldRename; -import org.bukkit.craftbukkit.v1_21_R5.persistence.CraftPersistentDataContainer; -import org.bukkit.craftbukkit.v1_21_R5.util.*; +import org.bukkit.craftbukkit.v1_21_R7.CraftRegistry; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_21_R7.boss.CraftBossBar; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryCustom; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.legacy.FieldRename; +import org.bukkit.craftbukkit.v1_21_R7.persistence.CraftPersistentDataContainer; +import org.bukkit.craftbukkit.v1_21_R7.util.*; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; @@ -151,7 +150,7 @@ public void undisableAsyncCatcher() { @Override public boolean isExactServerVersionMatch() { - return Denizen.supportsPaper ? SharedConstants.getCurrentVersion().id().equals("1.21.8") : CraftMagicNumbers.INSTANCE.getMappingsVersion().equals("98b42190c84edaa346fd96106ee35d6f"); + return Denizen.supportsPaper ? SharedConstants.getCurrentVersion().id().equals("1.21.11") : CraftMagicNumbers.INSTANCE.getMappingsVersion().equals("e3cd927e07e6ff434793a0474c51b2b9"); } @Override @@ -184,24 +183,23 @@ public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) { GameProfile profile = null; MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer(); if (playerProfile.getUniqueId() != null) { - profile = minecraftServer.getProfileCache().get(playerProfile.getUniqueId()).orElse(null); + profile = minecraftServer.services().nameToIdCache().get(playerProfile.getUniqueId()).map(result -> new GameProfile(result.id(), result.name())).orElse(null); } if (profile == null && playerProfile.getName() != null) { - profile = minecraftServer.getProfileCache().get(playerProfile.getName()).orElse(null); + profile = minecraftServer.services().nameToIdCache().get(playerProfile.getName()).map(result -> new GameProfile(result.id(), result.name())).orElse(null); } if (profile == null) { profile = ProfileEditorImpl.getGameProfileNoProperties(playerProfile); } - Property textures = profile.getProperties().containsKey("textures") ? Iterables.getFirst(profile.getProperties().get("textures"), null) : null; - if (textures == null || !textures.hasSignature() || profile.getName() == null || profile.getId() == null) { - ProfileResult actualProfile = minecraftServer.getSessionService().fetchProfile(profile.getId(), true); - if (actualProfile == null) { + Property textures = profile.properties().containsKey("textures") ? Iterables.getFirst(profile.properties().get("textures"), null) : null; + if (textures == null || !textures.hasSignature() || profile.name() == null || profile.id() == null) { + profile = minecraftServer.services().profileResolver().fetchById(profile.id()).orElse(null); + if (profile == null) { return null; } - profile = actualProfile.profile(); - textures = profile.getProperties().containsKey("textures") ? Iterables.getFirst(profile.getProperties().get("textures"), null) : null; + textures = profile.properties().containsKey("textures") ? Iterables.getFirst(profile.properties().get("textures"), null) : null; } - return new PlayerProfile(profile.getName(), profile.getId(), textures == null ? null : textures.value(), textures == null ? null : textures.signature()); + return new PlayerProfile(profile.name(), profile.id(), textures == null ? null : textures.value(), textures == null ? null : textures.signature()); } catch (Exception e) { if (CoreConfiguration.debugVerbose) { @@ -281,8 +279,8 @@ public void setInventoryTitle(InventoryView view, String title) { @Override public PlayerProfile getPlayerProfile(Player player) { GameProfile gameProfile = ((CraftPlayer) player).getProfile(); - Property property = Iterables.getFirst(gameProfile.getProperties().get("textures"), null); - return new PlayerProfile(gameProfile.getName(), gameProfile.getId(), + Property property = Iterables.getFirst(gameProfile.properties().get("textures"), null); + return new PlayerProfile(gameProfile.name(), gameProfile.id(), property != null ? property.value() : null, property != null ? property.signature() : null); } @@ -296,7 +294,7 @@ public ProfileEditor getProfileEditor() { public List getBiomes(World world) { ServerLevel level = ((CraftWorld) world).getHandle(); ArrayList output = new ArrayList<>(); - for (ResourceLocation key : level.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { + for (Identifier key : level.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key))); } return output; @@ -316,7 +314,7 @@ public BiomeNMS getBiomeAt(Block block) { // Based on CraftWorld source ServerLevel level = ((CraftWorld) block.getWorld()).getHandle(); Holder biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2); - ResourceLocation key = level.registryAccess().lookupOrThrow(Registries.BIOME).getKey(biome.value()); + Identifier key = level.registryAccess().lookupOrThrow(Registries.BIOME).getKey(biome.value()); return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key)); } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/ReflectionMappingsInfo.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/ReflectionMappingsInfo.java index 811303325a..1eefe598b5 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/ReflectionMappingsInfo.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/ReflectionMappingsInfo.java @@ -11,34 +11,39 @@ public class ReflectionMappingsInfo { public static String MappedRegistry_registrationInfos = "h"; // net.minecraft.world.entity.Entity - public static String Entity_onGround = "bb"; - public static String Entity_DATA_SHARED_FLAGS_ID = "az"; - public static String Entity_DATA_CUSTOM_NAME = "bl"; - public static String Entity_DATA_CUSTOM_NAME_VISIBLE = "bm"; + public static String Entity_onGround = "bc"; + public static String Entity_DATA_SHARED_FLAGS_ID = "aA"; + public static String Entity_DATA_CUSTOM_NAME = "bm"; + public static String Entity_DATA_CUSTOM_NAME_VISIBLE = "bn"; // net.minecraft.world.entity.LivingEntity - public static String LivingEntity_attackStrengthTicker = "bo"; - public static String LivingEntity_autoSpinAttackTicks = "bL"; + public static String LivingEntity_attackStrengthTicker = "bz"; + public static String LivingEntity_autoSpinAttackTicks = "bW"; public static String LivingEntity_setLivingEntityFlag_method = "c"; // net.minecraft.world.entity.player.Player - public static String Player_DATA_PLAYER_ABSORPTION_ID = "d"; - public static String Player_DATA_PLAYER_MODE_CUSTOMISATION = "cg"; + public static String Player_DATA_PLAYER_ABSORPTION_ID = "b"; // net.minecraft.server.level.ServerPlayer - public static String ServerPlayer_respawnConfig = "dA"; + public static String ServerPlayer_respawnConfig = "dr"; // net.minecraft.world.entity.monster.EnderMan - public static String EnderMan_DATA_CREEPY = "cl"; + public static String EnderMan_DATA_CREEPY = "cx"; - // net.minecraft.world.entity.monster.Zombie - public static String Zombie_inWaterTime = "cA"; + // net.minecraft.world.entity.monster.zombie.Zombie + public static String Zombie_inWaterTime = "cL"; // net.minecraft.world.item.Item public static String Item_components = "c"; + // net.minecraft.world.item.component.ResolvableProfile + public static String ResolvableProfile_unpack_method = "a"; + + // net.minecraft.world.item.component.ResolvableProfile$Partial + public static String ResolvableProfilePartial_id = "d"; + // net.minecraft.world.level.Level - public static String Level_isClientSide = "C"; + public static String Level_isClientSide = "D"; // net.minecraft.server.level.ThreadedLevelLightEngine public static String ThreadedLevelLightEngine_addTask_method = "a"; @@ -54,22 +59,19 @@ public class ReflectionMappingsInfo { // net.minecraft.world.level.biome.Biome public static String Biome_climateSettings = "i"; - - // net.minecraft.world.level.biome.BiomeSpecialEffects - public static String BiomeSpecialEffects_foliageColorOverride = "f"; - public static String BiomeSpecialEffects_fogColor = "b"; - public static String BiomeSpecialEffects_waterFogColor = "d"; + public static String Biome_attributes = "l"; + public static String Biome_specialEffects = "m"; // net.minecraft.network.Connection - public static String Connection_receiving = "k"; - public static String Connection_packetListener = "q"; + public static String Connection_receiving = "h"; + public static String Connection_packetListener = "n"; // net.minecraft.server.network.ServerGamePacketListenerImpl - public static String ServerGamePacketListenerImpl_aboveGroundTickCount = "J"; - public static String ServerGamePacketListenerImpl_aboveGroundVehicleTickCount = "L"; - public static String ServerGamePacketListenerImpl_awaitingPositionFromClient = "F"; - public static String ServerGamePacketListenerImpl_awaitingTeleport = "G"; - public static String ServerGamePacketListenerImpl_chunkSender = "g"; + public static String ServerGamePacketListenerImpl_aboveGroundTickCount = "L"; + public static String ServerGamePacketListenerImpl_aboveGroundVehicleTickCount = "N"; + public static String ServerGamePacketListenerImpl_awaitingPositionFromClient = "H"; + public static String ServerGamePacketListenerImpl_awaitingTeleport = "I"; + public static String ServerGamePacketListenerImpl_chunkSender = "h"; // net.minecraft.server.network.ServerCommonPacketListenerImpl public static String ServerCommonPacketListenerImpl_connection = "e"; @@ -96,7 +98,12 @@ public class ReflectionMappingsInfo { // net.minecraft.network.protocol.game.ClientboundSetPassengersPacket public static String ClientboundSetPassengersPacket_passengers = "c"; + // net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData + public static String ClientboundLevelChunkPacketData_buffer = "d"; + public static String ClientboundLevelChunkPacketData_blockEntitiesData = "e"; + // net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo + public static String ClientboundLevelChunkPacketDataBlockEntityInfo_create_method = "a"; public static String ClientboundLevelChunkPacketDataBlockEntityInfo_packedXZ = "c"; public static String ClientboundLevelChunkPacketDataBlockEntityInfo_y = "d"; @@ -111,14 +118,8 @@ public class ReflectionMappingsInfo { // net.minecraft.tags.TagNetworkSerialization$NetworkPayload public static String TagNetworkSerializationNetworkPayload_tags = "b"; - // net.minecraft.core.HolderSet$Named - public static String HolderSetNamed_bind_method = "b"; - - // net.minecraft.core.Holder$Reference - public static String HolderReference_bindTags_method = "a"; - // net.minecraft.server.level.ServerLevel - public static String ServerLevel_sleepStatus = "R"; + public static String ServerLevel_sleepStatus = "O"; // net.minecraft.world.item.AdventureModePredicate public static String AdventureModePredicate_predicates = "g"; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AdvancementHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AdvancementHelperImpl.java index 0b2ca9366e..1c9d91c52c 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AdvancementHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AdvancementHelperImpl.java @@ -6,18 +6,18 @@ import com.google.common.collect.ImmutableMap; import net.md_5.bungee.api.ChatColor; import net.minecraft.advancements.*; -import net.minecraft.advancements.critereon.ImpossibleTrigger; +import net.minecraft.advancements.criterion.ImpossibleTrigger; import net.minecraft.core.ClientAsset; import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.ServerAdvancementManager; import net.minecraft.server.level.ServerPlayer; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftNamespacedKey; import org.bukkit.entity.Player; import java.util.*; @@ -38,8 +38,8 @@ public void register(com.denizenscript.denizen.nms.util.Advancement advancement) return; } AdvancementHolder nmsAdvancementHolder = asNMSCopy(advancement); - Map nmsAdvancements = getNMSAdvancementManager().advancements; - ImmutableMap.Builder mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() + 1); + Map nmsAdvancements = getNMSAdvancementManager().advancements; + ImmutableMap.Builder mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() + 1); mapBuilder.putAll(nmsAdvancements); mapBuilder.put(nmsAdvancementHolder.id(), nmsAdvancementHolder); getNMSAdvancementManager().advancements = mapBuilder.build(); @@ -65,10 +65,10 @@ public void unregister(com.denizenscript.denizen.nms.util.Advancement advancemen if (advancement.temporary || !advancement.registered) { return; } - ResourceLocation nmsKey = CraftNamespacedKey.toMinecraft(advancement.key); - Map nmsAdvancements = getNMSAdvancementManager().advancements; - ImmutableMap.Builder mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() - 1); - for (Map.Entry entry : nmsAdvancements.entrySet()) { + Identifier nmsKey = CraftNamespacedKey.toMinecraft(advancement.key); + Map nmsAdvancements = getNMSAdvancementManager().advancements; + ImmutableMap.Builder mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() - 1); + for (Map.Entry entry : nmsAdvancements.entrySet()) { if (!entry.getKey().equals(nmsKey)) { mapBuilder.put(entry); } @@ -176,7 +176,7 @@ private static AdvancementHolder asNMSCopy(com.denizenscript.denizen.nms.util.Ad : null; DisplayInfo display = new DisplayInfo(CraftItemStack.asNMSCopy(advancement.icon), Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)), - Optional.ofNullable(advancement.background).map(CraftNamespacedKey::toMinecraft).map(ClientAsset::new), AdvancementType.valueOf(advancement.frame.name()), + Optional.ofNullable(advancement.background).map(CraftNamespacedKey::toMinecraft).map(ClientAsset.ResourceTexture::new), AdvancementType.valueOf(advancement.frame.name()), advancement.toast, advancement.announceToChat, advancement.hidden); display.setLocation(advancement.xOffset, advancement.yOffset); Map> criteria = IMPOSSIBLE_CRITERIA; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AnimationHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AnimationHelperImpl.java index a59f65bee6..b000df11bc 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AnimationHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AnimationHelperImpl.java @@ -2,9 +2,9 @@ import com.denizenscript.denizen.nms.abstracts.AnimationHelper; import net.minecraft.world.entity.Entity; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftHorse; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPolarBear; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftHorse; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPolarBear; import org.bukkit.entity.EntityType; import org.bukkit.entity.Horse; import org.bukkit.entity.IronGolem; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/BlockHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/BlockHelperImpl.java index 99ae170c91..8a8b9188c9 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/BlockHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/BlockHelperImpl.java @@ -34,17 +34,17 @@ import org.bukkit.block.Block; import org.bukkit.block.CreatureSpawner; import org.bukkit.block.Skull; -import org.bukkit.craftbukkit.v1_21_R5.CraftChunk; -import org.bukkit.craftbukkit.v1_21_R5.CraftRegistry; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.block.CraftBlock; -import org.bukkit.craftbukkit.v1_21_R5.block.CraftBlockEntityState; -import org.bukkit.craftbukkit.v1_21_R5.block.CraftCreatureSpawner; -import org.bukkit.craftbukkit.v1_21_R5.block.CraftSkull; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftLocation; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.v1_21_R7.CraftChunk; +import org.bukkit.craftbukkit.v1_21_R7.CraftRegistry; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.block.CraftBlock; +import org.bukkit.craftbukkit.v1_21_R7.block.CraftBlockEntityState; +import org.bukkit.craftbukkit.v1_21_R7.block.CraftCreatureSpawner; +import org.bukkit.craftbukkit.v1_21_R7.block.CraftSkull; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftLocation; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftMagicNumbers; import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; @@ -85,15 +85,15 @@ public PlayerProfile getPlayerProfile(Skull skull) { if (profile == null) { return null; } - com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.properties().get("textures"), null); - return new PlayerProfile(profile.name().orElse(null), profile.id().orElse(null), property != null ? property.value() : null); + com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.partialProfile().properties().get("textures"), null); + return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile), property != null ? property.value() : null); } @Override public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) { GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile); try { - craftSkull_profile.set(skull, new ResolvableProfile(gameProfile)); + craftSkull_profile.set(skull, ResolvableProfile.createResolved(gameProfile)); } catch (Throwable ex) { Debug.echoError(ex); diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ChunkHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ChunkHelperImpl.java index 79e765a6e6..4157cf2074 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ChunkHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ChunkHelperImpl.java @@ -20,8 +20,8 @@ import net.minecraft.world.level.levelgen.Heightmap; import org.bukkit.Chunk; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_21_R5.CraftChunk; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.CraftChunk; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/CustomEntityHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/CustomEntityHelperImpl.java index 9b0aa2a05a..0df283220e 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/CustomEntityHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/CustomEntityHelperImpl.java @@ -20,8 +20,8 @@ import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.scoreboard.Scoreboard; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityDataNameMapper.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityDataNameMapper.java index f55647bb01..01b43c07ca 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityDataNameMapper.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityDataNameMapper.java @@ -5,39 +5,69 @@ import com.denizenscript.denizencore.objects.core.ElementTag; import net.minecraft.world.entity.*; import net.minecraft.world.entity.ambient.Bat; -import net.minecraft.world.entity.animal.*; import net.minecraft.world.entity.animal.axolotl.Axolotl; +import net.minecraft.world.entity.animal.bee.Bee; import net.minecraft.world.entity.animal.camel.Camel; +import net.minecraft.world.entity.animal.cow.MushroomCow; +import net.minecraft.world.entity.animal.dolphin.Dolphin; +import net.minecraft.world.entity.animal.equine.AbstractChestedHorse; +import net.minecraft.world.entity.animal.equine.AbstractHorse; +import net.minecraft.world.entity.animal.equine.Horse; +import net.minecraft.world.entity.animal.equine.Llama; +import net.minecraft.world.entity.animal.feline.Cat; +import net.minecraft.world.entity.animal.feline.Ocelot; +import net.minecraft.world.entity.animal.fish.AbstractFish; +import net.minecraft.world.entity.animal.fish.Pufferfish; +import net.minecraft.world.entity.animal.fish.TropicalFish; +import net.minecraft.world.entity.animal.fox.Fox; import net.minecraft.world.entity.animal.frog.Frog; import net.minecraft.world.entity.animal.goat.Goat; -import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; -import net.minecraft.world.entity.animal.horse.AbstractHorse; -import net.minecraft.world.entity.animal.horse.Horse; -import net.minecraft.world.entity.animal.horse.Llama; +import net.minecraft.world.entity.animal.golem.IronGolem; +import net.minecraft.world.entity.animal.golem.SnowGolem; +import net.minecraft.world.entity.animal.panda.Panda; +import net.minecraft.world.entity.animal.parrot.Parrot; +import net.minecraft.world.entity.animal.pig.Pig; +import net.minecraft.world.entity.animal.polarbear.PolarBear; +import net.minecraft.world.entity.animal.rabbit.Rabbit; import net.minecraft.world.entity.animal.sheep.Sheep; import net.minecraft.world.entity.animal.sniffer.Sniffer; +import net.minecraft.world.entity.animal.turtle.Turtle; import net.minecraft.world.entity.animal.wolf.Wolf; import net.minecraft.world.entity.boss.enderdragon.EndCrystal; import net.minecraft.world.entity.boss.enderdragon.EnderDragon; import net.minecraft.world.entity.boss.wither.WitherBoss; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.decoration.ItemFrame; -import net.minecraft.world.entity.decoration.Painting; +import net.minecraft.world.entity.decoration.painting.Painting; import net.minecraft.world.entity.item.PrimedTnt; import net.minecraft.world.entity.monster.*; import net.minecraft.world.entity.monster.hoglin.Hoglin; +import net.minecraft.world.entity.monster.illager.Pillager; +import net.minecraft.world.entity.monster.illager.SpellcasterIllager; import net.minecraft.world.entity.monster.piglin.AbstractPiglin; import net.minecraft.world.entity.monster.piglin.Piglin; +import net.minecraft.world.entity.monster.spider.Spider; import net.minecraft.world.entity.monster.warden.Warden; -import net.minecraft.world.entity.npc.AbstractVillager; -import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.entity.monster.zombie.Zombie; +import net.minecraft.world.entity.monster.zombie.ZombieVillager; +import net.minecraft.world.entity.npc.villager.AbstractVillager; +import net.minecraft.world.entity.npc.villager.Villager; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.projectile.*; +import net.minecraft.world.entity.projectile.EyeOfEnder; +import net.minecraft.world.entity.projectile.FireworkRocketEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.entity.projectile.ThrowableProjectile; +import net.minecraft.world.entity.projectile.arrow.AbstractArrow; +import net.minecraft.world.entity.projectile.arrow.Arrow; +import net.minecraft.world.entity.projectile.arrow.ThrownTrident; +import net.minecraft.world.entity.projectile.hurtingprojectile.Fireball; +import net.minecraft.world.entity.projectile.hurtingprojectile.SmallFireball; +import net.minecraft.world.entity.projectile.hurtingprojectile.WitherSkull; import net.minecraft.world.entity.raid.Raider; -import net.minecraft.world.entity.vehicle.AbstractMinecart; -import net.minecraft.world.entity.vehicle.Boat; -import net.minecraft.world.entity.vehicle.MinecartCommandBlock; -import net.minecraft.world.entity.vehicle.MinecartFurnace; +import net.minecraft.world.entity.vehicle.boat.Boat; +import net.minecraft.world.entity.vehicle.minecart.AbstractMinecart; +import net.minecraft.world.entity.vehicle.minecart.MinecartCommandBlock; +import net.minecraft.world.entity.vehicle.minecart.MinecartFurnace; import java.util.HashMap; import java.util.Map; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityHelperImpl.java index 09c17b076c..b57de3441d 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityHelperImpl.java @@ -7,6 +7,7 @@ import com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo; import com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl; import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.properties.entity.EntityState; import com.denizenscript.denizen.utilities.Utilities; import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; import com.denizenscript.denizencore.objects.ObjectTag; @@ -45,6 +46,7 @@ import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.navigation.PathNavigation; +import net.minecraft.world.entity.animal.armadillo.Armadillo; import net.minecraft.world.entity.item.FallingBlockEntity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.PrimedTnt; @@ -67,14 +69,14 @@ import org.bukkit.attribute.AttributeInstance; import org.bukkit.block.CreatureSpawner; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.block.CraftBlock; -import org.bukkit.craftbukkit.v1_21_R5.block.CraftCreatureSpawner; -import org.bukkit.craftbukkit.v1_21_R5.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_21_R5.entity.*; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftLocation; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.block.CraftBlock; +import org.bukkit.craftbukkit.v1_21_R7.block.CraftCreatureSpawner; +import org.bukkit.craftbukkit.v1_21_R7.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_21_R7.entity.*; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftLocation; import org.bukkit.entity.*; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.inventory.EquipmentSlot; @@ -685,7 +687,7 @@ public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) { return new EntityTag(nmsEntity.getBukkitEntity()); } - public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.Zombie.class).get(ReflectionMappingsInfo.Zombie_inWaterTime, int.class); + public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.zombie.Zombie.class).get(ReflectionMappingsInfo.Zombie_inWaterTime, int.class); @Override public int getInWaterTime(Zombie zombie) { @@ -821,7 +823,7 @@ public void stopUsingItem(LivingEntity entity) { @Override public void openHorseInventory(Player player, AbstractHorse horse) { - net.minecraft.world.entity.animal.horse.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle(); + net.minecraft.world.entity.animal.equine.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle(); ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory); } @@ -843,4 +845,26 @@ public void modifyRawNBT(Entity entity, CompoundBinaryTag tag) { Handler.useValueInput(nmsMergedTag, nmsEntity::load); nmsEntity.setUUID(uuid); } + + @Override + public EntityState.ArmadilloState getArmadilloState(org.bukkit.entity.Armadillo entity) { + Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle(); + return switch (armadillo.getState()) { + case IDLE -> EntityState.ArmadilloState.IDLE; + case ROLLING -> EntityState.ArmadilloState.ROLLING; + case SCARED -> EntityState.ArmadilloState.SCARED; + case UNROLLING -> EntityState.ArmadilloState.UNROLLING; + }; + } + + @Override + public void setArmadilloState(org.bukkit.entity.Armadillo entity, EntityState.ArmadilloState state) { + Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle(); + armadillo.switchToState(switch (state) { + case IDLE -> Armadillo.ArmadilloState.IDLE; + case ROLLING -> Armadillo.ArmadilloState.ROLLING; + case SCARED -> Armadillo.ArmadilloState.SCARED; + case UNROLLING -> Armadillo.ArmadilloState.UNROLLING; + }); + } } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/FishingHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/FishingHelperImpl.java index b5d75d105a..a6cd25a56c 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/FishingHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/FishingHelperImpl.java @@ -18,10 +18,10 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.phys.Vec3; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftFishHook; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftFishHook; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; import org.bukkit.entity.FishHook; import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ItemHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ItemHelperImpl.java index 14298cc907..5ab295a2d6 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ItemHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ItemHelperImpl.java @@ -1,5 +1,6 @@ package com.denizenscript.denizen.nms.v1_21.helpers; +import com.denizenscript.denizen.Denizen; import com.denizenscript.denizen.nms.interfaces.ItemHelper; import com.denizenscript.denizen.nms.util.PlayerProfile; import com.denizenscript.denizen.nms.v1_21.Handler; @@ -24,8 +25,8 @@ import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.md_5.bungee.api.ChatColor; -import net.minecraft.advancements.critereon.BlockPredicate; -import net.minecraft.advancements.critereon.DataComponentMatchers; +import net.minecraft.advancements.criterion.BlockPredicate; +import net.minecraft.advancements.criterion.DataComponentMatchers; import net.minecraft.core.*; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPatch; @@ -36,9 +37,9 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.util.datafix.fixes.References; import net.minecraft.world.flag.FeatureFlagSet; @@ -49,10 +50,12 @@ import net.minecraft.world.item.component.CustomData; import net.minecraft.world.item.component.ItemLore; import net.minecraft.world.item.component.ResolvableProfile; +import net.minecraft.world.item.component.TypedEntityData; import net.minecraft.world.item.crafting.*; import net.minecraft.world.item.crafting.BlastingRecipe; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.minecraft.world.item.crafting.SmithingTransformRecipe; import net.minecraft.world.item.crafting.SmokingRecipe; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; @@ -69,15 +72,16 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_21_R5.CraftRegistry; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_21_R5.inventory.*; -import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapView; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftMagicNumbers; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.v1_21_R7.CraftRegistry; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntityType; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.inventory.*; +import org.bukkit.craftbukkit.v1_21_R7.map.CraftMapView; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftNamespacedKey; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.*; @@ -200,7 +204,7 @@ public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) { } public static ResourceKey> createRecipeKey(String name) { - return ResourceKey.create(Registries.RECIPE, ResourceLocation.fromNamespaceAndPath("denizen", name)); + return ResourceKey.create(Registries.RECIPE, Identifier.fromNamespaceAndPath("denizen", name)); } @Override @@ -237,15 +241,13 @@ public void registerStonecuttingRecipe(String keyName, String group, ItemStack r @Override public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) { ResourceKey> key = createRecipeKey(keyName); - Ingredient templateItemRecipe = itemArrayToRecipe(templateItem, templateExact); + Ingredient templateItemRecipe = templateItem.length == 0 ? null : itemArrayToRecipe(templateItem, templateExact); Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact); Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact); - // TODO: 1.21.5: TransmuteRecipe? - /* - SmithingTransformRecipe recipe = new SmithingTransformRecipe(Optional.of(templateItemRecipe), baseItemRecipe, Optional.of(upgradeItemRecipe), CraftItemStack.asNMSCopy(result)); + net.minecraft.world.item.ItemStack nmsCopy = CraftItemStack.asNMSCopy(result); + SmithingTransformRecipe recipe = new SmithingTransformRecipe(Optional.ofNullable(templateItemRecipe), baseItemRecipe, Optional.of(upgradeItemRecipe), new TransmuteResult(nmsCopy.getItemHolder(), nmsCopy.getCount(), nmsCopy.getComponentsPatch())); RecipeHolder holder = new RecipeHolder<>(key, recipe); getRecipeManager().addRecipe(holder); - */ } @Override @@ -342,8 +344,8 @@ public PlayerProfile getSkullSkin(ItemStack is) { net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is); ResolvableProfile profile = itemStack.get(DataComponents.PROFILE); if (profile != null) { - Property property = Iterables.getFirst(profile.properties().get("textures"), null); - return new PlayerProfile(profile.name().orElse(null), profile.id().orElse(null), + Property property = Iterables.getFirst(profile.partialProfile().properties().get("textures"), null); + return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile), property != null ? property.value() : null, property != null ? property.signature() : null); } @@ -354,7 +356,7 @@ public PlayerProfile getSkullSkin(ItemStack is) { public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) { GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile); net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); - nmsItemStack.set(DataComponents.PROFILE, new ResolvableProfile(gameProfile)); + nmsItemStack.set(DataComponents.PROFILE, ResolvableProfile.createResolved(gameProfile)); return CraftItemStack.asBukkitCopy(nmsItemStack); } @@ -385,7 +387,7 @@ public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) @Override public CompoundBinaryTag getCustomData(ItemStack item) { CustomData customData = CraftItemStack.asNMSCopy(item).get(DataComponents.CUSTOM_DATA); - return customData != null ? NBTAdapter.toAPI(customData.getUnsafe()) : null; + return customData != null ? NBTAdapter.toAPI(customData.copyTag()) : null; } @Override @@ -417,21 +419,21 @@ public ItemStack setPartialOldNbt(ItemStack item, CompoundBinaryTag oldTag) { @Override public CompoundBinaryTag getEntityData(ItemStack item) { - CustomData entityData = CraftItemStack.asNMSCopy(item).get(DataComponents.ENTITY_DATA); + TypedEntityData> entityData = CraftItemStack.asNMSCopy(item).get(DataComponents.ENTITY_DATA); return entityData != null ? NBTAdapter.toAPI(entityData.getUnsafe()) : null; } - public static final CompoundTag EMPTY_TAG = new CompoundTag(); - @Override public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) { - CompoundTag nmsEntityNbt = EMPTY_TAG; - if (entityNbt != null && !entityNbt.isEmpty() && (!entityNbt.keySet().contains("id") || entityNbt.size() > 1)) { - nmsEntityNbt = NBTAdapter.toNMS(entityNbt); - nmsEntityNbt.putString("id", entityType.getKey().toString()); - } net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); - CustomData.set(DataComponents.ENTITY_DATA, nmsItemStack, nmsEntityNbt); + if (entityNbt == null || entityNbt.isEmpty() || (entityNbt.size() == 1 && entityNbt.contains("id"))) { + nmsItemStack.remove(DataComponents.ENTITY_DATA); + } + else { + CompoundTag nmsEntityNbt = NBTAdapter.toNMS(entityNbt); + nmsEntityNbt.remove("id"); + nmsItemStack.set(DataComponents.ENTITY_DATA, TypedEntityData.of(CraftEntityType.bukkitToMinecraft(entityType), nmsEntityNbt)); + } return CraftItemStack.asBukkitCopy(nmsItemStack); } @@ -441,7 +443,7 @@ public MapTag getRawComponentsPatch(ItemStack item, boolean excludeHandled) { DataComponentPatch patch = nmsItemStack.getComponentsPatch(); if (excludeHandled) { patch = patch.forget(componentType -> { - ResourceLocation componentId = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(componentType); + Identifier componentId = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(componentType); return ItemComponentsPatch.propertyHandledComponents.contains(componentId.toString()); }); } @@ -450,6 +452,12 @@ public MapTag getRawComponentsPatch(ItemStack item, boolean excludeHandled) { } RegistryOps registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE); CompoundTag nmsPatch = (CompoundTag) DataComponentPatch.CODEC.encodeStart(registryOps, patch).getOrThrow(); + if (excludeHandled && Denizen.supportsPaper) { + nmsPatch.keySet().removeIf(s -> s.charAt(0) == '!'); + if (nmsPatch.isEmpty()) { + return new MapTag(); + } + } MapTag rawComponents = (MapTag) ItemRawNBT.nbtTagToObject(NBTAdapter.toAPI(nmsPatch)); rawComponents.putObject(ItemComponentsPatch.DATA_VERSION_KEY, new ElementTag(CraftMagicNumbers.INSTANCE.getDataVersion())); return rawComponents; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/NBTAdapter.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/NBTAdapter.java index fd6bf645f5..427e1a62b3 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/NBTAdapter.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/NBTAdapter.java @@ -105,11 +105,11 @@ else if (nmsTag instanceof EndTag) { } public static ListBinaryTag toAPI(ListTag nmsListTag) { - List tags = new ArrayList<>(nmsListTag.size()); + ListBinaryTag.Builder builder = ListBinaryTag.heterogeneousListBinaryTag(nmsListTag.size()); for (Tag nmsValue : nmsListTag) { - tags.add(toAPI(nmsValue)); + builder.add(toAPI(nmsValue)); } - return ListBinaryTag.listBinaryTag(BinaryTagTypes.LIST_WILDCARD, tags); + return builder.build(); } public static ListTag toNMS(ListBinaryTag listTag) { @@ -121,11 +121,11 @@ public static ListTag toNMS(ListBinaryTag listTag) { } public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) { - Map tags = new HashMap<>(nmsCompoundTag.size()); + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size()); for (Map.Entry nmsEntry : nmsCompoundTag.entrySet()) { - tags.put(nmsEntry.getKey(), toAPI(nmsEntry.getValue())); + builder.put(nmsEntry.getKey(), toAPI(nmsEntry.getValue())); } - return CompoundBinaryTag.from(tags); + return builder.build(); } public static CompoundTag toNMS(CompoundBinaryTag compoundTag) { diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PacketHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PacketHelperImpl.java index 0156bee78a..de8b320707 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PacketHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PacketHelperImpl.java @@ -13,6 +13,7 @@ import com.denizenscript.denizen.utilities.maps.MapImage; import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; import com.denizenscript.denizencore.objects.core.ColorTag; +import com.denizenscript.denizencore.utilities.CoreUtilities; import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; import net.md_5.bungee.api.ChatColor; @@ -21,8 +22,6 @@ import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; import net.minecraft.network.protocol.common.custom.BrandPayload; -import net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload; -import net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload; import net.minecraft.network.protocol.game.*; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.SynchedEntityData; @@ -33,10 +32,10 @@ import net.minecraft.world.entity.Relative; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.Attributes; -import net.minecraft.world.entity.monster.CaveSpider; +import net.minecraft.world.entity.monster.spider.CaveSpider; import net.minecraft.world.entity.monster.Creeper; import net.minecraft.world.entity.monster.EnderMan; -import net.minecraft.world.entity.monster.Spider; +import net.minecraft.world.entity.monster.spider.Spider; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.saveddata.maps.MapItemSavedData; @@ -50,14 +49,14 @@ import org.bukkit.block.Sign; import org.bukkit.block.sign.Side; import org.bukkit.block.sign.SignSide; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; -import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapCanvas; -import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapView; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftLocation; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.map.CraftMapCanvas; +import org.bukkit.craftbukkit.v1_21_R7.map.CraftMapView; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftLocation; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; @@ -160,13 +159,11 @@ public void showTabListHeaderFooter(Player player, String header, String footer) @Override public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { - send(player, new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks)); - if (title != null) { - send(player, new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE)))); - } - if (subtitle != null) { - send(player, new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE)))); - } + send(player, new ClientboundBundlePacket(List.of( + new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks), + new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))), + new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE))) + ))); } @Override @@ -354,15 +351,13 @@ public void enableNetworkManager() { @Override public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) { - int colorInt = color.blue | (color.green << 8) | (color.red << 16) | (color.alpha << 24); - GameTestAddMarkerDebugPayload payload = new GameTestAddMarkerDebugPayload(CraftLocation.toBlockPosition(location), colorInt, name, time); - send(player, new ClientboundCustomPayloadPacket(payload)); + BlockPos nmsPos = CraftLocation.toBlockPosition(location); + LocationTag displayPos = !name.isEmpty() ? LocationTag.valueOf(name, CoreUtilities.noDebugContext) : null; + send(player, new ClientboundGameTestHighlightPosPacket(nmsPos, displayPos != null ? CraftLocation.toBlockPosition(displayPos) : nmsPos)); } @Override public void clearDebugTestMarker(Player player) { - GameTestClearMarkersDebugPayload payload = new GameTestClearMarkersDebugPayload(); - send(player, new ClientboundCustomPayloadPacket(payload)); } @Override diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PlayerHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PlayerHelperImpl.java index c3ee8fcced..6f73e27c02 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PlayerHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PlayerHelperImpl.java @@ -24,23 +24,22 @@ import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; import it.unimi.dsi.fastutil.ints.IntList; import net.md_5.bungee.api.ChatColor; -import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket; import net.minecraft.network.protocol.game.*; -import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerEntity; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.NameAndId; import net.minecraft.server.players.PlayerList; import net.minecraft.server.players.ServerOpList; import net.minecraft.server.players.ServerOpListEntry; @@ -48,6 +47,7 @@ import net.minecraft.tags.BlockTags; import net.minecraft.tags.TagNetworkSerialization; import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Avatar; import net.minecraft.world.entity.Leashable; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.PositionMoveRotation; @@ -57,18 +57,18 @@ import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.GameType; import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.phys.AABB; import org.bukkit.*; import org.bukkit.boss.BossBar; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.boss.CraftBossBar; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftLocation; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftMagicNumbers; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.boss.CraftBossBar; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftNamespacedKey; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -77,6 +77,7 @@ import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; import java.util.*; +import java.util.function.Predicate; public class PlayerHelperImpl extends PlayerHelper { @@ -88,8 +89,6 @@ public class PlayerHelperImpl extends PlayerHelper { public static final MethodHandle PLAYER_RESPAWNCONFIG_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, ReflectionMappingsInfo.ServerPlayer_respawnConfig, ServerPlayer.RespawnConfig.class); public static final MethodHandle SERVER_RECIPE_BOOK_ADD_HIGHLIGHT = ReflectionHelper.getMethodHandle(ServerRecipeBook.class, ReflectionMappingsInfo.ServerRecipeBook_addHighlight_method, ResourceKey.class); - public static final EntityDataAccessor PLAYER_DATA_ACCESSOR_SKINLAYERS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, ReflectionMappingsInfo.Player_DATA_PLAYER_MODE_CUSTOMISATION, null); - @Override public void stopSound(Player player, NamespacedKey sound, SoundCategory category) { ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(sound == null ? null : CraftNamespacedKey.toMinecraft(sound), null)); @@ -127,6 +126,31 @@ public void addFakePassenger(List players, Entity vehicle, FakeEntity } } + public record FakeEntitySynchronizer(ServerGamePacketListenerImpl target) implements ServerEntity.Synchronizer { + + @Override + public void sendToTrackingPlayers(Packet packet) { + target.send(packet); + } + + @Override + public void sendToTrackingPlayersAndSelf(Packet packet) { + sendToTrackingPlayers(packet); + } + + @Override + public void sendToTrackingPlayersFiltered(Packet packet, Predicate predicate) { + if (predicate.test(target.getPlayer())) { + sendToTrackingPlayers(packet); + } + } + + @Override + public void sendToTrackingPlayersFilteredAndSelf(Packet packet, Predicate predicate) { + sendToTrackingPlayersFiltered(packet, predicate); + } + } + @Override public FakeEntity sendEntitySpawn(List players, DenizenEntityType entityType, LocationTag location, ArrayList mechanisms, int customId, UUID customUUID, boolean autoTrack) { CraftWorld world = ((CraftWorld) location.getWorld()); @@ -191,8 +215,7 @@ else if (mechanism.matches("skin_blob")) { fake.triggerSpawnPacket = (player) -> { ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle(); ServerGamePacketListenerImpl conn = nmsPlayer.connection; - // TODO: 1.21.5: what's the correct input for `BiConsumer, List> biconsumer`? - final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, conn::send, (p, u) -> conn.send(p), Collections.singleton(nmsPlayer.connection)); + final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, new FakeEntitySynchronizer(conn), Set.of(conn)); tracker.addPairing(nmsPlayer); final TrackerData data = new TrackerData(player, tracker); trackers.add(data); @@ -313,13 +336,13 @@ public boolean hasChunkLoaded(Player player, Chunk chunk) { @Override public void setTemporaryOp(Player player, boolean op) { MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); - GameProfile profile = ((CraftPlayer) player).getProfile(); + NameAndId nameAndId = new NameAndId(((CraftPlayer) player).getProfile()); ServerOpList opList = server.getPlayerList().getOps(); if (op) { - opList.add(new ServerOpListEntry(profile, server.getOperatorUserPermissionLevel(), opList.canBypassPlayerLimit(profile))); + opList.add(new ServerOpListEntry(nameAndId, server.operatorUserPermissions(), opList.canBypassPlayerLimit(nameAndId))); } else { - opList.remove(profile); + opList.remove(nameAndId); } player.recalculatePermissions(); } @@ -367,12 +390,12 @@ public void quietlyAddRecipe(Player player, NamespacedKey key) { @Override public byte getSkinLayers(Player player) { - return ((CraftPlayer) player).getHandle().getEntityData().get(PLAYER_DATA_ACCESSOR_SKINLAYERS); + return ((CraftPlayer) player).getHandle().getEntityData().get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION); } @Override public void setSkinLayers(Player player, byte flags) { - ((CraftPlayer) player).getHandle().getEntityData().set(PLAYER_DATA_ACCESSOR_SKINLAYERS, flags); + ((CraftPlayer) player).getHandle().getEntityData().set(Avatar.DATA_PLAYER_MODE_CUSTOMISATION, flags); } @Override @@ -391,7 +414,7 @@ public void setSpawnForced(Player player, boolean forced) { ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); try { ServerPlayer.RespawnConfig config = nmsPlayer.getRespawnConfig(); - config = new ServerPlayer.RespawnConfig(config.dimension(), config.pos(), config.angle(), forced); + config = new ServerPlayer.RespawnConfig(config.respawnData(), forced); PLAYER_RESPAWNCONFIG_SETTER.invoke(nmsPlayer, config); } catch (Throwable ex) { @@ -406,11 +429,9 @@ public Location getBedSpawnLocation(Player player) { if (nmsRespawnConfig == null) { return null; } - Level nmsWorld = MinecraftServer.getServer().getLevel(nmsRespawnConfig.dimension() != null ? nmsRespawnConfig.dimension() : Level.OVERWORLD); - if (nmsWorld == null) { - return null; - } - return CraftLocation.toBukkit(nmsRespawnConfig.pos(), nmsWorld.getWorld(), nmsRespawnConfig.angle(), 0); + LevelData.RespawnData nmsRespawnData = nmsRespawnConfig.respawnData(); + Level nmsWorld = MinecraftServer.getServer().getLevel(nmsRespawnData.dimension()); + return nmsWorld != null ? new Location(nmsWorld.getWorld(), nmsRespawnData.pos().getX(), nmsRespawnData.pos().getY(), nmsRespawnData.pos().getZ(), nmsRespawnData.yaw(), nmsRespawnData.pitch()) : null; } @Override @@ -430,10 +451,7 @@ public void sendPlayerInfoAddPacket(Player player, EnumSet edit case UPDATE_LISTED -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED; }); } - GameProfile profile = new GameProfile(id, name != null ? name : ProfileEditorImpl.EMPTY_NAME); - if (texture != null) { - profile.getProperties().put("textures", new Property("textures", texture, signature)); - } + GameProfile profile = ProfileEditorImpl.createGameProfile(id, name, texture, signature); // TODO: 1.21.3: Player list order and hat visibility support ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), true, player.getPlayerListOrder(), null); PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(actions, List.of(entry))); @@ -447,7 +465,7 @@ public void sendPlayerInfoRemovePacket(Player player, UUID id) { @Override public void sendClimbableMaterials(Player player, List materials) { Map>, TagNetworkSerialization.NetworkPayload> packetInput = TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries()); - Map tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, ReflectionMappingsInfo.TagNetworkSerializationNetworkPayload_tags, packetInput.get(BuiltInRegistries.BLOCK.key())); + Map tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, ReflectionMappingsInfo.TagNetworkSerializationNetworkPayload_tags, packetInput.get(BuiltInRegistries.BLOCK.key())); IntList climbableBlocks = tags.get(BlockTags.CLIMBABLE.location()); climbableBlocks.clear(); for (Material material : materials) { @@ -474,7 +492,7 @@ public void refreshPlayer(Player player) { } if (!nmsPlayer.getCooldowns().cooldowns.isEmpty()) { int tickCount = nmsPlayer.getCooldowns().tickCount; - for (Map.Entry entry : nmsPlayer.getCooldowns().cooldowns.entrySet()) { + for (Map.Entry entry : nmsPlayer.getCooldowns().cooldowns.entrySet()) { nmsPlayer.connection.send(new ClientboundCooldownPacket(entry.getKey(), entry.getValue().endTime - tickCount)); } } @@ -483,7 +501,7 @@ public void refreshPlayer(Player player) { nmsPlayer.connection.send(new ClientboundUpdateMobEffectPacket(nmsPlayer.getId(), nmsEffect, false)); } nmsPlayer.onUpdateAbilities(); - PlayerList nmsPlayerList = nmsPlayer.getServer().getPlayerList(); + PlayerList nmsPlayerList = MinecraftServer.getServer().getPlayerList(); nmsPlayerList.sendPlayerPermissionLevel(nmsPlayer); nmsPlayerList.sendLevelInfo(nmsPlayer, nmsWorld); nmsPlayerList.sendAllPlayerInfo(nmsPlayer); diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/WorldHelperImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/WorldHelperImpl.java index 0cb2efa1a7..5f70b7347e 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/WorldHelperImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/WorldHelperImpl.java @@ -16,13 +16,13 @@ import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; public class WorldHelperImpl implements WorldHelper { @Override public boolean isStatic(World world) { - return ((CraftWorld) world).getHandle().isClientSide; + return ((CraftWorld) world).getHandle().isClientSide(); } @Override @@ -98,7 +98,7 @@ public void wakeUpAllPlayers(World world) { // net.minecraft.server.level.ServerLevel#resetWeatherCycle() @Override public void clearWeather(World world) { - PrimaryLevelData data = ((CraftWorld) world).getHandle().L; + PrimaryLevelData data = ((CraftWorld) world).getHandle().serverLevelData; data.setRaining(false); if (!data.isRaining()) { data.setRainTime(0); diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/BiomeNMSImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/BiomeNMSImpl.java index 423fdff0f5..d360c4318a 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/BiomeNMSImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/BiomeNMSImpl.java @@ -14,6 +14,9 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.util.random.Weighted; import net.minecraft.util.random.WeightedList; +import net.minecraft.world.attribute.EnvironmentAttribute; +import net.minecraft.world.attribute.EnvironmentAttributeMap; +import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeSpecialEffects; @@ -22,10 +25,10 @@ import org.bukkit.Location; import org.bukkit.NamespacedKey; import org.bukkit.block.Block; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntityType; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftLocation; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntityType; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftLocation; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftNamespacedKey; import org.bukkit.entity.EntityType; import java.lang.invoke.MethodHandle; @@ -38,6 +41,7 @@ public class BiomeNMSImpl extends BiomeNMS { public static final MethodHandle BIOME_CLIMATESETTINGS_CONSTRUCTOR = ReflectionHelper.getConstructor(Biome.ClimateSettings.class, boolean.class, float.class, Biome.TemperatureModifier.class, float.class); public static final MethodHandle MAPPED_REGISTRY_REGISTRATION_INFOS = ReflectionHelper.getFields(MappedRegistry.class).getGetter(ReflectionMappingsInfo.MappedRegistry_registrationInfos); + public static final MethodHandle BIOME_ATTRIBUTES_SETTER = ReflectionHelper.getFields(Biome.class).getSetter(ReflectionMappingsInfo.Biome_attributes); public Holder.Reference biomeHolder; public ServerLevel world; @@ -147,29 +151,47 @@ public void setHasDownfall(boolean hasDownfall) { @Override public void setFoliageColor(int color) { - ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_foliageColorOverride, biomeHolder.value().getSpecialEffects(), Optional.of(color)); + BiomeSpecialEffects nmsCurrEffects = biomeHolder.value().getSpecialEffects(); + BiomeSpecialEffects nmsNewEffects = new BiomeSpecialEffects( + nmsCurrEffects.waterColor(), Optional.of(color), nmsCurrEffects.dryFoliageColorOverride(), nmsCurrEffects.grassColorOverride(), nmsCurrEffects.grassColorModifier() + ); + ReflectionHelper.setFieldValue(Biome.class, ReflectionMappingsInfo.Biome_specialEffects, biomeHolder.value(), nmsNewEffects); setNetworkedRegistrationInfo(); } @Override public int getFogColor() { - return biomeHolder.value().getFogColor(); + return getEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR); } @Override public void setFogColor(int color) { - ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_fogColor, biomeHolder.value().getSpecialEffects(), color); - setNetworkedRegistrationInfo(); + setEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR, color); } @Override public int getWaterFogColor() { - return biomeHolder.value().getWaterFogColor(); + return getEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR); } @Override public void setWaterFogColor(int color) { - ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_waterFogColor, biomeHolder.value().getSpecialEffects(), color); + setEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR, color); + } + + public T getEnvironmentAttribute(EnvironmentAttribute attribute) { + return biomeHolder.value().getAttributes().applyModifier(attribute, attribute.defaultValue()); + } + + public void setEnvironmentAttribute(EnvironmentAttribute attribute, T value) { + Biome nmsBiome = biomeHolder.value(); + EnvironmentAttributeMap newAttributeMap = EnvironmentAttributeMap.builder().putAll(nmsBiome.getAttributes()).set(attribute, value).build(); + try { + BIOME_ATTRIBUTES_SETTER.invokeExact(nmsBiome, newAttributeMap); + } + catch (Throwable e) { + Debug.echoError(e); + } setNetworkedRegistrationInfo(); } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ImprovedOfflinePlayerImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ImprovedOfflinePlayerImpl.java index 14b59f20d9..7a33a97f69 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ImprovedOfflinePlayerImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ImprovedOfflinePlayerImpl.java @@ -24,10 +24,10 @@ import net.minecraft.world.level.storage.ValueOutput; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryPlayer; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryPlayer; import org.bukkit.entity.HumanEntity; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; @@ -105,9 +105,9 @@ public void setInventory(org.bukkit.inventory.PlayerInventory inventory) { @Override public Inventory getEnderChest() { if (enderchest == null) { - PlayerEnderChestContainer endchest = new PlayerEnderChestContainer(null); - Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> endchest.fromSlots(valueInput.listOrEmpty("EnderItems", ItemStackWithSlot.CODEC))); - enderchest = new CraftInventory(endchest); + PlayerEnderChestContainer nmsEnderChest = new PlayerEnderChestContainer(getFakeNmsPlayer()); + Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> nmsEnderChest.fromSlots(valueInput.listOrEmpty("EnderItems", ItemStackWithSlot.CODEC))); + enderchest = new CraftInventory(nmsEnderChest); } return enderchest; } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ProfileEditorImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ProfileEditorImpl.java index 2f25a8878d..196c039c05 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ProfileEditorImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ProfileEditorImpl.java @@ -5,31 +5,34 @@ import com.denizenscript.denizen.nms.abstracts.ProfileEditor; import com.denizenscript.denizen.nms.util.PlayerProfile; import com.denizenscript.denizen.nms.v1_21.Handler; +import com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo; import com.denizenscript.denizen.nms.v1_21.helpers.PacketHelperImpl; import com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl; import com.denizenscript.denizen.scripts.commands.entity.RenameCommand; import com.denizenscript.denizen.utilities.FormattedTextHelper; import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.collect.ImmutableMultimap; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.datafixers.util.Either; import net.md_5.bungee.api.ChatColor; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.component.ResolvableProfile; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerRespawnEvent; +import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; +import java.util.*; public class ProfileEditorImpl extends ProfileEditor { @@ -88,14 +91,15 @@ public static ClientboundPlayerInfoUpdatePacket processPlayerInfoUpdatePacket(De } String rename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), false); GameProfile baseProfile = fakeProfiles.containsKey(entry.profileId()) ? getGameProfile(fakeProfiles.get(entry.profileId())) : entry.profile(); - GameProfile modifiedProfile = new GameProfile(baseProfile.getId(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : baseProfile.getName()); + PropertyMap modifiedProperties; if (ProfileEditor.mirrorUUIDs.contains(entry.profileId())) { - modifiedProfile.getProperties().putAll(ownProfile.getProperties()); + modifiedProperties = ownProfile.properties(); } else { // On Paper 1.19+, we use Paper's PlayerProfile API instead of this system - modifiedProfile.getProperties().putAll(Denizen.supportsPaper ? entry.profile().getProperties() : baseProfile.getProperties()); + modifiedProperties = Denizen.supportsPaper ? entry.profile().properties() : baseProfile.properties(); } + GameProfile modifiedProfile = new GameProfile(baseProfile.id(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : baseProfile.name(), modifiedProperties); String listRename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), true); Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : entry.displayName(); ClientboundPlayerInfoUpdatePacket.Entry modifiedEntry = new ClientboundPlayerInfoUpdatePacket.Entry(entry.profileId(), modifiedProfile, entry.listed(), entry.latency(), entry.gameMode(), displayName, entry.showHat(), entry.listOrder(), entry.chatSession()); @@ -121,17 +125,45 @@ public static ClientboundPlayerInfoUpdatePacket createInfoPacket(EnumSet unpacked = (Either) RESOLVABLEPROFILE_UNPACK.invokeExact(resolvableProfile); + return unpacked.map(GameProfile::id, + partial -> { + try { + Optional uuid = (Optional) RESOLVABLEPROFILE_PARTIAL_ID.invoke(partial); + return uuid.orElse(null); + } + catch (Throwable e) { + Debug.echoError(e); + return null; + } + } + ); + } + catch (Throwable e) { + Debug.echoError(e); + return null; } - return gameProfile; } } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/blocks/BlockLightImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/blocks/BlockLightImpl.java index 0565fa5845..78c81da8e2 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/blocks/BlockLightImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/blocks/BlockLightImpl.java @@ -21,8 +21,8 @@ import net.minecraft.world.level.lighting.LevelLightEngine; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_21_R5.CraftChunk; -import org.bukkit.craftbukkit.v1_21_R5.block.CraftBlock; +import org.bukkit.craftbukkit.v1_21_R7.CraftChunk; +import org.bukkit.craftbukkit.v1_21_R7.block.CraftBlock; import org.bukkit.util.Vector; import java.lang.invoke.MethodHandle; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakeArrowImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakeArrowImpl.java index cc8338ff35..4b45bfded0 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakeArrowImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakeArrowImpl.java @@ -1,9 +1,9 @@ package com.denizenscript.denizen.nms.v1_21.impl.entities; import com.denizenscript.denizen.nms.interfaces.FakeArrow; -import net.minecraft.world.entity.projectile.AbstractArrow; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftAbstractArrow; +import net.minecraft.world.entity.projectile.arrow.AbstractArrow; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftAbstractArrow; public class CraftFakeArrowImpl extends CraftAbstractArrow implements FakeArrow { diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakePlayerImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakePlayerImpl.java index 376e62d8e1..92a5ac8942 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakePlayerImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakePlayerImpl.java @@ -4,8 +4,8 @@ import com.denizenscript.denizen.nms.NMSHandler; import org.bukkit.Material; import org.bukkit.block.Block; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.Plugin; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftItemProjectileImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftItemProjectileImpl.java index d1d84be876..a2b8de7f84 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftItemProjectileImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftItemProjectileImpl.java @@ -3,9 +3,9 @@ import com.denizenscript.denizen.nms.interfaces.ItemProjectile; import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; import org.bukkit.projectiles.ProjectileSource; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakeArrowImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakeArrowImpl.java index 10aba52738..bad4b878de 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakeArrowImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakeArrowImpl.java @@ -2,13 +2,13 @@ import com.denizenscript.denizen.nms.v1_21.Handler; import com.denizenscript.denizencore.utilities.debugging.Debug; -import net.minecraft.world.entity.projectile.SpectralArrow; +import net.minecraft.world.entity.projectile.arrow.SpectralArrow; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; import org.bukkit.event.entity.CreatureSpawnEvent; public class EntityFakeArrowImpl extends SpectralArrow { diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakePlayerImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakePlayerImpl.java index 9cdf134882..9c64a6636b 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakePlayerImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakePlayerImpl.java @@ -14,7 +14,7 @@ import net.minecraft.server.network.CommonListenerCookie; import net.minecraft.world.entity.player.Player; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_21_R5.CraftServer; +import org.bukkit.craftbukkit.v1_21_R7.CraftServer; import org.bukkit.event.entity.CreatureSpawnEvent; public class EntityFakePlayerImpl extends ServerPlayer { diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/AbstractListenerPlayInImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/AbstractListenerPlayInImpl.java index 69c4d3f6e3..d67b131ff5 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/AbstractListenerPlayInImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/AbstractListenerPlayInImpl.java @@ -31,7 +31,7 @@ import net.minecraft.world.entity.Relative; import net.minecraft.world.phys.Vec3; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; import org.bukkit.event.player.PlayerTeleportEvent; import java.lang.invoke.MethodHandle; @@ -608,9 +608,9 @@ public void handleContainerSlotStateChanged(ServerboundContainerSlotStateChanged } @Override - public void handleDebugSampleSubscription(ServerboundDebugSampleSubscriptionPacket packet) { + public void handleDebugSubscriptionRequest(ServerboundDebugSubscriptionRequestPacket packet) { if (handlePacketIn(packet)) { return; } - oldListener.handleDebugSampleSubscription(packet); + oldListener.handleDebugSubscriptionRequest(packet); } @Override diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/DenizenNetworkManagerImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/DenizenNetworkManagerImpl.java index bcde3c8c85..76f2da81aa 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/DenizenNetworkManagerImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/DenizenNetworkManagerImpl.java @@ -32,9 +32,9 @@ import net.minecraft.util.debugchart.LocalSampleLogger; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_21_R5.CraftRegistry; -import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.CraftRegistry; +import org.bukkit.craftbukkit.v1_21_R7.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; import org.bukkit.entity.Player; import javax.annotation.Nullable; @@ -66,7 +66,7 @@ public static > T copyPacket(T original, StreamCodec> { - Packet handlePacket(DenizenNetworkManagerImpl networkManager, T packet) throws Exception; + Packet handlePacket(DenizenNetworkManagerImpl networkManager, T packet) throws Throwable; } public static final Map>, List>> packetHandlers = new HashMap<>(); @@ -272,7 +272,7 @@ public void debugOutputPacket(Packet packet) { } else if (packet instanceof ClientboundSetEntityMotionPacket) { ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet; - doPacketOutput("Packet: ClientboundSetEntityMotionPacket sent to " + player.getScoreboardName() + " for entity ID: " + velPacket.getId() + ": " + velPacket.getXa() + "," + velPacket.getYa() + "," + velPacket.getZa()); + doPacketOutput("Packet: ClientboundSetEntityMotionPacket sent to " + player.getScoreboardName() + " for entity ID: " + velPacket.getId() + ": " + velPacket.getMovement()); } else if (packet instanceof ClientboundAddEntityPacket) { ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet; @@ -290,8 +290,8 @@ else if (packet instanceof ClientboundRemoveEntitiesPacket) { else if (packet instanceof ClientboundPlayerInfoUpdatePacket) { ClientboundPlayerInfoUpdatePacket playerInfoPacket = (ClientboundPlayerInfoUpdatePacket) packet; doPacketOutput("Packet: ClientboundPlayerInfoPacket sent to " + player.getScoreboardName() + " of types " + playerInfoPacket.actions() + " for player profiles: " + - playerInfoPacket.entries().stream().map(p -> "mode=" + p.gameMode() + "/latency=" + p.latency() + "/display=" + p.displayName() + "/name=" + p.profile().getName() + "/id=" + p.profile().getId() + "/" - + p.profile().getProperties().asMap().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue().stream().map(v -> v.value() + ";" + v.signature()).collect(Collectors.joining(";;;"))).collect(Collectors.joining("/"))).collect(Collectors.joining(", "))); + playerInfoPacket.entries().stream().map(p -> "mode=" + p.gameMode() + "/latency=" + p.latency() + "/display=" + p.displayName() + "/name=" + p.profile().name() + "/id=" + p.profile().id() + "/" + + p.profile().properties().asMap().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue().stream().map(v -> v.value() + ";" + v.signature()).collect(Collectors.joining(";;;"))).collect(Collectors.joining("/"))).collect(Collectors.joining(", "))); } else { doPacketOutput("Packet: " + packet.getClass().getCanonicalName() + " sent to " + player.getScoreboardName()); @@ -376,7 +376,7 @@ public Packet processPacketHandlersFor(Packet PALETTEDCONTAINER_CTOR = Arrays.stream(PalettedContainer.class.getConstructors()).filter(c -> c.getParameterCount() == 3).findFirst().get(); + public static final MethodHandle CHUNKDATA_BLOCK_ENTITIES = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getGetter(ReflectionMappingsInfo.ClientboundLevelChunkPacketData_blockEntitiesData, List.class); + public static final MethodHandle CHUNKDATA_BUFFER_SETTER = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getSetter(ReflectionMappingsInfo.ClientboundLevelChunkPacketData_buffer, byte[].class); + public static final Class CHUNKDATA_BLOCKENTITYINFO_CLASS = ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0]; + public static final MethodHandle CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).getGetter(ReflectionMappingsInfo.ClientboundLevelChunkPacketDataBlockEntityInfo_packedXZ); + public static final MethodHandle CHUNKDATA_BLOCKENTITYINFO_Y = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).getGetter(ReflectionMappingsInfo.ClientboundLevelChunkPacketDataBlockEntityInfo_y); + + public static final PalettedContainer EMPTY_BLOCKS_CONTAINER = new PalettedContainer<>(Blocks.AIR.defaultBlockState(), Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY)); + public static final BlockState MAX_LIGHT_LIGHT_BLOCK = Blocks.LIGHT.defaultBlockState().setValue(LightBlock.LEVEL, 15), AIR = Blocks.AIR.defaultBlockState(); + public static final byte[] MAX_LIGHT_SECTION = new DataLayer(15).getData(), MIN_LIGHT_SECTION = new DataLayer(0).getData(); public static BlockState getNMSState(FakeBlock block) { return ((CraftBlockData) block.material.getModernData()).getState(); } - public static boolean anyBlocksInSection(List blocks, int y) { - int minY = y << 4; - int maxY = (y << 4) + 16; - for (FakeBlock block : blocks) { + public static boolean anyBlocksInSection(List blocksInChunk, int y) { + int minY = SectionPos.sectionToBlockCoord(y); + int maxY = minY + 16; + for (FakeBlock block : blocksInChunk) { int blockY = block.location.getBlockY(); if (blockY >= minY && blockY < maxY) { return true; @@ -65,7 +70,7 @@ public static boolean anyBlocksInSection(List blocks, int y) { public static Field PAPER_CHUNK_READY; public static boolean tryPaperPatch = true; - public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket, ClientboundLevelChunkWithLightPacket oldPacket) { + public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket) { if (!Denizen.supportsPaper || !tryPaperPatch) { return; } @@ -88,80 +93,174 @@ public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket new } } - public static ClientboundLevelChunkWithLightPacket handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalPacket, int chunkX, int chunkZ, List blocks) { - try { - ClientboundLevelChunkWithLightPacket duplicateCorePacket = DenizenNetworkManagerImpl.copyPacket(originalPacket, ClientboundLevelChunkWithLightPacket.STREAM_CODEC); - copyPacketPaperPatch(duplicateCorePacket, originalPacket); - RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry()); - originalPacket.getChunkData().write(copier); - ClientboundLevelChunkPacketData packet = new ClientboundLevelChunkPacketData(copier, chunkX, chunkZ); - FriendlyByteBuf serial = originalPacket.getChunkData().getReadBuffer(); - FriendlyByteBuf outputSerial = new FriendlyByteBuf(Unpooled.buffer(serial.readableBytes())); - List blockEntities = new ArrayList((List) CHUNKDATA_BLOCK_ENTITIES.get(originalPacket.getChunkData())); - CHUNKDATA_BLOCK_ENTITIES.set(packet, blockEntities); - for (int i = 0; i < blockEntities.size(); i++) { - Object blockEnt = blockEntities.get(i); - int xz = CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.getInt(blockEnt); - int y = CHUNKDATA_BLOCKENTITYINFO_Y.getInt(blockEnt); - int x = (chunkX << 4) + ((xz >> 4) & 15); - int z = (chunkZ << 4) + (xz & 15); - for (FakeBlock block : blocks) { - LocationTag loc = block.location; - if (loc.getBlockX() == x && loc.getBlockY() == y && loc.getBlockZ() == z && block.material != null) { - BlockEntity newBlockEnt = CraftBlockStates.createNewTileEntity(block.material.getMaterial()); - Object newData = CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR.invoke(xz, y, newBlockEnt.getType(), newBlockEnt.getUpdateTag(CraftRegistry.getMinecraftRegistry())); - blockEntities.set(i, newData); - break; + public static Packet handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalChunkPacket, int chunkX, int chunkZ, Int2ObjectMap> blocksBySection, FakeBlock.FakeBlockMap fakeBlockMap) throws Throwable { + ClientboundLevelChunkWithLightPacket copiedChunkPacket = DenizenNetworkManagerImpl.copyPacket(originalChunkPacket, ClientboundLevelChunkWithLightPacket.STREAM_CODEC); + copyPacketPaperPatch(copiedChunkPacket); + // TODO pass coord? + boolean isNaturalLoad = !FakeBlock.scheduled.containsKey(new ChunkCoordinate(chunkX, chunkZ, world.getName())); + // A list of ClientboundLevelChunkPacketData$BlockEntityInfo + List blockEntities = (List) CHUNKDATA_BLOCK_ENTITIES.invokeExact(copiedChunkPacket.getChunkData()); + LocationTag location = new LocationTag(world, 0, 0, 0); + ListIterator blockEntitiesIterator = blockEntities.listIterator(); + while (blockEntitiesIterator.hasNext()) { + Object blockEnt = blockEntitiesIterator.next(); + int xz = (int) CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.invoke(blockEnt); + int y = (int) CHUNKDATA_BLOCKENTITYINFO_Y.invoke(blockEnt); + int relativeX = SectionPos.sectionRelative(xz >> 4); + int relativeZ = SectionPos.sectionRelative(xz); + int x = SectionPos.sectionToBlockCoord(chunkX, relativeX); + int z = SectionPos.sectionToBlockCoord(chunkZ, relativeZ); + location.setX(x); + location.setY(y); + location.setZ(z); + if (fakeBlockMap.byLocation.containsKey(location)) { + blockEntitiesIterator.remove(); + } + } + List> packets = new ArrayList<>(blocksBySection.size() + 1); + packets.add(copiedChunkPacket); + + // Get the original chunk data to read, and a new buf of the same size to write + FriendlyByteBuf rawChunkData = originalChunkPacket.getChunkData().getReadBuffer(); + FriendlyByteBuf newChunkData = new FriendlyByteBuf(Unpooled.buffer(rawChunkData.readableBytes())); + final int minChunkY = SectionPos.blockToSectionCoord(world.getMinHeight()); + final int maxChunkY = SectionPos.blockToSectionCoord(world.getMaxHeight()); + Registry biomeRegistry = CraftRegistry.getMinecraftRegistry(Registries.BIOME); + ClientboundLightUpdatePacketData lightData = copiedChunkPacket.getLightData(); + BitSet blockLightMask = lightData.getBlockYMask(), blockNoLightMask = lightData.getEmptyBlockYMask(), skyLightMask = lightData.getSkyYMask(), skyNoLightMask = lightData.getEmptySkyYMask(); + int blockLitSections = 0, skyLitSections = 0; + boolean foundFirstSkyLight = false; + for (int chunkY = minChunkY; chunkY < maxChunkY; chunkY++) { + int blockCount = rawChunkData.readShort(); + PalettedContainer states = new PalettedContainer<>(Blocks.AIR.defaultBlockState(), Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY)); + states.read(rawChunkData); + PalettedContainer> biomes = new PalettedContainer<>(biomeRegistry.getOrThrow(Biomes.PLAINS), Strategy.createForBiomes(biomeRegistry.asHolderIdMap())); + biomes.read(rawChunkData); + List fakeBlocksInSection = blocksBySection.get(chunkY); + // The light data counts up from 0 instead of minChunkY, and has a buffer of 1 extra section above and below the world (hence + 1) + int sectionIndex = chunkY - minChunkY + 1; + boolean hasBlockLight = false, hasSkyLight = false; + if (blockLightMask.get(sectionIndex)) { + hasBlockLight = true; + } + if (skyLightMask.get(sectionIndex)) { + hasSkyLight = true; + } + if (fakeBlocksInSection != null) { + List> lightPatch = new ArrayList<>(); + DataLayer blockLayer = null, skyLayer = null; + Debug.log("--------------"); + Debug.log("Has block light: " + hasBlockLight); + Debug.log("Has sky light: " + hasSkyLight); + if (hasSkyLight) { + skyLightMask.clear(sectionIndex); + skyLayer = new DataLayer(lightData.getSkyUpdates().remove(skyLitSections)); + hasSkyLight = false; + if (!foundFirstSkyLight) { + Debug.log(">>> Found first sky light"); } + foundFirstSkyLight = true; } - } - int worldMinY = world.getMinHeight(); - int worldMaxY = world.getMaxHeight(); - int minChunkY = worldMinY >> 4; - int maxChunkY = worldMaxY >> 4; - Registry biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().lookupOrThrow(Registries.BIOME); - for (int y = minChunkY; y < maxChunkY; y++) { - int blockCount = serial.readShort(); - // reflected constructors as workaround for spigot remapper bug - Mojang "IdMap" became Spigot "IRegistry" but should be "Registry" - PalettedContainer states = (PalettedContainer) PALETTEDCONTAINER_CTOR.newInstance(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); - states.read(serial); - PalettedContainer biomes = (PalettedContainer) PALETTEDCONTAINER_CTOR.newInstance(biomeRegistry, biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); - biomes.read(serial); - if (anyBlocksInSection(blocks, y)) { - int minY = y << 4; - int maxY = (y << 4) + 16; - for (FakeBlock block : blocks) { - int blockY = block.location.getBlockY(); - if (blockY >= minY && blockY < maxY && block.material != null) { - int blockX = block.location.getBlockX(); - int blockZ = block.location.getBlockZ(); - blockX -= (blockX >> 4) * 16; - blockY -= (blockY >> 4) * 16; - blockZ -= (blockZ >> 4) * 16; - BlockState oldState = states.get(blockX, blockY, blockZ); - BlockState newState = getNMSState(block); - if (oldState.isAir() && !newState.isAir()) { - blockCount++; - } - else if (newState.isAir() && !oldState.isAir()) { - blockCount--; + else { + skyNoLightMask.clear(sectionIndex); + } + if (isNaturalLoad) { + Debug.log(">>> Adding " + (foundFirstSkyLight ? "bright" : "dark") + " sky light"); + lightData.getSkyUpdates().add(skyLitSections, foundFirstSkyLight ? MAX_LIGHT_SECTION : MIN_LIGHT_SECTION); + skyLightMask.set(sectionIndex); + hasSkyLight = true; + skyLayer = new DataLayer(lightData.getSkyUpdates().get(skyLitSections)); + } + if (hasBlockLight) { + blockLightMask.clear(sectionIndex); + blockLayer = new DataLayer(lightData.getBlockUpdates().remove(blockLitSections)); + hasBlockLight = false; + } + else { + blockNoLightMask.clear(sectionIndex); + } + Debug.log("Post has block light: " + blockLightMask.get(sectionIndex)); + Debug.log("Post has sky light: " + skyLightMask.get(sectionIndex)); + Debug.log("--------------"); + if (blockLayer != null || skyLayer != null) { + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + BlockState state = states.get(x, y, z); + boolean isAir = state.isAir(); + if (isAir && (y == 0 || states.get(x, y - 1, z).isAir())) { + continue; + } + if (!isAir && (blockLayer == null || blockLayer.get(x, y, z) == 0)) { + continue; + } + // Based on SectionPos#sectionRelativePos + short offset = (short) (x << 8 | z << 4 | y << 0); + lightPatch.add(ShortObjectPair.of(offset, isAir ? MAX_LIGHT_LIGHT_BLOCK : AIR)); + lightPatch.add(ShortObjectPair.of(offset, state)); + if (!isAir) { + blockCount--; + states.set(x, y, z, AIR); + } } - states.set(blockX, blockY, blockZ, newState); } } } - outputSerial.writeShort(blockCount); - states.write(outputSerial); - biomes.write(outputSerial); + int size = fakeBlocksInSection.size(); + short[] offsets = new short[size]; + BlockState[] statesArr = new BlockState[size]; + for (int blockIndex = 0; blockIndex < size; blockIndex++) { + FakeBlock fakeBlock = fakeBlocksInSection.get(blockIndex); + int relativeX = SectionPos.sectionRelative(fakeBlock.location.getBlockX()); + int relativeY = SectionPos.sectionRelative(fakeBlock.location.getBlockY()); + int relativeZ = SectionPos.sectionRelative(fakeBlock.location.getBlockZ()); + BlockState oldState = states.get(relativeX, relativeY, relativeZ); + BlockState newState = ((CraftBlockData) fakeBlock.material.getModernData()).getState(); + short sectionRelativePos = SectionPos.sectionRelativePos(CraftLocation.toBlockPosition(fakeBlock.location)); + offsets[blockIndex] = sectionRelativePos; + statesArr[blockIndex] = newState; + int maxLight = Math.max( + skyLayer != null ? skyLayer.get(relativeX, relativeY, relativeZ) : 0, + blockLayer != null ? blockLayer.get(relativeX, relativeY, relativeZ) : 0 + ); + if (!oldState.isAir()) { + blockCount--; + states.set(relativeX, relativeY, relativeZ, Blocks.AIR.defaultBlockState()); + if (maxLight <= 0 && !newState.isSolidRender()) { + lightPatch.add(ShortObjectPair.of(sectionRelativePos, MAX_LIGHT_LIGHT_BLOCK)); + } + } + } + SectionPos sectionPos = SectionPos.of(chunkX, chunkY, chunkZ); + if (!lightPatch.isEmpty()) { + int lightCount = lightPatch.size(); + short[] lightOffsets = new short[lightCount]; + BlockState[] lightStates = new BlockState[lightCount]; + for (int i = 0; i < lightCount; i++) { + ShortObjectPair pair = lightPatch.get(i); + lightOffsets[i] = pair.leftShort(); + lightStates[i] = pair.right(); + } + packets.add(new ClientboundSectionBlocksUpdatePacket(sectionPos, new ShortArraySet(lightOffsets), lightStates)); + } + packets.add(new ClientboundSectionBlocksUpdatePacket(sectionPos, new ShortArraySet(offsets), statesArr)); } - byte[] outputBytes = outputSerial.array(); - CHUNKDATA_BUFFER_SETTER.invoke(packet, outputBytes); - CHUNKPACKET_CHUNKDATA_SETTER.invoke(duplicateCorePacket, packet); - return duplicateCorePacket; - } - catch (Throwable ex) { - Debug.echoError(ex); + if (hasBlockLight) { + blockLitSections++; + } + if (hasSkyLight) { + skyLitSections++; + } + newChunkData.writeShort(blockCount); + if (blockCount > 0) { + states.write(newChunkData); + } + else { + EMPTY_BLOCKS_CONTAINER.write(newChunkData); + } + biomes.write(newChunkData); } - return null; + CHUNKDATA_BUFFER_SETTER.invokeExact(copiedChunkPacket.getChunkData(), newChunkData.array()); + return new ClientboundBundlePacket(packets); } } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/ActionBarEventPacketHandlers.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/ActionBarEventPacketHandlers.java index 0f3ef55965..62ad8bd09b 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/ActionBarEventPacketHandlers.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/ActionBarEventPacketHandlers.java @@ -8,7 +8,7 @@ import com.denizenscript.denizencore.objects.core.ElementTag; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftChatMessage; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftChatMessage; public class ActionBarEventPacketHandlers { diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/AttachPacketHandlers.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/AttachPacketHandlers.java index dcea0cfffe..7219ee9108 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/AttachPacketHandlers.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/AttachPacketHandlers.java @@ -13,8 +13,8 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.PositionMoveRotation; import net.minecraft.world.phys.Vec3; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftVector; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftVector; import org.bukkit.util.Vector; import java.lang.reflect.Field; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/DisguisePacketHandlers.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/DisguisePacketHandlers.java index a281b4c20d..a15b517fb5 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/DisguisePacketHandlers.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/DisguisePacketHandlers.java @@ -14,7 +14,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.PositionMoveRotation; import net.minecraft.world.level.Level; -import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeBlocksPacketHandlers.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeBlocksPacketHandlers.java index 4938114cac..75c5142acf 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeBlocksPacketHandlers.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeBlocksPacketHandlers.java @@ -7,106 +7,130 @@ import com.denizenscript.denizen.utilities.blocks.ChunkCoordinate; import com.denizenscript.denizen.utilities.blocks.FakeBlock; import com.denizenscript.denizencore.utilities.ReflectionHelper; -import com.denizenscript.denizencore.utilities.debugging.Debug; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.shorts.ShortArraySet; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.game.*; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.World; -import java.lang.reflect.Field; +import java.lang.invoke.MethodHandle; import java.util.Arrays; import java.util.List; public class FakeBlocksPacketHandlers { public static void registerHandlers() { - DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelChunkWithLightPacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket); - DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSectionBlocksUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket); - DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelChunkWithLightPacket.class, FakeBlocksPacketHandlers::processLevelChunkWithLightPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSectionBlocksUpdatePacket.class, FakeBlocksPacketHandlers::processSectionBlocksUpdatePacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, FakeBlocksPacketHandlers::processBlockUpdatePacket); } - public static Field SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_sectionPos, SectionPos.class); - public static Field OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_positions, short[].class); - public static Field BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_states, BlockState[].class); + public static final MethodHandle SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).getGetter(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_sectionPos, SectionPos.class); + public static final MethodHandle OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).getGetter(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_positions, short[].class); + public static final MethodHandle BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).getGetter(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_states, BlockState[].class); - public static Packet processShowFakeForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + public static Packet processLevelChunkWithLightPacket(DenizenNetworkManagerImpl networkManager, ClientboundLevelChunkWithLightPacket chunkPacket) throws Throwable { if (FakeBlock.blocks.isEmpty()) { - return packet; + return chunkPacket; } - try { - if (packet instanceof ClientboundLevelChunkWithLightPacket) { - FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID()); - if (map == null) { - return packet; - } - int chunkX = ((ClientboundLevelChunkWithLightPacket) packet).getX(); - int chunkZ = ((ClientboundLevelChunkWithLightPacket) packet).getZ(); - ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, networkManager.player.level().getWorld().getName()); - List blocks = FakeBlock.getFakeBlocksFor(networkManager.player.getUUID(), chunkCoord); - if (blocks == null || blocks.isEmpty()) { - return packet; - } - ClientboundLevelChunkWithLightPacket newPacket = FakeBlockHelper.handleMapChunkPacket(networkManager.player.getBukkitEntity().getWorld(), (ClientboundLevelChunkWithLightPacket) packet, chunkX, chunkZ, blocks); - return newPacket; - } - else if (packet instanceof ClientboundSectionBlocksUpdatePacket sectionBlocksUpdatePacket) { - FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID()); - if (map == null) { - return sectionBlocksUpdatePacket; - } - SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.get(sectionBlocksUpdatePacket); - ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), networkManager.player.level().getWorld().getName()); - if (!map.byChunk.containsKey(coordinateDenizen)) { - return sectionBlocksUpdatePacket; - } - ClientboundSectionBlocksUpdatePacket newPacket = DenizenNetworkManagerImpl.copyPacket(sectionBlocksUpdatePacket, ClientboundSectionBlocksUpdatePacket.STREAM_CODEC); - LocationTag location = new LocationTag(networkManager.player.level().getWorld(), 0, 0, 0); - short[] originalOffsetArray = (short[])OFFSETARRAY_MULTIBLOCKCHANGE.get(newPacket); - BlockState[] originalDataArray = (BlockState[])BLOCKARRAY_MULTIBLOCKCHANGE.get(newPacket); - short[] offsetArray = Arrays.copyOf(originalOffsetArray, originalOffsetArray.length); - BlockState[] dataArray = Arrays.copyOf(originalDataArray, originalDataArray.length); - OFFSETARRAY_MULTIBLOCKCHANGE.set(newPacket, offsetArray); - BLOCKARRAY_MULTIBLOCKCHANGE.set(newPacket, dataArray); - for (int i = 0; i < offsetArray.length; i++) { - short offset = offsetArray[i]; - BlockPos pos = coord.relativeToBlockPos(offset); - location.setX(pos.getX()); - location.setY(pos.getY()); - location.setZ(pos.getZ()); - FakeBlock block = map.byLocation.get(location); - if (block != null) { - dataArray[i] = FakeBlockHelper.getNMSState(block); - } - } - return newPacket; - } - else if (packet instanceof ClientboundBlockUpdatePacket) { - BlockPos pos = ((ClientboundBlockUpdatePacket) packet).getPos(); - LocationTag loc = new LocationTag(networkManager.player.level().getWorld(), pos.getX(), pos.getY(), pos.getZ()); - FakeBlock block = FakeBlock.getFakeBlockFor(networkManager.player.getUUID(), loc); - if (block != null) { - ClientboundBlockUpdatePacket newPacket = new ClientboundBlockUpdatePacket(((ClientboundBlockUpdatePacket) packet).getPos(), FakeBlockHelper.getNMSState(block)); - return newPacket; - } - } - else if (packet instanceof ClientboundBlockChangedAckPacket) { - // TODO: 1.19: Can no longer determine what block this packet is for. Would have to track separately? Possibly from the inbound packet rather than the outbound one. - /* - ClientboundBlockChangedAckPacket origPack = (ClientboundBlockChangedAckPacket) packet; - BlockPos pos = origPack.pos(); - LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ()); - FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc); - if (block != null) { - ClientboundBlockChangedAckPacket newPacket = new ClientboundBlockChangedAckPacket(origPack.pos(), FakeBlockHelper.getNMSState(block), origPack.action(), false); - oldManager.send(newPacket, genericfuturelistener); - return true; - }*/ + FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID()); + if (map == null) { + return chunkPacket; + } + int chunkX = chunkPacket.getX(); + int chunkZ = chunkPacket.getZ(); + World world = networkManager.player.level().getWorld(); + ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, world.getName()); + Int2ObjectMap> blocksBySection = map.byChunk.get(chunkCoord); + if (blocksBySection == null || blocksBySection.isEmpty()) { + return chunkPacket; + } + return FakeBlockHelper.handleMapChunkPacket(networkManager.player.getBukkitEntity().getWorld(), chunkPacket, chunkX, chunkZ, blocksBySection, map); +// List> packets = new ArrayList<>(blocksBySection.size() + 1); +// packets.add(mappedPacket); +// for (Int2ObjectMap.Entry> entry : blocksBySection.int2ObjectEntrySet()) { +// List fakeBlocks = entry.getValue(); +// int size = fakeBlocks.size(); +// short[] offsets = new short[size]; +// BlockState[] states = new BlockState[size]; +// for (int i = 0; i < size; i++) { +// FakeBlock fakeBlock = fakeBlocks.get(i); +// offsets[i] = SectionPos.sectionRelativePos(CraftLocation.toBlockPosition(fakeBlock.location)); +// states[i] = ((CraftBlockData) fakeBlock.material.getModernData()).getState(); +// } +// packets.add(new ClientboundSectionBlocksUpdatePacket(SectionPos.of(chunkX, entry.getIntKey(), chunkZ), new ShortArraySet(offsets), states)); +// } +// return new ClientboundBundlePacket(packets); + } + + public static ClientboundSectionBlocksUpdatePacket processSectionBlocksUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundSectionBlocksUpdatePacket sectionUpdatePacket) throws Throwable { + if (FakeBlock.blocks.isEmpty()) { + return sectionUpdatePacket; + } + FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID()); + if (map == null) { + return sectionUpdatePacket; + } + SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.invokeExact(sectionUpdatePacket); + ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), networkManager.player.level().getWorld().getName()); + if (!map.byChunk.containsKey(coordinateDenizen)) { + return sectionUpdatePacket; + } + short[] originalOffsetArray = (short[]) OFFSETARRAY_MULTIBLOCKCHANGE.invokeExact(sectionUpdatePacket); + BlockState[] originalStatesArray = (BlockState[]) BLOCKARRAY_MULTIBLOCKCHANGE.invokeExact(sectionUpdatePacket); + BlockState[] statesArray = Arrays.copyOf(originalStatesArray, originalStatesArray.length); + LocationTag location = new LocationTag(networkManager.player.level().getWorld(), 0, 0, 0); + boolean hasAny = false; + for (int i = 0; i < originalOffsetArray.length; i++) { + short offset = originalOffsetArray[i]; + location.setX(coord.relativeToBlockX(offset)); + location.setY(coord.relativeToBlockY(offset)); + location.setZ(coord.relativeToBlockZ(offset)); + FakeBlock block = map.byLocation.get(location); + if (block != null) { + statesArray[i] = FakeBlockHelper.getNMSState(block); + hasAny = true; } } - catch (Throwable ex) { - Debug.echoError(ex); + if (!hasAny) { + return sectionUpdatePacket; + } + return new ClientboundSectionBlocksUpdatePacket(coord, new ShortArraySet(originalOffsetArray), statesArray); + } + + public static ClientboundBlockUpdatePacket processBlockUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockUpdatePacket blockUpdatePacket) { + if (FakeBlock.blocks.isEmpty()) { + return blockUpdatePacket; + } + BlockPos pos = blockUpdatePacket.getPos(); + LocationTag loc = new LocationTag(networkManager.player.level().getWorld(), pos.getX(), pos.getY(), pos.getZ()); + FakeBlock block = FakeBlock.getFakeBlockFor(networkManager.player.getUUID(), loc); + if (block != null) { + return new ClientboundBlockUpdatePacket(blockUpdatePacket.getPos(), FakeBlockHelper.getNMSState(block)); + } + return blockUpdatePacket; + } + + // TODO: 1.19: Can no longer determine what block this packet is for. Would have to track separately? Possibly from the inbound packet rather than the outbound one. + /* + public static ClientboundBlockChangedAckPacket processBlockChangedAckPacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockChangedAckPacket blockChangedAckPacket) { + if (FakeBlock.blocks.isEmpty()) { + return blockChangedAckPacket; + } + BlockPos pos = blockChangedAckPacket.pos(); + LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ()); + FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc); + if (block != null) { + ClientboundBlockChangedAckPacket newPacket = new ClientboundBlockChangedAckPacket(blockChangedAckPacket.pos(), FakeBlockHelper.getNMSState(block), blockChangedAckPacket.action(), false); + oldManager.send(newPacket, genericfuturelistener); + return true; } - return packet; } + */ } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java index 9e886625a7..97d21d284a 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java @@ -4,7 +4,6 @@ import com.denizenscript.denizen.objects.ItemTag; import com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand; import com.mojang.datafixers.util.Pair; -import net.minecraft.core.NonNullList; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.*; import net.minecraft.server.level.ServerPlayer; @@ -12,7 +11,7 @@ import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.ItemStack; -import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; import java.util.ArrayList; import java.util.List; @@ -86,7 +85,7 @@ public static ClientboundContainerSetContentPacket processContainerSetContentPac if (override == null) { return setContentPacket; } - NonNullList items = (NonNullList) setContentPacket.items(); + List items = setContentPacket.items(); if (override.head != null) { items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack())); } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/HideParticlesPacketHandlers.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/HideParticlesPacketHandlers.java index ba98b2ef6a..09328473fc 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/HideParticlesPacketHandlers.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/HideParticlesPacketHandlers.java @@ -4,7 +4,7 @@ import com.denizenscript.denizen.utilities.packets.HideParticles; import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; import org.bukkit.Particle; -import org.bukkit.craftbukkit.v1_21_R5.CraftParticle; +import org.bukkit.craftbukkit.v1_21_R7.CraftParticle; import java.util.Set; diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java index a23de7900f..21e5d5ba0c 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java @@ -73,13 +73,13 @@ public static Packet processTablistPacket(DenizenNetwo for (ClientboundPlayerInfoUpdatePacket.Entry update : infoPacket.entries()) { GameProfile profile = update.profile(); String texture = null, signature = null; - if (profile.getProperties().containsKey("textures")) { - Property property = profile.getProperties().get("textures").stream().findFirst().get(); + if (profile.properties().containsKey("textures")) { + Property property = profile.properties().get("textures").stream().findFirst().get(); texture = property.value(); signature = property.signature(); } String modeText = update.gameMode() == null ? null : update.gameMode().name(); - PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.getId(), update.listed(), profile.getName(), + PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.id(), update.listed(), profile.name(), update.displayName() == null ? null : FormattedTextHelper.stringify(Handler.componentToSpigot(update.displayName())), modeText, texture, signature, update.latency()); PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data); if (data.modified) { @@ -93,11 +93,8 @@ public static Packet processTablistPacket(DenizenNetwo } } if (!data.cancelled) { - GameProfile newProfile = new GameProfile(data.id, data.name); - if (data.texture != null) { - newProfile.getProperties().put("textures", new Property("textures", data.texture, data.signature)); - } - ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(newProfile.getId(), newProfile, data.isListed, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)), + GameProfile newProfile = ProfileEditorImpl.createGameProfile(data.id, data.name, data.texture, data.signature); + ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(newProfile.id(), newProfile, data.isListed, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)), data.display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(data.display, ChatColor.WHITE)), update.showHat(), update.listOrder(), update.chatSession()); networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(entry))); } diff --git a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/packets/PacketOutChatImpl.java b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/packets/PacketOutChatImpl.java index 56ec98bb64..13d5d02757 100644 --- a/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/packets/PacketOutChatImpl.java +++ b/v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/packets/PacketOutChatImpl.java @@ -5,7 +5,7 @@ import net.md_5.bungee.chat.ComponentSerializer; import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; -import org.bukkit.craftbukkit.v1_21_R5.util.CraftChatMessage; +import org.bukkit.craftbukkit.v1_21_R7.util.CraftChatMessage; public class PacketOutChatImpl extends PacketOutChat { diff --git a/v26_1/pom.xml b/v26_1/pom.xml new file mode 100644 index 0000000000..0fdd1d1f36 --- /dev/null +++ b/v26_1/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + com.denizenscript + denizen-v26_1 + 1.3.3-SNAPSHOT + + + + com.denizenscript + denizen + ${project.version} + compile + true + + + org.spigotmc + spigot-api + 26.1.2-R0.1-SNAPSHOT + provided + + + org.spigotmc + spigot + 26.1.2-R0.1-SNAPSHOT + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + 16 + 16 + + + + + + diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/Handler.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/Handler.java new file mode 100644 index 0000000000..17c27ce687 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/Handler.java @@ -0,0 +1,435 @@ +package com.denizenscript.denizen.nms.v26_1; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.BiomeNMS; +import com.denizenscript.denizen.nms.abstracts.BlockLight; +import com.denizenscript.denizen.nms.abstracts.ProfileEditor; +import com.denizenscript.denizen.nms.abstracts.Sidebar; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_1.helpers.*; +import com.denizenscript.denizen.nms.v26_1.impl.BiomeNMSImpl; +import com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_1.impl.SidebarImpl; +import com.denizenscript.denizen.nms.v26_1.impl.blocks.BlockLightImpl; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.objects.MaterialTag; +import com.denizenscript.denizen.objects.properties.item.ItemRawNBT; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizen.utilities.PaperAPITools; +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.core.QuaternionTag; +import com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand; +import com.denizenscript.denizencore.utilities.CoreConfiguration; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.denizenscript.denizencore.utilities.debugging.DebugInternals; +import com.google.common.collect.Iterables; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DynamicOps; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.minecraft.SharedConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Rotations; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.BossEvent; +import net.minecraft.world.Container; +import net.minecraft.world.Nameable; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.TagValueInput; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.boss.BossBar; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.boss.CraftBossBar; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftInventoryCustom; +import org.bukkit.craftbukkit.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.legacy.FieldRename; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; +import org.bukkit.craftbukkit.util.ApiVersion; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.persistence.PersistentDataContainer; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.spigotmc.AsyncCatcher; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +public class Handler extends NMSHandler { + + public static BlockPos toBlockPos(Location location) { // TODO: Paper renamed 'CraftLocation#toBlockPosition', switch back once on Paper NMS + return new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + // TODO: Paper renamed some NMS methods, switch back to direct calls once on Paper NMS + public static MethodHandle reflectPaperRenamed(Class clazz, String spigot, String paper, Class... params) { + return ReflectionHelper.getMethodHandle(clazz, Denizen.supportsPaper ? paper : spigot, params); + } + + public Handler() { + advancementHelper = new AdvancementHelperImpl(); + animationHelper = new AnimationHelperImpl(); + blockHelper = new BlockHelperImpl(); + chunkHelper = new ChunkHelperImpl(); + customEntityHelper = new CustomEntityHelperImpl(); + entityHelper = new EntityHelperImpl(); + fishingHelper = new FishingHelperImpl(); + itemHelper = new ItemHelperImpl(); + packetHelper = new PacketHelperImpl(); + playerHelper = new PlayerHelperImpl(); + worldHelper = new WorldHelperImpl(); + enchantmentHelper = new EnchantmentHelperImpl(); + + registerConversion(ItemTag.class, ItemStack.class, item -> CraftItemStack.asNMSCopy(item.getItemStack())); + registerConversion(ElementTag.class, Component.class, element -> componentToNMS(FormattedTextHelper.parse(element.asString(), ChatColor.WHITE))); + registerConversion(MaterialTag.class, BlockState.class, material -> ((CraftBlockData) material.getModernData()).getState()); + registerConversion(LocationTag.class, Rotations.class, location -> new Rotations((float) location.getX(), (float) location.getY(), (float) location.getZ())); + registerConversion(LocationTag.class, BlockPos.class, Handler::toBlockPos); + registerConversion(MapTag.class, CompoundTag.class, map -> { + CompoundBinaryTag compoundTag = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(map, CoreUtilities.noDebugContext, "(item)."); + return compoundTag != null ? NBTAdapter.toNMS(compoundTag) : null; + }); + registerConversion(LocationTag.class, Vector3f.class, location -> new Vector3f((float) location.getX(), (float) location.getY(), (float) location.getZ())); + registerConversion(QuaternionTag.class, Quaternionf.class, quaternion -> new Quaternionf(quaternion.x, quaternion.y, quaternion.z, quaternion.w)); + } + + public static
void registerConversion(Class
denizenType, Class javaType, Function convertor) { + ReflectionSetCommand.typeConverters.put(javaType, objectTag -> { + DT denizenObject = objectTag.asType(denizenType, CoreUtilities.noDebugContext); + return denizenObject != null ? convertor.apply(denizenObject) : null; + }); + } + + private final ProfileEditor profileEditor = new ProfileEditorImpl(); + + private boolean wasAsyncCatcherEnabled; + + @Override + public void disableAsyncCatcher() { + wasAsyncCatcherEnabled = AsyncCatcher.enabled; + AsyncCatcher.enabled = false; + } + + @Override + public void undisableAsyncCatcher() { + AsyncCatcher.enabled = wasAsyncCatcherEnabled; + } + + @Override + public boolean isExactServerVersionMatch() { + return Denizen.supportsPaper ? SharedConstants.getCurrentVersion().id().equals("26.1.2") : CraftMagicNumbers.INSTANCE.getMappingsVersion().equals("e8ece90188c951d866bd2fffc52c803e"); + } + + @Override + public double[] getRecentTps() { + return ((CraftServer) Bukkit.getServer()).getServer().recentTps; + } + + @Override + public Sidebar createSidebar(Player player) { + return new SidebarImpl(player); + } + + @Override + public BlockLight createBlockLight(Location location, int lightLevel, long ticks) { + return BlockLightImpl.createLight(location, lightLevel, ticks); + } + + @Override + public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) { + if (playerProfile == null) { + return null; + } + if (playerProfile.getName() == null && playerProfile.getUniqueId() == null) { + return playerProfile; // Cannot fill without lookup data + } + if (playerProfile.hasTexture() && playerProfile.hasTextureSignature() && playerProfile.getName() != null && playerProfile.getUniqueId() != null) { + return playerProfile; // Already filled + } + try { + GameProfile profile = null; + MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer(); + if (playerProfile.getUniqueId() != null) { + profile = minecraftServer.services().nameToIdCache().get(playerProfile.getUniqueId()).map(result -> new GameProfile(result.id(), result.name())).orElse(null); + } + if (profile == null && playerProfile.getName() != null) { + profile = minecraftServer.services().nameToIdCache().get(playerProfile.getName()).map(result -> new GameProfile(result.id(), result.name())).orElse(null); + } + if (profile == null) { + profile = ProfileEditorImpl.getGameProfileNoProperties(playerProfile); + } + Property textures = profile.properties().containsKey("textures") ? Iterables.getFirst(profile.properties().get("textures"), null) : null; + if (textures == null || !textures.hasSignature() || profile.name() == null || profile.id() == null) { + profile = minecraftServer.services().profileResolver().fetchById(profile.id()).orElse(null); + if (profile == null) { + return null; + } + textures = profile.properties().containsKey("textures") ? Iterables.getFirst(profile.properties().get("textures"), null) : null; + } + return new PlayerProfile(profile.name(), profile.id(), textures == null ? null : textures.value(), textures == null ? null : textures.signature()); + } + catch (Exception e) { + if (CoreConfiguration.debugVerbose) { + Debug.echoError(e); + } + } + return null; + } + + public static MethodHandle PAPER_INVENTORY_TITLE_GETTER; + + @Override + public String getTitle(Inventory inventory) { + Container nms = ((CraftInventory) inventory).getInventory(); + if (inventory instanceof CraftInventoryCustom && Denizen.supportsPaper) { + try { + if (PAPER_INVENTORY_TITLE_GETTER == null) { + PAPER_INVENTORY_TITLE_GETTER = ReflectionHelper.getMethodHandle(nms.getClass(), "title"); + } + return PaperAPITools.instance.parseComponent(PAPER_INVENTORY_TITLE_GETTER.invoke(nms)); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + if (nms instanceof Nameable) { + return CraftChatMessage.fromComponent(((Nameable) nms).getDisplayName()); + } + else if (MINECRAFT_INVENTORY.isInstance(nms)) { + try { + return (String) INVENTORY_TITLE.get(nms); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + } + return "Chest"; + } + + public static MethodHandle AbstractContainerMenu_title_SETTER = ReflectionHelper.getFinalSetter(AbstractContainerMenu.class, "title"); + + @Override + public void setInventoryTitle(InventoryView view, String title) { + AbstractContainerMenu menu = ((CraftInventoryView) view).getHandle(); + try { + AbstractContainerMenu_title_SETTER.invoke(menu, componentToNMS(FormattedTextHelper.parse(title, ChatColor.DARK_GRAY))); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static final Class MINECRAFT_INVENTORY; + public static final Field INVENTORY_TITLE; + public static final Field ENTITY_BUKKITYENTITY = ReflectionHelper.getFields(Entity.class).get("bukkitEntity"); + + static { + Class minecraftInv = null; + Field title = null; + try { + for (Class clzz : CraftInventoryCustom.class.getDeclaredClasses()) { + if (CoreUtilities.toLowerCase(clzz.getName()).contains("minecraftinventory")) { // MinecraftInventory. + minecraftInv = clzz; + title = clzz.getDeclaredField("title"); + title.setAccessible(true); + break; + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + MINECRAFT_INVENTORY = minecraftInv; + INVENTORY_TITLE = title; + } + + @Override + public PlayerProfile getPlayerProfile(Player player) { + GameProfile gameProfile = ((CraftPlayer) player).getProfile(); + Property property = Iterables.getFirst(gameProfile.properties().get("textures"), null); + return new PlayerProfile(gameProfile.name(), gameProfile.id(), + property != null ? property.value() : null, + property != null ? property.signature() : null); + } + + @Override + public ProfileEditor getProfileEditor() { + return profileEditor; + } + + @Override + public List getBiomes(World world) { + ServerLevel level = ((CraftWorld) world).getHandle(); + ArrayList output = new ArrayList<>(); + for (Identifier key : level.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { + output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key))); + } + return output; + } + + @Override + public BiomeNMS getBiomeNMS(World world, NamespacedKey key) { + BiomeNMSImpl impl = new BiomeNMSImpl(((CraftWorld) world).getHandle(), key); + if (impl.biomeHolder == null) { + return null; + } + return impl; + } + + @Override + public BiomeNMS getBiomeAt(Block block) { + // Based on CraftWorld source + ServerLevel level = ((CraftWorld) block.getWorld()).getHandle(); + Holder biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2); + Identifier key = level.registryAccess().lookupOrThrow(Registries.BIOME).getKey(biome.value()); + return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key)); + } + + @Override + public ArrayList containerListFlags(PersistentDataContainer container, String prefix) { + prefix = "denizen:" + prefix; + ArrayList output = new ArrayList<>(); + for (String key : ((CraftPersistentDataContainer) container).getRaw().keySet()) { + if (key.startsWith(prefix)) { + output.add(key.substring(prefix.length())); + } + } + return output; + } + + @Override + public boolean containerHas(PersistentDataContainer container, String key) { + return ((CraftPersistentDataContainer) container).getRaw().containsKey(key); + } + + @Override + public String containerGetString(PersistentDataContainer container, String key) { + net.minecraft.nbt.Tag base = ((CraftPersistentDataContainer) container).getRaw().get(key); + if (base instanceof StringTag) { + return base.asString().get(); + } + else if (base instanceof ByteArrayTag) { + return new String(((ByteArrayTag) base).getAsByteArray(), StandardCharsets.UTF_8); + } + return null; + } + + @Override + public UUID getBossbarUUID(BossBar bar) { + return ((CraftBossBar) bar).getHandle().getId(); + } + + public static MethodHandle BOSSBAR_ID_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BossEvent.class, UUID.class); + + @Override + public void setBossbarUUID(BossBar bar, UUID id) { + try { + BOSSBAR_ID_SETTER.invoke(((CraftBossBar) bar).getHandle(), id); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static BaseComponent[] componentToSpigot(Component nms) { + if (nms == null) { + return null; + } + return FormattedTextHelper.parseJson(CraftChatMessage.toJSON(nms)); + } + + public static Component componentToNMS(BaseComponent[] spigot) { + if (spigot == null) { + return null; + } + return CraftChatMessage.fromJSONOrNull(FormattedTextHelper.componentToJson(spigot)); + } + + public static final MethodHandle TAG_VALUE_OUTPUT_CONSTRUCTOR = ReflectionHelper.getConstructor(TagValueOutput.class, ProblemReporter.class, DynamicOps.class, CompoundTag.class); + + public static CompoundTag useValueOutput(Consumer handler) { + ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector(); + TagValueOutput nmsValueOutput = TagValueOutput.createWithContext(nmsProblemReporter, CraftRegistry.getMinecraftRegistry()); + handler.accept(nmsValueOutput); + handleProblems(nmsProblemReporter); + return nmsValueOutput.buildResult(); + } + + public static CompoundTag useValueOutput(CompoundTag nmsExistingValue, Consumer handler) { + ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector(); + TagValueOutput nmsValueOutput; + try { + nmsValueOutput = (TagValueOutput) TAG_VALUE_OUTPUT_CONSTRUCTOR.invoke(nmsProblemReporter, CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsExistingValue); + } + catch (Throwable e) { + Debug.echoError(e); + return nmsExistingValue; + } + handler.accept(nmsValueOutput); + handleProblems(nmsProblemReporter); + return nmsValueOutput.buildResult(); + } + + public static void useValueInput(CompoundTag nmsTag, Consumer handler) { + ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector(); + ValueInput nmsValueInput = TagValueInput.create(nmsProblemReporter, CraftRegistry.getMinecraftRegistry(), nmsTag); + handler.accept(nmsValueInput); + handleProblems(nmsProblemReporter); + } + + private static void handleProblems(ProblemReporter.Collector nmsProblemReporter) { + if (!nmsProblemReporter.isEmpty()) { + Debug.echoError(nmsProblemReporter.getTreeReport()); + } + } + + @Override + public String updateLegacyName(Class type, String legacyName) { + return FieldRename.rename(ApiVersion.FIELD_NAME_PARITY, DebugInternals.getFullClassNameOpti(type).replace('.', '/'), legacyName); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/AdvancementHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/AdvancementHelperImpl.java new file mode 100644 index 0000000000..a7e50ed538 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/AdvancementHelperImpl.java @@ -0,0 +1,195 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.interfaces.AdvancementHelper; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.google.common.collect.ImmutableMap; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.advancements.*; +import net.minecraft.advancements.criterion.ImpossibleTrigger; +import net.minecraft.core.ClientAsset; +import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket; +import net.minecraft.resources.Identifier; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.ServerAdvancementManager; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.Player; + +import java.util.*; + +public class AdvancementHelperImpl extends AdvancementHelper { + + private static final String IMPOSSIBLE_KEY = "impossible"; + private static final Map> IMPOSSIBLE_CRITERIA = Map.of(IMPOSSIBLE_KEY, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance())); + private static final List> IMPOSSIBLE_REQUIREMENTS = List.of(List.of(IMPOSSIBLE_KEY)); + + public static ServerAdvancementManager getNMSAdvancementManager() { + return ((CraftServer) Bukkit.getServer()).getServer().getAdvancements(); + } + + @Override + public void register(com.denizenscript.denizen.nms.util.Advancement advancement) { + if (advancement.temporary || advancement.registered) { + return; + } + AdvancementHolder nmsAdvancementHolder = asNMSCopy(advancement); + Map nmsAdvancements = getNMSAdvancementManager().advancements; + ImmutableMap.Builder mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() + 1); + mapBuilder.putAll(nmsAdvancements); + mapBuilder.put(nmsAdvancementHolder.id(), nmsAdvancementHolder); + getNMSAdvancementManager().advancements = mapBuilder.build(); + + AdvancementTree tree = getNMSAdvancementManager().tree(); + tree.addAll(List.of(nmsAdvancementHolder)); + // recalculate advancement tree from this advancement's root + AdvancementNode node = tree.get(nmsAdvancementHolder.id()); + if (node != null) { + AdvancementNode root = node.root(); + if (root.holder().value().display().isPresent()) { + TreeNodePosition.run(root); + } + } + advancement.registered = true; + if (!advancement.hidden && advancement.parent != null) { + PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancementHolder), Set.of(), Map.of(), false)); + } + } + + @Override + public void unregister(com.denizenscript.denizen.nms.util.Advancement advancement) { + if (advancement.temporary || !advancement.registered) { + return; + } + Identifier nmsKey = CraftNamespacedKey.toMinecraft(advancement.key); + Map nmsAdvancements = getNMSAdvancementManager().advancements; + ImmutableMap.Builder mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() - 1); + for (Map.Entry entry : nmsAdvancements.entrySet()) { + if (!entry.getKey().equals(nmsKey)) { + mapBuilder.put(entry); + } + } + getNMSAdvancementManager().advancements = mapBuilder.build(); + getNMSAdvancementManager().tree().remove(Set.of(nmsKey)); + advancement.registered = false; + PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(nmsKey), Map.of(), false)); + } + + @Override + public void grantPartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) { + if (advancement.length <= 1) { + grant(advancement, player); + return; + } + if (advancement.temporary) { + AdvancementHolder nmsAdvancement = asNMSCopy(advancement); + AdvancementProgress progress = new AdvancementProgress(); + progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS)); + for (int i = 0; i < len; i++) { + progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria + } + PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), false)); + } + else { + AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key)); + for (int i = 0; i < len; i++) { + ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY + i); + } + } + } + + @Override + public void grant(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) { + if (advancement.length > 1) { + grantPartial(advancement, player, advancement.length); + return; + } + if (advancement.temporary) { + AdvancementHolder nmsAdvancement = asNMSCopy(advancement); + AdvancementProgress progress = new AdvancementProgress(); + progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS)); + progress.grantProgress(IMPOSSIBLE_KEY); // complete impossible criteria + PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), true)); + } + else { + AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key)); + ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY); + } + } + + @Override + public void revokePartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) { + if (advancement.length <= 1) { + revoke(advancement, player); + return; + } + if (advancement.temporary) { + AdvancementHolder nmsAdvancement = asNMSCopy(advancement); + AdvancementProgress progress = new AdvancementProgress(); + progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS)); + for (int i = 0; i < len; i++) { + progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria + } + PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), false)); + } + else { + AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key)); + PlayerAdvancements advancements = ((CraftPlayer) player).getHandle().getAdvancements(); + for (int i = len; i < advancement.length; i++) { + advancements.revoke(nmsAdvancement, IMPOSSIBLE_KEY + i); + } + } + } + + @Override + public void revoke(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) { + if (advancement.temporary) { + PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(CraftNamespacedKey.toMinecraft(advancement.key)), Map.of(), false)); + } + else { + AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key)); + PlayerAdvancements advancements = ((CraftPlayer) player).getHandle().getAdvancements(); + for (String criterion : nmsAdvancement.value().criteria().keySet()) { + advancements.revoke(nmsAdvancement, criterion); + } + } + } + + @Override + public void update(Player player) { + // TODO: 1.21.5: should showAdvancements be true? + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + nmsPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(true, List.of(), Set.of(), Map.of(), false)); + PlayerAdvancements data = nmsPlayer.getAdvancements(); + data.save(); // save progress + data.reload(getNMSAdvancementManager()); // clear progress + data.flushDirty(nmsPlayer, false); // load progress and update client + } + + private static AdvancementHolder asNMSCopy(com.denizenscript.denizen.nms.util.Advancement advancement) { + AdvancementHolder parent = advancement.parent != null + ? getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.parent)) + : null; + DisplayInfo display = new DisplayInfo(ItemHelperImpl.asNMSTemplate(advancement.icon), + Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)), + Optional.ofNullable(advancement.background).map(CraftNamespacedKey::toMinecraft).map(ClientAsset.ResourceTexture::new), AdvancementType.valueOf(advancement.frame.name()), + advancement.toast, advancement.announceToChat, advancement.hidden); + display.setLocation(advancement.xOffset, advancement.yOffset); + Map> criteria = IMPOSSIBLE_CRITERIA; + List> requirements = IMPOSSIBLE_REQUIREMENTS; + if (advancement.length > 1) { + criteria = new HashMap<>(); + requirements = new ArrayList<>(advancement.length); + for (int i = 0; i < advancement.length; i++) { + criteria.put(IMPOSSIBLE_KEY + i, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance())); + requirements.add(List.of(IMPOSSIBLE_KEY + i)); + } + } + AdvancementRequirements reqs = new AdvancementRequirements(requirements); + Advancement adv = new Advancement(parent == null ? Optional.empty() : Optional.of(parent.id()), Optional.of(display), AdvancementRewards.EMPTY, criteria, reqs, false); // TODO: 1.20: do we want to ever enable telemetry? + return new AdvancementHolder(CraftNamespacedKey.toMinecraft(advancement.key), adv); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/AnimationHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/AnimationHelperImpl.java new file mode 100644 index 0000000000..2c12daf4ba --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/AnimationHelperImpl.java @@ -0,0 +1,48 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.abstracts.AnimationHelper; +import net.minecraft.world.entity.Entity; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftHorse; +import org.bukkit.craftbukkit.entity.CraftPolarBear; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Horse; +import org.bukkit.entity.IronGolem; + +public class AnimationHelperImpl extends AnimationHelper { + + public AnimationHelperImpl() { + register("POLAR_BEAR_START_STANDING", entity -> { + if (entity.getType() == EntityType.POLAR_BEAR) { + ((CraftPolarBear) entity).getHandle().setStanding(true); + } + }); + register("POLAR_BEAR_STOP_STANDING", entity -> { + if (entity.getType() == EntityType.POLAR_BEAR) { + ((CraftPolarBear) entity).getHandle().setStanding(false); + } + }); + // TODO: 1.21.6: this is a tick duration now, should become a mechanism + register("HORSE_START_STANDING", entity -> { + if (entity instanceof Horse) { + ((CraftHorse) entity).getHandle().setStanding(Integer.MAX_VALUE); + } + }); + register("HORSE_STOP_STANDING", entity -> { + if (entity instanceof Horse) { + ((CraftHorse) entity).getHandle().clearStanding(); + } + }); + register("HORSE_BUCK", entity -> { + if (entity instanceof Horse) { + ((CraftHorse) entity).getHandle().makeMad(); + } + }); + register("IRON_GOLEM_ATTACK", entity -> { + if (entity instanceof IronGolem) { + Entity nmsEntity = ((CraftEntity) entity).getHandle(); + nmsEntity.level().broadcastEntityEvent(nmsEntity, (byte) 4); + } + }); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/BlockHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/BlockHelperImpl.java new file mode 100644 index 0000000000..3a39a8215c --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/BlockHelperImpl.java @@ -0,0 +1,271 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.interfaces.BlockHelper; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.collect.Iterables; +import com.mojang.authlib.GameProfile; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.InclusiveRange; +import net.minecraft.util.random.WeightedList; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.component.ResolvableProfile; +import net.minecraft.world.level.BaseSpawner; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.SpawnData; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.PushReaction; +import org.bukkit.Instrument; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.Skull; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.block.CraftBlockEntityState; +import org.bukkit.craftbukkit.block.CraftCreatureSpawner; +import org.bukkit.craftbukkit.block.CraftSkull; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.Optional; + +public class BlockHelperImpl implements BlockHelper { + + public static final Field craftBlockEntityState_tileEntity; + public static final Field craftBlockEntityState_snapshot = ReflectionHelper.getFields(CraftBlockEntityState.class).get("snapshot"); + public static final Field craftSkull_profile = ReflectionHelper.getFields(CraftSkull.class).get("profile"); + + static { + Field blockEntityField = ReflectionHelper.getFields(CraftBlockEntityState.class).getNoCheck("blockEntity"); + if (blockEntityField == null) { + blockEntityField = ReflectionHelper.getFields(CraftBlockEntityState.class).get("tileEntity"); + } + craftBlockEntityState_tileEntity = blockEntityField; + } + + public static final MethodHandle CRAFT_BLOCK_GET_STATE = Handler.reflectPaperRenamed(CraftBlock.class, "getNMS", "getBlockState"); + + public static BlockState getNMSState(Block block) { + try { + return (BlockState) CRAFT_BLOCK_GET_STATE.invokeExact((CraftBlock) block); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public void applyPhysics(Location location) { + ((CraftWorld) location.getWorld()).getHandle().updateNeighborsAt(Handler.toBlockPos(location), CraftMagicNumbers.getBlock(location.getBlock().getType())); + } + + public static T getTE(CraftBlockEntityState cbs) { + try { + return (T) craftBlockEntityState_tileEntity.get(cbs); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + return null; + } + + @Override + public PlayerProfile getPlayerProfile(Skull skull) { + ResolvableProfile profile = getTE(((CraftSkull) skull)).owner; + if (profile == null) { + return null; + } + com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.partialProfile().properties().get("textures"), null); + return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile), property != null ? property.value() : null); + } + + @Override + public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) { + GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile); + try { + craftSkull_profile.set(skull, ResolvableProfile.createResolved(gameProfile)); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + skull.update(); + } + + public static final MethodHandle CRAFT_BLOCK_GET_LEVEL = Handler.reflectPaperRenamed(CraftBlock.class, "getHandle", "getLevel"); + + public BlockEntity getBlockEntity(Block block) { + CraftBlock craftBlock = ((CraftBlock) block); + try { + return ((LevelAccessor) CRAFT_BLOCK_GET_LEVEL.invokeExact(craftBlock)).getBlockEntity(craftBlock.getPosition()); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public CompoundBinaryTag getNbtData(Block block) { + BlockEntity nmsBlockEntity = getBlockEntity(block); + if (nmsBlockEntity != null) { + CompoundTag compound = nmsBlockEntity.saveWithFullMetadata(CraftRegistry.getMinecraftRegistry()); + return NBTAdapter.toAPI(compound); + } + return null; + } + + @Override + public void setNbtData(Block block, CompoundBinaryTag ctag) { + CompoundTag nmsData = NBTAdapter.toNMS(ctag); + nmsData.putInt("x", block.getX()); + nmsData.putInt("y", block.getY()); + nmsData.putInt("z", block.getZ()); + Handler.useValueInput(nmsData, getBlockEntity(block)::loadWithComponents); + } + + @Override + public boolean setBlockResistance(Material material, float resistance) { + net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material); + if (block == null) { + return false; + } + ReflectionHelper.setFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, "explosionResistance", block, resistance); + return true; + } + + @Override + public float getBlockResistance(Material material) { + net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material); + if (block == null) { + return 0; + } + return ReflectionHelper.getFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, "explosionResistance", block); + } + + public static final MethodHandle MATERIAL_PUSH_REACTION_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BlockBehaviour.BlockStateBase.class, PushReaction.class); + + public static final MethodHandle BLOCK_STRENGTH_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.class, float.class); // destroySpeed + + public net.minecraft.world.level.block.state.BlockState getMaterialBlockState(Material bukkitMaterial) { + net.minecraft.world.level.block.Block nmsBlock = CraftMagicNumbers.getBlock(bukkitMaterial); + return nmsBlock != null ? nmsBlock.defaultBlockState() : null; + } + + @Override + public void setPushReaction(Material mat, PistonPushReaction reaction) { + try { + MATERIAL_PUSH_REACTION_SETTER.invoke(getMaterialBlockState(mat), PushReaction.values()[reaction.ordinal()]); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public float getBlockStrength(Material mat) { + return getMaterialBlockState(mat).destroySpeed; + } + + @Override + public void setBlockStrength(Material mat, float strength) { + try { + BLOCK_STRENGTH_SETTER.invoke(getMaterialBlockState(mat), strength); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void doRandomTick(Location location) { + BlockPos pos = Handler.toBlockPos(location); + ChunkAccess nmsChunk = ((CraftChunk) location.getChunk()).getHandle(ChunkStatus.FULL); + net.minecraft.world.level.block.state.BlockState nmsBlock = nmsChunk.getBlockState(pos); + ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle(); + if (nmsBlock.isRandomlyTicking()) { + nmsBlock.randomTick(nmsWorld, pos, nmsWorld.getRandom()); + } + FluidState fluid = nmsBlock.getFluidState(); + if (fluid.isRandomlyTicking()) { + fluid.animateTick(nmsWorld, pos, nmsWorld.getRandom()); + } + } + + @Override + public Instrument getInstrumentFor(Material mat) { + return Instrument.values()[getMaterialBlockState(mat).instrument().ordinal()]; + } + + @Override + public int getExpDrop(Block block, org.bukkit.inventory.ItemStack item) { + net.minecraft.world.level.block.Block blockType = CraftMagicNumbers.getBlock(block.getType()); + if (blockType == null) { + return 0; + } + return blockType.getExpDrop(getNMSState(block), ((CraftBlock) block).getCraftWorld().getHandle(), ((CraftBlock) block).getPosition(), + item == null ? null : CraftItemStack.asNMSCopy(item), true); + } + + @Override + public void setSpawnerSpawnedType(CreatureSpawner spawner, EntityTag entity) { + spawner.setSpawnedType(entity.getBukkitEntityType()); + if (entity.getWaitingMechanisms() == null || entity.getWaitingMechanisms().size() == 0) { + return; + } + try { + // Wrangle a fake entity + // TODO: 1.21.6: seems to have a bug where the "Pos" value being set prevents it from spawning? + org.bukkit.entity.Entity bukkitEntity = ((CraftWorld) spawner.getWorld()).createEntity(spawner.getLocation(), entity.getBukkitEntityType().getEntityClass()); + Entity nmsEntity = ((CraftEntity) bukkitEntity).getHandle(); + EntityTag entityTag = new EntityTag(bukkitEntity); + entityTag.isFake = true; + entityTag.isFakeValid = true; + for (Mechanism mechanism : entity.getWaitingMechanisms()) { + entityTag.safeAdjustDuplicate(mechanism); + } + nmsEntity.unsetRemoved(); + // Store it into the spawner + SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(spawner); + Handler.useValueOutput(nmsSnapshot.getSpawner().nextSpawnData.getEntityToSpawn(), nmsEntity::saveWithoutId); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void setSpawnerCustomRules(CreatureSpawner spawner, int skyMin, int skyMax, int blockMin, int blockMax) { + try { + CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner; + SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner); + BaseSpawner nmsSpawner = nmsSnapshot.getSpawner(); + SpawnData toSpawn = nmsSpawner.nextSpawnData; + SpawnData.CustomSpawnRules rules = skyMin == -1 ? null : new SpawnData.CustomSpawnRules(new InclusiveRange<>(skyMin, skyMax), new InclusiveRange<>(blockMin, blockMax)); + nmsSpawner.nextSpawnData = new SpawnData(toSpawn.entityToSpawn(), Optional.ofNullable(rules), toSpawn.equipment()); + nmsSpawner.spawnPotentials = WeightedList.of(); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/ChunkHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/ChunkHelperImpl.java new file mode 100644 index 0000000000..b203e0a2b5 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/ChunkHelperImpl.java @@ -0,0 +1,119 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.abstracts.BiomeNMS; +import com.denizenscript.denizen.nms.interfaces.ChunkHelper; +import com.denizenscript.denizen.nms.v26_1.impl.BiomeNMSImpl; +import com.denizenscript.denizencore.tags.TagManager; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.Holder; +import net.minecraft.core.QuartPos; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.Heightmap; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.CraftWorld; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; + +public class ChunkHelperImpl implements ChunkHelper { + + public final static Field chunkProviderServerThreadField; + public final static MethodHandle chunkProviderServerThreadFieldSetter; + public final static Field worldThreadField; + public final static MethodHandle worldThreadFieldSetter; + + static { + chunkProviderServerThreadField = ReflectionHelper.getFields(ServerChunkCache.class).getFirstOfType(Thread.class); + chunkProviderServerThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(ServerChunkCache.class, Thread.class); + worldThreadField = ReflectionHelper.getFields(net.minecraft.world.level.Level.class).getFirstOfType(Thread.class); + worldThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.Level.class, Thread.class); + } + + public Thread resetServerThread; + + @Override + public void changeChunkServerThread(World world) { + if (TagManager.tagThread == null) { + return; + } + if (resetServerThread != null) { + return; + } + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + ServerChunkCache provider = nmsWorld.getChunkSource(); + try { + resetServerThread = (Thread) chunkProviderServerThreadField.get(provider); + chunkProviderServerThreadFieldSetter.invoke(provider, Thread.currentThread()); + worldThreadFieldSetter.invoke(nmsWorld, Thread.currentThread()); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void restoreServerThread(World world) { + if (TagManager.tagThread == null) { + return; + } + if (resetServerThread == null) { + return; + } + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + ServerChunkCache provider = nmsWorld.getChunkSource(); + try { + chunkProviderServerThreadFieldSetter.invoke(provider, resetServerThread); + worldThreadFieldSetter.invoke(nmsWorld, resetServerThread); + resetServerThread = null; + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public int[] getHeightMap(Chunk chunk) { + Heightmap map = ((CraftChunk) chunk).getHandle(ChunkStatus.FEATURES).heightmaps.get(Heightmap.Types.MOTION_BLOCKING); + int[] outputMap = new int[256]; + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + outputMap[x * 16 + y] = map.getFirstAvailable(x, y); + } + } + return outputMap; + } + + @Override + public void setAllBiomes(Chunk chunk, BiomeNMS biome) { + Holder nmsBiome = ((BiomeNMSImpl) biome).biomeHolder; + ChunkAccess nmsChunk = ((CraftChunk) chunk).getHandle(ChunkStatus.BIOMES); + ChunkPos chunkcoordintpair = nmsChunk.getPos(); + int i = QuartPos.fromBlock(chunkcoordintpair.getMinBlockX()); + int j = QuartPos.fromBlock(chunkcoordintpair.getMinBlockZ()); + LevelHeightAccessor levelheightaccessor = nmsChunk.getHeightAccessorForGeneration(); + for(int k = levelheightaccessor.getMinSectionY(); k < levelheightaccessor.getMaxSectionY(); ++k) { + LevelChunkSection chunksection = nmsChunk.getSection(nmsChunk.getSectionIndexFromSectionY(k)); + PalettedContainer> datapaletteblock = (PalettedContainer>) chunksection.getBiomes(); + datapaletteblock.acquire(); + for(int l = 0; l < 4; ++l) { + for(int i1 = 0; i1 < 4; ++i1) { + for(int j1 = 0; j1 < 4; ++j1) { + datapaletteblock.getAndSetUnchecked(l, i1, j1, nmsBiome); + } + } + } + datapaletteblock.release(); + } + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/CustomEntityHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/CustomEntityHelperImpl.java new file mode 100644 index 0000000000..9905e036ef --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/CustomEntityHelperImpl.java @@ -0,0 +1,149 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.interfaces.CustomEntityHelper; +import com.denizenscript.denizen.nms.interfaces.FakeArrow; +import com.denizenscript.denizen.nms.interfaces.FakePlayer; +import com.denizenscript.denizen.nms.interfaces.ItemProjectile; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_1.impl.entities.CraftFakePlayerImpl; +import com.denizenscript.denizen.nms.v26_1.impl.entities.EntityFakeArrowImpl; +import com.denizenscript.denizen.nms.v26_1.impl.entities.EntityFakePlayerImpl; +import com.denizenscript.denizen.nms.v26_1.impl.entities.EntityItemProjectileImpl; +import com.denizenscript.denizen.utilities.BukkitImplDeprecations; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.scoreboard.Team; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public class CustomEntityHelperImpl implements CustomEntityHelper { + + @Override + public FakeArrow spawnFakeArrow(Location location) { + CraftWorld world = (CraftWorld) location.getWorld(); + EntityFakeArrowImpl arrow = new EntityFakeArrowImpl(world, location); + return arrow.getBukkitEntity(); + } + + @Override + public ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack) { + CraftWorld world = (CraftWorld) location.getWorld(); + EntityItemProjectileImpl entity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack)); + world.getHandle().addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM); + return entity.getBukkitEntity(); + } + + public FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd) throws IllegalArgumentException { + BukkitImplDeprecations.fakePlayer.warn(); + String fullName = name; + String prefix = null; + String suffix = null; + if (name == null) { + Debug.echoError("FAKE_PLAYER: null name, cannot spawn"); + return null; + } + else if (fullName.length() > 16) { + prefix = fullName.substring(0, 16); + if (fullName.length() > 30) { + int len = 30; + name = fullName.substring(16, 30); + if (name.matches(".*[^A-Za-z0-9_].*")) { + if (fullName.length() >= 32) { + len = 32; + name = fullName.substring(16, 32); + } + else if (fullName.length() == 31) { + len = 31; + name = fullName.substring(16, 31); + } + } + else if (name.length() > 46) { + throw new IllegalArgumentException("You must specify a name with no more than 46 characters for FAKE_PLAYER entities!"); + } + else { + name = ChatColor.RESET + name; + } + suffix = fullName.substring(len); + } + else { + name = fullName.substring(16); + if (!name.matches(".*[^A-Za-z0-9_].*")) { + name = ChatColor.RESET + name; + } + if (name.length() > 16) { + suffix = name.substring(16); + name = name.substring(0, 16); + } + } + } + if (skin != null && skin.length() > 16) { + throw new IllegalArgumentException("You must specify a name with no more than 16 characters for FAKE_PLAYER entity skins!"); + } + CraftWorld world = (CraftWorld) location.getWorld(); + ServerLevel worldServer = world.getHandle(); + PlayerProfile playerProfile = new PlayerProfile(name, null); + if (blob != null) { + int sc = blob.indexOf(';'); + if (sc != -1) { + playerProfile.setTexture(blob.substring(0, sc)); + playerProfile.setTextureSignature(blob.substring(sc + 1)); + } + } + else if (skin == null && !name.matches(".*[^A-Za-z0-9_].*")) { + playerProfile = NMSHandler.instance.fillPlayerProfile(playerProfile); + } + if (skin != null) { + PlayerProfile skinProfile = new PlayerProfile(skin, null); + skinProfile = NMSHandler.instance.fillPlayerProfile(skinProfile); + playerProfile.setTexture(skinProfile.getTexture()); + playerProfile.setTextureSignature(skinProfile.getTextureSignature()); + } + UUID uuid = UUID.randomUUID(); + playerProfile.setUniqueId(uuid); + + GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile); + final EntityFakePlayerImpl fakePlayer = new EntityFakePlayerImpl(worldServer.getServer(), worldServer, gameProfile, ClientInformation.createDefault(), doAdd); + + fakePlayer.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), + location.getYaw(), location.getPitch()); + CraftFakePlayerImpl craftFakePlayer = fakePlayer.getBukkitEntity(); + craftFakePlayer.fullName = fullName; + if (prefix != null) { + Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard(); + String teamName = "FAKE_PLAYER_TEAM_" + fullName; + String hash = null; + try { + hash = CoreUtilities.hash_md5(teamName.getBytes(StandardCharsets.UTF_8)).substring(0, 16); + } + catch (Exception e) { + Debug.echoError(e); + } + if (hash != null) { + Team team = scoreboard.getTeam(hash); + if (team == null) { + team = scoreboard.registerNewTeam(hash); + team.setPrefix(prefix); + if (suffix != null) { + team.setSuffix(suffix); + } + } + team.addPlayer(craftFakePlayer); + } + } + return craftFakePlayer; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EnchantmentHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EnchantmentHelperImpl.java new file mode 100644 index 0000000000..ae556afc23 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EnchantmentHelperImpl.java @@ -0,0 +1,195 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.interfaces.EnchantmentHelper; + +public class EnchantmentHelperImpl extends EnchantmentHelper { + // TODO: 1.21: Enchantments were entirely reworked, need to update this + /* + public static final Field REGISTRY_FROZEN = ReflectionHelper.getFields(MappedRegistry.class).get("frozen", boolean.class); + public static final Field REGISTRY_INTRUSIVE_HOLDERS = ReflectionHelper.getFields(MappedRegistry.class).get("unregisteredIntrusiveHolders", Map.class); + + @Override + public org.bukkit.enchantments.Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) { + try { + Map holders = (Map) REGISTRY_INTRUSIVE_HOLDERS.get(BuiltInRegistries.ENCHANTMENT); + if (holders == null) { + REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, new IdentityHashMap()); + } + boolean wasFrozen = REGISTRY_FROZEN.getBoolean(BuiltInRegistries.ENCHANTMENT); + REGISTRY_FROZEN.setBoolean(BuiltInRegistries.ENCHANTMENT, false); + EquipmentSlot[] slots = new EquipmentSlot[script.script.slots.size()]; + for (int i = 0; i < slots.length; i++) { + slots[i] = EquipmentSlot.valueOf(CoreUtilities.toUpperCase(script.script.slots.get(i))); + } + // TODO: 1.20.6: rarity is provided as an int, can make our own mirror enum; categories seemed to only over control #canEnchant(ItemStack), so can probably safely phase them out? + net.minecraft.world.item.enchantment.Enchantment.Rarity.valueOf(script.script.rarity), EnchantmentCategory.valueOf(script.script.category), slots + net.minecraft.world.item.enchantment.Enchantment nmsEnchant = new net.minecraft.world.item.enchantment.Enchantment(null) { + // TODO: 1.20.6: methods are final now and the values are provided by EnchantmentDefinition - would probably need to create a new one on reload and modify the existing enchantment + @Override + public int getMinLevel() { + return script.script.minLevel; + } + @Override + public int getMaxLevel() { + return script.script.maxLevel; + } + @Override + public int getMinCost(int level) { + return script.script.getMinCost(level); + } + @Override + public int getMaxCost(int level) { + return script.script.getMaxCost(level); + } + @Override + public int getDamageProtection(int level, DamageSource src) { + return script.script.getDamageProtection(level, src.getMsgId(), src.getEntity() == null ? null : src.getEntity().getBukkitEntity()); + } + // TODO: 1.20.6: Takes an EntityType now, and MobType seems to have been removed in favor of vanilla tags - can probably use these to backsupport & properly pass the entity type + @Override + public float getDamageBonus(int level, EntityType type) { + String typeName = "UNDEFINED"; + if (type == MobType.ARTHROPOD) { + typeName = "ARTHROPOD"; + } + else if (type == MobType.ILLAGER) { + typeName = "ILLAGER"; + } + else if (type == MobType.UNDEAD) { + typeName = "UNDEAD"; + } + else if (type == MobType.WATER) { + typeName = "WATER"; + } + return script.script.getDamageBonus(level, typeName); + } + @Override + protected boolean checkCompatibility(net.minecraft.world.item.enchantment.Enchantment nmsEnchantment) { + ResourceLocation nmsKey = BuiltInRegistries.ENCHANTMENT.getKey(nmsEnchantment); + NamespacedKey bukkitKey = CraftNamespacedKey.fromMinecraft(nmsKey); + org.bukkit.enchantments.Enchantment bukkitEnchant = CraftEnchantment.getByKey(bukkitKey); + return script.script.isCompatible(bukkitEnchant); + } + @Override + protected String getOrCreateDescriptionId() { + return script.script.descriptionId; + } + @Override + public String getDescriptionId() { + return script.script.descriptionId; + } + @Override + public Component getFullname(int level) { + return Handler.componentToNMS(script.script.getFullName(level)); + } + @Override + public boolean canEnchant(net.minecraft.world.item.ItemStack var0) { + return super.canEnchant(var0) && script.script.canEnchant(CraftItemStack.asBukkitCopy(var0)); + } + @Override + public void doPostAttack(LivingEntity attacker, Entity victim, int level) { + script.script.doPostAttack(attacker.getBukkitEntity(), victim.getBukkitEntity(), level); + } + @Override + public void doPostHurt(LivingEntity victim, Entity attacker, int level) { + script.script.doPostHurt(victim.getBukkitEntity(), attacker.getBukkitEntity(), level); + } + @Override + public boolean isTreasureOnly() { + return script.script.isTreasureOnly; + } + @Override + public boolean isCurse() { + return script.script.isCurse; + } + @Override + public boolean isTradeable() { + return script.script.isTradable; + } + @Override + public boolean isDiscoverable() { + return script.script.isDiscoverable; + } + }; + NamespacedKey enchantmentKey = new NamespacedKey(Denizen.getInstance(), script.script.id); + Registry.register(BuiltInRegistries.ENCHANTMENT, enchantmentKey.toString(), nmsEnchant); + String enchName = CoreUtilities.toUpperCase(script.script.id); + CraftEnchantment ench = new CraftEnchantment(enchantmentKey, nmsEnchant) { + @Override + public String getName() { + return enchName; + } + }; + REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, holders); + if (wasFrozen) { + BuiltInRegistries.ENCHANTMENT.freeze(); + } + return ench; + } + catch (Throwable ex) { + Debug.echoError("Failed to register enchantment " + script.script.id); + Debug.echoError(ex); + return null; + } + } + + // TODO: 1.20.6: rarity is just an int now (weight), can deprecate & backsupport by estimating it based on the weight + @Override + public String getRarity(Enchantment enchantment) { + return ((CraftEnchantment) enchantment).getHandle().getRarity().name(); + } + + @Override + public boolean isDiscoverable(Enchantment enchantment) { + return ((CraftEnchantment) enchantment).getHandle().isDiscoverable(); + } + + @Override + public boolean isTradable(Enchantment enchantment) { + return ((CraftEnchantment) enchantment).getHandle().isTradeable(); + } + + @Override + public boolean isCurse(Enchantment enchantment) { + return ((CraftEnchantment) enchantment).getHandle().isCurse(); + } + + @Override + public int getMinCost(Enchantment enchantment, int level) { + return ((CraftEnchantment) enchantment).getHandle().getMinCost(level); + } + + @Override + public int getMaxCost(Enchantment enchantment, int level) { + return ((CraftEnchantment) enchantment).getHandle().getMaxCost(level); + } + + @Override + public String getFullName(Enchantment enchantment, int level) { + return FormattedTextHelper.stringify(Handler.componentToSpigot(((CraftEnchantment) enchantment).getHandle().getFullname(level))); + } + + // TODO: 1.20.6: MobType was removed in favor of using the entity type directly - deprecate + potentially backsupport with vanilla tags + @Override + public float getDamageBonus(Enchantment enchantment, int level, String type) { + MobType mobType = switch (type) { + case "illager" -> MobType.ILLAGER; + case "undead" -> MobType.UNDEAD; + case "water" -> MobType.WATER; + case "arthropod" -> MobType.ARTHROPOD; + default -> MobType.UNDEFINED; + }; + return ((CraftEnchantment) enchantment).getHandle().getDamageBonus(level, mobType); + } + + @Override + public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, org.bukkit.entity.Entity attacker) { + Entity nmsAttacker = attacker == null ? null : ((CraftEntity) attacker).getHandle(); + DamageSource src = EntityHelperImpl.getSourceFor(nmsAttacker, type, nmsAttacker); + if (src instanceof EntityHelperImpl.FakeDamageSrc fakeDamageSrc) { + src = fakeDamageSrc.real; + } + return ((CraftEnchantment) enchantment).getHandle().getDamageProtection(level, src); + } + */ +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EntityDataNameMapper.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EntityDataNameMapper.java new file mode 100644 index 0000000000..8ce7da8054 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EntityDataNameMapper.java @@ -0,0 +1,484 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + + +import com.denizenscript.denizencore.objects.ArgumentHelper; +import com.denizenscript.denizencore.objects.core.ElementTag; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ambient.Bat; +import net.minecraft.world.entity.animal.axolotl.Axolotl; +import net.minecraft.world.entity.animal.bee.Bee; +import net.minecraft.world.entity.animal.camel.Camel; +import net.minecraft.world.entity.animal.cow.MushroomCow; +import net.minecraft.world.entity.animal.dolphin.Dolphin; +import net.minecraft.world.entity.animal.equine.AbstractChestedHorse; +import net.minecraft.world.entity.animal.equine.AbstractHorse; +import net.minecraft.world.entity.animal.equine.Horse; +import net.minecraft.world.entity.animal.equine.Llama; +import net.minecraft.world.entity.animal.feline.Cat; +import net.minecraft.world.entity.animal.feline.Ocelot; +import net.minecraft.world.entity.animal.fish.AbstractFish; +import net.minecraft.world.entity.animal.fish.Pufferfish; +import net.minecraft.world.entity.animal.fish.TropicalFish; +import net.minecraft.world.entity.animal.fox.Fox; +import net.minecraft.world.entity.animal.frog.Frog; +import net.minecraft.world.entity.animal.goat.Goat; +import net.minecraft.world.entity.animal.golem.IronGolem; +import net.minecraft.world.entity.animal.golem.SnowGolem; +import net.minecraft.world.entity.animal.panda.Panda; +import net.minecraft.world.entity.animal.parrot.Parrot; +import net.minecraft.world.entity.animal.pig.Pig; +import net.minecraft.world.entity.animal.polarbear.PolarBear; +import net.minecraft.world.entity.animal.rabbit.Rabbit; +import net.minecraft.world.entity.animal.sheep.Sheep; +import net.minecraft.world.entity.animal.sniffer.Sniffer; +import net.minecraft.world.entity.animal.turtle.Turtle; +import net.minecraft.world.entity.animal.wolf.Wolf; +import net.minecraft.world.entity.boss.enderdragon.EndCrystal; +import net.minecraft.world.entity.boss.enderdragon.EnderDragon; +import net.minecraft.world.entity.boss.wither.WitherBoss; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.decoration.ItemFrame; +import net.minecraft.world.entity.decoration.painting.Painting; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.world.entity.monster.*; +import net.minecraft.world.entity.monster.hoglin.Hoglin; +import net.minecraft.world.entity.monster.illager.Pillager; +import net.minecraft.world.entity.monster.illager.SpellcasterIllager; +import net.minecraft.world.entity.monster.piglin.AbstractPiglin; +import net.minecraft.world.entity.monster.piglin.Piglin; +import net.minecraft.world.entity.monster.spider.Spider; +import net.minecraft.world.entity.monster.warden.Warden; +import net.minecraft.world.entity.monster.zombie.Zombie; +import net.minecraft.world.entity.monster.zombie.ZombieVillager; +import net.minecraft.world.entity.npc.villager.AbstractVillager; +import net.minecraft.world.entity.npc.villager.Villager; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.EyeOfEnder; +import net.minecraft.world.entity.projectile.FireworkRocketEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.entity.projectile.ThrowableProjectile; +import net.minecraft.world.entity.projectile.arrow.AbstractArrow; +import net.minecraft.world.entity.projectile.arrow.Arrow; +import net.minecraft.world.entity.projectile.arrow.ThrownTrident; +import net.minecraft.world.entity.projectile.hurtingprojectile.Fireball; +import net.minecraft.world.entity.projectile.hurtingprojectile.SmallFireball; +import net.minecraft.world.entity.projectile.hurtingprojectile.WitherSkull; +import net.minecraft.world.entity.raid.Raider; +import net.minecraft.world.entity.vehicle.boat.Boat; +import net.minecraft.world.entity.vehicle.minecart.AbstractMinecart; +import net.minecraft.world.entity.vehicle.minecart.MinecartCommandBlock; +import net.minecraft.world.entity.vehicle.minecart.MinecartFurnace; + +import java.util.HashMap; +import java.util.Map; + +public class EntityDataNameMapper { + + public static final Map, Map> entityDataNames = new HashMap<>(); + + public static void registerDataName(Class entityClass, int id, String name) { + entityDataNames.computeIfAbsent(entityClass, k -> new HashMap<>()).put(name, id); + } + + static { + // Entity + registerDataName(Entity.class, 0, "entity_flags"); + registerDataName(Entity.class, 1, "air_ticks"); + registerDataName(Entity.class, 2, "custom_name"); + registerDataName(Entity.class, 3, "custom_name_visible"); + registerDataName(Entity.class, 4, "silent"); + registerDataName(Entity.class, 5, "no_gravity"); + registerDataName(Entity.class, 6, "pose"); + registerDataName(Entity.class, 7, "frozen_ticks"); + + // Interaction + registerDataName(Interaction.class, 8, "width"); + registerDataName(Interaction.class, 9, "height"); + registerDataName(Interaction.class, 10, "responsive"); + + // Display + registerDataName(Display.class, 8, "transform_interpolation_start"); + registerDataName(Display.class, 9, "transform_interpolation_duration"); + registerDataName(Display.class, 10, "movement_interpolation_duration"); + registerDataName(Display.class, 11, "translation"); + registerDataName(Display.class, 12, "scale"); + registerDataName(Display.class, 13, "left_rotation"); + registerDataName(Display.class, 14, "right_rotation"); + registerDataName(Display.class, 15, "billboard"); + registerDataName(Display.class, 16, "brightness"); + registerDataName(Display.class, 17, "view_range"); + registerDataName(Display.class, 18, "shadow_radius"); + registerDataName(Display.class, 19, "shadow_strength"); + registerDataName(Display.class, 20, "width"); + registerDataName(Display.class, 21, "height"); + registerDataName(Display.class, 22, "glow_color"); + + // Block display + registerDataName(Display.BlockDisplay.class, 23, "material"); + + // Item display + registerDataName(Display.ItemDisplay.class, 23, "item"); + registerDataName(Display.ItemDisplay.class, 24, "model_transform"); + + // Text display + registerDataName(Display.TextDisplay.class, 23, "text"); + registerDataName(Display.TextDisplay.class, 24, "line_width"); + registerDataName(Display.TextDisplay.class, 25, "background_color"); + registerDataName(Display.TextDisplay.class, 26, "text_opacity"); + registerDataName(Display.TextDisplay.class, 27, "text_display_flags"); + + // Thrown item projectile + registerDataName(ThrowableProjectile.class, 8, "item"); + + // Eye of ender + registerDataName(EyeOfEnder.class, 8, "item"); + + // Falling block + registerDataName(FireworkRocketEntity.class, 8, "spawn_position"); + + // Area effect cloud + registerDataName(AreaEffectCloud.class, 8, "radius"); + registerDataName(AreaEffectCloud.class, 9, "color"); + registerDataName(AreaEffectCloud.class, 10, "waiting"); + registerDataName(AreaEffectCloud.class, 11, "particle"); + + // Fishing hook + registerDataName(FishingHook.class, 8, "hooked_entity_id"); + registerDataName(FishingHook.class, 9, "catchable"); + + // Abstract arrow + registerDataName(AbstractArrow.class, 8, "abstract_arrow_flags"); + registerDataName(AbstractArrow.class, 9, "piercing_level"); + + // Arrow + registerDataName(Arrow.class, 10, "color"); + + // Thrown trident + registerDataName(ThrownTrident.class, 10, "loyalty_level"); + registerDataName(ThrownTrident.class, 11, "enchantment_glint"); + + // Boat + registerDataName(Boat.class, 8, "shaking_ticks"); + registerDataName(Boat.class, 9, "shaking_direction"); + registerDataName(Boat.class, 10, "damage_taken"); + registerDataName(Boat.class, 11, "type"); + registerDataName(Boat.class, 12, "left_paddle_moving"); + registerDataName(Boat.class, 13, "right_paddle_moving"); + registerDataName(Boat.class, 14, "bubble_shaking_ticks"); + + // End crystal + registerDataName(EndCrystal.class, 8, "beam_target"); + registerDataName(EndCrystal.class, 9, "showing_bottom"); + + // Small fireball + registerDataName(SmallFireball.class, 8, "item"); + + // Fireball + registerDataName(Fireball.class, 8, "item"); + + // Wither skull + registerDataName(WitherSkull.class, 8, "invulnerable"); + + // Firework rocket + registerDataName(FireworkRocketEntity.class, 8, "item"); + registerDataName(FireworkRocketEntity.class, 9, "shooter_id"); + registerDataName(FireworkRocketEntity.class, 10, "shot_at_angle"); + + // Item frame + registerDataName(ItemFrame.class, 8, "item"); + registerDataName(ItemFrame.class, 9, "rotation"); + + // Painting + registerDataName(Painting.class, 8, "painting_variant"); + + // Living entity + registerDataName(LivingEntity.class, 8, "living_entity_flags"); + registerDataName(LivingEntity.class, 9, "health"); + registerDataName(LivingEntity.class, 10, "potion_effect_color"); + registerDataName(LivingEntity.class, 11, "is_potion_effect_ambient"); + registerDataName(LivingEntity.class, 12, "arrows_in_body"); + registerDataName(LivingEntity.class, 13, "bee_stingers_in_body"); + registerDataName(LivingEntity.class, 14, "bed_location"); + + // Player + registerDataName(Player.class, 15, "additional_hearts"); + registerDataName(Player.class, 16, "score"); + registerDataName(Player.class, 17, "skin_parts"); + registerDataName(Player.class, 18, "main_hand"); + registerDataName(Player.class, 19, "left_shoulder_entity"); + registerDataName(Player.class, 20, "right_shoulder_entity"); + + // Armor stand + registerDataName(ArmorStand.class, 15, "armor_stand_flags"); + registerDataName(ArmorStand.class, 16, "head_rotation"); + registerDataName(ArmorStand.class, 17, "body_rotation"); + registerDataName(ArmorStand.class, 18, "left_arm_rotation"); + registerDataName(ArmorStand.class, 19, "right_arm_rotation"); + registerDataName(ArmorStand.class, 20, "left_leg_rotation"); + registerDataName(ArmorStand.class, 21, "right_leg_rotation"); + + // Mob + registerDataName(Mob.class, 15, "mob_flags"); + + // Bat flags + registerDataName(Bat.class, 16, "bat_flags"); + + // Dolphin + registerDataName(Dolphin.class, 16, "treasure_location"); + registerDataName(Dolphin.class, 17, "has_fish"); + registerDataName(Dolphin.class, 18, "moisture_level"); + + // Abstract Fish + registerDataName(AbstractFish.class, 16, "from_bucket"); + + // PufferFish + registerDataName(Pufferfish.class, 17, "puff_state"); + + // Tropical fish + registerDataName(TropicalFish.class, 17, "variant"); + + // Ageable mob + registerDataName(AgeableMob.class, 16, "is_baby"); + + // Sniffer + registerDataName(Sniffer.class, 17, "sniffer_state"); + registerDataName(Sniffer.class, 18, "finish_dig_time"); + + // Abstract horse + registerDataName(AbstractHorse.class, 17, "horse_flags"); + + // Horse + registerDataName(Horse.class, 18, "variant"); + + // Camel + registerDataName(Camel.class, 18, "is_dashing"); + registerDataName(Camel.class, 19, "last_pose_change"); + + // Chested horse + registerDataName(AbstractChestedHorse.class, 18, "has_chest"); + + // Llama + registerDataName(Llama.class, 19, "strength"); + registerDataName(Llama.class, 20, "carpet_color"); + registerDataName(Llama.class, 21, "variant"); + + // Axolotl + registerDataName(Axolotl.class, 17, "variant"); + registerDataName(Axolotl.class, 18, "playing_dead"); + registerDataName(Axolotl.class, 19, "from_bucket"); + + // Bee + registerDataName(Bee.class, 17, "bee_flags"); + registerDataName(Bee.class, 18, "anger_time"); + + // Fox + registerDataName(Fox.class, 17, "type"); + registerDataName(Fox.class, 18, "fox_flags"); + registerDataName(Fox.class, 19, "first_trusted_uuid"); + registerDataName(Fox.class, 20, "second_trusted_uuid"); + + // Frog + registerDataName(Frog.class, 17, "variant"); + registerDataName(Frog.class, 18, "target_id"); + + // Ocelot + registerDataName(Ocelot.class, 17, "is_trusting"); + + // Panda + registerDataName(Panda.class, 17, "ask_for_bamboo_timer"); + registerDataName(Panda.class, 18, "sneeze_timer"); + registerDataName(Panda.class, 19, "eat_timer"); + registerDataName(Panda.class, 20, "main_gene"); + registerDataName(Panda.class, 21, "hidden_gene"); + registerDataName(Panda.class, 22, "panda_flags"); + + // Pig + registerDataName(Pig.class, 17, "has_saddle"); + registerDataName(Pig.class, 18, "boost_ticks"); + + // Rabbit + registerDataName(Rabbit.class, 17, "type"); + + // Turtle + registerDataName(Turtle.class, 17, "home_location"); + registerDataName(Turtle.class, 18, "has_egg"); + registerDataName(Turtle.class, 19, "laying_egg"); + registerDataName(Turtle.class, 20, "travel_location"); + registerDataName(Turtle.class, 21, "going_home"); + registerDataName(Turtle.class, 20, "traveling"); + + // Polar bear + registerDataName(PolarBear.class, 17, "standing_up"); + + // Hoglin + registerDataName(Hoglin.class, 17, "immune_to_zombification"); + + // Mooshroom + registerDataName(MushroomCow.class, 17, "variant"); + + // Sheep + registerDataName(Sheep.class, 17, "sheep_wool_flags"); + + // Strider + registerDataName(Strider.class, 17, "boost_ticks"); + registerDataName(Strider.class, 18, "shaking"); + registerDataName(Strider.class, 19, "has_saddle"); + + // Tamable animal + registerDataName(TamableAnimal.class, 17, "tamable_animal_flags"); + registerDataName(TamableAnimal.class, 18, "owner"); + + // Cat + registerDataName(Cat.class, 19, "variant"); + registerDataName(Cat.class, 20, "lying"); + registerDataName(Cat.class, 20, "relaxed"); + registerDataName(Cat.class, 21, "collar_color"); + + // Wolf + registerDataName(Wolf.class, 19, "begging"); + registerDataName(Wolf.class, 20, "collar_color"); + registerDataName(Wolf.class, 21, "anger_time"); + + // Parrot + registerDataName(Parrot.class, 19, "variant"); + + // Abstract villager + registerDataName(AbstractVillager.class, 17, "head_shake_ticks"); + + // Villager + registerDataName(Villager.class, 18, "villager_data"); + + // Iron golem + registerDataName(IronGolem.class, 16, "iron_golem_flags"); + + // Snow golem + registerDataName(SnowGolem.class, 16, "snow_golem_pumpkin_flags"); + + // Shulker + registerDataName(Shulker.class, 16, "attach_face"); + registerDataName(Shulker.class, 17, "attachment_location"); + registerDataName(Shulker.class, 18, "peek"); + registerDataName(Shulker.class, 19, "color"); + + // Base piglin + registerDataName(AbstractPiglin.class, 16, "immune_to_zombification"); + + // Piglin + registerDataName(Piglin.class, 17, "is_baby"); + registerDataName(Piglin.class, 18, "charging_crossbow"); + registerDataName(Piglin.class, 19, "dancing"); + + // Blaze + registerDataName(Blaze.class, 16, "blaze_flags"); + + // Creeper + registerDataName(Creeper.class, 16, "state"); + registerDataName(Creeper.class, 17, "charged"); + registerDataName(Creeper.class, 18, "ignited"); + + // Goat + registerDataName(Goat.class, 17, "screaming"); + registerDataName(Goat.class, 18, "has_left_horn"); + registerDataName(Goat.class, 19, "has_right_horn"); + + // Guardian + registerDataName(Guardian.class, 16, "spikes_retracted"); + registerDataName(Guardian.class, 17, "target_id"); + + // Raider + registerDataName(Raider.class, 16, "celebrating"); + + // Pillager + registerDataName(Pillager.class, 17, "charging_crossbow"); + + // Spellcaster illager + registerDataName(SpellcasterIllager.class, 17, "spell"); + + // Witch + registerDataName(Witch.class, 17, "drinking_potion"); + + // Vex + registerDataName(Vex.class, 16, "vex_flags"); + + // Spider + registerDataName(Spider.class, 16, "spider_flags"); + + // Warden + registerDataName(Warden.class, 16, "anger_level"); + + // Wither + registerDataName(WitherBoss.class, 16, "center_head_target"); + registerDataName(WitherBoss.class, 17, "left_head_target"); + registerDataName(WitherBoss.class, 18, "right_head_target"); + registerDataName(WitherBoss.class, 19, "invulnerable_time"); + + // Zoglin + registerDataName(Zoglin.class, 16, "is_baby"); + + // Zombie + registerDataName(Zombie.class, 16, "is_baby"); + registerDataName(Zombie.class, 17, "type"); // Unused + registerDataName(Zombie.class, 18, "converting_in_water"); + + // Zombie villager + registerDataName(ZombieVillager.class, 19, "is_converting"); + registerDataName(ZombieVillager.class, 20, "villager_data"); + + // Enderman + registerDataName(EnderMan.class, 16, "carried_block"); + registerDataName(EnderMan.class, 17, "screaming"); + registerDataName(EnderMan.class, 18, "staring"); + + // Ender dragon + registerDataName(EnderDragon.class, 16, "phase"); + + // Ghast + registerDataName(Ghast.class, 16, "attacking"); + + // Phantom + registerDataName(Phantom.class, 16, "size"); + + // Slime + registerDataName(Slime.class, 16, "size"); + + // Abstract minecart + registerDataName(AbstractMinecart.class, 8, "shaking_ticks"); + registerDataName(AbstractMinecart.class, 9, "shaking_direction"); + registerDataName(AbstractMinecart.class, 10, "damage_taken"); + registerDataName(AbstractMinecart.class, 11, "display_block_id"); + registerDataName(AbstractMinecart.class, 12, "display_block_y"); + registerDataName(AbstractMinecart.class, 13, "show_display_block"); + + // Minecraft furnace + registerDataName(MinecartFurnace.class, 14, "has_fuel"); + + // Minecraft command block + registerDataName(MinecartCommandBlock.class, 14, "command"); + registerDataName(MinecartCommandBlock.class, 15, "last_output"); + + // Primed TNT + registerDataName(PrimedTnt.class, 8, "fuse_ticks"); + } + + public static int getIdForName(Class entityClass, String name) { + Class currentClass = entityClass; + int id = getIdFromClass(currentClass, name); + while (id == -1) { + currentClass = currentClass.getSuperclass(); + if (currentClass == Object.class) { + break; + } + id = getIdFromClass(currentClass, name); + } + return id; + } + + private static int getIdFromClass(Class entityClass, String name) { + Map nameToId = entityDataNames.get(entityClass); + int id = nameToId != null ? nameToId.getOrDefault(name, -1) : -1; + if (id == -1 && ArgumentHelper.matchesInteger(name)) { + id = new ElementTag(name).asInt(); + } + return id; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EntityHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EntityHelperImpl.java new file mode 100644 index 0000000000..ac3e95b7b5 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EntityHelperImpl.java @@ -0,0 +1,867 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.interfaces.EntityHelper; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.properties.entity.EntityState; +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.MapTag; +import com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.denizenscript.denizencore.utilities.text.StringHolder; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minecraft.commands.arguments.EntityAnchorArgument; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; +import net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.dedicated.DedicatedPlayerList; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.players.PlayerList; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.damagesource.CombatRules; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.damagesource.DamageSources; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MoverType; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.goal.Goal; +import net.minecraft.world.entity.ai.navigation.PathNavigation; +import net.minecraft.world.entity.animal.armadillo.Armadillo; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.world.entity.monster.EnderMan; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.pathfinder.Path; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftCreatureSpawner; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.entity.*; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.*; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.BiConsumer; + +public class EntityHelperImpl extends EntityHelper { + + public static final MethodHandle ENTITY_ONGROUND_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.Entity.class, "onGround", boolean.class); + + public static final EntityDataAccessor ENDERMAN_DATA_ACCESSOR_SCREAMING = ReflectionHelper.getFieldValue(EnderMan.class, "DATA_CREEPY", null); + + @Override + public void setInvisible(Entity entity, boolean invisible) { + ((CraftEntity) entity).getHandle().setInvisible(invisible); + } + + @Override + public boolean isInvisible(Entity entity) { + return ((CraftEntity) entity).getHandle().isInvisible(); + } + + @Override + public void setPose(Entity entity, Pose pose) { + ((CraftEntity) entity).getHandle().setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]); + } + + @Override + public double getDamageTo(LivingEntity attacker, Entity target) { + double damage = 0; + AttributeInstance attrib = attacker.getAttribute(Attribute.ATTACK_DAMAGE); + if (attrib != null) { + damage = attrib.getValue(); + } + if (damage <= 0) { + return 0; + } + if (target == null) { + // Target is required as of MC 1.21, so if unspecified just assume target is equivalent to attacker + target = attacker; + } + DamageSource source; + net.minecraft.world.entity.Entity nmsTarget = ((CraftEntity) target).getHandle(); + ServerLevel nmsWorld = ((CraftWorld) attacker.getWorld()).getHandle(); + if (attacker instanceof CraftPlayer playerAttacker) { + source = nmsTarget.level().damageSources().playerAttack(playerAttacker.getHandle()); + } + else { + source = nmsTarget.level().damageSources().mobAttack(((CraftLivingEntity) attacker).getHandle()); + } + net.minecraft.world.entity.LivingEntity nmsLivingTarget = nmsTarget instanceof net.minecraft.world.entity.LivingEntity living ? living : null; + if (nmsLivingTarget != null ? nmsLivingTarget.isInvulnerableTo(nmsWorld, source) : nmsTarget.isInvulnerableToBase(source)) { + return 0; + } + if (attacker.getEquipment() != null) { + damage = EnchantmentHelper.modifyDamage(nmsWorld, CraftItemStack.asNMSCopy(attacker.getEquipment().getItemInMainHand()), nmsTarget, source, (float) damage); + } + if (nmsLivingTarget == null) { + return damage; + } + damage = CombatRules.getDamageAfterAbsorb(nmsLivingTarget, (float) damage, source, (float) nmsLivingTarget.getArmorValue(), (float) nmsLivingTarget.getAttributeValue(Attributes.ARMOR_TOUGHNESS)); + float enchantDamageModifier = EnchantmentHelper.getDamageProtection(nmsWorld, nmsLivingTarget, source); + if (enchantDamageModifier > 0) { + damage = CombatRules.getDamageAfterMagicAbsorb((float) damage, enchantDamageModifier); + } + return damage; + } + + public static final MethodHandle LIVINGENTITY_AUTOSPINATTACK_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.LivingEntity.class, "autoSpinAttackTicks"); + public static final MethodHandle LIVINGENTITY_SETLIVINGENTITYFLAG = ReflectionHelper.getMethodHandle(net.minecraft.world.entity.LivingEntity.class, "setLivingEntityFlag", int.class, boolean.class); + + @Override + public void setRiptide(Entity entity, boolean state) { + try { + net.minecraft.world.entity.LivingEntity nmsEntity = ((CraftLivingEntity) entity).getHandle(); + LIVINGENTITY_AUTOSPINATTACK_SETTER.invoke(nmsEntity, state ? 0 : 1); + LIVINGENTITY_SETLIVINGENTITYFLAG.invoke(nmsEntity, 4, true); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void forceInteraction(Player player, Location location) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + BlockHelperImpl.getNMSState(location.getBlock()).useItemOn(nmsPlayer.getMainHandItem(), ((CraftWorld) location.getWorld()).getHandle(), + nmsPlayer, InteractionHand.MAIN_HAND, + new BlockHitResult(new Vec3(0, 0, 0), null, Handler.toBlockPos(location), false)); + } + + @Override + public CompoundBinaryTag getNbtData(Entity entity) { + CompoundTag nmsTag = Handler.useValueOutput(((CraftEntity) entity).getHandle()::saveAsPassenger); + return NBTAdapter.toAPI(nmsTag); + } + + @Override + public void setNbtData(Entity entity, CompoundBinaryTag compoundTag) { + Handler.useValueInput(NBTAdapter.toNMS(compoundTag), ((CraftEntity) entity).getHandle()::load); + } + + /* + Entity Movement + */ + + private final static Map followTasks = new HashMap<>(); + + @Override + public void stopFollowing(Entity follower) { + if (follower == null) { + return; + } + UUID uuid = follower.getUniqueId(); + if (followTasks.containsKey(uuid)) { + followTasks.get(uuid).cancel(); + } + } + + @Override + public void stopWalking(Entity entity) { + if (((CraftEntity) entity).getHandle() instanceof Mob nmsMob) { + nmsMob.getNavigation().stop(); + } + } + + @Override + public void follow(final Entity target, final Entity follower, final double speed, final double lead, + final double maxRange, final boolean allowWander, final boolean teleport) { + if (target == null || follower == null) { + return; + } + + final net.minecraft.world.entity.Entity nmsEntityFollower = ((CraftEntity) follower).getHandle(); + if (!(nmsEntityFollower instanceof Mob nmsFollower)) { + return; + } + final PathNavigation followerNavigation = nmsFollower.getNavigation(); + + UUID uuid = follower.getUniqueId(); + + if (followTasks.containsKey(uuid)) { + followTasks.get(uuid).cancel(); + } + + final int locationNearInt = (int) Math.floor(lead); + final boolean hasMax = maxRange > lead; + + followTasks.put(follower.getUniqueId(), new BukkitRunnable() { + + private boolean inRadius = false; + + public void run() { + if (!target.isValid() || !follower.isValid()) { + this.cancel(); + } + followerNavigation.setSpeedModifier(2D); + Location targetLocation = target.getLocation(); + Path path; + + if (hasMax && !Utilities.checkLocation(targetLocation, follower.getLocation(), maxRange) + && !target.isDead() && target.isOnGround()) { + if (!inRadius) { + if (teleport) { + follower.teleport(Utilities.getWalkableLocationNear(targetLocation, locationNearInt)); + } + else { + cancel(); + } + } + else { + inRadius = false; + path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0); + if (path != null) { + followerNavigation.moveTo(path, 1D); + followerNavigation.setSpeedModifier(2D); + } + } + } + else if (!inRadius && !Utilities.checkLocation(targetLocation, follower.getLocation(), lead)) { + path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0); + if (path != null) { + followerNavigation.moveTo(path, 1D); + followerNavigation.setSpeedModifier(2D); + } + } + else { + inRadius = true; + } + if (inRadius && !allowWander) { + followerNavigation.stop(); + } + nmsFollower.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed); + } + }.runTaskTimer(NMSHandler.getJavaPlugin(), 0, 10)); + } + + @Override + public void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback) { + if (entity == null || location == null) { + return; + } + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + if (!(nmsEntity instanceof final Mob nmsMob)) { + return; + } + final PathNavigation entityNavigation = nmsMob.getNavigation(); + final Path path; + final boolean aiDisabled = !entity.hasAI(); + if (aiDisabled) { + entity.setAI(true); + try { + ENTITY_ONGROUND_SETTER.invoke(nmsMob, true); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + path = entityNavigation.createPath(location.getX(), location.getY(), location.getZ(), 1); + if (path != null) { + nmsMob.goalSelector.enableControlFlag(Goal.Flag.MOVE); + entityNavigation.moveTo(path, 1D); + final double oldSpeed = nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue(); + if (speed != null) { + nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed); + } + new BukkitRunnable() { + @Override + public void run() { + if (!entity.isValid()) { + if (callback != null) { + callback.run(); + } + cancel(); + return; + } + if (aiDisabled && entity instanceof Wolf wolf) { + wolf.setAngry(false); + } + if (entityNavigation.isDone() || path.isDone()) { + if (callback != null) { + callback.run(); + } + if (speed != null) { + nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(oldSpeed); + } + if (aiDisabled) { + entity.setAI(false); + } + cancel(); + } + } + }.runTaskTimer(NMSHandler.getJavaPlugin(), 1, 1); + } + //if (!Utilities.checkLocation(location, entity.getLocation(), 20)) { + // TODO: generate waypoints to the target location? + else { + entity.teleport(location); + } + } + + @Override + public void sendAllUpdatePackets(Entity entity) { + ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap; + ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId()); + if (entityTracker == null) { + return; + } + try { + ServerEntity serverEntity = (ServerEntity) PacketHelperImpl.ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker); + serverEntity.sendChanges(); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + /* + Hide Entity + */ + + @Override + public void sendHidePacket(Player pl, Entity entity) { + if (entity instanceof Player player) { + pl.hidePlayer(Denizen.getInstance(), player); + return; + } + ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle(); + if (nmsPlayer.connection != null && !pl.equals(entity)) { + ChunkMap.TrackedEntity entry = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); + if (entry != null) { + entry.removePlayer(nmsPlayer); + } + if (Denizen.supportsPaper) { // Workaround for Paper issue + nmsPlayer.connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId())); + } + } + } + + @Override + public void sendShowPacket(Player pl, Entity entity) { + if (entity instanceof Player player) { + pl.showPlayer(Denizen.getInstance(), player); + return; + } + ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle(); + if (nmsPlayer.connection != null && !pl.equals(entity)) { + ChunkMap.TrackedEntity entry = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); + if (entry != null) { + entry.removePlayer(nmsPlayer); + entry.updatePlayer(nmsPlayer); + } + } + } + + @Override + public void rotate(Entity entity, float yaw, float pitch) { + // If this entity is a real player instead of a player type NPC, + // it will appear to be online + if (entity instanceof Player player && player.isOnline()) { + NetworkInterceptHelper.enable(); + float relYaw = (yaw - entity.getLocation().getYaw()) % 360; + if (relYaw > 180) { + relYaw -= 360; + } + final float actualRelYaw = relYaw; + float relPitch = pitch - entity.getLocation().getPitch(); + NMSHandler.packetHelper.sendRelativeLookPacket(player, actualRelYaw, relPitch); + } + else if (entity instanceof LivingEntity) { + if (entity instanceof EnderDragon) { + yaw = normalizeYaw(yaw - 180); + } + look(entity, yaw, pitch); + } + else { + net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle(); + handle.setYRot(yaw - 360); + handle.setXRot(pitch); + } + } + + @Override + public float getBaseYaw(LivingEntity entity) { + return ((CraftLivingEntity) entity).getHandle().yBodyRot; + } + + @Override + public void look(Entity entity, float yaw, float pitch) { + net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle(); + if (handle == null) { + Debug.echoError("Cannot set look direction for unspawned entity " + entity.getUniqueId()); + return; + } + handle.setYRot(yaw); + if (handle instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) { + while (yaw < -180.0F) { + yaw += 360.0F; + } + while (yaw >= 180.0F) { + yaw -= 360.0F; + } + nmsLivingEntity.yBodyRotO = yaw; + if (!(handle instanceof net.minecraft.world.entity.player.Player)) { + nmsLivingEntity.setYBodyRot(yaw); + } + nmsLivingEntity.setYHeadRot(yaw); + } + handle.setXRot(pitch); + } + + private static HitResult rayTrace(World world, Vector start, Vector end) { + try { + NMSHandler.chunkHelper.changeChunkServerThread(world); + return ((CraftWorld) world).getHandle().clip(new ClipContext(new Vec3(start.getX(), start.getY(), start.getZ()), + new Vec3(end.getX(), end.getY(), end.getZ()), + ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, CollisionContext.empty())); + } + finally { + NMSHandler.chunkHelper.restoreServerThread(world); + } + } + + @Override + public boolean canTrace(World world, Vector start, Vector end) { + HitResult pos = rayTrace(world, start, end); + if (pos == null) { + return true; + } + return pos.getType() == HitResult.Type.MISS; + } + + @Override + public void snapPositionTo(Entity entity, Vector vector) { + ((CraftEntity) entity).getHandle().setPosRaw(vector.getX(), vector.getY(), vector.getZ()); + } + + @Override + public void move(Entity entity, Vector vector) { + ((CraftEntity) entity).getHandle().move(MoverType.SELF, new Vec3(vector.getX(), vector.getY(), vector.getZ())); + } + + @Override + public boolean internalLook(Player player, Location at) { + PacketHelperImpl.send(player, new ClientboundPlayerLookAtPacket(EntityAnchorArgument.Anchor.EYES, at.getX(), at.getY(), at.getZ())); + return true; + } + + public static long entityToPacket(double x) { + return Mth.lfloor(x * 4096.0D); + } + + @Override + public void fakeMove(Entity entity, Vector vector) { + long x = entityToPacket(vector.getX()); + long y = entityToPacket(vector.getY()); + long z = entityToPacket(vector.getZ()); + ClientboundMoveEntityPacket packet = new ClientboundMoveEntityPacket.Pos(entity.getEntityId(), (short) x, (short) y, (short) z, entity.isOnGround()); + for (Player player : getPlayersThatSee(entity)) { + PacketHelperImpl.send(player, packet); + } + } + + @Override + public void fakeTeleport(Entity entity, Location location) { + FriendlyByteBuf packetData = new FriendlyByteBuf(Unpooled.buffer()); + // Referenced from ClientboundTeleportEntityPacket source + packetData.writeVarInt(entity.getEntityId()); + packetData.writeDouble(location.getX()); + packetData.writeDouble(location.getY()); + packetData.writeDouble(location.getZ()); + packetData.writeByte((byte)((int)(location.getYaw() * 256.0F / 360.0F))); + packetData.writeByte((byte)((int)(location.getPitch() * 256.0F / 360.0F))); + packetData.writeBoolean(entity.isOnGround()); + ClientboundTeleportEntityPacket packet = ClientboundTeleportEntityPacket.STREAM_CODEC.decode(packetData); + for (Player player : getPlayersThatSee(entity)) { + PacketHelperImpl.send(player, packet); + } + } + + @Override + public void clientResetLoc(Entity entity) { + ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(entity.getEntityId(), PositionMoveRotation.of(((CraftEntity) entity).getHandle()), Set.of(), entity.isOnGround()); + for (Player player : getPlayersThatSee(entity)) { + PacketHelperImpl.send(player, packet); + } + } + + @Override + public void teleport(Entity entity, Location loc) { + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + nmsEntity.setYRot(loc.getYaw()); + nmsEntity.setXRot(loc.getPitch()); + if (nmsEntity instanceof ServerPlayer) { + nmsEntity.teleportTo(loc.getX(), loc.getY(), loc.getZ()); + } + nmsEntity.setPos(loc.getX(), loc.getY(), loc.getZ()); + } + + @Override + public void setBoundingBox(Entity entity, BoundingBox box) { + ((CraftEntity) entity).getHandle().setBoundingBox(new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ())); + } + + public static final Field EXPERIENCE_ORB_AGE = ReflectionHelper.getFields(net.minecraft.world.entity.ExperienceOrb.class).get("age", int.class); + + @Override + public void setTicksLived(Entity entity, int ticks) { + // Bypass Spigot's must-be-at-least-1-tick requirement, as negative tick counts are useful + ((CraftEntity) entity).getHandle().tickCount = ticks; + if (entity instanceof CraftFallingBlock craftFallingBlock) { + craftFallingBlock.getHandle().time = ticks; + } + else if (entity instanceof CraftItem craftItem) { + ((ItemEntity) craftItem.getHandle()).age = ticks; + } + else if (entity instanceof CraftExperienceOrb craftExperienceOrb) { + try { + EXPERIENCE_ORB_AGE.setInt(craftExperienceOrb.getHandle(), ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + } + + @Override + public void setHeadAngle(LivingEntity entity, float angle) { + ((CraftLivingEntity) entity).getHandle().setYHeadRot(angle); + } + + @Override + public void setEndermanAngry(Enderman enderman, boolean angry) { + ((CraftEnderman) enderman).getHandle().getEntityData().set(ENDERMAN_DATA_ACCESSOR_SCREAMING, angry); + } + + public static class FakeDamageSrc extends DamageSource { public DamageSource real; public FakeDamageSrc(DamageSource src) { super(null); real = src; } } + + public static DamageSources backupDamageSources; + + public static DamageSources getReusableDamageSources() { + if (backupDamageSources == null) { + backupDamageSources = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().damageSources(); + } + return backupDamageSources; + } + + public static DamageSource getSourceFor(net.minecraft.world.entity.Entity nmsSource, EntityDamageEvent.DamageCause cause, net.minecraft.world.entity.Entity nmsSourceProvider) { + DamageSources sources = nmsSourceProvider == null ? getReusableDamageSources() : nmsSourceProvider.level().damageSources(); + DamageSource src = sources.generic(); + if (nmsSource != null) { + if (nmsSource instanceof net.minecraft.world.entity.player.Player nmsPlayer) { + src = nmsSource.level().damageSources().playerAttack(nmsPlayer); + } + else if (nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) { + src = nmsSource.level().damageSources().mobAttack(nmsLivingEntity); + } + } + if (cause == null) { + return src; + } + return switch (cause) { + case CONTACT -> sources.cactus(); + case ENTITY_ATTACK -> sources.mobAttack(nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity ? nmsLivingEntity : null); + case ENTITY_SWEEP_ATTACK -> src != sources.generic() ? src.sweep() : src; + case PROJECTILE -> sources.thrown(nmsSource, nmsSource != null && nmsSource.getBukkitEntity() instanceof Projectile projectile + && projectile.getShooter() instanceof CraftEntity shooter ? shooter.getHandle() : null); + case SUFFOCATION -> sources.inWall(); + case FALL -> sources.fall(); + case FIRE -> sources.inFire(); + case FIRE_TICK -> sources.onFire(); + case MELTING -> sources.melting(); + case LAVA -> sources.lava(); + case DROWNING -> sources.drown(); + case BLOCK_EXPLOSION -> nmsSource instanceof PrimedTnt primedTnt ? sources.explosion(primedTnt, primedTnt.getOwner()) : sources.explosion(null); + case ENTITY_EXPLOSION -> sources.explosion(nmsSource, null); + case VOID -> sources.fellOutOfWorld(); + case LIGHTNING -> sources.lightningBolt(); + case STARVATION -> sources.starve(); + case POISON -> sources.poison(); + case MAGIC -> sources.magic(); + case WITHER -> sources.wither(); + case FALLING_BLOCK -> sources.fallingBlock(nmsSource); + case THORNS -> sources.thorns(nmsSource); + case DRAGON_BREATH -> sources.dragonBreath(); + case CUSTOM -> sources.generic(); + case FLY_INTO_WALL -> sources.flyIntoWall(); + case HOT_FLOOR -> sources.hotFloor(); + case CAMPFIRE -> sources.campfire(); + case CRAMMING -> sources.cramming(); + case DRYOUT -> sources.dryOut(); + case FREEZE -> sources.freeze(); + case SONIC_BOOM -> sources.sonicBoom(nmsSource); + case WORLD_BORDER -> sources.outOfBorder(); + case KILL -> sources.genericKill(); + case SUICIDE -> new FakeDamageSrc(src); + }; + } + + @Override + public void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause) { + if (target == null) { + return; + } + net.minecraft.world.entity.LivingEntity nmsTarget = ((CraftLivingEntity) target).getHandle(); + net.minecraft.world.entity.Entity nmsSource = source == null ? null : ((CraftEntity) source.getBukkitEntity()).getHandle(); + DamageSource src = getSourceFor(nmsSource, cause, nmsTarget); + if (src instanceof FakeDamageSrc fakeDamageSrc) { + src = fakeDamageSrc.real; + if (fireFakeDamageEvent(target, source, sourceLoc, cause, amount).isCancelled()) { + return; + } + } + nmsTarget.hurt(src, amount); + } + + @Override + public void setLastHurtBy(LivingEntity mob, LivingEntity damager) { + ((CraftLivingEntity) mob).getHandle().setLastHurtByMob(((CraftLivingEntity) damager).getHandle()); + } + + public static final Field FALLINGBLOCK_BLOCK_STATE = ReflectionHelper.getFields(FallingBlockEntity.class).getFirstOfType(BlockState.class); + + @Override + public void setFallingBlockType(FallingBlock fallingBlock, BlockData block) { + BlockState state = ((CraftBlockData) block).getState(); + FallingBlockEntity nmsEntity = ((CraftFallingBlock) fallingBlock).getHandle(); + try { + FALLINGBLOCK_BLOCK_STATE.set(nmsEntity, state); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) { + SpawnerBlockEntity nmsSpawner = BlockHelperImpl.getTE((CraftCreatureSpawner) spawner); + ServerLevel level = ((CraftWorld) spawner.getWorld()).getHandle(); + net.minecraft.world.entity.Entity nmsEntity = nmsSpawner.getSpawner().getOrCreateDisplayEntity(level, nmsSpawner.getBlockPos()); + return new EntityTag(nmsEntity.getBukkitEntity()); + } + + public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.zombie.Zombie.class).get("inWaterTime", int.class); + + @Override + public int getInWaterTime(Zombie zombie) { + try { + return ZOMBIE_INWATERTIME.getInt(((CraftZombie) zombie).getHandle()); + } + catch (Throwable ex) { + Debug.echoError(ex); + return 0; + } + } + + @Override + public void setInWaterTime(Zombie zombie, int ticks) { + try { + ZOMBIE_INWATERTIME.setInt(((CraftZombie) zombie).getHandle(), ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static final MethodHandle TRACKING_RANGE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ChunkMap.TrackedEntity.class, int.class); + + @Override + public void setTrackingRange(Entity entity, int range) { + try { + ChunkMap map = ((CraftWorld) entity.getWorld()).getHandle().getChunkSource().chunkMap; + ChunkMap.TrackedEntity entry = map.entityMap.get(entity.getEntityId()); + if (entry != null) { + TRACKING_RANGE_SETTER.invoke(entry, range); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public boolean isAggressive(org.bukkit.entity.Mob mob) { + return ((CraftMob) mob).getHandle().isAggressive(); + } + + @Override + public void setAggressive(org.bukkit.entity.Mob mob, boolean aggressive) { + ((CraftMob) mob).getHandle().setAggressive(aggressive); + } + + // Use reflection because Paper changes the method return type + public static final MethodHandle PLAYERLIST_REMOVE = ReflectionHelper.getMethodHandle(PlayerList.class, "remove", ServerPlayer.class); + + @Override + public void setUUID(Entity entity, UUID id) { + try { + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + nmsEntity.stopRiding(); + nmsEntity.getPassengers().forEach(net.minecraft.world.entity.Entity::stopRiding); + Level level = nmsEntity.level(); + DedicatedPlayerList playerList = ((CraftServer) Bukkit.getServer()).getHandle(); + if (nmsEntity instanceof ServerPlayer nmsPlayer) { + PLAYERLIST_REMOVE.invoke(playerList, nmsPlayer); + } + else { + nmsEntity.remove(net.minecraft.world.entity.Entity.RemovalReason.DISCARDED); + } + nmsEntity.unsetRemoved(); + nmsEntity.setUUID(id); + if (nmsEntity instanceof ServerPlayer nmsPlayer) { + playerList.placeNewPlayer(DenizenNetworkManagerImpl.getConnection(nmsPlayer), nmsPlayer, new CommonListenerCookie(nmsPlayer.getGameProfile(), nmsPlayer.connection.latency(), nmsPlayer.clientInformation(), nmsPlayer.connection.isTransferred())); + } + else { + level.addFreshEntity(nmsEntity); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static final Field SynchedEntityData_itemsById = ReflectionHelper.getFields(SynchedEntityData.class).get("itemsById"); + + public static Int2ObjectMap> getDataItems(Entity entity) { + try { + return (Int2ObjectMap>) SynchedEntityData_itemsById.get(((CraftEntity) entity).getHandle().getEntityData()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); // Stop the code here to avoid NPEs down the road + } + } + + public static void convertToInternalData(Entity entity, MapTag internalData, BiConsumer, Object> processConverted) { + Int2ObjectMap> dataItemsById = getDataItems(entity); + for (Map.Entry entry : internalData.entrySet()) { + int id = EntityDataNameMapper.getIdForName(((CraftEntity) entity).getHandle().getClass(), entry.getKey().low); + if (id == -1) { + Debug.echoError("Invalid internal data key: " + entry.getKey()); + return; + } + SynchedEntityData.DataItem dataItem = dataItemsById.get(id); + if (dataItem == null) { + Debug.echoError("Invalid internal data id '" + id + "': couldn't be matched to any internal data for entity of type '" + entity.getType() + "'."); + return; + } + Object converted = ReflectionSetCommand.convertObjectTypeFor(dataItem.getValue().getClass(), entry.getValue()); + if (converted != null) { + processConverted.accept(dataItem, converted); + } + } + } + + @Override + public List convertInternalEntityDataValues(Entity entity, MapTag internalData) { + List dataValues = new ArrayList<>(internalData.size()); + convertToInternalData(entity, internalData, (dataItem, converted) -> dataValues.add(PacketHelperImpl.createEntityData(dataItem.getAccessor(), converted))); + return dataValues; + } + + @Override + public void modifyInternalEntityData(Entity entity, MapTag internalData) { + SynchedEntityData nmsEntityData = ((CraftEntity) entity).getHandle().getEntityData(); + convertToInternalData(entity, internalData, (dataItem, converted) -> nmsEntityData.set(dataItem.getAccessor(), converted)); + } + + @Override + public void startUsingItem(LivingEntity entity, EquipmentSlot hand) { + ((CraftLivingEntity) entity).getHandle().startUsingItem(hand == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND); + } + + @Override + public void stopUsingItem(LivingEntity entity) { + ((CraftLivingEntity) entity).getHandle().stopUsingItem(); + } + + @Override + public void openHorseInventory(Player player, AbstractHorse horse) { + net.minecraft.world.entity.animal.equine.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle(); + ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory); + } + + private CompoundTag getRawEntityNBT(net.minecraft.world.entity.Entity entity) { + return Handler.useValueOutput(entity::saveWithoutId); + } + + @Override + public CompoundBinaryTag getRawNBT(Entity entity) { + return NBTAdapter.toAPI(getRawEntityNBT(((CraftEntity) entity).getHandle())); + } + + @Override + public void modifyRawNBT(Entity entity, CompoundBinaryTag tag) { + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + CompoundTag nmsTag = NBTAdapter.toNMS(tag); + CompoundTag nmsMergedTag = getRawEntityNBT(nmsEntity).merge(nmsTag); + UUID uuid = nmsEntity.getUUID(); + Handler.useValueInput(nmsMergedTag, nmsEntity::load); + nmsEntity.setUUID(uuid); + } + + @Override + public EntityState.ArmadilloState getArmadilloState(org.bukkit.entity.Armadillo entity) { + Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle(); + return switch (armadillo.getState()) { + case IDLE -> EntityState.ArmadilloState.IDLE; + case ROLLING -> EntityState.ArmadilloState.ROLLING; + case SCARED -> EntityState.ArmadilloState.SCARED; + case UNROLLING -> EntityState.ArmadilloState.UNROLLING; + }; + } + + @Override + public void setArmadilloState(org.bukkit.entity.Armadillo entity, EntityState.ArmadilloState state) { + Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle(); + armadillo.switchToState(switch (state) { + case IDLE -> Armadillo.ArmadilloState.IDLE; + case ROLLING -> Armadillo.ArmadilloState.ROLLING; + case SCARED -> Armadillo.ArmadilloState.SCARED; + case UNROLLING -> Armadillo.ArmadilloState.UNROLLING; + }); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/FishingHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/FishingHelperImpl.java new file mode 100644 index 0000000000..f6630b5a3c --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/FishingHelperImpl.java @@ -0,0 +1,156 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.interfaces.FishingHelper; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.storage.loot.BuiltInLootTables; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftFishHook; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent; + +import java.lang.reflect.Field; +import java.util.List; + +public class FishingHelperImpl implements FishingHelper { + + @Override + public org.bukkit.inventory.ItemStack getResult(FishHook fishHook, CatchType catchType) { + FishingHook nmsHook = ((CraftFishHook) fishHook).getHandle(); + ItemStack result = switch (catchType) { + case DEFAULT -> { + ServerLevel nmsWorld = ((CraftWorld) fishHook.getWorld()).getHandle(); + ItemStack nmsFishingRod = nmsHook.getPlayerOwner().getMainHandItem(); + float f = nmsWorld.getRandom().nextFloat(); + float i = EnchantmentHelper.getFishingTimeReduction(nmsWorld, nmsFishingRod, nmsHook.getPlayerOwner()); + int j = EnchantmentHelper.getFishingLuckBonus(nmsWorld, nmsFishingRod, nmsHook.getPlayerOwner()); + float f1 = 0.1F - i * 0.025F - (float) j * 0.01F; + float f2 = 0.05F + i * 0.01F - (float) j * 0.01F; + + f1 = Mth.clamp(f1, 0.0F, 1.0F); + f2 = Mth.clamp(f2, 0.0F, 1.0F); + if (f < f1) { + yield catchRandomJunk(nmsHook); + } + else { + f -= f1; + if (f < f2) { + yield catchRandomTreasure(nmsHook); + } + else { + yield catchRandomFish(nmsHook); + } + } + } + case JUNK -> catchRandomJunk(nmsHook); + case TREASURE -> catchRandomTreasure(nmsHook); + case FISH -> catchRandomFish(nmsHook); + default -> null; + }; + return result != null ? CraftItemStack.asBukkitCopy(result) : null; + } + + public ItemStack getRandomReward(FishingHook nmsHook, ResourceKey key) { + ServerLevel nmsWorld = (ServerLevel) nmsHook.level(); + LootParams nmsLootParams = new LootParams.Builder(nmsWorld) + .withParameter(LootContextParams.ORIGIN, new Vec3(nmsHook.getX(), nmsHook.getY(), nmsHook.getZ())) + .withParameter(LootContextParams.TOOL, new ItemStack(Items.FISHING_ROD)) + .create(LootContextParamSets.FISHING); + List nmsItems = nmsWorld.getServer().reloadableRegistries().getLootTable(key).getRandomItems(nmsLootParams); + return nmsItems.get(nmsWorld.getRandom().nextInt(nmsItems.size())); + } + + @Override + public FishHook spawnHook(Location location, Player player) { + ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle(); + FishingHook hook = new FishingHook(((CraftPlayer) player).getHandle(), nmsWorld, 0, 0); + nmsWorld.addFreshEntity(hook, CreatureSpawnEvent.SpawnReason.CUSTOM); + return (FishHook) hook.getBukkitEntity(); + } + + private ItemStack catchRandomJunk(FishingHook fishHook) { + return getRandomReward(fishHook, BuiltInLootTables.FISHING_JUNK); + } + + private ItemStack catchRandomTreasure(FishingHook fishHook) { + return getRandomReward(fishHook, BuiltInLootTables.FISHING_TREASURE); + } + + private ItemStack catchRandomFish(FishingHook fishHook) { + return getRandomReward(fishHook, BuiltInLootTables.FISHING_FISH); + } + + public static final Field FISHING_HOOK_NIBBLE = ReflectionHelper.getFields(FishingHook.class).get("nibble", int.class); + public static final Field FISHING_HOOK_LURE_TIME = ReflectionHelper.getFields(FishingHook.class).get("timeUntilLured", int.class); + public static final Field FISHING_HOOK_HOOK_TIME = ReflectionHelper.getFields(FishingHook.class).get("timeUntilHooked", int.class); + + @Override + public FishHook getHookFrom(Player player) { + FishingHook nmsHook = ((CraftPlayer) player).getHandle().fishing; + if (nmsHook == null) { + return null; + } + return (FishHook) nmsHook.getBukkitEntity(); + } + + @Override + public void setNibble(FishHook hook, int ticks) { + FishingHook nmsHook = ((CraftFishHook) hook).getHandle(); + try { + FISHING_HOOK_NIBBLE.setInt(nmsHook, ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void setHookTime(FishHook hook, int ticks) { + FishingHook nmsHook = ((CraftFishHook) hook).getHandle(); + try { + FISHING_HOOK_HOOK_TIME.setInt(nmsHook, ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public int getLureTime(FishHook hook) { + FishingHook nmsHook = ((CraftFishHook) hook).getHandle(); + try { + return FISHING_HOOK_LURE_TIME.getInt(nmsHook); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + return -1; + } + + @Override + public void setLureTime(FishHook hook, int ticks) { + FishingHook nmsHook = ((CraftFishHook) hook).getHandle(); + try { + FISHING_HOOK_LURE_TIME.setInt(nmsHook, ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/ItemHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/ItemHelperImpl.java new file mode 100644 index 0000000000..8312001b82 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/ItemHelperImpl.java @@ -0,0 +1,796 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.interfaces.ItemHelper; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.properties.item.ItemComponentsPatch; +import com.denizenscript.denizen.objects.properties.item.ItemRawNBT; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizen.utilities.PaperAPITools; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.objects.core.MapTag; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.collect.*; +import com.google.gson.JsonObject; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.JsonOps; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.advancements.criterion.BlockPredicate; +import net.minecraft.advancements.criterion.DataComponentMatchers; +import net.minecraft.core.*; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.fixes.References; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.item.AdventureModePredicate; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStackTemplate; +import net.minecraft.world.item.alchemy.PotionBrewing; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.item.component.ItemLore; +import net.minecraft.world.item.component.ResolvableProfile; +import net.minecraft.world.item.component.TypedEntityData; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.item.crafting.BlastingRecipe; +import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.minecraft.world.item.crafting.SmithingTransformRecipe; +import net.minecraft.world.item.crafting.SmokingRecipe; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.saveddata.maps.MapId; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.entity.CraftEntityType; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.*; +import org.bukkit.craftbukkit.map.CraftMapView; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.*; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.SmithingTrimRecipe; +import org.bukkit.inventory.TransmuteRecipe; +import org.bukkit.map.MapView; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class ItemHelperImpl extends ItemHelper { + + public static final Recipe.CommonInfo BASE_RECIPE_INFO = new Recipe.CommonInfo(true); + + public static net.minecraft.world.item.crafting.RecipeHolder getNMSRecipe(NamespacedKey key) { + ResourceKey> nmsKey = ResourceKey.create(Registries.RECIPE, CraftNamespacedKey.toMinecraft(key)); + return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().byKey(nmsKey).orElse(null); + } + + public static final MethodHandle CRAFT_ITEM_STACK_AS_TEMPLATE = Handler.reflectPaperRenamed(CraftItemStack.class, "asNMSTemplate", "asTemplate", ItemStack.class); + + public static ItemStackTemplate asNMSTemplate(ItemStack item) { + try { + return (ItemStackTemplate) CRAFT_ITEM_STACK_AS_TEMPLATE.invokeExact(item); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static final Field RecipeManager_featureFlagSet = ReflectionHelper.getFields(RecipeManager.class).getFirstOfType(FeatureFlagSet.class); + + public void setMaxStackSize(Material material, int size) { + try { + ReflectionHelper.getFinalSetter(Material.class, "maxStack").invoke(material, size); + Holder.Reference nmsItemHolder = BuiltInRegistries.ITEM.get(CraftNamespacedKey.toMinecraft(material.getKey())).orElseThrow(); + nmsItemHolder.bindComponents(DataComponentMap.composite(nmsItemHolder.components(), DataComponentMap.builder().set(DataComponents.MAX_STACK_SIZE, size).build())); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static RecipeManager getRecipeManager() { + return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager(); + } + + public static CompoundTag serializeNmsItem(net.minecraft.world.item.ItemStack nmsItem) { + return (CompoundTag) net.minecraft.world.item.ItemStack.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsItem).getOrThrow(); + } + + public static net.minecraft.world.item.ItemStack parseNmsItem(CompoundTag nmsTag) { + return net.minecraft.world.item.ItemStack.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsTag).getOrThrow(); + } + + public Object recipeManagerFeatureFlagSetCache = null; + + @Override + public void blockRecipeFinalization() { + try { + RecipeManager manager = getRecipeManager(); + Object flags = RecipeManager_featureFlagSet.get(manager); + if (flags != null) { + recipeManagerFeatureFlagSetCache = flags; + RecipeManager_featureFlagSet.set(manager, null); + + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void restoreRecipeFinalization() { + try { + RecipeManager manager = getRecipeManager(); + if (recipeManagerFeatureFlagSetCache != null) { + RecipeManager_featureFlagSet.set(manager, recipeManagerFeatureFlagSetCache); + manager.finalizeRecipeLoading(); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void removeRecipes(List keys) { + blockRecipeFinalization(); + RecipeManager manager = getRecipeManager(); + for (NamespacedKey key: keys) { + ResourceKey> nmsKey = ResourceKey.create(Registries.RECIPE, CraftNamespacedKey.toMinecraft(key)); + manager.removeRecipe(nmsKey); + } + restoreRecipeFinalization(); + } + + @Override + public Integer burnTime(Material material) { + return MinecraftServer.getServer().fuelValues().burnDuration(new net.minecraft.world.item.ItemStack(CraftMagicNumbers.getItem(material))); + } + + @Override + public void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact) { + if (item.length == 1 && item[0].getType() == Material.AIR) { + recipe.setIngredient(c, new RecipeChoice.MaterialChoice(Material.AIR)); + } + else if (exact) { + recipe.setIngredient(c, new RecipeChoice.ExactChoice(item)); + } + else { + Material[] mats = new Material[item.length]; + for (int i = 0; i < item.length; i++) { + mats[i] = item[i].getType(); + } + recipe.setIngredient(c, new RecipeChoice.MaterialChoice(mats)); + } + } + + // TODO: Recipe registration should be moved to the API + public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) { + if (!exact) { + return Ingredient.of(Arrays.stream(items).map(item -> CraftMagicNumbers.getItem(item.getType()))); + } + return Ingredient.ofStacks(Arrays.stream(items).map(CraftItemStack::asNMSCopy).toList()); + } + + public static ResourceKey> createRecipeKey(String name) { + return ResourceKey.create(Registries.RECIPE, Identifier.fromNamespaceAndPath("denizen", name)); + } + + @Override + public void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category) { + ResourceKey> key = createRecipeKey(keyName); + Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact); + AbstractCookingRecipe recipe; + AbstractCookingRecipe.CookingBookInfo bookInfo = new AbstractCookingRecipe.CookingBookInfo(category == null ? CookingBookCategory.MISC : CookingBookCategory.valueOf(CoreUtilities.toUpperCase(category)), group); + if (type.equalsIgnoreCase("smoker")) { + recipe = new SmokingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time); + } + else if (type.equalsIgnoreCase("blast")) { + recipe = new BlastingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time); + } + else if (type.equalsIgnoreCase("campfire")) { + recipe = new CampfireCookingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time); + } + else { + recipe = new SmeltingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time); + } + RecipeHolder holder = new RecipeHolder<>(key, recipe); + getRecipeManager().addRecipe(holder); + } + + @Override + public void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact) { + ResourceKey> key = createRecipeKey(keyName); + Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact); + StonecutterRecipe recipe = new StonecutterRecipe(BASE_RECIPE_INFO, itemRecipe, asNMSTemplate(result)); + RecipeHolder holder = new RecipeHolder<>(key, recipe); + getRecipeManager().addRecipe(holder); + } + + @Override + public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) { + ResourceKey> key = createRecipeKey(keyName); + Ingredient templateItemRecipe = templateItem.length == 0 ? null : itemArrayToRecipe(templateItem, templateExact); + Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact); + Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact); + SmithingTransformRecipe recipe = new SmithingTransformRecipe(BASE_RECIPE_INFO, Optional.ofNullable(templateItemRecipe), baseItemRecipe, Optional.of(upgradeItemRecipe), asNMSTemplate(result)); + RecipeHolder holder = new RecipeHolder<>(key, recipe); + getRecipeManager().addRecipe(holder); + } + + @Override + public void registerShapelessRecipe(String keyName, String group, ItemStack result, List ingredients, boolean[] exact, String category) { + ResourceKey> key = createRecipeKey(keyName); + ArrayList ingredientList = new ArrayList<>(); + CraftingBookCategory categoryValue = category == null ? CraftingBookCategory.MISC : CraftingBookCategory.valueOf(CoreUtilities.toUpperCase(category)); + for (int i = 0; i < ingredients.size(); i++) { + ingredientList.add(itemArrayToRecipe(ingredients.get(i), exact[i])); + } + ShapelessRecipe recipe = new ShapelessRecipe(BASE_RECIPE_INFO, new CraftingRecipe.CraftingBookInfo(categoryValue, group), asNMSTemplate(result), NonNullList.of(null, ingredientList.toArray(new Ingredient[0]))); + RecipeHolder holder = new RecipeHolder<>(key, recipe); + getRecipeManager().addRecipe(holder); + } + + public static final MethodHandle CRAFT_RECIPE_ADD_TO_MANAGER = Handler.reflectPaperRenamed(CraftRecipe.class, "addToCraftingManager", "addToRecipeManager"); + + @Override + public void registerOtherRecipe(org.bukkit.inventory.Recipe recipe) { + // This method copied from Bukkit CraftServer source, just to bypass unwanted paper patch + CraftRecipe toAdd; + if (recipe instanceof CraftRecipe craft) { + toAdd = craft; + } + else if (recipe instanceof ShapedRecipe) { + toAdd = CraftShapedRecipe.fromBukkitRecipe((ShapedRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.ShapelessRecipe) { + toAdd = CraftShapelessRecipe.fromBukkitRecipe((org.bukkit.inventory.ShapelessRecipe)recipe); + } + else if (recipe instanceof FurnaceRecipe) { + toAdd = CraftFurnaceRecipe.fromBukkitRecipe((FurnaceRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.BlastingRecipe) { + toAdd = CraftBlastingRecipe.fromBukkitRecipe((org.bukkit.inventory.BlastingRecipe)recipe); + } + else if (recipe instanceof CampfireRecipe) { + toAdd = CraftCampfireRecipe.fromBukkitRecipe((CampfireRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.SmokingRecipe) { + toAdd = CraftSmokingRecipe.fromBukkitRecipe((org.bukkit.inventory.SmokingRecipe)recipe); + } + else if (recipe instanceof StonecuttingRecipe) { + toAdd = CraftStonecuttingRecipe.fromBukkitRecipe((StonecuttingRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.SmithingTransformRecipe) { + toAdd = CraftSmithingTransformRecipe.fromBukkitRecipe((org.bukkit.inventory.SmithingTransformRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.SmithingTrimRecipe) { + toAdd = CraftSmithingTrimRecipe.fromBukkitRecipe((SmithingTrimRecipe)recipe); + } + else { + if (!(recipe instanceof org.bukkit.inventory.TransmuteRecipe)) { + if (recipe instanceof ComplexRecipe) { + throw new UnsupportedOperationException("Cannot add custom complex recipe"); + } + return; + } + toAdd = CraftTransmuteRecipe.fromBukkitRecipe((TransmuteRecipe)recipe); + } + try { + CRAFT_RECIPE_ADD_TO_MANAGER.invokeExact(toAdd); + } + catch (Throwable e) { + Debug.echoError(e); + } + } + + @Override + public String getJsonString(ItemStack itemStack) { + String json = CraftItemStack.asNMSCopy(itemStack).getDisplayName().getStyle().toString().replace("\\", "\\\\").replace("\"", "\\\""); + return json.substring(176, json.length() - 185); + } + + @Override + public JsonObject getRawHoverComponentsJson(ItemStack item) { + DataComponentPatch nmsComponents = CraftItemStack.asNMSCopy(item).getComponentsPatch(); + if (nmsComponents.isEmpty()) { + return null; + } + return DataComponentPatch.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), nmsComponents).getOrThrow().getAsJsonObject(); + } + + @Override + public ItemStack applyRawHoverComponentsJson(ItemStack item, JsonObject components) { + return DataComponentPatch.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), components).mapOrElse( + nmsComponents -> { + net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); + nmsItem.applyComponents(nmsComponents); + return CraftItemStack.asCraftMirror(nmsItem); + }, + error -> { + Debug.echoError("Invalid hover item data '" + components + "': " + error.message()); + return item; + }); + } + + @Override + public PlayerProfile getSkullSkin(ItemStack is) { + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is); + ResolvableProfile profile = itemStack.get(DataComponents.PROFILE); + if (profile != null) { + Property property = Iterables.getFirst(profile.partialProfile().properties().get("textures"), null); + return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile), + property != null ? property.value() : null, + property != null ? property.signature() : null); + } + return null; + } + + @Override + public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) { + GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile); + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + nmsItemStack.set(DataComponents.PROFILE, ResolvableProfile.createResolved(gameProfile)); + return CraftItemStack.asBukkitCopy(nmsItemStack); + } + + @Override + public ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + nmsItemStack.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, customData -> customData.update(nmsCompoundTag -> nmsCompoundTag.put(key, NBTAdapter.toNMS(value)))); + return CraftItemStack.asBukkitCopy(nmsItemStack); + } + + // TODO: 1.20.6: this now needs to serialize components into NBT every single time, should probably only return custom NBT data with specialized methods for other usages + // TODO: 1.20.6: NBT structure is different basically everywhere, usages of this will need an update + @Override + public CompoundBinaryTag getNbtData(ItemStack itemStack) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + if (nmsItemStack != null && !nmsItemStack.isEmpty()) { + return NBTAdapter.toAPI(serializeNmsItem(nmsItemStack)); + } + return CompoundBinaryTag.empty(); + } + + // TODO: 1.20.6: same as getNbtData, ideally needs to only set custom NBT data and have specialized methods for other usages + @Override + public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) { + return CraftItemStack.asBukkitCopy(parseNmsItem(NBTAdapter.toNMS(compoundTag))); + } + + @Override + public CompoundBinaryTag getCustomData(ItemStack item) { + CustomData customData = CraftItemStack.asNMSCopy(item).get(DataComponents.CUSTOM_DATA); + return customData != null ? NBTAdapter.toAPI(customData.copyTag()) : null; + } + + @Override + public ItemStack setCustomData(ItemStack item, CompoundBinaryTag data) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + if (data == null) { + nmsItemStack.remove(DataComponents.CUSTOM_DATA); + } + else { + nmsItemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(NBTAdapter.toNMS(data))); + } + return CraftItemStack.asBukkitCopy(nmsItemStack); + } + + public static final int DATA_VERSION_1_20_4 = 3700; + + @Override + public ItemStack setPartialOldNbt(ItemStack item, CompoundBinaryTag oldTag) { + int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion(); + CompoundTag nmsOldTag = new CompoundTag(); + nmsOldTag.putString("id", item.getType().getKey().toString()); + nmsOldTag.putByte("Count", (byte) item.getAmount()); + nmsOldTag.put("tag", NBTAdapter.toNMS(oldTag)); + CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, nmsOldTag), DATA_VERSION_1_20_4, currentDataVersion).getValue(); + CompoundTag nmsCurrentTag = serializeNmsItem(CraftItemStack.asNMSCopy(item)); + CompoundTag nmsMergedTag = nmsCurrentTag.merge(nmsUpdatedTag); + return CraftItemStack.asBukkitCopy(parseNmsItem(nmsMergedTag)); + } + + @Override + public CompoundBinaryTag getEntityData(ItemStack item) { + TypedEntityData> entityData = CraftItemStack.asNMSCopy(item).get(DataComponents.ENTITY_DATA); + return entityData != null ? NBTAdapter.toAPI(entityData.getUnsafe()) : null; + } + + @Override + public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + if (entityNbt == null || entityNbt.isEmpty() || (entityNbt.size() == 1 && entityNbt.contains("id"))) { + nmsItemStack.remove(DataComponents.ENTITY_DATA); + } + else { + CompoundTag nmsEntityNbt = NBTAdapter.toNMS(entityNbt); + nmsEntityNbt.remove("id"); + nmsItemStack.set(DataComponents.ENTITY_DATA, TypedEntityData.of(CraftEntityType.bukkitToMinecraft(entityType), nmsEntityNbt)); + } + return CraftItemStack.asBukkitCopy(nmsItemStack); + } + + @Override + public MapTag getRawComponentsPatch(ItemStack item, boolean excludeHandled) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + DataComponentPatch patch = nmsItemStack.getComponentsPatch(); + if (excludeHandled) { + patch = patch.forget(componentType -> { + Identifier componentId = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(componentType); + return ItemComponentsPatch.propertyHandledComponents.contains(componentId.toString()); + }); + } + if (patch.isEmpty()) { + return new MapTag(); + } + RegistryOps registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE); + CompoundTag nmsPatch = (CompoundTag) DataComponentPatch.CODEC.encodeStart(registryOps, patch).getOrThrow(); + if (excludeHandled && Denizen.supportsPaper) { + nmsPatch.keySet().removeIf(s -> s.charAt(0) == '!'); + if (nmsPatch.isEmpty()) { + return new MapTag(); + } + } + MapTag rawComponents = (MapTag) ItemRawNBT.nbtTagToObject(NBTAdapter.toAPI(nmsPatch)); + rawComponents.putObject(ItemComponentsPatch.DATA_VERSION_KEY, new ElementTag(CraftMagicNumbers.INSTANCE.getDataVersion())); + return rawComponents; + } + + @Override + public ItemStack setRawComponentsPatch(ItemStack item, MapTag rawComponentsMap, int dataVersion, Consumer errorHandler) { + int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion(); + CompoundBinaryTag rawComponents = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(rawComponentsMap, CoreUtilities.errorButNoDebugContext, ""); + CompoundTag nmsRawComponents = NBTAdapter.toNMS(rawComponents); + RegistryOps registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE); + if (dataVersion < currentDataVersion) { + CompoundTag legacyItemData = new CompoundTag(); + legacyItemData.putString("id", item.getType().getKey().toString()); + legacyItemData.putInt("count", item.getAmount()); + legacyItemData.put("components", nmsRawComponents); + CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(registryOps, legacyItemData), dataVersion, currentDataVersion).getValue(); + nmsRawComponents = nmsUpdatedTag.getCompound("components").orElseGet(CompoundTag::new); + } + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + DataComponentPatch.CODEC.parse(registryOps, nmsRawComponents) + .ifError(error -> errorHandler.accept(error.message())) + .ifSuccess(nmsItemStack::applyComponents); + return CraftItemStack.asBukkitCopy(nmsItemStack); + } + + public static final Field AdventureModePredicate_predicates = ReflectionHelper.getFields(AdventureModePredicate.class).get("predicates"); + + @Override + public List getCanPlaceOn(ItemStack item) { + return getAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON); + } + + @Override + public ItemStack setCanPlaceOn(ItemStack item, List canPlaceOn) { + return setAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON, canPlaceOn); + } + + @Override + public List getCanBreak(ItemStack item) { + return getAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK); + } + + @Override + public ItemStack setCanBreak(ItemStack item, List canBreak) { + return setAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK, canBreak); + } + + private List getAdventureModePredicateMaterials(ItemStack item, DataComponentType nmsComponent) { + AdventureModePredicate nmsAdventurePredicate = CraftItemStack.asNMSCopy(item).get(nmsComponent); + if (nmsAdventurePredicate == null) { + return null; + } + List nmsPredicates; + try { + nmsPredicates = (List) AdventureModePredicate_predicates.get(nmsAdventurePredicate); + } + catch (Throwable e) { + Debug.echoError(e); + return null; + } + List materials = new ArrayList<>(); + for (BlockPredicate nmsPredicate : nmsPredicates) { + nmsPredicate.blocks().ifPresent(nmsHolderSet -> { + for (Holder nmsHolder : nmsHolderSet) { + materials.add(CraftMagicNumbers.getMaterial(nmsHolder.value())); + } + }); + } + return materials; + } + + private ItemStack setAdventureModePredicateMaterials(ItemStack item, DataComponentType nmsComponent, List materials) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + AdventureModePredicate nmsAdventurePredicate = nmsItemStack.get(nmsComponent); + if (materials == null) { + if (nmsAdventurePredicate == null) { + return item; + } + nmsItemStack.remove(nmsComponent); + return CraftItemStack.asBukkitCopy(nmsItemStack); + } + BlockPredicate nmsPredicate = new BlockPredicate(Optional.of( + HolderSet.direct(material -> BuiltInRegistries.BLOCK.get(CraftNamespacedKey.toMinecraft(material.getKey())).orElseThrow(), materials) + ), Optional.empty(), Optional.empty(), DataComponentMatchers.ANY); + nmsItemStack.set(nmsComponent, new AdventureModePredicate(List.of(nmsPredicate))); + return CraftItemStack.asBukkitCopy(nmsItemStack); + } + + @Override + public void setInventoryItem(Inventory inventory, ItemStack item, int slot) { + if (inventory instanceof CraftInventoryPlayer && ((CraftInventoryPlayer) inventory).getInventory().player == null) { + ((CraftInventoryPlayer) inventory).getInventory().setItem(slot, CraftItemStack.asNMSCopy(item)); + } + else { + inventory.setItem(slot, item); + } + } + + @Override + public String getDisplayName(ItemTag item) { + if (!item.getItemMeta().hasDisplayName()) { + return null; + } + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack()); + Component nmsDisplayName = nmsItemStack.get(DataComponents.CUSTOM_NAME); + return FormattedTextHelper.stringify(Handler.componentToSpigot(nmsDisplayName)); + } + + @Override + public List getLore(ItemTag item) { + if (!item.getItemMeta().hasLore()) { + return null; + } + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack()); + ItemLore nmsLore = nmsItemStack.get(DataComponents.LORE); + List outList = new ArrayList<>(nmsLore.lines().size()); + for (Component nmsLoreLine : nmsLore.lines()) { + outList.add(FormattedTextHelper.stringify(Handler.componentToSpigot(nmsLoreLine))); + } + return outList; + } + + @Override + public void setDisplayName(ItemTag item, String name) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack()); + if (name == null || name.isEmpty()) { + nmsItemStack.remove(DataComponents.CUSTOM_NAME); + } + else { + nmsItemStack.set(DataComponents.CUSTOM_NAME, Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE))); + } + item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack)); + } + + @Override + public void setLore(ItemTag item, List lore) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack()); + if (lore == null || lore.isEmpty()) { + nmsItemStack.remove(DataComponents.LORE); + } + else { + List nmsLore = new ArrayList<>(lore.size()); + for (String loreLine : lore) { + nmsLore.add(Handler.componentToNMS(FormattedTextHelper.parse(loreLine, ChatColor.WHITE))); + } + nmsItemStack.set(DataComponents.LORE, new ItemLore(nmsLore)); + } + item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack)); + } + + /** + * Copied from MapItem.getCorrectStateForFluidBlock. + */ + public static BlockState getCorrectStateForFluidBlock(Level world, BlockState blockState, BlockPos blockPos) { + FluidState fluid = blockState.getFluidState(); + return !fluid.isEmpty() && !blockState.isFaceSturdy(world, blockPos, Direction.UP) ? fluid.createLegacyBlock() : blockState; + } + + /** + * Copied from MapItem.update, redesigned slightly to render totally rather than just relative to a player. + * Some variables manually renamed for readability. + */ + public static void renderFullMap(MapItemSavedData worldmap, int xMin, int zMin, int xMax, int zMax) { + Level world = ((CraftWorld) worldmap.mapView.getWorld()).getHandle(); + int scale = 1 << worldmap.scale; + int mapX = worldmap.centerX; + int mapZ = worldmap.centerZ; + for (int x = xMin; x < xMax; x++) { + double d0 = 0.0D; + for (int z = zMin; z < zMax; z++) { + int k2 = (mapX / scale + x - 64) * scale; + int l2 = (mapZ / scale + z - 64) * scale; + Multiset multiset = LinkedHashMultiset.create(); + LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2)); + if (!chunk.isEmpty()) { + ChunkPos chunkcoordintpair = chunk.getPos(); + int i3 = k2 & 15; + int j3 = l2 & 15; + int k3 = 0; + double d1 = 0.0D; + if (world.dimensionType().hasCeiling()) { + int l3 = k2 + l2 * 231871; + l3 = l3 * l3 * 31287121 + l3 * 11; + if ((l3 >> 20 & 1) == 0) { + multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(world, BlockPos.ZERO), 10); + } + else { + multiset.add(Blocks.STONE.defaultBlockState().getMapColor(world, BlockPos.ZERO), 100); + } + + d1 = 100.0D; + } + else { + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + BlockPos.MutableBlockPos blockposition_mutableblockposition1 = new BlockPos.MutableBlockPos(); + for (int i4 = 0; i4 < scale; ++i4) { + for (int j4 = 0; j4 < scale; ++j4) { + int k4 = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i4 + i3, j4 + j3) + 1; + BlockState iblockdata; + if (k4 <= world.getMinY() + 1) { + iblockdata = Blocks.BEDROCK.defaultBlockState(); + } + else { + do { + --k4; + blockposition_mutableblockposition.set(chunkcoordintpair.getMinBlockX() + i4 + i3, k4, chunkcoordintpair.getMinBlockZ() + j4 + j3); + iblockdata = chunk.getBlockState(blockposition_mutableblockposition); + } while (iblockdata.getMapColor(world, blockposition_mutableblockposition) == MapColor.NONE && k4 > world.getMinY()); + if (k4 > world.getMinY() && !iblockdata.getFluidState().isEmpty()) { + int l4 = k4 - 1; + blockposition_mutableblockposition1.set(blockposition_mutableblockposition); + + BlockState iblockdata1; + do { + blockposition_mutableblockposition1.setY(l4--); + iblockdata1 = chunk.getBlockState(blockposition_mutableblockposition1); + k3++; + } while (l4 > world.getMinY() && !iblockdata1.getFluidState().isEmpty()); + iblockdata = getCorrectStateForFluidBlock(world, iblockdata, blockposition_mutableblockposition); + } + } + worldmap.checkBanners(world, chunkcoordintpair.getMinBlockX() + i4 + i3, chunkcoordintpair.getMinBlockZ() + j4 + j3); + d1 += (double) k4 / (double) (scale * scale); + multiset.add(iblockdata.getMapColor(world, blockposition_mutableblockposition)); + } + } + } + k3 /= scale * scale; + double d2 = (d1 - d0) * 4.0D / (double) (scale + 4) + ((double) (x + z & 1) - 0.5D) * 0.4D; + byte b0 = 1; + if (d2 > 0.6D) { + b0 = 2; + } + if (d2 < -0.6D) { + b0 = 0; + } + MapColor materialmapcolor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MapColor.NONE); + if (materialmapcolor == MapColor.WATER) { + d2 = (double) k3 * 0.1D + (double) (x + z & 1) * 0.2D; + b0 = 1; + if (d2 < 0.5D) { + b0 = 2; + } + if (d2 > 0.9D) { + b0 = 0; + } + } + d0 = d1; + worldmap.updateColor(x, z, (byte) (materialmapcolor.id * 4 + b0)); + } + } + } + } + + @Override + public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) { + MapItemSavedData worldmap = ((CraftServer) Bukkit.getServer()).getServer().getLevel(net.minecraft.world.level.Level.OVERWORLD).getMapData(new MapId(mapId)); + if (worldmap == null) { + return false; + } + renderFullMap(worldmap, xMin, zMin, xMax, zMax); + return true; + } + + @Override + public BlockData getPlacedBlock(Material material) { + Item nmsItem = BuiltInRegistries.ITEM.getOptional(CraftNamespacedKey.toMinecraft(material.getKey())).orElse(null); + if (nmsItem instanceof BlockItem) { + Block block = ((BlockItem) nmsItem).getBlock(); + return CraftBlockData.fromData(block.defaultBlockState()); + } + return null; + } + + @Override + public boolean isValidMix(ItemStack input, ItemStack ingredient) { + net.minecraft.world.item.ItemStack nmsInput = CraftItemStack.asNMSCopy(input); + net.minecraft.world.item.ItemStack nmsIngredient = CraftItemStack.asNMSCopy(ingredient); + return MinecraftServer.getServer().potionBrewing().hasMix(nmsInput, nmsIngredient); + } + + public static Class PaperPotionMix_CLASS = null; + public static Map customBrewingRecipes = null; + + @Override + public Map getCustomBrewingRecipes() { + if (customBrewingRecipes == null) { + customBrewingRecipes = Maps.transformValues((Map) ReflectionHelper.getFieldValue(PotionBrewing.class, "customMixes", MinecraftServer.getServer().potionBrewing()), paperMix -> { + if (PaperPotionMix_CLASS == null) { + PaperPotionMix_CLASS = paperMix.getClass(); + } + RecipeChoice ingredient = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, "ingredient", paperMix)); + RecipeChoice input = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, "input", paperMix)); + ItemStack result = CraftItemStack.asBukkitCopy(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, "result", paperMix)); + return new BrewingRecipe(input, ingredient, result); + }); + } + return customBrewingRecipes; + } + + private RecipeChoice convertChoice(Predicate nmsPredicate) { + // Not an instance of net.minecraft.world.item.crafting.Ingredient = a predicate recipe choice + if (nmsPredicate instanceof Ingredient ingredient) { + return CraftRecipe.toBukkit(ingredient); + } + return PaperAPITools.instance.createPredicateRecipeChoice(item -> nmsPredicate.test(CraftItemStack.asNMSCopy(item))); + } + + @Override + public byte[] renderMap(MapView mapView, Player player) { + return ((CraftMapView) mapView).render((CraftPlayer) player).buffer; + } + + @Override + public int getFoodPoints(Material itemType) { + return CraftMagicNumbers.getItem(itemType).components().get(DataComponents.FOOD).nutrition(); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/NBTAdapter.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/NBTAdapter.java new file mode 100644 index 0000000000..9072903cba --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/NBTAdapter.java @@ -0,0 +1,143 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import net.kyori.adventure.nbt.*; +import net.minecraft.nbt.*; + +import java.lang.invoke.MethodHandle; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class NBTAdapter { + + public static final MethodHandle COMPOUND_TAG_MAP_CONSTRUCTOR = ReflectionHelper.getConstructor(CompoundTag.class, Map.class); + + public static Tag toNMS(BinaryTag tag) { + if (tag instanceof ByteBinaryTag byteTag) { + return switch (byteTag.value()) { + case 0 -> ByteTag.ZERO; + case 1 -> ByteTag.ONE; + default -> ByteTag.valueOf(byteTag.value()); + }; + } + else if (tag instanceof ShortBinaryTag shortTag) { + return ShortTag.valueOf(shortTag.value()); + } + else if (tag instanceof IntBinaryTag intTag) { + return IntTag.valueOf(intTag.value()); + } + else if (tag instanceof LongBinaryTag longTag) { + return LongTag.valueOf(longTag.value()); + } + else if (tag instanceof FloatBinaryTag floatTag) { + return FloatTag.valueOf(floatTag.value()); + } + else if (tag instanceof DoubleBinaryTag doubleTag) { + return DoubleTag.valueOf(doubleTag.value()); + } + else if (tag instanceof ByteArrayBinaryTag byteArrayTag) { + return new ByteArrayTag(byteArrayTag.value()); + } + else if (tag instanceof IntArrayBinaryTag intArrayTag) { + return new IntArrayTag(intArrayTag.value()); + } + else if (tag instanceof LongArrayBinaryTag longArrayTag) { + return new LongArrayTag(longArrayTag.value()); + } + else if (tag instanceof StringBinaryTag stringTag) { + return StringTag.valueOf(stringTag.value()); + } + else if (tag instanceof ListBinaryTag listTag) { + return toNMS(listTag); + } + else if (tag instanceof CompoundBinaryTag compoundTag) { + return toNMS(compoundTag); + } + else if (tag instanceof EndBinaryTag) { + return EndTag.INSTANCE; + } + throw new IllegalStateException("Unrecognized API tag of type '" + tag.type() + "': " + tag); + } + + public static BinaryTag toAPI(Tag nmsTag) { + if (nmsTag instanceof ByteTag nmsByteTag) { + return ByteBinaryTag.byteBinaryTag(nmsByteTag.value()); + } + else if (nmsTag instanceof ShortTag nmsShortTag) { + return ShortBinaryTag.shortBinaryTag(nmsShortTag.value()); + } + else if (nmsTag instanceof IntTag nmsIntTag) { + return IntBinaryTag.intBinaryTag(nmsIntTag.value()); + } + else if (nmsTag instanceof LongTag nmsLongTag) { + return LongBinaryTag.longBinaryTag(nmsLongTag.value()); + } + else if (nmsTag instanceof FloatTag nmsFloatTag) { + return FloatBinaryTag.floatBinaryTag(nmsFloatTag.value()); + } + else if (nmsTag instanceof DoubleTag nmsDoubleTag) { + return DoubleBinaryTag.doubleBinaryTag(nmsDoubleTag.value()); + } + else if (nmsTag instanceof ByteArrayTag nmsByteArrayTag) { + return ByteArrayBinaryTag.byteArrayBinaryTag(nmsByteArrayTag.getAsByteArray()); + } + else if (nmsTag instanceof IntArrayTag nmsIntArrayTag) { + return IntArrayBinaryTag.intArrayBinaryTag(nmsIntArrayTag.getAsIntArray()); + } + else if (nmsTag instanceof LongArrayTag nmsLongArrayTag) { + return LongArrayBinaryTag.longArrayBinaryTag(nmsLongArrayTag.getAsLongArray()); + } + else if (nmsTag instanceof StringTag nmsStringTag) { + return StringBinaryTag.stringBinaryTag(nmsStringTag.value()); + } + else if (nmsTag instanceof ListTag nmsListTag) { + return toAPI(nmsListTag); + } + else if (nmsTag instanceof CompoundTag nmsCompoundTag) { + return toAPI(nmsCompoundTag); + } + else if (nmsTag instanceof EndTag) { + return EndBinaryTag.endBinaryTag(); + } + throw new IllegalStateException("Unrecognized NMS tag of type '" + nmsTag.getClass().getName() + '/' + nmsTag.getType().getName() + "': " + nmsTag); + } + + public static ListBinaryTag toAPI(ListTag nmsListTag) { + ListBinaryTag.Builder builder = ListBinaryTag.heterogeneousListBinaryTag(nmsListTag.size()); + for (Tag nmsValue : nmsListTag) { + builder.add(toAPI(nmsValue)); + } + return builder.build(); + } + + public static ListTag toNMS(ListBinaryTag listTag) { + List nmsTags = new ArrayList<>(listTag.size()); + for (BinaryTag value : listTag) { + nmsTags.add(toNMS(value)); + } + return new ListTag(nmsTags); + } + + public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size()); + for (Map.Entry nmsEntry : nmsCompoundTag.entrySet()) { + builder.put(nmsEntry.getKey(), toAPI(nmsEntry.getValue())); + } + return builder.build(); + } + + public static CompoundTag toNMS(CompoundBinaryTag compoundTag) { + Map nmsTags = new HashMap<>(compoundTag.size()); + for (Map.Entry entry : compoundTag) { + nmsTags.put(entry.getKey(), toNMS(entry.getValue())); + } + try { + return (CompoundTag) COMPOUND_TAG_MAP_CONSTRUCTOR.invokeExact(nmsTags); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/PacketHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/PacketHelperImpl.java new file mode 100644 index 0000000000..38a889be6f --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/PacketHelperImpl.java @@ -0,0 +1,433 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.interfaces.PacketHelper; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.impl.SidebarImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.scripts.commands.entity.TeleportCommand; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizen.utilities.maps.MapImage; +import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; +import com.denizenscript.denizencore.objects.core.ColorTag; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.BrandPayload; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.Relative; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.monster.Creeper; +import net.minecraft.world.entity.monster.EnderMan; +import net.minecraft.world.entity.monster.spider.CaveSpider; +import net.minecraft.world.entity.monster.spider.Spider; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Team; +import org.bukkit.Bukkit; +import org.bukkit.EntityEffect; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; +import org.bukkit.block.sign.SignSide; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.map.CraftMapCanvas; +import org.bukkit.craftbukkit.map.CraftMapView; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.map.MapCanvas; +import org.bukkit.map.MapPalette; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; + +public class PacketHelperImpl implements PacketHelper { + + public static final EntityDataAccessor PLAYER_DATA_ACCESSOR_ABSORPTION = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, "DATA_PLAYER_ABSORPTION_ID", null); + + public static final EntityDataAccessor ENTITY_DATA_ACCESSOR_FLAGS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, "DATA_SHARED_FLAGS_ID", null); + + public static final MethodHandle ABILITIES_PACKET_FOV_SETTER = ReflectionHelper.getFinalSetter(ClientboundPlayerAbilitiesPacket.class, "walkingSpeed"); + + public static final Field ENTITY_TRACKER_ENTRY_GETTER = ReflectionHelper.getFields(ChunkMap.TrackedEntity.class).getFirstOfType(ServerEntity.class); + + public static final MethodHandle CANVAS_GET_BUFFER = ReflectionHelper.getMethodHandle(CraftMapCanvas.class, "getBuffer"); + public static final Field MAPVIEW_WORLDMAP = ReflectionHelper.getFields(CraftMapView.class).get("worldMap"); + + public static final EntityDataAccessor> ENTITY_DATA_ACCESSOR_CUSTOM_NAME = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, "DATA_CUSTOM_NAME", null); + public static final EntityDataAccessor ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, "DATA_CUSTOM_NAME_VISIBLE", null); + + @Override + public void setFakeAbsorption(Player player, float value) { + send(player, new ClientboundSetEntityDataPacket(player.getEntityId(), List.of(createEntityData(PLAYER_DATA_ACCESSOR_ABSORPTION, value)))); + } + + @Override + public void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly) { + AbstractContainerMenu menu = ((CraftPlayer) player).getHandle().containerMenu; + int windowId = playerOnly ? 0 : menu.containerId; + send(player, new ClientboundContainerSetSlotPacket(windowId, menu.incrementStateId(), slot, CraftItemStack.asNMSCopy(itemStack))); + } + + @Override + public void setFieldOfView(Player player, float fov) { + ClientboundPlayerAbilitiesPacket packet = new ClientboundPlayerAbilitiesPacket(((CraftPlayer) player).getHandle().getAbilities()); + if (!Float.isNaN(fov)) { + try { + ABILITIES_PACKET_FOV_SETTER.invoke(packet, fov); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + send(player, packet); + } + + @Override + public void respawn(Player player) { + ((CraftPlayer) player).getHandle().connection.handleClientCommand(new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN)); + } + + @Override + public void setVision(Player player, EntityType entityType) { + final net.minecraft.world.entity.LivingEntity entity; + if (entityType == EntityType.CREEPER) { + entity = new Creeper(net.minecraft.world.entity.EntityType.CREEPER, ((CraftWorld) player.getWorld()).getHandle()); + } + else if (entityType == EntityType.SPIDER) { + entity = new Spider(net.minecraft.world.entity.EntityType.SPIDER, ((CraftWorld) player.getWorld()).getHandle()); + } + else if (entityType == EntityType.CAVE_SPIDER) { + entity = new CaveSpider(net.minecraft.world.entity.EntityType.CAVE_SPIDER, ((CraftWorld) player.getWorld()).getHandle()); + } + else if (entityType == EntityType.ENDERMAN) { + entity = new EnderMan(net.minecraft.world.entity.EntityType.ENDERMAN, ((CraftWorld) player.getWorld()).getHandle()); + } + else { + return; + } + + // Spectating an entity then immediately respawning the player prevents a client shader update, + // allowing the player to retain whatever vision the mob they spectated had. + send(player, new ClientboundAddEntityPacket(entity, 0, BlockPos.ZERO)); + send(player, new ClientboundSetCameraPacket(entity)); + NMSHandler.playerHelper.refreshPlayer(player); + send(player, new ClientboundRemoveEntitiesPacket(entity.getId())); + } + + @Override + public void showBlockAction(Player player, Location location, int action, int state) { + BlockPos position = Handler.toBlockPos(location); + Block block = ((CraftWorld) location.getWorld()).getHandle().getBlockState(position).getBlock(); + send(player, new ClientboundBlockEventPacket(position, block, action, state)); + } + + @Override + public void showTabListHeaderFooter(Player player, String header, String footer) { + Component cHeader = Handler.componentToNMS(FormattedTextHelper.parse(header, ChatColor.WHITE)); + Component cFooter = Handler.componentToNMS(FormattedTextHelper.parse(footer, ChatColor.WHITE)); + send(player, new ClientboundTabListPacket(cHeader, cFooter)); + } + + @Override + public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { + send(player, new ClientboundBundlePacket(List.of( + new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks), + new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))), + new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE))) + ))); + } + + @Override + public void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth) { + AttributeInstance attr = new AttributeInstance(Attributes.MAX_HEALTH, (a) -> {}); + attr.setBaseValue(maxHealth); + send(player, new ClientboundUpdateAttributesPacket(mob.getEntityId(), List.of(attr))); + send(player, new ClientboundSetEntityDataPacket(mob.getEntityId(), List.of(createEntityData(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) health)))); + } + + @Override + public void showSignEditor(Player player, Location location) { // TODO: MC 1.18: once 1.18 is removed, remove 'location' arg + NetworkInterceptHelper.enable(); + Sign sign = null; + BlockPos toOpen = null; + // It actually allows 8 blocks of distance, but we limit to 7 because the client doesn't properly round down + for (int i = 0; i < 8; i++) { + Location toCheck = player.getLocation(); + toCheck.setY(toCheck.getY() - i); + if (toCheck.getBlock().getState() instanceof Sign foundSign) { + sign = foundSign; + } + else { + sign = null; + toOpen = Handler.toBlockPos(toCheck); + break; + } + } + if (sign != null) { + toOpen = Handler.toBlockPos(sign.getLocation()); + SignSide front = sign.getSide(Side.FRONT); + for (int line = 0; line < 4; line++) { + front.setLine(line, ""); + } + player.sendBlockUpdate(sign.getLocation(), sign); + } + else { + player.sendBlockChange(new LocationTag(player.getWorld(), toOpen.getX(), toOpen.getY(), toOpen.getZ()), Material.OAK_WALL_SIGN.createBlockData()); + } + DenizenNetworkManagerImpl.getNetworkManager(player).packetListener.fakeSignExpected = toOpen; + send(player, new ClientboundOpenSignEditorPacket(toOpen, true)); + } + + @Override + public void forceSpectate(Player player, Entity entity) { + send(player, new ClientboundSetCameraPacket(((CraftEntity) entity).getHandle())); + } + + public static void forceRespawnPlayerEntity(Entity entity, Player viewer) { + ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap; + ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId()); + if (entityTracker != null) { + try { + ServerEntity entry = (ServerEntity) ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker); + if (entry != null) { + entry.removePairing(((CraftPlayer) viewer).getHandle()); + entry.addPairing(((CraftPlayer) viewer).getHandle()); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + } + + @Override + public void sendRename(Player player, Entity entity, String name, boolean listMode) { + try { + if (entity.getType() == EntityType.PLAYER) { + if (listMode) { + send(player, new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ((CraftPlayer) player).getHandle())); + } + else { + // For player entities, force a respawn packet and let the dynamic intercept correct the details + forceRespawnPlayerEntity(entity, player); + } + return; + } + List> list = List.of( + createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)))), + createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true) + ); + send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), list)); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static HashMap> noCollideTeamMap = new HashMap<>(); + + @Override + public void generateNoCollideTeam(Player player, UUID noCollide) { + removeNoCollideTeam(player, noCollide); + PlayerTeam team = new PlayerTeam(SidebarImpl.dummyScoreboard, Utilities.generateRandomColors(8)); + team.getPlayers().add(noCollide.toString()); + team.setCollisionRule(Team.CollisionRule.NEVER); + HashMap map = noCollideTeamMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()); + map.put(noCollide, team); + send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); + } + + @Override + public void removeNoCollideTeam(Player player, UUID noCollide) { + if (noCollide == null || !player.isOnline()) { + noCollideTeamMap.remove(player.getUniqueId()); + return; + } + HashMap map = noCollideTeamMap.get(player.getUniqueId()); + if (map == null) { + return; + } + PlayerTeam team = map.remove(noCollide); + if (team != null) { + send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team)); + } + if (map.isEmpty()) { + noCollideTeamMap.remove(player.getUniqueId()); + } + } + + @Override + public void sendEntityMetadataFlagsUpdate(Player player, Entity entity) { + byte flags = ((CraftEntity) entity).getHandle().getEntityData().get(ENTITY_DATA_ACCESSOR_FLAGS); + send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), List.of(createEntityData(ENTITY_DATA_ACCESSOR_FLAGS, flags)))); + } + + @Override + public void sendEntityEffect(Player player, Entity entity, EntityEffect effect) { + send(player, new ClientboundEntityEventPacket(((CraftEntity) entity).getHandle(), effect.getData())); + } + + @Override + public int getPacketStats(Player player, boolean sent) { + DenizenNetworkManagerImpl netMan = DenizenNetworkManagerImpl.getNetworkManager(player); + return sent ? netMan.packetsSent : netMan.packetsReceived; + } + + @Override + public void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) { + if (x > 127 || y > 127) { + return; + } + int width = Math.min(image.width, 128 - x), + height = Math.min(image.height, 128 - y); + if (x + width <= 0 || y + height <= 0) { + return; + } + try { + boolean anyChanged = false; + byte[] buffer = (byte[]) CANVAS_GET_BUFFER.invoke(canvas); + for (int x2 = x < 0 ? -x : 0; x2 < width; ++x2) { + for (int y2 = y < 0 ? -y : 0; y2 < height; ++y2) { + byte p = bytes[y2 * image.width + x2]; + if (p != MapPalette.TRANSPARENT) { + int index = (y2 + y) * 128 + (x2 + x); + if (buffer[index] != p) { + buffer[index] = p; + anyChanged = true; + } + } + } + } + if (anyChanged) { + // Flag the whole image as dirty + MapItemSavedData map = (MapItemSavedData) MAPVIEW_WORLDMAP.get(canvas.getMapView()); + map.setColorsDirty(Math.max(x, 0), Math.max(y, 0)); + map.setColorsDirty(width + x - 1, height + y - 1); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void setNetworkManagerFor(Player player) { + DenizenNetworkManagerImpl.setNetworkManager(player); + } + + @Override + public void enableNetworkManager() { + DenizenNetworkManagerImpl.enableNetworkManager(); + } + + @Override + public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) { + BlockPos nmsPos = Handler.toBlockPos(location); + LocationTag displayPos = !name.isEmpty() ? LocationTag.valueOf(name, CoreUtilities.noDebugContext) : null; + send(player, new ClientboundGameTestHighlightPosPacket(nmsPos, displayPos != null ? Handler.toBlockPos(displayPos) : nmsPos)); + } + + @Override + public void clearDebugTestMarker(Player player) { + } + + @Override + public void sendBrand(Player player, String brand) { + BrandPayload payload = new BrandPayload(brand); + send(player, new ClientboundCustomPayloadPacket(payload)); + } + + @Override + public void sendCollectItemEntity(Player player, Entity taker, Entity item, int amount) { + send(player, new ClientboundTakeItemEntityPacket(item.getEntityId(), taker.getEntityId(), amount)); + } + + public Relative toNmsRelativeMovement(TeleportCommand.Relative relative) { + // TODO: 1.21.3: There seem to be more relative movement types now + return switch (relative) { + case X -> Relative.X; + case Y -> Relative.Y; + case Z -> Relative.Z; + case YAW -> Relative.Y_ROT; + case PITCH -> Relative.X_ROT; + }; + } + + @Override + public void sendRelativePositionPacket(Player player, double x, double y, double z, float yaw, float pitch, List relativeAxis) { + Set relativeMovements; + if (relativeAxis == null) { + relativeMovements = Relative.ALL; + } + else { + relativeMovements = EnumSet.noneOf(Relative.class); + for (TeleportCommand.Relative relative : relativeAxis) { + relativeMovements.add(toNmsRelativeMovement(relative)); + } + } + ClientboundPlayerPositionPacket packet = new ClientboundPlayerPositionPacket(0, new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), relativeMovements); + sendAsyncSafe(player, packet); + } + + @Override + public void sendRelativeLookPacket(Player player, float yaw, float pitch) { + sendRelativePositionPacket(player, 0, 0, 0, yaw, pitch, null); + } + + @Override + public void sendEntityDataPacket(List players, Entity entity, List data) { + ClientboundSetEntityDataPacket setEntityDataPacket = new ClientboundSetEntityDataPacket(entity.getEntityId(), (List>) (Object) data); + Iterator playerIterator = players.iterator(); + while (playerIterator.hasNext()) { + Player player = playerIterator.next(); + if (!DenizenNetworkManagerImpl.getConnection(player).isConnected()) { + playerIterator.remove(); + continue; + } + sendAsyncSafe(player, setEntityDataPacket); + } + } + + public static void send(Player player, Packet packet) { + ((CraftPlayer) player).getHandle().connection.send(packet); + } + + public static void broadcast(Packet packet) { + ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(packet); + } + + public static void sendAsyncSafe(Player player, Packet packet) { + DenizenNetworkManagerImpl.getConnection(player).channel.writeAndFlush(packet); + } + + public static SynchedEntityData.DataValue createEntityData(EntityDataAccessor accessor, T value) { + return new SynchedEntityData.DataValue<>(accessor.id(), accessor.serializer(), value); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/PlayerHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/PlayerHelperImpl.java new file mode 100644 index 0000000000..0760d3bbee --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/PlayerHelperImpl.java @@ -0,0 +1,508 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer; +import com.denizenscript.denizen.nms.enums.CustomEntityType; +import com.denizenscript.denizen.nms.interfaces.PlayerHelper; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.impl.ImprovedOfflinePlayerImpl; +import com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_1.impl.entities.CraftFakePlayerImpl; +import com.denizenscript.denizen.nms.v26_1.impl.entities.EntityItemProjectileImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.AbstractListenerPlayInImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizen.utilities.entity.DenizenEntityType; +import com.denizenscript.denizen.utilities.entity.FakeEntity; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import it.unimi.dsi.fastutil.ints.IntList; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket; +import net.minecraft.network.protocol.game.*; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.NameAndId; +import net.minecraft.server.players.PlayerList; +import net.minecraft.server.players.ServerOpList; +import net.minecraft.server.players.ServerOpListEntry; +import net.minecraft.stats.ServerRecipeBook; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagNetworkSerialization; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Avatar; +import net.minecraft.world.entity.Leashable; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.item.ItemCooldowns; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.LevelData; +import net.minecraft.world.phys.AABB; +import org.bukkit.*; +import org.bukkit.boss.BossBar; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.boss.CraftBossBar; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.Predicate; + +public class PlayerHelperImpl extends PlayerHelper { + + public static final Field ATTACK_COOLDOWN_TICKS = ReflectionHelper.getFields(LivingEntity.class).get("attackStrengthTicker", int.class); + + public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("aboveGroundTickCount", int.class); + public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("aboveGroundVehicleTickCount", int.class); + public static final Field PASSENGERS_PACKET_PASSENGERS = ReflectionHelper.getFields(ClientboundSetPassengersPacket.class).get("passengers", int[].class); + public static final MethodHandle PLAYER_RESPAWNCONFIG_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, "respawnConfig", ServerPlayer.RespawnConfig.class); + public static final MethodHandle SERVER_RECIPE_BOOK_ADD_HIGHLIGHT = ReflectionHelper.getMethodHandle(ServerRecipeBook.class, "addHighlight", ResourceKey.class); + + @Override + public void stopSound(Player player, NamespacedKey sound, SoundCategory category) { + ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(sound == null ? null : CraftNamespacedKey.toMinecraft(sound), null)); + } + + @Override + public void deTrackEntity(Player player, Entity entity) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + ChunkMap.TrackedEntity tracker = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); + if (tracker == null) { + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Failed to de-track entity " + entity.getEntityId() + " for " + player.getName() + ": tracker null"); + } + return; + } + sendEntityDestroy(player, entity); + tracker.removePlayer(nmsPlayer); + } + + public record TrackerData(PlayerTag player, ServerEntity tracker) {} + + @Override + public void addFakePassenger(List players, Entity vehicle, FakeEntity fakePassenger) { + ClientboundSetPassengersPacket packet = new ClientboundSetPassengersPacket(((CraftEntity) vehicle).getHandle()); + int[] newPassengers = Arrays.copyOf(packet.getPassengers(), packet.getPassengers().length + 1); + newPassengers[packet.getPassengers().length] = fakePassenger.id; + try { + PASSENGERS_PACKET_PASSENGERS.set(packet, newPassengers); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + for (PlayerTag player : players) { + PacketHelperImpl.send(player.getPlayerEntity(), packet); + } + } + + public record FakeEntitySynchronizer(ServerGamePacketListenerImpl target) implements ServerEntity.Synchronizer { + + @Override + public void sendToTrackingPlayers(Packet packet) { + target.send(packet); + } + + @Override + public void sendToTrackingPlayersAndSelf(Packet packet) { + sendToTrackingPlayers(packet); + } + + @Override + public void sendToTrackingPlayersFiltered(Packet packet, Predicate predicate) { + if (predicate.test(target.getPlayer())) { + sendToTrackingPlayers(packet); + } + } + + @Override + public void sendToTrackingPlayersFilteredAndSelf(Packet packet, Predicate predicate) { + sendToTrackingPlayersFiltered(packet, predicate); + } + } + + @Override + public FakeEntity sendEntitySpawn(List players, DenizenEntityType entityType, LocationTag location, ArrayList mechanisms, int customId, UUID customUUID, boolean autoTrack) { + CraftWorld world = ((CraftWorld) location.getWorld()); + net.minecraft.world.entity.Entity nmsEntity; + if (entityType.isCustom()) { + if (entityType.customEntityType == CustomEntityType.ITEM_PROJECTILE) { + ItemStack itemStack = new ItemStack(Material.STONE); + for (Mechanism mechanism : mechanisms) { + if (mechanism.matches("item") && mechanism.requireObject(ItemTag.class)) { + itemStack = mechanism.valueAsType(ItemTag.class).getItemStack(); + } + } + nmsEntity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack)); + } + else if (entityType.customEntityType == CustomEntityType.FAKE_PLAYER) { + String name = null; + String skin = null; + String blob = null; + for (Mechanism mechanism : new ArrayList<>(mechanisms)) { + if (mechanism.matches("name")) { + name = mechanism.getValue().asString(); + mechanisms.remove(mechanism); + } + else if (mechanism.matches("skin")) { + skin = mechanism.getValue().asString(); + mechanisms.remove(mechanism); + } + else if (mechanism.matches("skin_blob")) { + blob = mechanism.getValue().asString(); + mechanisms.remove(mechanism); + } + if (name != null && (skin != null || blob != null)) { + break; + } + } + nmsEntity = ((CraftFakePlayerImpl) NMSHandler.customEntityHelper.spawnFakePlayer(location, name, skin, blob, false)).getHandle(); + } + else { + throw new IllegalArgumentException("entityType"); + } + } + else { + org.bukkit.entity.Entity entity = world.createEntity(location, entityType.getBukkitEntityType().getEntityClass()); + nmsEntity = ((CraftEntity) entity).getHandle(); + } + if (customUUID != null) { + nmsEntity.setId(customId); + nmsEntity.setUUID(customUUID); + } + EntityTag entity = new EntityTag(nmsEntity.getBukkitEntity()); + entity.isFake = true; + entity.isFakeValid = true; + for (Mechanism mechanism : mechanisms) { + entity.safeAdjustDuplicate(mechanism); + } + nmsEntity.unsetRemoved(); + FakeEntity fake = new FakeEntity(players, location, entity.getBukkitEntity().getEntityId()); + fake.entity = new EntityTag(entity.getBukkitEntity()); + fake.entity.isFake = true; + fake.entity.isFakeValid = true; + List trackers = new ArrayList<>(); + fake.triggerSpawnPacket = (player) -> { + ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle(); + ServerGamePacketListenerImpl conn = nmsPlayer.connection; + final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, new FakeEntitySynchronizer(conn), Set.of(conn)); + tracker.addPairing(nmsPlayer); + final TrackerData data = new TrackerData(player, tracker); + trackers.add(data); + if (autoTrack) { + new BukkitRunnable() { + boolean wasOnline = true; + @Override + public void run() { + if (!fake.entity.isFakeValid) { + cancel(); + return; + } + if (player.isOnline()) { + if (!wasOnline) { + tracker.addPairing(((CraftPlayer) player.getPlayerEntity()).getHandle()); + wasOnline = true; + } + tracker.sendChanges(); + } + else if (wasOnline) { + wasOnline = false; + } + } + }.runTaskTimer(Denizen.getInstance(), 1, 1); + } + }; + for (PlayerTag player : players) { + fake.triggerSpawnPacket.accept(player); + } + fake.triggerUpdatePacket = () -> { + for (TrackerData tracker : trackers) { + if (tracker.player.isOnline()) { + tracker.tracker.sendChanges(); + } + } + }; + fake.triggerDestroyPacket = () -> { + for (TrackerData tracker : trackers) { + if (tracker.player.isOnline()) { + tracker.tracker.removePairing(((CraftPlayer) tracker.player.getPlayerEntity()).getHandle()); + } + } + trackers.clear(); + }; + return fake; + } + + @Override + public void sendEntityDestroy(Player player, Entity entity) { + ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId())); + } + + @Override + public int getFlyKickCooldown(Player player) { + ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection; + if (conn instanceof AbstractListenerPlayInImpl denizenListener) { + conn = denizenListener.oldListener; + } + try { + return Math.max(80 - Math.max(FLY_TICKS.getInt(conn), VEHICLE_FLY_TICKS.getInt(conn)), 0); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + return 80; + } + + @Override + public void setFlyKickCooldown(Player player, int ticks) { + ticks = 80 - ticks; + ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection; + if (conn instanceof AbstractListenerPlayInImpl denizenListener) { + conn = denizenListener.oldListener; + } + try { + FLY_TICKS.setInt(conn, ticks); + VEHICLE_FLY_TICKS.setInt(conn, ticks); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + } + + @Override + public int ticksPassedDuringCooldown(Player player) { + try { + return ATTACK_COOLDOWN_TICKS.getInt(((CraftPlayer) player).getHandle()); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + return -1; + } + + @Override + public float getMaxAttackCooldownTicks(Player player) { + return ((CraftPlayer) player).getHandle().getCurrentItemAttackStrengthDelay() + 3; + } + + @Override + public void setAttackCooldown(Player player, int ticks) { + try { + ATTACK_COOLDOWN_TICKS.setInt(((CraftPlayer) player).getHandle(), ticks); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + + } + + @Override + public boolean hasChunkLoaded(Player player, Chunk chunk) { + return ((CraftWorld) chunk.getWorld()).getHandle().getChunkSource().chunkMap + .getPlayers(new ChunkPos(chunk.getX(), chunk.getZ()), false).stream() + .anyMatch(entityPlayer -> entityPlayer.getUUID().equals(player.getUniqueId())); + } + + @Override + public void setTemporaryOp(Player player, boolean op) { + MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); + NameAndId nameAndId = new NameAndId(((CraftPlayer) player).getProfile()); + ServerOpList opList = server.getPlayerList().getOps(); + if (op) { + opList.add(new ServerOpListEntry(nameAndId, server.operatorUserPermissions(), opList.canBypassPlayerLimit(nameAndId))); + } + else { + opList.remove(nameAndId); + } + player.recalculatePermissions(); + } + + @Override + public void showEndCredits(Player player) { + ((CraftPlayer) player).getHandle().wonGame = true; + ((CraftPlayer) player).getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1f)); + } + + @Override + public ImprovedOfflinePlayer getOfflineData(UUID uuid) { + return new ImprovedOfflinePlayerImpl(uuid); + } + + @Override + public void resendRecipeDetails(Player player) { + RecipeManager recipeManager = ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager(); + ClientboundUpdateRecipesPacket updatePacket = new ClientboundUpdateRecipesPacket(recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes()); + ((CraftPlayer) player).getHandle().connection.send(updatePacket); + } + + @Override + public void resendDiscoveredRecipes(Player player) { + ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook(); + recipeBook.sendInitialRecipeBook(((CraftPlayer) player).getHandle()); + } + + @Override + public void quietlyAddRecipe(Player player, NamespacedKey key) { + ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook(); + RecipeHolder recipe = ItemHelperImpl.getNMSRecipe(key); + if (recipe == null) { + Debug.echoError("Cannot add recipe '" + key + "': it does not exist."); + return; + } + recipeBook.add(recipe.id()); + try { + SERVER_RECIPE_BOOK_ADD_HIGHLIGHT.invoke(recipeBook, recipe.id()); + } + catch (Throwable e) { + Debug.echoError(e); + } + } + + @Override + public byte getSkinLayers(Player player) { + return ((CraftPlayer) player).getHandle().getEntityData().get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION); + } + + @Override + public void setSkinLayers(Player player, byte flags) { + ((CraftPlayer) player).getHandle().getEntityData().set(Avatar.DATA_PLAYER_MODE_CUSTOMISATION, flags); + } + + @Override + public void setBossBarTitle(BossBar bar, String title) { + ((CraftBossBar) bar).getHandle().name = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE)); + ((CraftBossBar) bar).getHandle().broadcast(ClientboundBossEventPacket::createUpdateNamePacket); + } + + @Override + public boolean getSpawnForced(Player player) { + return ((CraftPlayer) player).getHandle().getRespawnConfig().forced(); + } + + @Override + public void setSpawnForced(Player player, boolean forced) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + try { + ServerPlayer.RespawnConfig config = nmsPlayer.getRespawnConfig(); + config = new ServerPlayer.RespawnConfig(config.respawnData(), forced); + PLAYER_RESPAWNCONFIG_SETTER.invoke(nmsPlayer, config); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public Location getBedSpawnLocation(Player player) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + ServerPlayer.RespawnConfig nmsRespawnConfig = nmsPlayer.getRespawnConfig(); + if (nmsRespawnConfig == null) { + return null; + } + LevelData.RespawnData nmsRespawnData = nmsRespawnConfig.respawnData(); + Level nmsWorld = MinecraftServer.getServer().getLevel(nmsRespawnData.dimension()); + return nmsWorld != null ? new Location(nmsWorld.getWorld(), nmsRespawnData.pos().getX(), nmsRespawnData.pos().getY(), nmsRespawnData.pos().getZ(), nmsRespawnData.yaw(), nmsRespawnData.pitch()) : null; + } + + @Override + public long getLastActionTime(Player player) { + return ((CraftPlayer) player).getHandle().getLastActionTime(); + } + + @Override + public void sendPlayerInfoAddPacket(Player player, EnumSet editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) { + EnumSet actions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class); + for (ProfileEditMode editMode : editModes) { + actions.add(switch (editMode) { + case ADD -> ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER; + case UPDATE_DISPLAY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME; + case UPDATE_LATENCY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY; + case UPDATE_GAME_MODE -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE; + case UPDATE_LISTED -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED; + }); + } + GameProfile profile = ProfileEditorImpl.createGameProfile(id, name, texture, signature); + // TODO: 1.21.3: Player list order and hat visibility support + ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), true, player.getPlayerListOrder(), null); + PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(actions, List.of(entry))); + } + + @Override + public void sendPlayerInfoRemovePacket(Player player, UUID id) { + PacketHelperImpl.send(player, new ClientboundPlayerInfoRemovePacket(List.of(id))); + } + + @Override + public void sendClimbableMaterials(Player player, List materials) { + Map>, TagNetworkSerialization.NetworkPayload> packetInput = TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries()); + Map tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, "tags", packetInput.get(BuiltInRegistries.BLOCK.key())); + IntList climbableBlocks = tags.get(BlockTags.CLIMBABLE.location()); + climbableBlocks.clear(); + for (Material material : materials) { + climbableBlocks.add(BuiltInRegistries.BLOCK.getId(CraftMagicNumbers.getBlock(material))); + } + PacketHelperImpl.send(player, new ClientboundUpdateTagsPacket(packetInput)); + } + + @Override + public void refreshPlayer(Player player) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + ServerLevel nmsWorld = nmsPlayer.level(); + nmsPlayer.connection.send(new ClientboundRespawnPacket(nmsPlayer.createCommonSpawnInfo(nmsWorld), ClientboundRespawnPacket.KEEP_ALL_DATA)); + nmsPlayer.connection.internalTeleport(PositionMoveRotation.of(nmsPlayer), Set.of()); + if (nmsPlayer.isPassenger()) { + nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer.getVehicle())); + } + if (nmsPlayer.isVehicle()) { + nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer)); + } + AABB boundingBox = AABB.ofSize(nmsPlayer.getBoundingBox().getCenter(), 32, 32, 32); + for (net.minecraft.world.entity.Entity nmsEntity : nmsWorld.getEntitiesOfClass(net.minecraft.world.entity.Entity.class, boundingBox, nmsEntity -> nmsEntity instanceof Leashable nmsLeashable && nmsPlayer.equals(nmsLeashable.getLeashHolder()))) { + nmsPlayer.connection.send(new ClientboundSetEntityLinkPacket(nmsEntity, nmsPlayer)); + } + if (!nmsPlayer.getCooldowns().cooldowns.isEmpty()) { + int tickCount = nmsPlayer.getCooldowns().tickCount; + for (Map.Entry entry : nmsPlayer.getCooldowns().cooldowns.entrySet()) { + nmsPlayer.connection.send(new ClientboundCooldownPacket(entry.getKey(), entry.getValue().endTime - tickCount)); + } + } + nmsPlayer.connection.send(new ClientboundSetExperiencePacket(nmsPlayer.experienceProgress, nmsPlayer.totalExperience, nmsPlayer.experienceLevel)); + for (MobEffectInstance nmsEffect : nmsPlayer.getActiveEffects()) { + nmsPlayer.connection.send(new ClientboundUpdateMobEffectPacket(nmsPlayer.getId(), nmsEffect, false)); + } + nmsPlayer.onUpdateAbilities(); + PlayerList nmsPlayerList = MinecraftServer.getServer().getPlayerList(); + nmsPlayerList.sendPlayerPermissionLevel(nmsPlayer); + nmsPlayerList.sendLevelInfo(nmsPlayer, nmsWorld); + nmsPlayerList.sendAllPlayerInfo(nmsPlayer); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/WorldHelperImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/WorldHelperImpl.java new file mode 100644 index 0000000000..ce84fabd38 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/WorldHelperImpl.java @@ -0,0 +1,103 @@ +package com.denizenscript.denizen.nms.v26_1.helpers; + +import com.denizenscript.denizen.nms.interfaces.WorldHelper; +import com.denizenscript.denizen.nms.v26_1.impl.BiomeNMSImpl; +import com.denizenscript.denizen.objects.BiomeTag; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.players.SleepStatus; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.storage.PrimaryLevelData; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.event.world.TimeSkipEvent; + +public class WorldHelperImpl implements WorldHelper { + + @Override + public boolean isStatic(World world) { + return ((CraftWorld) world).getHandle().isClientSide(); + } + + @Override + public void setStatic(World world, boolean isStatic) { + ServerLevel worldServer = ((CraftWorld) world).getHandle(); + ReflectionHelper.setFieldValue(net.minecraft.world.level.Level.class, "isClientSide", worldServer, isStatic); + } + + @Override + public float getLocalDifficulty(Location location) { + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + DifficultyInstance scaler = ((CraftWorld) location.getWorld()).getHandle().getCurrentDifficultyAt(pos); + return scaler.getEffectiveDifficulty(); + } + + @Override + public Location getNearestBiomeLocation(Location start, BiomeTag biome) { + Pair> result = ((CraftWorld) start.getWorld()).getHandle() + .findClosestBiome3d(b -> b.is(((BiomeNMSImpl) biome.getBiome()).biomeHolder.unwrapKey().get()), new BlockPos(start.getBlockX(), start.getBlockY(), start.getBlockZ()), 6400, 32, 64); + if (result == null || result.getFirst() == null) { + return null; + } + return new Location(start.getWorld(), result.getFirst().getX(), result.getFirst().getY(), result.getFirst().getZ()); + } + + @Override + public boolean areEnoughSleeping(World world, int percentage) { + SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, "sleepStatus", ((CraftWorld) world).getHandle()); + return status.areEnoughSleeping(percentage); + } + + @Override + public boolean areEnoughDeepSleeping(World world, int percentage) { + ServerLevel level = ((CraftWorld) world).getHandle(); + SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, "sleepStatus", level); + return status.areEnoughDeepSleeping(percentage, level.players()); + } + + @Override + public int getSkyDarken(World world) { + return ((CraftWorld) world).getHandle().getSkyDarken(); + } + + @Override + public boolean isDay(World world) { + return ((CraftWorld) world).getHandle().isBrightOutside(); + } + + @Override + public boolean isNight(World world) { + return ((CraftWorld) world).getHandle().isDarkOutside(); + } + + @Override + public void setDayTime(World world, long time) { + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + nmsWorld.dimensionType().defaultClock().ifPresent((clock) -> nmsWorld.clockManager().setTotalTicks(clock, time, TimeSkipEvent.SkipReason.CUSTOM)); + } + + @Override + public void setGameTime(World world, long time) { + ((PrimaryLevelData) ((CraftWorld) world).getHandle().levelData).setGameTime(time); + } + + // net.minecraft.server.level.ServerLevel#wakeUpAllPlayers() + @Override + public void wakeUpAllPlayers(World world) { + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, "sleepStatus", nmsWorld); + status.removeAllSleepers(); + nmsWorld.getPlayers(LivingEntity::isSleeping).forEach((player) -> player.stopSleepInBed(false, false)); + } + + @Override + public void clearWeather(World world) { + ((CraftWorld) world).getHandle().resetWeatherCycle(); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/BiomeNMSImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/BiomeNMSImpl.java new file mode 100644 index 0000000000..47e5afc040 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/BiomeNMSImpl.java @@ -0,0 +1,241 @@ +package com.denizenscript.denizen.nms.v26_1.impl; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.BiomeNMS; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.RegistrationInfo; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.random.Weighted; +import net.minecraft.util.random.WeightedList; +import net.minecraft.world.attribute.EnvironmentAttribute; +import net.minecraft.world.attribute.EnvironmentAttributeMap; +import net.minecraft.world.attribute.EnvironmentAttributes; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; +import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftEntityType; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.EntityType; + +import java.lang.invoke.MethodHandle; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class BiomeNMSImpl extends BiomeNMS { + + public static final MethodHandle BIOME_CLIMATESETTINGS_CONSTRUCTOR = ReflectionHelper.getConstructor(Biome.ClimateSettings.class, boolean.class, float.class, Biome.TemperatureModifier.class, float.class); + public static final MethodHandle MAPPED_REGISTRY_REGISTRATION_INFOS = ReflectionHelper.getFields(MappedRegistry.class).getGetter("registrationInfos"); + public static final MethodHandle BIOME_ATTRIBUTES_SETTER = ReflectionHelper.getFields(Biome.class).getSetter("attributes"); + + public Holder.Reference biomeHolder; + public ServerLevel world; + + public BiomeNMSImpl(ServerLevel world, NamespacedKey key) { + super(world.getWorld(), key); + this.world = world; + this.biomeHolder = getBiomeRegistry().get(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(key))).orElse(null); + } + + private MappedRegistry getBiomeRegistry() { + return (MappedRegistry) world.registryAccess().lookupOrThrow(Registries.BIOME); + } + + @Override + public DownfallType getDownfallTypeAt(Location location) { + Biome.Precipitation precipitation = biomeHolder.value().getPrecipitationAt(Handler.toBlockPos(location), world.getSeaLevel()); + return switch (precipitation) { + case RAIN -> DownfallType.RAIN; + case SNOW -> DownfallType.SNOW; + case NONE -> DownfallType.NONE; + }; + } + + @Override + public float getHumidity() { + return biomeHolder.value().climateSettings.downfall(); + } + + @Override + public float getBaseTemperature() { + return biomeHolder.value().getBaseTemperature(); + } + + @Override + public float getTemperatureAt(Location location) { + return biomeHolder.value().getTemperature(Handler.toBlockPos(location), world.getSeaLevel()); + } + + @Override + public boolean hasDownfall() { + return biomeHolder.value().hasPrecipitation(); + } + + @Override + public List getAmbientEntities() { + return getSpawnableEntities(MobCategory.AMBIENT); + } + + @Override + public List getCreatureEntities() { + return getSpawnableEntities(MobCategory.CREATURE); + } + + @Override + public List getMonsterEntities() { + return getSpawnableEntities(MobCategory.MONSTER); + } + + @Override + public List getWaterEntities() { + return getSpawnableEntities(MobCategory.WATER_CREATURE); + } + + @Override + public int getFoliageColor() { + // Check if the biome already has a default color + if (biomeHolder.value().getFoliageColor() != 0) { + return biomeHolder.value().getFoliageColor(); + } + // Based on net.minecraft.world.level.biome.Biome#getFoliageColorFromTexture() + float temperature = clampColor(getBaseTemperature()); + float humidity = clampColor(getHumidity()); + // Based on net.minecraft.world.level.FoliageColor#get() + humidity *= temperature; + int humidityValue = (int)((1.0f - humidity) * 255.0f); + int temperatureValue = (int)((1.0f - temperature) * 255.0f); + int index = temperatureValue << 8 | humidityValue; + return index >= 65536 ? 4764952 : getColor(index / 256, index % 256).asRGB(); + } + + public void setClimate(boolean hasPrecipitation, float temperature, Biome.TemperatureModifier temperatureModifier, float downfall) { + try { + Object newClimate = BIOME_CLIMATESETTINGS_CONSTRUCTOR.invoke(hasPrecipitation, temperature, temperatureModifier, downfall); + ReflectionHelper.setFieldValue(Biome.class, "climateSettings", biomeHolder.value(), newClimate); + setNetworkedRegistrationInfo(); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void setHumidity(float humidity) { + setClimate(hasDownfall(), getBaseTemperature(), getTemperatureModifier(), humidity); + } + + @Override + public void setBaseTemperature(float baseTemperature) { + setClimate(hasDownfall(), baseTemperature, getTemperatureModifier(), getHumidity()); + } + + @Override + public void setHasDownfall(boolean hasDownfall) { + setClimate(hasDownfall, getBaseTemperature(), getTemperatureModifier(), getHumidity()); + } + + @Override + public void setFoliageColor(int color) { + BiomeSpecialEffects nmsCurrEffects = biomeHolder.value().getSpecialEffects(); + BiomeSpecialEffects nmsNewEffects = new BiomeSpecialEffects( + nmsCurrEffects.waterColor(), Optional.of(color), nmsCurrEffects.dryFoliageColorOverride(), nmsCurrEffects.grassColorOverride(), nmsCurrEffects.grassColorModifier() + ); + ReflectionHelper.setFieldValue(Biome.class, "specialEffects", biomeHolder.value(), nmsNewEffects); + setNetworkedRegistrationInfo(); + } + + @Override + public int getFogColor() { + return getEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR); + } + + @Override + public void setFogColor(int color) { + setEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR, color); + } + + @Override + public int getWaterFogColor() { + return getEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR); + } + + @Override + public void setWaterFogColor(int color) { + setEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR, color); + } + + public T getEnvironmentAttribute(EnvironmentAttribute attribute) { + return biomeHolder.value().getAttributes().applyModifier(attribute, attribute.defaultValue()); + } + + public void setEnvironmentAttribute(EnvironmentAttribute attribute, T value) { + Biome nmsBiome = biomeHolder.value(); + EnvironmentAttributeMap newAttributeMap = EnvironmentAttributeMap.builder().putAll(nmsBiome.getAttributes()).set(attribute, value).build(); + try { + BIOME_ATTRIBUTES_SETTER.invokeExact(nmsBiome, newAttributeMap); + } + catch (Throwable e) { + Debug.echoError(e); + } + setNetworkedRegistrationInfo(); + } + + private List getSpawnableEntities(MobCategory creatureType) { + MobSpawnSettings mobs = biomeHolder.value().getMobSettings(); + WeightedList typeSettingList = mobs.getMobs(creatureType); + List entityTypes = new ArrayList<>(); + if (typeSettingList == null) { + return entityTypes; + } + for (Weighted meta : typeSettingList.unwrap()) { + entityTypes.add(CraftEntityType.minecraftToBukkit(meta.value().type())); + } + return entityTypes; + } + + @Override + public void setTo(Block block) { + if (((CraftWorld) block.getWorld()).getHandle() != this.world) { + NMSHandler.instance.getBiomeNMS(block.getWorld(), getKey()).setTo(block); + return; + } + // Based on CraftWorld source + BlockPos pos = new BlockPos(block.getX(), 0, block.getZ()); + if (world.hasChunkAt(pos)) { + LevelChunk chunk = world.getChunkAt(pos); + if (chunk != null) { + chunk.setBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2, biomeHolder); + chunk.markUnsaved(); + } + } + } + + public Biome.TemperatureModifier getTemperatureModifier() { + return biomeHolder.value().climateSettings.temperatureModifier(); + } + + private void setNetworkedRegistrationInfo() { + try { + Map, RegistrationInfo> registrationInfos = (Map, RegistrationInfo>) MAPPED_REGISTRY_REGISTRATION_INFOS.invokeExact(getBiomeRegistry()); + registrationInfos.put(biomeHolder.key(), RegistrationInfo.BUILT_IN); + } + catch (Throwable e) { + Debug.echoError("Failed to set biome registration info, changes may not be synced correctly."); + Debug.echoError(e); + } + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/ImprovedOfflinePlayerImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/ImprovedOfflinePlayerImpl.java new file mode 100644 index 0000000000..dfed25aac1 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/ImprovedOfflinePlayerImpl.java @@ -0,0 +1,169 @@ +package com.denizenscript.denizen.nms.v26_1.impl; + +import com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.helpers.NBTAdapter; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ParticleStatus; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.ItemStackWithSlot; +import net.minecraft.world.entity.EntityEquipment; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeMap; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.attributes.DefaultAttributes; +import net.minecraft.world.entity.player.ChatVisiblity; +import net.minecraft.world.inventory.PlayerEnderChestContainer; +import net.minecraft.world.level.storage.ValueOutput; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftInventoryPlayer; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.UUID; +import java.util.function.Consumer; + +public class ImprovedOfflinePlayerImpl extends ImprovedOfflinePlayer { + + public ImprovedOfflinePlayerImpl(UUID playeruuid) { + super(playeruuid); + } + + public static class OfflinePlayerInventory extends net.minecraft.world.entity.player.Inventory { + + public OfflinePlayerInventory(net.minecraft.world.entity.player.Player entityhuman) { + super(entityhuman, new EntityEquipment()); // TODO: 1.21.5: is the new Equipment right here? + } + + @Override + public InventoryHolder getOwner() { + return null; + } + } + + public static class OfflineCraftInventoryPlayer extends CraftInventoryPlayer { + + public OfflineCraftInventoryPlayer(net.minecraft.world.entity.player.Inventory inventory) { + super(inventory); + } + + @Override + public HumanEntity getHolder() { + return null; + } + } + + public static ServerPlayer fakeNmsPlayer; + + public static ServerPlayer getFakeNmsPlayer() { + if (fakeNmsPlayer == null) { + MinecraftServer server = ((CraftServer)Bukkit.getServer()).getServer(); + World world = Bukkit.getWorlds().getFirst(); + GameProfile fakeProfile = new GameProfile(new UUID(0, 0xABC123), "fakeplayer"); + ClientInformation fakeClientInfo = new ClientInformation("en", 0, ChatVisiblity.HIDDEN, false, 0, HumanoidArm.LEFT, true, false, ParticleStatus.MINIMAL); + fakeNmsPlayer = new ServerPlayer(server, ((CraftWorld) world).getHandle(), fakeProfile, fakeClientInfo); + } + return fakeNmsPlayer; + } + + public void editData(Consumer editor) { + this.compound = NBTAdapter.toAPI(Handler.useValueOutput(NBTAdapter.toNMS(this.compound), editor)); + markModified(); + } + + @Override + public org.bukkit.inventory.PlayerInventory getInventory() { + if (inventory == null) { + net.minecraft.world.entity.player.Inventory newInv = new OfflinePlayerInventory(getFakeNmsPlayer()); + Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> newInv.load(valueInput.listOrEmpty("Inventory", ItemStackWithSlot.CODEC))); + inventory = new OfflineCraftInventoryPlayer(newInv); + } + return inventory; + } + + @Override + public void setInventory(org.bukkit.inventory.PlayerInventory inventory) { + CraftInventoryPlayer inv = (CraftInventoryPlayer) inventory; + editData(valueOutput -> inv.getInventory().save(valueOutput.list("Inventory", ItemStackWithSlot.CODEC))); + } + + @Override + public Inventory getEnderChest() { + if (enderchest == null) { + PlayerEnderChestContainer nmsEnderChest = new PlayerEnderChestContainer(getFakeNmsPlayer()); + Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> nmsEnderChest.fromSlots(valueInput.listOrEmpty("EnderItems", ItemStackWithSlot.CODEC))); + enderchest = new CraftInventory(nmsEnderChest); + } + return enderchest; + } + + @Override + public void setEnderChest(Inventory inventory) { + editData(valueOutput -> ((PlayerEnderChestContainer) ((CraftInventory) inventory).getInventory()).storeAsSlots(valueOutput.list("EnderItems", ItemStackWithSlot.CODEC))); + } + + @Override + public double getMaxHealth() { + AttributeInstance maxHealth = getAttributes().getInstance(Attributes.MAX_HEALTH); + return maxHealth == null ? Attributes.MAX_HEALTH.value().getDefaultValue() : maxHealth.getValue(); + } + + @Override + public void setMaxHealth(double input) { + AttributeMap attributes = getAttributes(); + AttributeInstance maxHealth = attributes.getInstance(Attributes.MAX_HEALTH); + maxHealth.setBaseValue(input); + setAttributes(attributes); + } + + private AttributeMap getAttributes() { + AttributeMap amb = new AttributeMap(DefaultAttributes.getSupplier(net.minecraft.world.entity.EntityType.PLAYER)); + Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> valueInput.read("attributes", AttributeInstance.Packed.LIST_CODEC).ifPresent(amb::apply)); + return amb; + } + + public void setAttributes(AttributeMap attributes) { + editData(valueOutput -> valueOutput.store("attributes", AttributeInstance.Packed.LIST_CODEC, attributes.pack())); + } + + @Override + protected boolean loadPlayerData(UUID uuid) { + try { + this.player = uuid; + this.file = new File(MinecraftServer.getServer().playerDataStorage.getPlayerDir(), this.player + ".dat"); + if (this.file.exists()) { + this.compound = NBTAdapter.toAPI(NbtIo.readCompressed(new FileInputStream(this.file), NbtAccounter.unlimitedHeap())); + return true; + } + } + catch (Exception e) { + Debug.echoError(e); + } + return false; + } + + @Override + public void saveInternal(CompoundBinaryTag compound) { + try { + NbtIo.writeCompressed(NBTAdapter.toNMS(compound), new FileOutputStream(this.file)); + } + catch (Exception e) { + Debug.echoError(e); + } + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/ProfileEditorImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/ProfileEditorImpl.java new file mode 100644 index 0000000000..d620ff984f --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/ProfileEditorImpl.java @@ -0,0 +1,168 @@ +package com.denizenscript.denizen.nms.v26_1.impl; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.ProfileEditor; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.helpers.PacketHelperImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.scripts.commands.entity.RenameCommand; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.collect.ImmutableMultimap; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.datafixers.util.Either; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.component.ResolvableProfile; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerRespawnEvent; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; + +public class ProfileEditorImpl extends ProfileEditor { + + @Override + protected void updatePlayer(final Player player, final boolean isSkinChanging) { + final ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + final UUID uuid = player.getUniqueId(); + ClientboundPlayerInfoRemovePacket removePlayerInfoPacket = new ClientboundPlayerInfoRemovePacket(List.of(uuid)); + ClientboundPlayerInfoUpdatePacket addPlayerInfoPacket = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(nmsPlayer)); + for (Player otherPlayer : Bukkit.getServer().getOnlinePlayers()) { + PacketHelperImpl.send(otherPlayer, removePlayerInfoPacket); + PacketHelperImpl.send(otherPlayer, addPlayerInfoPacket); + } + for (Player otherPlayer : NMSHandler.entityHelper.getPlayersThatSee(player)) { + if (!otherPlayer.getUniqueId().equals(uuid)) { + PacketHelperImpl.forceRespawnPlayerEntity(player, otherPlayer); + } + } + if (isSkinChanging) { + ((CraftServer) Bukkit.getServer()).getHandle().respawn(nmsPlayer, true, Entity.RemovalReason.CHANGED_DIMENSION, PlayerRespawnEvent.RespawnReason.PLUGIN); + } + else { + NMSHandler.playerHelper.refreshPlayer(player); + } + player.updateInventory(); + } + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, ProfileEditorImpl::processPlayerInfoUpdatePacket); + } + + public static ClientboundPlayerInfoUpdatePacket processPlayerInfoUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket) { + if (ProfileEditor.mirrorUUIDs.isEmpty() && !RenameCommand.hasAnyDynamicRenames() && fakeProfiles.isEmpty()) { + return playerInfoUpdatePacket; + } + EnumSet actions = playerInfoUpdatePacket.actions(); + if (!actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) && !actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) { + return playerInfoUpdatePacket; + } + boolean any = false; + for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) { + if (shouldChange(entry)) { + any = true; + break; + } + } + if (!any) { + return playerInfoUpdatePacket; + } + GameProfile ownProfile = networkManager.player.getGameProfile(); + List modifiedEntries = new ArrayList<>(playerInfoUpdatePacket.entries().size()); + for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) { + if (!shouldChange(entry)) { + modifiedEntries.add(entry); + continue; + } + String rename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), false); + GameProfile baseProfile = fakeProfiles.containsKey(entry.profileId()) ? getGameProfile(fakeProfiles.get(entry.profileId())) : entry.profile(); + PropertyMap modifiedProperties; + if (ProfileEditor.mirrorUUIDs.contains(entry.profileId())) { + modifiedProperties = ownProfile.properties(); + } + else { + // On Paper 1.19+, we use Paper's PlayerProfile API instead of this system + modifiedProperties = Denizen.supportsPaper ? entry.profile().properties() : baseProfile.properties(); + } + GameProfile modifiedProfile = new GameProfile(baseProfile.id(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : baseProfile.name(), modifiedProperties); + String listRename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), true); + Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : entry.displayName(); + ClientboundPlayerInfoUpdatePacket.Entry modifiedEntry = new ClientboundPlayerInfoUpdatePacket.Entry(entry.profileId(), modifiedProfile, entry.listed(), entry.latency(), entry.gameMode(), displayName, entry.showHat(), entry.listOrder(), entry.chatSession()); + modifiedEntries.add(modifiedEntry); + } + return createInfoPacket(actions, modifiedEntries); + } + + public static boolean shouldChange(ClientboundPlayerInfoUpdatePacket.Entry entry) { + return ProfileEditor.mirrorUUIDs.contains(entry.profileId()) || RenameCommand.customNames.containsKey(entry.profileId()) || fakeProfiles.containsKey(entry.profileId()); + } + + public static final Field ClientboundPlayerInfoUpdatePacket_entries = ReflectionHelper.getFields(ClientboundPlayerInfoUpdatePacket.class).getFirstOfType(List.class); + + public static ClientboundPlayerInfoUpdatePacket createInfoPacket(EnumSet actions, List entries) { + ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket = new ClientboundPlayerInfoUpdatePacket(actions, List.of()); + try { + ClientboundPlayerInfoUpdatePacket_entries.set(playerInfoUpdatePacket, entries); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + return playerInfoUpdatePacket; + } + + public static GameProfile createGameProfile(UUID uuid, String name, String texture, String signature) { + return new GameProfile( + uuid != null ? uuid : NIL_UUID, name != null ? name : EMPTY_NAME, + texture != null ? new PropertyMap(ImmutableMultimap.of("textures", new Property("textures", texture, signature))) : PropertyMap.EMPTY + ); + } + + public static GameProfile getGameProfileNoProperties(PlayerProfile playerProfile) { + return createGameProfile(playerProfile.getUniqueId(), playerProfile.getName(), null, null); + } + + public static GameProfile getGameProfile(PlayerProfile playerProfile) { + return createGameProfile(playerProfile.getUniqueId(), playerProfile.getName(), playerProfile.getTexture(), playerProfile.getTextureSignature()); + } + + public static final MethodHandle RESOLVABLEPROFILE_UNPACK = ReflectionHelper.getMethodHandle(ResolvableProfile.class, "unpack"); + public static final MethodHandle RESOLVABLEPROFILE_PARTIAL_ID = ReflectionHelper + .getFields(ReflectionHelper.getClassOrThrow("net.minecraft.world.item.component.ResolvableProfile$Partial")) + .getGetter("id", Optional.class); + + public static UUID getUUID(ResolvableProfile resolvableProfile) { + try { + Either unpacked = (Either) RESOLVABLEPROFILE_UNPACK.invokeExact(resolvableProfile); + return unpacked.map(GameProfile::id, + partial -> { + try { + Optional uuid = (Optional) RESOLVABLEPROFILE_PARTIAL_ID.invoke(partial); + return uuid.orElse(null); + } + catch (Throwable e) { + Debug.echoError(e); + return null; + } + } + ); + } + catch (Throwable e) { + Debug.echoError(e); + return null; + } + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/SidebarImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/SidebarImpl.java new file mode 100644 index 0000000000..0e08145f36 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/SidebarImpl.java @@ -0,0 +1,103 @@ +package com.denizenscript.denizen.nms.v26_1.impl; + +import com.denizenscript.denizen.nms.abstracts.Sidebar; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.helpers.PacketHelperImpl; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.numbers.StyledFormat; +import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket; +import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket; +import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; +import net.minecraft.network.protocol.game.ClientboundSetScorePacket; +import net.minecraft.world.scores.DisplaySlot; +import net.minecraft.world.scores.Objective; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.scores.criteria.ObjectiveCriteria; +import org.bukkit.entity.Player; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class SidebarImpl extends Sidebar { + + // TODO: 1.20.3: the scoreboard system saw significant changes, there is likely a better way to do this now + + public static Scoreboard dummyScoreboard = new Scoreboard(); + public static ObjectiveCriteria dummyCriteria; + + static { + try { + Constructor constructor = ObjectiveCriteria.class.getDeclaredConstructor(String.class); + constructor.setAccessible(true); + dummyCriteria = constructor.newInstance("dummy"); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public Objective obj1; + public Objective obj2; + + public SidebarImpl(Player player) { + super(player); + Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE)); + this.obj1 = new Objective(dummyScoreboard, "dummy_1", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT); + this.obj2 = new Objective(dummyScoreboard, "dummy_2", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT); + } + + @Override + protected void setDisplayName(String title) { + if (this.obj1 != null) { + Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE)); + this.obj1.setDisplayName(chatComponentTitle); + this.obj2.setDisplayName(chatComponentTitle); + } + } + + public List generatedTeams = new ArrayList<>(); + + @Override + public void sendUpdate() { + List oldTeams = generatedTeams; + generatedTeams = new ArrayList<>(); + PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj1, 0)); + String[] ids = getIds(); + for (int i = 0; i < this.lines.length; i++) { + String line = this.lines[i]; + if (line == null) { + break; + } + String lineId = ids[i]; + PlayerTeam team = new PlayerTeam(dummyScoreboard, lineId); + team.getPlayers().add(lineId); + team.setPlayerPrefix(Handler.componentToNMS(FormattedTextHelper.parse(line, ChatColor.WHITE))); + generatedTeams.add(team); + PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); + PacketHelperImpl.send(player, new ClientboundSetScorePacket(lineId, obj1.getName(), this.scores[i], Optional.empty(), Optional.of(StyledFormat.SIDEBAR_DEFAULT))); + } + PacketHelperImpl.send(player, new ClientboundSetDisplayObjectivePacket(DisplaySlot.SIDEBAR, this.obj1)); + PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1)); + Objective temp = this.obj2; + this.obj2 = this.obj1; + this.obj1 = temp; + for (PlayerTeam team : oldTeams) { + PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team)); + } + } + + @Override + public void remove() { + for (PlayerTeam team : generatedTeams) { + PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team)); + } + generatedTeams.clear(); + PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1)); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/blocks/BlockLightImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/blocks/BlockLightImpl.java new file mode 100644 index 0000000000..e64e9483fb --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/blocks/BlockLightImpl.java @@ -0,0 +1,263 @@ +package com.denizenscript.denizen.nms.v26_1.impl.blocks; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.BlockLight; +import com.denizenscript.denizen.utilities.blocks.ChunkCoordinate; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; +import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.DataLayer; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.lighting.LayerLightEventListener; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.util.Vector; + +import java.lang.invoke.MethodHandle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +public class BlockLightImpl extends BlockLight { + + public static final Class LIGHTENGINETHREADED_TASKTYPE = Arrays.stream(ThreadedLevelLightEngine.class.getDeclaredClasses()).filter(c -> c.isEnum()).findFirst().get(); // TaskType + public static final Object LIGHTENGINETHREADED_TASKTYPE_PRE; + + static { + Object preObj = null; + try { + preObj = ReflectionHelper.getFields(LIGHTENGINETHREADED_TASKTYPE).get("PRE_UPDATE").get(null); + } + catch (Throwable ex) { + ex.printStackTrace(); + } + LIGHTENGINETHREADED_TASKTYPE_PRE = preObj; + } + + public static final MethodHandle LIGHTENGINETHREADED_QUEUERUNNABLE = ReflectionHelper.getMethodHandle(ThreadedLevelLightEngine.class, "addTask", + int.class, int.class, LIGHTENGINETHREADED_TASKTYPE, Runnable.class); + + public static void enqueueRunnable(LevelChunk chunk, Runnable runnable) { + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + if (lightEngine instanceof ThreadedLevelLightEngine) { + ChunkPos coord = chunk.getPos(); + try { + LIGHTENGINETHREADED_QUEUERUNNABLE.invoke(lightEngine, coord.x(), coord.z(), LIGHTENGINETHREADED_TASKTYPE_PRE, runnable); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + else { + runnable.run(); + } + } + + private BlockLightImpl(Location location, long ticks) { + super(location, ticks); + } + + public static BlockLight createLight(Location location, int lightLevel, long ticks) { + location = location.getBlock().getLocation(); + BlockLight blockLight; + if (lightsByLocation.containsKey(location)) { + blockLight = lightsByLocation.get(location); + if (blockLight.removeTask != null) { + blockLight.removeTask.cancel(); + blockLight.removeTask = null; + } + if (blockLight.updateTask != null) { + blockLight.updateTask.cancel(); + blockLight.updateTask = null; + } + blockLight.removeLater(ticks); + } + else { + blockLight = new BlockLightImpl(location, ticks); + lightsByLocation.put(location, blockLight); + if (!lightsByChunk.containsKey(blockLight.chunkCoord)) { + lightsByChunk.put(blockLight.chunkCoord, new ArrayList<>()); + } + lightsByChunk.get(blockLight.chunkCoord).add(blockLight); + } + blockLight.intendedLevel = lightLevel; + blockLight.update(lightLevel, true); + return blockLight; + } + + public static void checkIfLightsBrokenByPacket(ClientboundBlockUpdatePacket packet, Level world) { + try { + BlockPos pos = packet.getPos(); + int chunkX = pos.getX() >> 4; + int chunkZ = pos.getZ() >> 4; + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> { + LevelChunk chunk = world.getChunk(chunkX, chunkZ); + boolean any = false; + for (Vector vec : RELATIVE_CHUNKS) { + ChunkAccess other = world.getChunk(chunkX + vec.getBlockX(), chunkZ + vec.getBlockZ(), ChunkStatus.FULL, false); + if (other instanceof LevelChunk) { + List lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) other))); + if (lights != null) { + any = true; + for (BlockLight light : lights) { + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1); + } + } + } + } + if (any) { + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates(chunk), 3); + } + }, 1); + } + catch (Exception ex) { + Debug.echoError(ex); + } + } + + public static void checkIfLightsBrokenByPacket(ClientboundLightUpdatePacket packet, Level world) { + if (doNotCheck) { + return; + } + try { + int cX = packet.getX(); + int cZ = packet.getZ(); + BitSet bitMask = packet.getLightData().getBlockYMask(); + List blockData = packet.getLightData().getBlockUpdates(); + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> { + ChunkAccess chk = world.getChunk(cX, cZ, ChunkStatus.FULL, false); + if (!(chk instanceof LevelChunk)) { + return; + } + List lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) chk))); + if (lights == null) { + return; + } + boolean any = false; + for (BlockLight light : lights) { + if (((BlockLightImpl) light).checkIfChangedBy(bitMask, blockData)) { + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1); + any = true; + } + } + if (any) { + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates((LevelChunk) chk), 3); + } + }, 1); + } + catch (Exception ex) { + Debug.echoError(ex); + } + } + + public static boolean doNotCheck = false; + + public boolean checkIfChangedBy(BitSet bitmask, List data) { + Location blockLoc = block.getLocation(); + int layer = (blockLoc.getBlockY() >> 4) + 1; + if (!bitmask.get(layer)) { + return false; + } + int found = 0; + for (int i = 0; i < 16; i++) { + if (bitmask.get(i)) { + if (i == layer) { + byte[] blocks = data.get(found); + DataLayer arr = new DataLayer(blocks); + int x = blockLoc.getBlockX() - (chunkCoord.x << 4); + int y = blockLoc.getBlockY() % 16; + int z = blockLoc.getBlockZ() - (chunkCoord.z << 4); + int level = arr.get(x, y, z); + return intendedLevel != level; + } + found++; + } + } + return false; + } + + public static void runResetFor(final LevelChunk chunk, final BlockPos pos) { + Runnable runnable = () -> { + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK); + engineBlock.checkBlock(pos); + }; + enqueueRunnable(chunk, runnable); + } + + public static void runSetFor(final LevelChunk chunk, final BlockPos pos, final int level) { + Runnable runnable = () -> { + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK); + // engineBlock.onBlockEmissionIncrease(pos, level); // TODO: 1.20: ? + }; + enqueueRunnable(chunk, runnable); + } + + @Override + public void reset(boolean updateChunk) { + runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition()); + if (updateChunk) { + // This runnable cast is necessary despite what your IDE may claim + updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1); + } + } + + @Override + public void update(int lightLevel, boolean updateChunk) { + runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition()); + updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), () -> { + updateTask = null; + runSetFor((LevelChunk) ((CraftChunk) chunk).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition(), lightLevel); + if (updateChunk) { + // This runnable cast is necessary despite what your IDE may claim + updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1); + } + }, 1); + } + + public static final Vector[] RELATIVE_CHUNKS = new Vector[] { + new Vector(0, 0, 0), + new Vector(-1, 0, 0), new Vector(1, 0, 0), new Vector(0, 0, -1), new Vector(0, 0, 1), + new Vector(-1, 0, -1), new Vector(-1, 0, 1), new Vector(1, 0, -1), new Vector(1, 0, 1) + }; + + public void sendNearbyChunkUpdates() { + sendNearbyChunkUpdates((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL)); + } + + public static void sendNearbyChunkUpdates(LevelChunk chunk) { + ChunkPos pos = chunk.getPos(); + for (Vector vec : RELATIVE_CHUNKS) { + ChunkAccess other = chunk.getLevel().getChunk(pos.x() + vec.getBlockX(), pos.z() + vec.getBlockZ(), ChunkStatus.FULL, false); + if (other instanceof LevelChunk) { + sendSingleChunkUpdate((LevelChunk) other); + } + } + } + + public static void sendSingleChunkUpdate(LevelChunk chunk) { + // TODO: 1.20: ? + /* + doNotCheck = true; + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + ChunkPos pos = chunk.getPos(); + //ClientboundLightUpdatePacket packet = new ClientboundLightUpdatePacket(pos, lightEngine, null, null, true); // TODO: 1.16: should 'trust edges' be true here? + ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(pos, false).forEach((player) -> { + player.connection.send(packet); + }); + doNotCheck = false;*/ + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftFakeArrowImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftFakeArrowImpl.java new file mode 100644 index 0000000000..160f12a001 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftFakeArrowImpl.java @@ -0,0 +1,26 @@ +package com.denizenscript.denizen.nms.v26_1.impl.entities; + +import com.denizenscript.denizen.nms.interfaces.FakeArrow; +import net.minecraft.world.entity.projectile.arrow.AbstractArrow; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftAbstractArrow; + +public class CraftFakeArrowImpl extends CraftAbstractArrow implements FakeArrow { + + public CraftFakeArrowImpl(CraftServer craftServer, AbstractArrow entityArrow) { + super(craftServer, entityArrow); + } + + @Override + public void remove() { + if (getPassenger() != null) { + return; + } + super.remove(); + } + + @Override + public String getEntityTypeName() { + return "FAKE_ARROW"; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftFakePlayerImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftFakePlayerImpl.java new file mode 100644 index 0000000000..79f8121011 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftFakePlayerImpl.java @@ -0,0 +1,66 @@ +package com.denizenscript.denizen.nms.v26_1.impl.entities; + +import com.denizenscript.denizen.nms.interfaces.FakePlayer; +import com.denizenscript.denizen.nms.NMSHandler; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; + +import java.util.List; +import java.util.Set; + +public class CraftFakePlayerImpl extends CraftPlayer implements FakePlayer { + + private final CraftServer server; + public String fullName; + + public CraftFakePlayerImpl(CraftServer server, EntityFakePlayerImpl entity) { + super(server, entity); + this.server = server; + setMetadata("NPC", new FixedMetadataValue(NMSHandler.getJavaPlugin(), true)); + } + + @Override + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue); + } + + @Override + public List getMetadata(String metadataKey) { + return this.server.getEntityMetadata().getMetadata(this, metadataKey); + } + + @Override + public boolean hasMetadata(String metadataKey) { + return this.server.getEntityMetadata().hasMetadata(this, metadataKey); + } + + @Override + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + this.server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin); + } + + @Override + public String getEntityTypeName() { + return "FAKE_PLAYER"; + } + + @Override + public String getFullName() { + return fullName; + } + + @Override + public Block getTargetBlock(Set transparent, int maxDistance) { + return null; + } + + @Override + public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { + return null; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftItemProjectileImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftItemProjectileImpl.java new file mode 100644 index 0000000000..da41f2da0c --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftItemProjectileImpl.java @@ -0,0 +1,117 @@ +package com.denizenscript.denizen.nms.v26_1.impl.entities; + +import com.denizenscript.denizen.nms.interfaces.ItemProjectile; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.projectiles.ProjectileSource; + +import java.lang.invoke.MethodHandle; +import java.util.UUID; + +public class CraftItemProjectileImpl extends CraftEntity implements ItemProjectile { + + private boolean doesBounce; + + public CraftItemProjectileImpl(CraftServer server, EntityItemProjectileImpl entity) { + super(server, entity); + MethodHandle handle = ReflectionHelper.getFinalSetterForFirstOfType(CraftEntity.class, EntityType.class); + if (handle != null) { + try { + handle.invoke(this, EntityType.ITEM); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + } + + @Override + public EntityItemProjectileImpl getHandle() { + return (EntityItemProjectileImpl) super.getHandle(); + } + + @Override + public String getEntityTypeName() { + return getType().name(); + } + + @Override + public ItemStack getItemStack() { + return CraftItemStack.asBukkitCopy(getHandle().getItemStack()); + } + + @Override + public void setItemStack(ItemStack itemStack) { + getHandle().setItemStack(CraftItemStack.asNMSCopy(itemStack)); + } + + @Override + public int getPickupDelay() { + return 0; + } + + @Override + public void setPickupDelay(int i) { + // Do nothing + } + + @Override + public void setUnlimitedLifetime(boolean b) { + // Do nothing + } + + @Override + public boolean isUnlimitedLifetime() { + return false; + } + + @Override + public void setOwner(UUID uuid) { + // Do nothing + } + + @Override + public UUID getOwner() { + return null; + } + + @Override + public void setThrower(UUID uuid) { + // Do nothing + } + + @Override + public UUID getThrower() { + return null; + } + + @Override + public ProjectileSource getShooter() { + return getHandle().projectileSource; + } + + @Override + public void setShooter(ProjectileSource projectileSource) { + if (projectileSource instanceof CraftEntity) { + getHandle().setOwner(((CraftEntity) projectileSource).getHandle()); + } + else { + getHandle().projectileSource = projectileSource; + } + } + + @Override + public boolean doesBounce() { + return doesBounce; + } + + @Override + public void setBounce(boolean doesBounce) { + this.doesBounce = doesBounce; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityFakeArrowImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityFakeArrowImpl.java new file mode 100644 index 0000000000..01b789f238 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityFakeArrowImpl.java @@ -0,0 +1,43 @@ +package com.denizenscript.denizen.nms.v26_1.impl.entities; + +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.world.entity.projectile.arrow.SpectralArrow; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.event.entity.CreatureSpawnEvent; + +public class EntityFakeArrowImpl extends SpectralArrow { + + public EntityFakeArrowImpl(CraftWorld craftWorld, Location location) { + super(net.minecraft.world.entity.EntityType.SPECTRAL_ARROW, craftWorld.getHandle()); + try { + Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakeArrowImpl((CraftServer) Bukkit.getServer(), this)); + } + catch (Exception ex) { + Debug.echoError(ex); + } + setPosRaw(location.getX(), location.getY(), location.getZ()); + setRot(location.getYaw(), location.getPitch()); + level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM); + } + + @Override + public void tick() { + // Do nothing + } + + @Override + protected ItemStack getPickupItem() { + return new ItemStack(Items.ARROW); + } + + @Override + public CraftFakeArrowImpl getBukkitEntity() { + return (CraftFakeArrowImpl) super.getBukkitEntity(); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityFakePlayerImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityFakePlayerImpl.java new file mode 100644 index 0000000000..d81df1ffa7 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityFakePlayerImpl.java @@ -0,0 +1,43 @@ +package com.denizenscript.denizen.nms.v26_1.impl.entities; + +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.impl.network.fakes.FakeNetworkManagerImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.fakes.FakePlayerConnectionImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.world.entity.player.Player; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.event.entity.CreatureSpawnEvent; + +public class EntityFakePlayerImpl extends ServerPlayer { + + public EntityFakePlayerImpl(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, ClientInformation clientInfo, boolean doAdd) { + super(minecraftserver, worldserver, gameprofile, clientInfo); + try { + Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakePlayerImpl((CraftServer) Bukkit.getServer(), this)); + net.minecraft.network.Connection networkManager = new FakeNetworkManagerImpl(PacketFlow.CLIENTBOUND); + connection = new FakePlayerConnectionImpl(minecraftserver, networkManager, this, new CommonListenerCookie(gameprofile, 0, clientInfo, false)); + DenizenNetworkManagerImpl.Connection_packetListener.set(networkManager, connection); + } + catch (Exception ex) { + Debug.echoError(ex); + } + getEntityData().set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127); + if (doAdd) { + worldserver.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM); + } + } + + @Override + public CraftFakePlayerImpl getBukkitEntity() { + return (CraftFakePlayerImpl) super.getBukkitEntity(); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityItemProjectileImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityItemProjectileImpl.java new file mode 100644 index 0000000000..e385265301 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityItemProjectileImpl.java @@ -0,0 +1,97 @@ +package com.denizenscript.denizen.nms.v26_1.impl.entities; + +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.base.Preconditions; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.ThrowableProjectile; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.BlockHitResult; +import org.bukkit.Location; + +import java.lang.invoke.MethodHandle; + +public class EntityItemProjectileImpl extends ThrowableProjectile { + + public static MethodHandle setBukkitEntityMethod = ReflectionHelper.getFinalSetter(Entity.class, "bukkitEntity"); + + public static final EntityDataAccessor ITEM; + + static { + EntityDataAccessor watcher = null; + try { + watcher = (EntityDataAccessor) ReflectionHelper.getFields(ItemEntity.class).get("DATA_ITEM").get(null); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + ITEM = watcher; + } + + public EntityItemProjectileImpl(Level world, Location location, ItemStack item) { + super((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.ITEM, world); + try { + setBukkitEntityMethod.invoke(this, new CraftItemProjectileImpl(((ServerLevel) world).getServer().server, this)); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + setPosRaw(location.getX(), location.getY(), location.getZ()); + setRot(location.getYaw(), location.getPitch()); + setItemStack(item); + } + + @Override + protected void defineSynchedData(SynchedEntityData.Builder builder) { + builder.define(ITEM, ItemStack.EMPTY); + } + + public ItemStack getItemStack() { + return this.getEntityData().get(ITEM); + } + + public void setItemStack(ItemStack itemstack) { + Preconditions.checkArgument(!itemstack.isEmpty(), "Cannot drop air"); + this.getEntityData().set(ITEM, itemstack); + this.getEntityData().markDirty(ITEM); + } + + @Override + protected void onHitBlock(BlockHitResult movingobjectpositionblock) { + super.onHitBlock(movingobjectpositionblock); + remove(RemovalReason.KILLED); + } + + @Override + public boolean save(ValueOutput nmsValueOutput) { + if (!this.getItemStack().isEmpty()) { + nmsValueOutput.store("Item", ItemStack.CODEC, this.getItemStack()); + } + super.save(nmsValueOutput); + return true; + } + + @Override + public void load(ValueInput nmsValueInput) { + ItemStack nmsItemStack = nmsValueInput.read("Item", ItemStack.CODEC).orElse(ItemStack.EMPTY); + if (nmsItemStack.isEmpty()) { + this.remove(RemovalReason.KILLED); + } + else { + this.setItemStack(nmsItemStack); + } + super.load(nmsValueInput); + } + + @Override + public CraftItemProjectileImpl getBukkitEntity() { + return (CraftItemProjectileImpl) super.getBukkitEntity(); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakeChannelImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakeChannelImpl.java new file mode 100644 index 0000000000..e265656f2e --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakeChannelImpl.java @@ -0,0 +1,80 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.fakes; + +import io.netty.channel.*; + +import java.net.SocketAddress; + +public class FakeChannelImpl extends AbstractChannel { + + private final ChannelConfig config = new DefaultChannelConfig(this); + + protected FakeChannelImpl(Channel parent) { + super(parent); + } + + @Override + public ChannelConfig config() { + config.setAutoRead(true); + return config; + } + + @Override + protected AbstractUnsafe newUnsafe() { + return null; + } + + @Override + protected boolean isCompatible(EventLoop eventLoop) { + return false; + } + + @Override + protected SocketAddress localAddress0() { + return null; + } + + @Override + protected SocketAddress remoteAddress0() { + return null; + } + + @Override + protected void doBind(SocketAddress socketAddress) throws Exception { + + } + + @Override + protected void doDisconnect() throws Exception { + + } + + @Override + protected void doClose() throws Exception { + + } + + @Override + protected void doBeginRead() throws Exception { + + } + + @Override + protected void doWrite(ChannelOutboundBuffer channelOutboundBuffer) throws Exception { + + } + + @Override + public boolean isOpen() { + return false; + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public ChannelMetadata metadata() { + return new ChannelMetadata(true); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakeNetworkManagerImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakeNetworkManagerImpl.java new file mode 100644 index 0000000000..a94b4367aa --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakeNetworkManagerImpl.java @@ -0,0 +1,16 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.fakes; + +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.PacketFlow; + +import java.net.SocketAddress; + +public class FakeNetworkManagerImpl extends Connection { + + public FakeNetworkManagerImpl(PacketFlow enumprotocoldirection) { + super(enumprotocoldirection); + channel = new FakeChannelImpl(null); + address = new SocketAddress() { + }; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakePlayerConnectionImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakePlayerConnectionImpl.java new file mode 100644 index 0000000000..373e74ada5 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakePlayerConnectionImpl.java @@ -0,0 +1,20 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.fakes; + +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.Packet; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerGamePacketListenerImpl; + +public class FakePlayerConnectionImpl extends ServerGamePacketListenerImpl { + + public FakePlayerConnectionImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer, CommonListenerCookie cookie) { + super(minecraftserver, networkmanager, entityplayer, cookie); + } + + @Override + public void send(Packet packet) { + // Do nothing + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/AbstractListenerPlayInImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/AbstractListenerPlayInImpl.java new file mode 100644 index 0000000000..6d5d5a2b48 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/AbstractListenerPlayInImpl.java @@ -0,0 +1,670 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers; + +import com.denizenscript.denizen.events.player.PlayerSendPacketScriptEvent; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import io.netty.channel.ChannelFutureListener; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.DisconnectionDetails; +import net.minecraft.network.chat.ChatType; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.*; +import net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.protocol.ping.ServerboundPingRequestPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerCommonPacketListenerImpl; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.Relative; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.event.player.PlayerTeleportEvent; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.net.SocketAddress; +import java.util.Set; + +public class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl { + + public static final Field ServerGamePacketListenerImpl_chunkSender = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("chunkSender"); + + public static final MethodHandle SERVER_COMMON_PACKET_LISTENER_IMPL_CREATE_COOKIE = ReflectionHelper.getMethodHandle(ServerCommonPacketListenerImpl.class, "createCookie", ClientInformation.class); + + public static CommonListenerCookie createCookie(ServerPlayer nmsPlayer) { + try { + return (CommonListenerCookie) SERVER_COMMON_PACKET_LISTENER_IMPL_CREATE_COOKIE.invoke(nmsPlayer.connection, nmsPlayer.clientInformation()); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public final ServerGamePacketListenerImpl oldListener; + public final DenizenNetworkManagerImpl denizenNetworkManager; + + public AbstractListenerPlayInImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer, ServerGamePacketListenerImpl oldListener) { + super(MinecraftServer.getServer(), networkManager, entityPlayer, createCookie(entityPlayer)); + this.oldListener = oldListener; + this.denizenNetworkManager = networkManager; + try { + ServerGamePacketListenerImpl_chunkSender.set(this, oldListener.chunkSender); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + } + + @Override + public void disconnect(Component ichatbasecomponent) { + oldListener.disconnect(ichatbasecomponent); + } + + @Override + public void disconnect(DisconnectionDetails disconnectiondetails) { + oldListener.disconnect(disconnectiondetails); + } + + @Override + public void kickPlayer(Component reason) { + oldListener.kickPlayer(reason); + } + + @Override + public void teleport(double d0, double d1, double d2, float f, float f1) { + oldListener.teleport(d0, d1, d2, f, f1); + } + + @Override + public boolean teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { + return oldListener.teleport(d0, d1, d2, f, f1, cause); + } + + @Override + public void teleport(PositionMoveRotation positionmoverotation, Set set) { + oldListener.teleport(positionmoverotation, set); + } + + @Override + public boolean teleport(PositionMoveRotation positionmoverotation, Set set, PlayerTeleportEvent.TeleportCause cause) { + return oldListener.teleport(positionmoverotation, set, cause); + } + + @Override + public void teleport(Location dest) { + oldListener.teleport(dest); + } + + @Override + public void internalTeleport(PositionMoveRotation positionmoverotation, Set set) { + oldListener.internalTeleport(positionmoverotation, set); + } + + @Override + public CraftPlayer getCraftPlayer() { + return oldListener.getCraftPlayer(); + } + + @Override + public void tick() { + oldListener.tick(); + } + + @Override + public void resetPosition() { + oldListener.resetPosition(); + } + + @Override + public boolean isAcceptingMessages() { + return oldListener.isAcceptingMessages(); + } + + @Override + public boolean shouldHandleMessage(Packet packet) { + return oldListener.shouldHandleMessage(packet); + } + + @Override + public void fillCrashReport(CrashReport var0) { + oldListener.fillCrashReport(var0); + } + + @Override + public void fillListenerSpecificCrashDetails(CrashReport var0, CrashReportCategory var1) { + oldListener.fillListenerSpecificCrashDetails(var0, var1); + } + + @Override + public GameProfile getOwner() { + return oldListener.getOwner(); + } + + @Override + public int latency() { + return oldListener.latency(); + } + + @Override + public void onDisconnect(DisconnectionDetails disconnectionDetails) { + oldListener.onDisconnect(disconnectionDetails); + } + + @Override + public void ackBlockChangesUpTo(int i) { + oldListener.ackBlockChangesUpTo(i); + } + + @Override + public void send(Packet packet) { + oldListener.send(packet); + } + + @Override + public void send(Packet packet, ChannelFutureListener channelfuturelistener) { + oldListener.send(packet, channelfuturelistener); + } + + public static Field AWAITING_POS_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("awaitingPositionFromClient", Vec3.class); + public static Field AWAITING_TELEPORT_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("awaitingTeleport", int.class); + + public void debugPacketOutput(Packet packet) { + try { + if (packet instanceof ServerboundMovePlayerPacket) { + ServerboundMovePlayerPacket movePacket = (ServerboundMovePlayerPacket) packet; + DenizenNetworkManagerImpl.doPacketOutput("Packet ServerboundMovePlayerPacket sent from " + player.getScoreboardName() + " with XYZ=" + + movePacket.x + ", " + movePacket.y + ", " + movePacket.z + ", yRot=" + movePacket.yRot + ", xRot=" + movePacket.xRot + + ", onGround=" + movePacket.isOnGround() + ", hasPos=" + movePacket.hasPos + ", hasRot=" + movePacket.hasRot); + } + else if (packet instanceof ServerboundAcceptTeleportationPacket) { + Vec3 awaitPos = (Vec3) AWAITING_POS_FIELD.get(oldListener); + int awaitTeleportId = AWAITING_TELEPORT_FIELD.getInt(oldListener); + ServerboundAcceptTeleportationPacket acceptPacket = (ServerboundAcceptTeleportationPacket) packet; + DenizenNetworkManagerImpl.doPacketOutput("Packet ServerboundAcceptTeleportationPacket sent from " + player.getScoreboardName() + + " with ID=" + acceptPacket.getId() + ", awaitingTeleport=" + awaitTeleportId + ", awaitPos=" + awaitPos); + } + else { + DenizenNetworkManagerImpl.doPacketOutput("Packet: " + packet.getClass().getCanonicalName() + " sent from " + player.getScoreboardName()); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public boolean handlePacketIn(Packet packet) { + denizenNetworkManager.packetsReceived++; + if (NMSHandler.debugPackets) { + debugPacketOutput(packet); + } + if (PlayerSendPacketScriptEvent.instance.eventData.isEnabled) { + if (PlayerSendPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) { + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Denied packet-in " + packet.getClass().getCanonicalName() + " from " + player.getScoreboardName() + " due to event"); + } + return true; + } + } + return false; + } + + @Override + public void handleChatAck(ServerboundChatAckPacket serverboundchatackpacket) { + oldListener.handleChatAck(serverboundchatackpacket); + } + + @Override + public void handlePlayerInput(ServerboundPlayerInputPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlayerInput(packet); + } + + @Override + public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleMoveVehicle(packet); + } + + @Override + public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleAcceptTeleportPacket(packet); + } + + @Override + public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleRecipeBookSeenRecipePacket(packet); + } + + @Override + public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleRecipeBookChangeSettingsPacket(packet); + } + + @Override + public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSeenAdvancements(packet); + } + + @Override + public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleCustomCommandSuggestions(packet); + } + + @Override + public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetCommandBlock(packet); + } + + @Override + public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetCommandMinecart(packet); + } + + @Override + public void handleAcceptPlayerLoad(ServerboundPlayerLoadedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleAcceptPlayerLoad(packet); + } + + @Override + public void handlePickItemFromBlock(ServerboundPickItemFromBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePickItemFromBlock(packet); + } + + @Override + public void handlePickItemFromEntity(ServerboundPickItemFromEntityPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePickItemFromEntity(packet); + } + + @Override + public void handleRenameItem(ServerboundRenameItemPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleRenameItem(packet); + } + + @Override + public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetBeaconPacket(packet); + } + + @Override + public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetStructureBlock(packet); + } + + @Override + public void handleSetTestBlock(ServerboundSetTestBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetTestBlock(packet); + } + + @Override + public void handleTestInstanceBlockAction(ServerboundTestInstanceBlockActionPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleTestInstanceBlockAction(packet); + } + + @Override + public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetJigsawBlock(packet); + } + + @Override + public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleJigsawGenerate(packet); + } + + @Override + public void handleSelectTrade(ServerboundSelectTradePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSelectTrade(packet); + } + + @Override + public void handleEditBook(ServerboundEditBookPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleEditBook(packet); + } + + @Override + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleMovePlayer(packet); + } + + @Override + public void handlePlayerAction(ServerboundPlayerActionPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlayerAction(packet); + } + + @Override + public void handleUseItemOn(ServerboundUseItemOnPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleUseItemOn(packet); + } + + @Override + public void handleUseItem(ServerboundUseItemPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleUseItem(packet); + } + + @Override + public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleTeleportToEntityPacket(packet); + } + + @Override + public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleResourcePackResponse(packet); + } + + @Override + public void suspendFlushing() { + oldListener.suspendFlushing(); + } + + @Override + public void resumeFlushing() { + oldListener.resumeFlushing(); + } + + @Override + public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePaddleBoat(packet); + } + + @Override + public void handlePong(ServerboundPongPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePong(packet); + } + + @Override + public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetCarriedItem(packet); + } + + @Override + public void handleChat(ServerboundChatPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChat(packet); + } + + @Override + public void handleChatCommand(ServerboundChatCommandPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChatCommand(packet); + } + + @Override + public void chat(String s, PlayerChatMessage original, boolean async) { + oldListener.chat(s, original, async); + } + + @Override + public ConnectionProtocol protocol() { + return oldListener == null ? ConnectionProtocol.PLAY : oldListener.protocol(); + } + + @Override + public void handleAnimate(ServerboundSwingPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleAnimate(packet); + } + + @Override + public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlayerCommand(packet); + } + + @Override + public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatType.Bound chatmessagetype_a) { + oldListener.sendPlayerChatMessage(playerchatmessage, chatmessagetype_a); + } + + @Override + public void sendDisguisedChatMessage(Component ichatbasecomponent, ChatType.Bound chatmessagetype_a) { + oldListener.sendDisguisedChatMessage(ichatbasecomponent, chatmessagetype_a); + } + + @Override + public SocketAddress getRemoteAddress() { + return oldListener.getRemoteAddress(); + } + + @Override + public SocketAddress getRawAddress() { + return oldListener.getRawAddress(); + } + + @Override + public void switchToConfig() { + oldListener.switchToConfig(); + } + + @Override + public void handleInteract(ServerboundInteractPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleInteract(packet); + } + + @Override + public void handleClientCommand(ServerboundClientCommandPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleClientCommand(packet); + } + + @Override + public void handleContainerClose(ServerboundContainerClosePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleContainerClose(packet); + } + + @Override + public void handleContainerClick(ServerboundContainerClickPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleContainerClick(packet); + } + + @Override + public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlaceRecipe(packet); + } + + @Override + public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleContainerButtonClick(packet); + } + + @Override + public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetCreativeModeSlot(packet); + } + + @Override + public void handleSignUpdate(ServerboundSignUpdatePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSignUpdate(packet); + } + + @Override + public void handleKeepAlive(ServerboundKeepAlivePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleKeepAlive(packet); + } + + @Override + public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlayerAbilities(packet); + } + + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleClientInformation(packet); + } + + @Override + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleCustomPayload(packet); + } + + @Override + public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChangeDifficulty(packet); + } + + @Override + public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleLockDifficulty(packet); + } + + @Override + public void handleChatSessionUpdate(ServerboundChatSessionUpdatePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChatSessionUpdate(packet); + } + + @Override + public void handleConfigurationAcknowledged(ServerboundConfigurationAcknowledgedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleConfigurationAcknowledged(packet); + } + + @Override + public void handleChunkBatchReceived(ServerboundChunkBatchReceivedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChunkBatchReceived(packet); + } + + @Override + public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQueryPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleBlockEntityTagQuery(packet); + } + + @Override + public void handleBundleItemSelectedPacket(ServerboundSelectBundleItemPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleBundleItemSelectedPacket(packet); + } + + @Override + public void handleClientTickEnd(ServerboundClientTickEndPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleClientTickEnd(packet); + } + + @Override + public boolean hasInfiniteMaterials() { + return oldListener.hasInfiniteMaterials(); + } + + @Override + public void handleContainerSlotStateChanged(ServerboundContainerSlotStateChangedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleContainerSlotStateChanged(packet); + } + + @Override + public void handleDebugSubscriptionRequest(ServerboundDebugSubscriptionRequestPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleDebugSubscriptionRequest(packet); + } + + @Override + public void handleEntityTagQuery(ServerboundEntityTagQueryPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleEntityTagQuery(packet); + } + + @Override + public void handlePingRequest(ServerboundPingRequestPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePingRequest(packet); + } + + @Override + public void handleSignedChatCommand(ServerboundChatCommandSignedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSignedChatCommand(packet); + } + + @Override + public void handleChangeGameMode(ServerboundChangeGameModePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChangeGameMode(packet); + } + + @Override + public void handleCustomClickAction(ServerboundCustomClickActionPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleCustomClickAction(packet); + } + + @Override + public void handleCookieResponse(ServerboundCookieResponsePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleCookieResponse(packet); + } + + @Override + public ServerPlayer getPlayer() { + return oldListener.getPlayer(); + } + + @Override + public PacketFlow flow() { + return oldListener == null ? PacketFlow.SERVERBOUND : oldListener.flow(); + } + + @Override + public void onPacketError(Packet var0, Exception var1) throws ReportedException { + oldListener.onPacketError(var0, var1); + } + + @Override + public DisconnectionDetails createDisconnectionInfo(Component var0, Throwable var1) { + return oldListener.createDisconnectionInfo(var0, var1); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/DenizenNetworkManagerImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/DenizenNetworkManagerImpl.java new file mode 100644 index 0000000000..4023acd8d5 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/DenizenNetworkManagerImpl.java @@ -0,0 +1,579 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers; + +import com.denizenscript.denizen.events.player.PlayerReceivesPacketScriptEvent; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet.*; +import com.denizenscript.denizen.utilities.Settings; +import com.denizenscript.denizen.utilities.packets.NetworkInterceptCodeGen; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import net.minecraft.network.*; +import net.minecraft.network.chat.Component; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.protocol.login.ClientLoginPacketListener; +import net.minecraft.network.protocol.status.ClientStatusPacketListener; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerCommonPacketListenerImpl; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.network.ServerPlayerConnection; +import net.minecraft.util.debugchart.LocalSampleLogger; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import javax.annotation.Nullable; +import javax.crypto.Cipher; +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class DenizenNetworkManagerImpl extends Connection { + + public static > T copyPacket(T original, StreamCodec packetCodec) { + try { + RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry()); + packetCodec.encode(copier, original); + return packetCodec.decode(copier); + } + catch (Throwable ex) { + Debug.echoError(ex); + return null; + } + } + + @FunctionalInterface + public interface PacketHandler> { + Packet handlePacket(DenizenNetworkManagerImpl networkManager, T packet) throws Exception; + } + + public static final Map>, List>> packetHandlers = new HashMap<>(); + + public static > void registerPacketHandler(Class packetClass, PacketHandler handler) { + packetHandlers.computeIfAbsent(packetClass, k -> new ArrayList<>()).add(handler); + } + + public static > void registerPacketHandler(Class packetClass, BiConsumer handler) { + registerPacketHandler(packetClass, (networkManager, packet) -> { + handler.accept(networkManager, packet); + return packet; + }); + } + + public final Connection oldManager; + public final DenizenPacketListenerImpl packetListener; + public final ServerPlayer player; + public int packetsSent, packetsReceived; + + public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManager) { + super(getProtocolDirection(oldManager)); + this.oldManager = oldManager; + this.channel = oldManager.channel; + this.player = entityPlayer; + packetListener = (DenizenPacketListenerImpl) NetworkInterceptCodeGen.generateAppropriateInterceptor(this, entityPlayer, DenizenPacketListenerImpl.class, AbstractListenerPlayInImpl.class, ServerGamePacketListenerImpl.class); + if (!(oldManager.getPacketListener() instanceof ServerConfigurationPacketListener)) { + setListener(packetListener); + } + } + + public void setListener(PacketListener listener) { + try { + Connection_packetListener.set(oldManager, listener); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + throw new RuntimeException("Failed to set packet listener due to reflection error", e); + } + } + + public static Connection getConnection(ServerPlayer player) { + try { + return (Connection) ServerGamePacketListener_ConnectionField.get(player.connection); + } + catch (Throwable ex) { + Debug.echoError(ex); + throw new RuntimeException("Failed to get connection from player due to reflection error", ex); + } + } + + public static Connection getConnection(Player player) { + return getConnection(((CraftPlayer) player).getHandle()); + } + + public static DenizenNetworkManagerImpl getNetworkManager(ServerPlayer player) { + return (DenizenNetworkManagerImpl) getConnection(player); + } + + public static DenizenNetworkManagerImpl getNetworkManager(Player player) { + return getNetworkManager(((CraftPlayer) player).getHandle()); + } + + public static void setNetworkManager(Player player) { + ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle(); + ServerGamePacketListenerImpl playerConnection = entityPlayer.connection; + setNetworkManager(playerConnection, new DenizenNetworkManagerImpl(entityPlayer, getConnection(entityPlayer))); + } + + public static void enableNetworkManager() { + for (World w : Bukkit.getWorlds()) { + for (ChunkMap.TrackedEntity tracker : ((CraftWorld) w).getHandle().getChunkSource().chunkMap.entityMap.values()) { + ArrayList connections = new ArrayList<>(tracker.seenBy); + tracker.seenBy.clear(); + for (ServerPlayerConnection connection : connections) { + tracker.seenBy.add(connection.getPlayer().connection); + } + } + } + } + + @Override + public int hashCode() { + return oldManager.hashCode(); + } + + @Override + public boolean equals(Object c2) { + return oldManager.equals(c2); + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + oldManager.channelRegistered(ctx); + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + oldManager.channelUnregistered(ctx); + } + + @Override + public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception { + oldManager.channelActive(channelhandlercontext); + } + + @Override + public void channelInactive(ChannelHandlerContext channelhandlercontext) { + oldManager.channelInactive(channelhandlercontext); + } + + @Override + public boolean isSharable() { + return oldManager.isSharable(); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + oldManager.handlerAdded(ctx); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + oldManager.handlerRemoved(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) { + oldManager.exceptionCaught(channelhandlercontext, throwable); + } + + @Override + public void setupInboundProtocol(ProtocolInfo protocolinfo, T t0) { + oldManager.setupInboundProtocol(protocolinfo, t0); + } + + @Override + public void setupOutboundProtocol(ProtocolInfo protocolinfo) { + oldManager.setupOutboundProtocol(protocolinfo); + } + + @Override + protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { + if (oldManager.channel.isOpen()) { + try { + packet.handle(this.packetListener); + } + catch (Exception e) { + // Do nothing + } + } + } + + @Override + public void setListenerForServerboundHandshake(PacketListener packetlistener) { + oldManager.setListenerForServerboundHandshake(packetlistener); + } + + @Override + public void initiateServerboundStatusConnection(String s, int i, ClientStatusPacketListener packetstatusoutlistener) { + oldManager.initiateServerboundStatusConnection(s, i, packetstatusoutlistener); + } + + @Override + public void initiateServerboundPlayConnection(String s, int i, ClientLoginPacketListener packetloginoutlistener) { + oldManager.initiateServerboundPlayConnection(s, i, packetloginoutlistener); + } + + @Override + public void initiateServerboundPlayConnection(String s, int i, ProtocolInfo protocolinfo, ProtocolInfo protocolinfo1, C c0, boolean flag) { + oldManager.initiateServerboundPlayConnection(s, i, protocolinfo, protocolinfo1, c0, flag); + } + + @Override + public void send(Packet packet) { + send(packet, null); + } + + public static void doPacketOutput(String text) { + if (!NMSHandler.debugPackets) { + return; + } + if (NMSHandler.debugPacketFilter == null || NMSHandler.debugPacketFilter.trim().isEmpty() + || CoreUtilities.toLowerCase(text).contains(NMSHandler.debugPacketFilter)) { + Debug.log(text); + } + } + + public void debugOutputPacket(Packet packet) { + if (packet instanceof ClientboundSetEntityDataPacket) { + StringBuilder output = new StringBuilder(128); + output.append("Packet: ClientboundSetEntityDataPacket sent to ").append(player.getScoreboardName()).append(" for entity ID: ").append(((ClientboundSetEntityDataPacket) packet).id()).append(": "); + List> list = ((ClientboundSetEntityDataPacket) packet).packedItems(); + if (list == null) { + output.append("None"); + } + else { + for (SynchedEntityData.DataValue data : list) { + output.append('[').append(data.id()).append(": ").append(data.value()).append("], "); + } + } + doPacketOutput(output.toString()); + } + else if (packet instanceof ClientboundSetEntityMotionPacket) { + ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet; + doPacketOutput("Packet: ClientboundSetEntityMotionPacket sent to " + player.getScoreboardName() + " for entity ID: " + velPacket.id() + ": " + velPacket.movement()); + } + else if (packet instanceof ClientboundAddEntityPacket) { + ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet; + doPacketOutput("Packet: ClientboundAddEntityPacket sent to " + player.getScoreboardName() + " for entity ID: " + addEntityPacket.getId() + ": " + "uuid: " + addEntityPacket.getUUID() + + ", type: " + addEntityPacket.getType() + ", at: " + addEntityPacket.getX() + "," + addEntityPacket.getY() + "," + addEntityPacket.getZ() + ", data: " + addEntityPacket.getData()); + } + else if (packet instanceof ClientboundMapItemDataPacket) { + ClientboundMapItemDataPacket mapPacket = (ClientboundMapItemDataPacket) packet; + doPacketOutput("Packet: ClientboundMapItemDataPacket sent to " + player.getScoreboardName() + " for map ID: " + mapPacket.mapId() + ", scale: " + mapPacket.scale() + ", locked: " + mapPacket.locked()); + } + else if (packet instanceof ClientboundRemoveEntitiesPacket) { + ClientboundRemoveEntitiesPacket removePacket = (ClientboundRemoveEntitiesPacket) packet; + doPacketOutput("Packet: ClientboundRemoveEntitiesPacket sent to " + player.getScoreboardName() + " for entities: " + removePacket.getEntityIds().stream().map(Object::toString).collect(Collectors.joining(", "))); + } + else if (packet instanceof ClientboundPlayerInfoUpdatePacket) { + ClientboundPlayerInfoUpdatePacket playerInfoPacket = (ClientboundPlayerInfoUpdatePacket) packet; + doPacketOutput("Packet: ClientboundPlayerInfoPacket sent to " + player.getScoreboardName() + " of types " + playerInfoPacket.actions() + " for player profiles: " + + playerInfoPacket.entries().stream().map(p -> "mode=" + p.gameMode() + "/latency=" + p.latency() + "/display=" + p.displayName() + "/name=" + p.profile().name() + "/id=" + p.profile().id() + "/" + + p.profile().properties().asMap().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue().stream().map(v -> v.value() + ";" + v.signature()).collect(Collectors.joining(";;;"))).collect(Collectors.joining("/"))).collect(Collectors.joining(", "))); + } + else { + doPacketOutput("Packet: " + packet.getClass().getCanonicalName() + " sent to " + player.getScoreboardName()); + } + } + + @Override + public void send(Packet packet, ChannelFutureListener channelFutureListener) { + send(packet, channelFutureListener, true); + } + + @Override + public void send(Packet packet, @Nullable ChannelFutureListener channelFutureListener, boolean flush) { + if (!Bukkit.isPrimaryThread()) { + if (Settings.cache_warnOnAsyncPackets + && !(packet instanceof ClientboundSystemChatPacket) && !(packet instanceof ClientboundPlayerChatPacket) // Vanilla supports an async chat system, though it's normally disabled, some plugins use this as justification for sending messages async + && !(packet instanceof ClientboundCommandSuggestionsPacket)) { // Async tab complete is wholly unsupported in Spigot (and will cause an exception), however Paper explicitly adds async support (for unclear reasons), so let it through too + Debug.echoError("Warning: packet sent off main thread! This is completely unsupported behavior! Denizen network interceptor ignoring packet to avoid crash. Packet class: " + + packet.getClass().getCanonicalName() + " sent to " + player.getScoreboardName() + " identify the sender of the packet from the stack trace:"); + try { + throw new RuntimeException("Trace"); + } + catch (Exception ex) { + Debug.echoError(ex); + } + } + oldManager.send(packet, channelFutureListener, flush); + return; + } + if (NMSHandler.debugPackets) { + debugOutputPacket(packet); + } + packetsSent++; + if (packet instanceof ClientboundBundlePacket bundlePacket) { + List> processedPackets = new ArrayList<>(); + boolean anyChange = false; + for (Packet _subPacket : bundlePacket.subPackets()) { + // Bundle packets with non-game packets shouldn't ever be sent while the Denizen interceptor is active + Packet subPacket = (Packet) _subPacket; + Packet processed = processPacketHandlersFor(subPacket); + anyChange = anyChange || processed != subPacket; + if (processed != null) { + processedPackets.add(processed); + } + } + if (processedPackets.isEmpty()) { + return; + } + if (anyChange) { + packet = new ClientboundBundlePacket(processedPackets); + } + } + else { + Packet processed = processPacketHandlersFor((Packet) packet); + if (processed == null) { + return; + } + packet = processed; + } + oldManager.send(packet, channelFutureListener, flush); + } + + @Override + public void runOnceConnected(Consumer consumer) { + oldManager.runOnceConnected(consumer); + } + + @Override + public void flushChannel() { + oldManager.flushChannel(); + } + + public Packet processPacketHandlersFor(Packet packet) { + if (packet == null) { + return null; + } + List> packetHandlers = DenizenNetworkManagerImpl.packetHandlers.get(packet.getClass()); + if (packetHandlers != null) { + for (PacketHandler _packetHandler : packetHandlers) { + PacketHandler> packetHandler = (PacketHandler>) _packetHandler; + Packet processed; + try { + processed = packetHandler.handlePacket(this, packet); + } + catch (Exception ex) { + Debug.echoError("Packet handler for " + packet.getClass().getCanonicalName() + " threw an exception:"); + Debug.echoError(ex); + continue; + } + if (processed == null) { + if (NMSHandler.debugPackets) { + doPacketOutput("DENIED PACKET - " + packet.getClass().getCanonicalName() + " DENIED FROM SEND TO " + player.getScoreboardName()); + } + return null; + } + packet = processed; + } + } + if (PlayerReceivesPacketScriptEvent.instance.eventData.isEnabled & PlayerReceivesPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) { + if (NMSHandler.debugPackets) { + doPacketOutput("DENIED PACKET - " + packet.getClass().getCanonicalName() + " DENIED FROM SEND TO " + player.getScoreboardName() + " due to event"); + } + return null; + } + return packet; + } + + static { + ActionBarEventPacketHandlers.registerHandlers(); + AttachPacketHandlers.registerHandlers(); + BlockLightPacketHandlers.registerHandlers(); + DenizenPacketHandlerPacketHandlers.registerHandlers(); + EntityMetadataPacketHandlers.registerHandlers(); + DisguisePacketHandlers.registerHandlers(); + FakeBlocksPacketHandlers.registerHandlers(); + FakeEquipmentPacketHandlers.registerHandlers(); + FakePlayerPacketHandlers.registerHandlers(); + HiddenEntitiesPacketHandlers.registerHandlers(); + HideParticlesPacketHandlers.registerHandlers(); + PlayerHearsSoundEventPacketHandlers.registerHandlers(); + ProfileEditorImpl.registerHandlers(); + TablistUpdateEventPacketHandlers.registerHandlers(); + } + + @Override + public void tick() { + oldManager.tick(); + } + + @Override + public SocketAddress getRemoteAddress() { + return oldManager.getRemoteAddress(); + } + + @Override + public String getLoggableAddress(boolean flag) { + return oldManager.getLoggableAddress(flag); + } + + @Override + public void disconnect(Component ichatbasecomponent) { + if (!player.getBukkitEntity().isOnline()) { // Workaround Paper duplicate quit event issue + return; + } + oldManager.disconnect(ichatbasecomponent); + } + + @Override + public void disconnect(DisconnectionDetails disconnectiondetails) { + oldManager.disconnect(disconnectiondetails); + } + + @Override + public boolean isMemoryConnection() { + return oldManager != null && oldManager.isMemoryConnection(); + } + + @Override + public PacketFlow getReceiving() { + return oldManager.getReceiving(); + } + + @Override + public PacketFlow getSending() { + return oldManager.getSending(); + } + + @Override + public void configurePacketHandler(ChannelPipeline channelpipeline) { + oldManager.configurePacketHandler(channelpipeline); + } + + @Override + public void setEncryptionKey(Cipher cipher, Cipher cipher1) { + oldManager.setEncryptionKey(cipher, cipher1); + } + + @Override + public boolean isEncrypted() { + return oldManager.isEncrypted(); + } + + @Override + public boolean isConnected() { + return oldManager.isConnected(); + } + + @Override + public boolean isConnecting() { + return oldManager.isConnecting(); + } + + @Override + public PacketListener getPacketListener() { + return oldManager.getPacketListener(); + } + + @Override + public DisconnectionDetails getDisconnectionDetails() { + return oldManager.getDisconnectionDetails(); + } + + @Override + public void setReadOnly() { + oldManager.setReadOnly(); + } + + @Override + public void setupCompression(int i, boolean b) { + oldManager.setupCompression(i, b); + } + + @Override + public void handleDisconnection() { + oldManager.handleDisconnection(); + } + + @Override + public float getAverageReceivedPackets() { + return oldManager.getAverageReceivedPackets(); + } + + @Override + public float getAverageSentPackets() { + return oldManager.getAverageSentPackets(); + } + + @Override + public void setBandwidthLogger(LocalSampleLogger localsamplelogger) { + oldManager.setBandwidthLogger(localsamplelogger); + } + + ////////////////////////////////// + //// Reflection Methods/Fields + /////////// + + private static final Field protocolDirectionField = ReflectionHelper.getFields(Connection.class).get("receiving", PacketFlow.class); + public static final Field Connection_packetListener = ReflectionHelper.getFields(Connection.class).get("packetListener", PacketListener.class); + private static final Field ServerGamePacketListener_ConnectionField = ReflectionHelper.getFields(ServerCommonPacketListenerImpl.class).get("connection"); + private static final MethodHandle ServerGamePacketListener_ConnectionSetter = ReflectionHelper.getFinalSetter(ServerCommonPacketListenerImpl.class, "connection"); + + private static PacketFlow getProtocolDirection(Connection networkManager) { + PacketFlow direction = null; + try { + direction = (PacketFlow) protocolDirectionField.get(networkManager); + } + catch (Exception e) { + Debug.echoError(e); + } + return direction; + } + + private static void setNetworkManager(ServerGamePacketListenerImpl playerConnection, Connection networkManager) { + try { + ServerGamePacketListener_ConnectionSetter.invoke(playerConnection, networkManager); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public boolean acceptInboundMessage(Object msg) throws Exception { + return oldManager.acceptInboundMessage(msg); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + oldManager.channelRead(ctx, msg); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + oldManager.channelReadComplete(ctx); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + oldManager.userEventTriggered(ctx, evt); + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + oldManager.channelWritabilityChanged(ctx); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/DenizenPacketListenerImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/DenizenPacketListenerImpl.java new file mode 100644 index 0000000000..224b0c76ba --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/DenizenPacketListenerImpl.java @@ -0,0 +1,121 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers; + +import com.denizenscript.denizen.events.player.PlayerChangesSignScriptEvent; +import com.denizenscript.denizen.events.player.PlayerSteersEntityScriptEvent; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v26_1.impl.network.packets.PacketInResourcePackStatusImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.packets.PacketInSteerVehicleImpl; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.objects.MaterialTag; +import com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand; +import com.denizenscript.denizen.utilities.packets.DenizenPacketHandler; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ServerboundResourcePackPacket; +import net.minecraft.network.protocol.game.*; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.event.block.SignChangeEvent; + +public class DenizenPacketListenerImpl extends AbstractListenerPlayInImpl { + + public BlockPos fakeSignExpected; + + public DenizenPacketListenerImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer) { + super(networkManager, entityPlayer, entityPlayer.connection); + } + + @Override + public void handlePlayerInput(final ServerboundPlayerInputPacket packet) { + if (!PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) { + super.handlePlayerInput(packet); + return; + } + DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInSteerVehicleImpl(packet), () -> super.handlePlayerInput(packet)); + } + + @Override + public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { + DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInResourcePackStatusImpl(packet)); + super.handleResourcePackResponse(packet); + } + + @Override + public void handleUseItem(ServerboundUseItemPacket packet) { + DenizenPacketHandler.instance.receivePlacePacket(player.getBukkitEntity()); + super.handleUseItem(packet); + } + + @Override + public void handlePlayerAction(ServerboundPlayerActionPacket packet) { + DenizenPacketHandler.instance.receiveDigPacket(player.getBukkitEntity()); + super.handlePlayerAction(packet); + } + + @Override + public void handleAnimate(ServerboundSwingPacket packet) { + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer()); + if (override != null && (override.hand != null || override.offhand != null)) { + player.getBukkitEntity().updateInventory(); + } + super.handleAnimate(packet); + } + + @Override + public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) { + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer()); + if (override != null && override.hand != null) { + Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 2); + } + super.handleSetCarriedItem(packet); + } + + @Override + public void handleContainerClick(ServerboundContainerClickPacket packet) { + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer()); + if (override != null && packet.containerId() == 0) { + Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 1); + } + super.handleContainerClick(packet); + } + + @Override + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { + if (NMSHandler.debugPackets) { + Debug.log("Custom packet payload: " + packet.payload().type().id().toString() + " sent from " + player.getScoreboardName()); + } + super.handleCustomPayload(packet); + } + + @Override + public void handleSignUpdate(ServerboundSignUpdatePacket packet) { + if (fakeSignExpected != null && packet.getPos().equals(fakeSignExpected)) { + LocationTag loc = new LocationTag(player.getBukkitEntity().getWorld(), fakeSignExpected.getX(), fakeSignExpected.getY(), fakeSignExpected.getZ()); + this.connection.send(new ClientboundBlockUpdatePacket(player.level(), fakeSignExpected)); + PlayerChangesSignScriptEvent evt = (PlayerChangesSignScriptEvent) PlayerChangesSignScriptEvent.instance.clone(); + evt.material = new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN); + evt.location = new LocationTag(player.getBukkitEntity().getLocation()); + evt.event = new SignChangeEvent(CraftBlock.at(player.level(), fakeSignExpected), player.getBukkitEntity(), packet.getLines()); + fakeSignExpected = null; + evt.fire(evt.event); + } + super.handleSignUpdate(packet); + } + + @Override + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + if (DenizenPacketHandler.forceNoclip.contains(player.getUUID())) { + player.noPhysics = true; + } + super.handleMovePlayer(packet); + } + + // For compatibility with other plugins using Reflection weirdly... + @Override + public void send(Packet packet) { + super.send(packet); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/FakeBlockHelper.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/FakeBlockHelper.java new file mode 100644 index 0000000000..11a61644f0 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/FakeBlockHelper.java @@ -0,0 +1,167 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.utilities.blocks.FakeBlock; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import io.netty.buffer.Unpooled; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.Strategy; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlockStates; +import org.bukkit.craftbukkit.block.data.CraftBlockData; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class FakeBlockHelper { + + public static Field CHUNKDATA_BLOCK_ENTITIES = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getFirstOfType(List.class); + public static MethodHandle CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR = ReflectionHelper.getConstructor(ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0], int.class, int.class, BlockEntityType.class, CompoundTag.class); + public static MethodHandle CHUNKDATA_BUFFER_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkPacketData.class, byte[].class); + public static Class CHUNKDATA_BLOCKENTITYINFO_CLASS = ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0]; + public static Field CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get("packedXZ"); + public static Field CHUNKDATA_BLOCKENTITYINFO_Y = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get("y"); + public static MethodHandle CHUNKPACKET_CHUNKDATA_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class); + public static Constructor PALETTEDCONTAINER_CTOR = Arrays.stream(PalettedContainer.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findFirst().get(); + + public static BlockState getNMSState(FakeBlock block) { + return ((CraftBlockData) block.material.getModernData()).getState(); + } + + public static boolean anyBlocksInSection(List blocks, int y) { + int minY = y << 4; + int maxY = (y << 4) + 16; + for (FakeBlock block : blocks) { + int blockY = block.location.getBlockY(); + if (blockY >= minY && blockY < maxY) { + return true; + } + } + return false; + } + + public static Field PAPER_CHUNK_READY; + public static boolean tryPaperPatch = true; + + public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket, ClientboundLevelChunkWithLightPacket oldPacket) { + if (!Denizen.supportsPaper || !tryPaperPatch) { + return; + } + try { + if (PAPER_CHUNK_READY == null) { + PAPER_CHUNK_READY = ReflectionHelper.getFields(ClientboundLevelChunkWithLightPacket.class).get("ready"); + } + } + catch (Throwable ex) { + tryPaperPatch = false; + Debug.echoError("Paper packet patch failed:"); + Debug.echoError(ex); + return; + } + try { + PAPER_CHUNK_READY.setBoolean(newPacket, true); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static ClientboundLevelChunkWithLightPacket handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalPacket, int chunkX, int chunkZ, List blocks) { + try { + ClientboundLevelChunkWithLightPacket duplicateCorePacket = DenizenNetworkManagerImpl.copyPacket(originalPacket, ClientboundLevelChunkWithLightPacket.STREAM_CODEC); + copyPacketPaperPatch(duplicateCorePacket, originalPacket); + RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry()); + originalPacket.getChunkData().write(copier); + ClientboundLevelChunkPacketData packet = new ClientboundLevelChunkPacketData(copier, chunkX, chunkZ); + FriendlyByteBuf serial = originalPacket.getChunkData().getReadBuffer(); + FriendlyByteBuf outputSerial = new FriendlyByteBuf(Unpooled.buffer(serial.readableBytes())); + List blockEntities = new ArrayList((List) CHUNKDATA_BLOCK_ENTITIES.get(originalPacket.getChunkData())); + CHUNKDATA_BLOCK_ENTITIES.set(packet, blockEntities); + for (int i = 0; i < blockEntities.size(); i++) { + Object blockEnt = blockEntities.get(i); + int xz = CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.getInt(blockEnt); + int y = CHUNKDATA_BLOCKENTITYINFO_Y.getInt(blockEnt); + int x = (chunkX << 4) + ((xz >> 4) & 15); + int z = (chunkZ << 4) + (xz & 15); + for (FakeBlock block : blocks) { + LocationTag loc = block.location; + if (loc.getBlockX() == x && loc.getBlockY() == y && loc.getBlockZ() == z && block.material != null) { + BlockEntity newBlockEnt = CraftBlockStates.createNewTileEntity(block.material.getMaterial()); + Object newData = CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR.invoke(xz, y, newBlockEnt.getType(), newBlockEnt.getUpdateTag(CraftRegistry.getMinecraftRegistry())); + blockEntities.set(i, newData); + break; + } + } + } + int worldMinY = world.getMinHeight(); + int worldMaxY = world.getMaxHeight(); + int minChunkY = worldMinY >> 4; + int maxChunkY = worldMaxY >> 4; + Registry biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().lookupOrThrow(Registries.BIOME); + for (int y = minChunkY; y < maxChunkY; y++) { + int blockCount = serial.readShort(); + // reflected constructors as workaround for spigot remapper bug - Mojang "IdMap" became Spigot "IRegistry" but should be "Registry" + PalettedContainer states = (PalettedContainer) PALETTEDCONTAINER_CTOR.newInstance(Blocks.AIR.defaultBlockState(), Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY)); + states.read(serial); + PalettedContainer biomes = (PalettedContainer) PALETTEDCONTAINER_CTOR.newInstance(biomeRegistry.getOrThrow(Biomes.PLAINS), Strategy.createForBiomes(biomeRegistry)); + biomes.read(serial); + if (anyBlocksInSection(blocks, y)) { + int minY = y << 4; + int maxY = (y << 4) + 16; + for (FakeBlock block : blocks) { + int blockY = block.location.getBlockY(); + if (blockY >= minY && blockY < maxY && block.material != null) { + int blockX = block.location.getBlockX(); + int blockZ = block.location.getBlockZ(); + blockX -= (blockX >> 4) * 16; + blockY -= (blockY >> 4) * 16; + blockZ -= (blockZ >> 4) * 16; + BlockState oldState = states.get(blockX, blockY, blockZ); + BlockState newState = getNMSState(block); + if (oldState.isAir() && !newState.isAir()) { + blockCount++; + } + else if (newState.isAir() && !oldState.isAir()) { + blockCount--; + } + states.set(blockX, blockY, blockZ, newState); + } + } + } + outputSerial.writeShort(blockCount); + states.write(outputSerial); + biomes.write(outputSerial); + } + byte[] outputBytes = outputSerial.array(); + CHUNKDATA_BUFFER_SETTER.invoke(packet, outputBytes); + CHUNKPACKET_CHUNKDATA_SETTER.invoke(duplicateCorePacket, packet); + return duplicateCorePacket; + } + catch (Throwable ex) { + Debug.echoError(ex); + } + return null; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/ActionBarEventPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/ActionBarEventPacketHandlers.java new file mode 100644 index 0000000000..c1f641f7b5 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/ActionBarEventPacketHandlers.java @@ -0,0 +1,39 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.events.player.PlayerReceivesActionbarScriptEvent; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.objects.core.ElementTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +public class ActionBarEventPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetActionBarTextPacket.class, ActionBarEventPacketHandlers::processActionbarPacket); + } + + public static ClientboundSetActionBarTextPacket processActionbarPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetActionBarTextPacket actionbarPacket) { + PlayerReceivesActionbarScriptEvent event = PlayerReceivesActionbarScriptEvent.instance; + if (!event.loaded) { + return actionbarPacket; + } + event.reset(); + Component actionbarText = actionbarPacket.text(); + event.message = new ElementTag(FormattedTextHelper.stringify(Handler.componentToSpigot(actionbarText)), true); + event.rawJson = new ElementTag(CraftChatMessage.toJSON(actionbarText), true); + event.system = new ElementTag(false); + event.player = PlayerTag.mirrorBukkitPlayer(networkManager.player.getBukkitEntity()); + event = (PlayerReceivesActionbarScriptEvent) event.triggerNow(); + if (event.cancelled) { + return null; + } + if (event.modified) { + return new ClientboundSetActionBarTextPacket(Handler.componentToNMS(event.altMessageDetermination)); + } + return actionbarPacket; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/AttachPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/AttachPacketHandlers.java new file mode 100644 index 0000000000..b1dc7adaf2 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/AttachPacketHandlers.java @@ -0,0 +1,307 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper; +import com.denizenscript.denizencore.utilities.CoreConfiguration; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.phys.Vec3; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.util.CraftVector; +import org.bukkit.util.Vector; + +import java.lang.reflect.Field; +import java.util.Set; + +public class AttachPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.class, AttachPacketHandlers::processAttachToForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRotateHeadPacket.class, AttachPacketHandlers::processAttachToForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, AttachPacketHandlers::processAttachToForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, AttachPacketHandlers::processAttachToForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRemoveEntitiesPacket.class, AttachPacketHandlers::processAttachToForPacket); + } + + public static Field POS_X_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("xa", short.class); + public static Field POS_Y_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("ya", short.class); + public static Field POS_Z_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("za", short.class); + public static Field YAW_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("yRot", byte.class); + public static Field PITCH_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("xRot", byte.class); + + public static Vector VECTOR_ZERO = new Vector(0, 0, 0); + + public static void tryProcessMovePacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket packet, Entity e) throws IllegalAccessException { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + ClientboundMoveEntityPacket pNew; + int newId = att.attached.getBukkitEntity().getEntityId(); + if (packet instanceof ClientboundMoveEntityPacket.Pos) { + pNew = new ClientboundMoveEntityPacket.Pos(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.isOnGround()); + } + else if (packet instanceof ClientboundMoveEntityPacket.Rot) { + pNew = new ClientboundMoveEntityPacket.Rot(newId, Mth.packDegrees(packet.getYRot()), Mth.packDegrees(packet.getXRot()), packet.isOnGround()); + } + else if (packet instanceof ClientboundMoveEntityPacket.PosRot) { + pNew = new ClientboundMoveEntityPacket.PosRot(newId, packet.getXa(), packet.getYa(), packet.getZa(), Mth.packDegrees(packet.getYRot()), Mth.packDegrees(packet.getXRot()), packet.isOnGround()); + } + else { + if (CoreConfiguration.debugVerbose) { + Debug.echoError("Impossible move-entity packet class: " + packet.getClass().getCanonicalName()); + } + return; + } + if (att.positionalOffset != null) { + boolean isRotate = packet instanceof ClientboundMoveEntityPacket.PosRot || packet instanceof ClientboundMoveEntityPacket.Rot; + float yaw, pitch; + if (att.noRotate) { + Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle(); + yaw = attachedEntity.getYRot(); + pitch = attachedEntity.getXRot(); + } + else if (isRotate) { + yaw = packet.getYRot(); + pitch = packet.getXRot(); + } + else { + yaw = e.getYRot(); + pitch = e.getXRot(); + } + if (att.noPitch) { + pitch = ((CraftEntity) att.attached.getBukkitEntity()).getHandle().getXRot(); + } + float newYaw = yaw; + if (isRotate) { + newYaw = EntityAttachmentHelper.normalizeAngle(newYaw + att.positionalOffset.getYaw()); + pitch = EntityAttachmentHelper.normalizeAngle(pitch + att.positionalOffset.getPitch()); + } + Vector goalPosition = att.fixedForOffset(new Vector(e.getX(), e.getY(), e.getZ()), e.getYRot(), e.getXRot()); + Vector oldPos = att.visiblePositions.get(networkManager.player.getUUID()); + boolean forceTele = false; + if (oldPos == null) { + oldPos = att.attached.getLocation().toVector(); + forceTele = true; + } + Vector moveNeeded = goalPosition.clone().subtract(oldPos); + att.visiblePositions.put(networkManager.player.getUUID(), goalPosition.clone()); + int offX = (int) (moveNeeded.getX() * (32 * 128)); + int offY = (int) (moveNeeded.getY() * (32 * 128)); + int offZ = (int) (moveNeeded.getZ() * (32 * 128)); + if ((isRotate && att.offsetRelative) || forceTele || offX < Short.MIN_VALUE || offX > Short.MAX_VALUE + || offY < Short.MIN_VALUE || offY > Short.MAX_VALUE + || offZ < Short.MIN_VALUE || offZ > Short.MAX_VALUE) { + ClientboundTeleportEntityPacket newTeleportPacket = new ClientboundTeleportEntityPacket( + att.attached.getBukkitEntity().getEntityId(), + new PositionMoveRotation(CraftVector.toNMS(goalPosition), Vec3.ZERO, newYaw, pitch), + Set.of(), + e.onGround() + ); + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Move-Tele Packet: " + newTeleportPacket.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName() + " with original yaw " + yaw + " adapted to " + newYaw); + } + networkManager.oldManager.send(newTeleportPacket); + } + else { + POS_X_PACKENT.setShort(pNew, (short) Mth.clamp(offX, Short.MIN_VALUE, Short.MAX_VALUE)); + POS_Y_PACKENT.setShort(pNew, (short) Mth.clamp(offY, Short.MIN_VALUE, Short.MAX_VALUE)); + POS_Z_PACKENT.setShort(pNew, (short) Mth.clamp(offZ, Short.MIN_VALUE, Short.MAX_VALUE)); + if (isRotate) { + YAW_PACKENT.setByte(pNew, EntityAttachmentHelper.compressAngle(yaw)); + PITCH_PACKENT.setByte(pNew, EntityAttachmentHelper.compressAngle(pitch)); + } + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Move Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName() + " with original yaw " + yaw + " adapted to " + newYaw); + } + networkManager.oldManager.send(pNew); + } + } + else { + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Replica-Move Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName()); + } + networkManager.oldManager.send(pNew); + } + } + } + } + if (e.passengers != null && !e.passengers.isEmpty()) { + for (Entity ent : e.passengers) { + tryProcessMovePacketForAttach(networkManager, packet, ent); + } + } + } + + public static void tryProcessRotateHeadPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundRotateHeadPacket packet, Entity e) throws IllegalAccessException { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + float yaw = packet.getYHeadRot(); + Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle(); + if (att.positionalOffset != null) { + if (att.noRotate) { + yaw = attachedEntity.getYRot(); + } + yaw = EntityAttachmentHelper.normalizeAngle(yaw + att.positionalOffset.getYaw()); + } + ClientboundRotateHeadPacket pNew = new ClientboundRotateHeadPacket(attachedEntity, EntityAttachmentHelper.compressAngle(yaw)); + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Head Rotation Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName()); + } + networkManager.oldManager.send(pNew); + } + } + } + if (e.passengers != null && !e.passengers.isEmpty()) { + for (Entity ent : e.passengers) { + tryProcessRotateHeadPacketForAttach(networkManager, packet, ent); + } + } + } + + public static void tryProcessVelocityPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityMotionPacket packet, Entity e) { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + ClientboundSetEntityMotionPacket pNew = new ClientboundSetEntityMotionPacket(att.attached.getBukkitEntity().getEntityId(), packet.movement()); + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Velocity Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName()); + } + networkManager.oldManager.send(pNew); + } + } + } + if (e.passengers != null && !e.passengers.isEmpty()) { + for (Entity ent : e.passengers) { + tryProcessVelocityPacketForAttach(networkManager, packet, ent); + } + } + } + + public static void tryProcessTeleportPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket packet, Entity e, Vector relative) throws IllegalAccessException { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + ClientboundTeleportEntityPacket pNew; + Vector resultPos = CraftVector.toBukkit(packet.change().position()).add(relative); + if (att.positionalOffset != null) { + resultPos = att.fixedForOffset(resultPos, e.getYRot(), e.getXRot()); + float yaw, pitch; + if (att.noRotate) { + Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle(); + yaw = attachedEntity.getYRot(); + pitch = attachedEntity.getXRot(); + } + else { + yaw = packet.change().yRot(); + pitch = packet.change().xRot(); + } + if (att.noPitch) { + pitch = ((CraftEntity) att.attached.getBukkitEntity()).getHandle().getXRot(); + } + float newYaw = EntityAttachmentHelper.normalizeAngle(yaw + att.positionalOffset.getYaw()); + pitch = EntityAttachmentHelper.normalizeAngle(pitch + att.positionalOffset.getPitch()); + pNew = new ClientboundTeleportEntityPacket( + att.attached.getBukkitEntity().getEntityId(), + new PositionMoveRotation(CraftVector.toNMS(resultPos), packet.change().deltaMovement(), newYaw, pitch), + packet.relatives(), + packet.onGround() + ); + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Teleport Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + + " sent to " + networkManager.player.getScoreboardName() + " with raw yaw " + yaw + " adapted to " + newYaw); + } + } + else { + pNew = new ClientboundTeleportEntityPacket(att.attached.getBukkitEntity().getEntityId(), packet.change(), packet.relatives(), packet.onGround()); + } + att.visiblePositions.put(networkManager.player.getUUID(), resultPos.clone()); + networkManager.oldManager.send(pNew); + } + } + } + if (e.passengers != null && !e.passengers.isEmpty()) { + for (Entity ent : e.passengers) { + tryProcessTeleportPacketForAttach(networkManager, packet, ent, new Vector(ent.getX() - e.getX(), ent.getY() - e.getY(), ent.getZ() - e.getZ())); + } + } + } + + public static Packet processAttachToForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (EntityAttachmentHelper.toEntityToData.isEmpty()) { + return packet; + } + try { + if (packet instanceof ClientboundMoveEntityPacket moveEntityPacket) { + Entity e = moveEntityPacket.getEntity(networkManager.player.level()); + if (e == null) { + return packet; + } + if (!e.isPassenger()) { + tryProcessMovePacketForAttach(networkManager, moveEntityPacket, e); + } + return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet; + } + else if (packet instanceof ClientboundRotateHeadPacket rotateHeadPacket) { + Entity e = rotateHeadPacket.getEntity(networkManager.player.level()); + if (e == null) { + return packet; + } + tryProcessRotateHeadPacketForAttach(networkManager, rotateHeadPacket, e); + return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet; + } + else if (packet instanceof ClientboundSetEntityMotionPacket setEntityMotionPacket) { + int ider = setEntityMotionPacket.id(); + Entity e = networkManager.player.level().getEntity(ider); + if (e == null) { + return packet; + } + tryProcessVelocityPacketForAttach(networkManager, setEntityMotionPacket, e); + return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet; + } + else if (packet instanceof ClientboundTeleportEntityPacket teleportEntityPacket) { + int ider = teleportEntityPacket.id(); + Entity e = networkManager.player.level().getEntity(ider); + if (e == null) { + return packet; + } + tryProcessTeleportPacketForAttach(networkManager, teleportEntityPacket, e, VECTOR_ZERO); + return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet; + } + else if (packet instanceof ClientboundRemoveEntitiesPacket removeEntitiesPacket) { + for (int id : removeEntitiesPacket.getEntityIds()) { + Entity e = networkManager.player.level().getEntity(id); + if (e != null) { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + att.visiblePositions.remove(networkManager.player.getUUID()); + } + } + } + } + } + } + } + catch (Exception ex) { + Debug.echoError(ex); + } + return packet; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/BlockLightPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/BlockLightPacketHandlers.java new file mode 100644 index 0000000000..0a6f0257bc --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/BlockLightPacketHandlers.java @@ -0,0 +1,27 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.abstracts.BlockLight; +import com.denizenscript.denizen.nms.v26_1.impl.blocks.BlockLightImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; + +public class BlockLightPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLightUpdatePacket.class, BlockLightPacketHandlers::processLightUpdatePacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, BlockLightPacketHandlers::processBlockUpdatePacket); + } + + public static void processLightUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundLightUpdatePacket lightUpdatePacket) { + if (!BlockLight.lightsByChunk.isEmpty()) { + BlockLightImpl.checkIfLightsBrokenByPacket(lightUpdatePacket, networkManager.player.level()); + } + } + + public static void processBlockUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockUpdatePacket blockUpdatePacket) { + if (!BlockLight.lightsByChunk.isEmpty()) { + BlockLightImpl.checkIfLightsBrokenByPacket(blockUpdatePacket, networkManager.player.level()); + } + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java new file mode 100644 index 0000000000..43f76611ee --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java @@ -0,0 +1,47 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.events.player.PlayerReceivesMessageScriptEvent; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.packets.PacketOutChatImpl; +import com.denizenscript.denizen.utilities.packets.DenizenPacketHandler; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; +import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; + +public class DenizenPacketHandlerPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSystemChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket); + } + + public static Packet processPacketHandlerForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (DenizenPacketHandler.instance.shouldInterceptChatPacket()) { + PacketOutChatImpl packetHelper = null; + boolean isActionbar = false; + if (packet instanceof ClientboundSystemChatPacket chatPacket) { + isActionbar = chatPacket.overlay(); + packetHelper = new PacketOutChatImpl(chatPacket); + if (packetHelper.rawJson == null) { // Makes no sense but this can be null in weird edge cases + return packet; + } + } + else if (packet instanceof ClientboundPlayerChatPacket playerChatPacket) { + packetHelper = new PacketOutChatImpl(playerChatPacket); + } + if (packetHelper != null) { + PlayerReceivesMessageScriptEvent result = DenizenPacketHandler.instance.sendPacket(networkManager.player.getBukkitEntity(), packetHelper); + if (result != null) { + if (result.cancelled) { + return null; + } + if (result.modified) { + return new ClientboundSystemChatPacket(result.altMessageDetermination, isActionbar); + } + } + } + } + return packet; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/DisguisePacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/DisguisePacketHandlers.java new file mode 100644 index 0000000000..12f13d8070 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/DisguisePacketHandlers.java @@ -0,0 +1,160 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v26_1.helpers.PacketHelperImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.scripts.commands.player.DisguiseCommand; +import com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper; +import com.denizenscript.denizen.utilities.entity.FakeEntity; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.level.Level; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.ToIntFunction; + +public class DisguisePacketHandlers { + + public static void registerHandlers() { + registerPacketHandler(ClientboundSetEntityDataPacket.class, ClientboundSetEntityDataPacket::id, DisguisePacketHandlers::processEntityDataPacket); + registerPacketHandler(ClientboundUpdateAttributesPacket.class, ClientboundUpdateAttributesPacket::getEntityId, DisguisePacketHandlers::processAttributesPacket); + registerPacketHandler(ClientboundAddEntityPacket.class, ClientboundAddEntityPacket::getId, DisguisePacketHandlers::sendDisguiseForPacket); + registerPacketHandler(ClientboundTeleportEntityPacket.class, ClientboundTeleportEntityPacket::id, DisguisePacketHandlers::processTeleportPacket); + registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityRotPacket); + registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityPosRotPacket); + } + + private static boolean antiDuplicate = false; + + public static > void registerPacketHandler(Class packetType, ToIntFunction idGetter, DisguisePacketHandler handler) { + registerPacketHandler(packetType, (packet, level) -> level.getEntity(idGetter.applyAsInt(packet)), handler); + } + + public static > void registerPacketHandler(Class packetType, BiFunction entityGetter, DisguisePacketHandler handler) { + DenizenNetworkManagerImpl.registerPacketHandler(packetType, (networkManager, packet) -> { + if (DisguiseCommand.disguises.isEmpty() || antiDuplicate) { + return packet; + } + Entity entity = entityGetter.apply(packet, networkManager.player.level()); + if (entity == null) { + return packet; + } + Map playerMap = DisguiseCommand.disguises.get(entity.getUUID()); + if (playerMap == null) { + return packet; + } + DisguiseCommand.TrackedDisguise disguise = playerMap.get(networkManager.player.getUUID()); + if (disguise == null) { + disguise = playerMap.get(null); + } + if (disguise == null || !disguise.isActive) { + return packet; + } + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("DISGUISED packet " + packet.getClass().getName() + " for entity " + entity.getId() + " to player " + networkManager.player.getScoreboardName()); + } + try { + return handler.handle(networkManager, packet, disguise); + } + catch (Exception e) { + antiDuplicate = false; + throw e; // "pass it" to the generic exception handling + } + }); + } + + @FunctionalInterface + public interface DisguisePacketHandler> { + + T handle(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) throws Exception; + } + + public static ClientboundSetEntityDataPacket processEntityDataPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket entityDataPacket, DisguiseCommand.TrackedDisguise disguise) { + if (entityDataPacket.id() == networkManager.player.getId()) { + if (!disguise.shouldFake) { + return entityDataPacket; + } + for (SynchedEntityData.DataValue dataValue : entityDataPacket.packedItems()) { + if (dataValue.id() == 0) { // Entity flags + List> newData = new ArrayList<>(entityDataPacket.packedItems()); + newData.remove(dataValue); + byte flags = (byte) dataValue.value(); + flags |= 0x20; // Invisible flag + newData.add(PacketHelperImpl.createEntityData(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags)); + return new ClientboundSetEntityDataPacket(entityDataPacket.id(), newData); + } + } + } + else { + List> data = ((CraftEntity) disguise.toOthers.entity.entity).getHandle().getEntityData().getNonDefaultValues(); + return data != null ? new ClientboundSetEntityDataPacket(entityDataPacket.id(), data) : null; + } + return entityDataPacket; + } + + public static ClientboundUpdateAttributesPacket processAttributesPacket(DenizenNetworkManagerImpl networkManager, ClientboundUpdateAttributesPacket attributesPacket, DisguiseCommand.TrackedDisguise disguise) { + FakeEntity fake = attributesPacket.getEntityId() == networkManager.player.getId() ? disguise.fakeToSelf : disguise.toOthers; + return fake == null || fake.entity.entity instanceof LivingEntity ? attributesPacket : null; // Non-living entities don't have attributes + } + + public static ClientboundTeleportEntityPacket processTeleportPacket(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket teleportEntityPacket, DisguiseCommand.TrackedDisguise disguise) throws IllegalAccessException { + if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) { + PositionMoveRotation oldChange = teleportEntityPacket.change(); + return new ClientboundTeleportEntityPacket( + teleportEntityPacket.id(), + new PositionMoveRotation(oldChange.position(), oldChange.deltaMovement(), EntityAttachmentHelper.normalizeAngle(oldChange.yRot() + 180), oldChange.xRot()), + teleportEntityPacket.relatives(), + teleportEntityPacket.onGround() + ); + } + return sendDisguiseForPacket(networkManager, teleportEntityPacket, disguise); + } + + + public static ClientboundMoveEntityPacket.Rot processMoveEntityRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.Rot rotPacket, DisguiseCommand.TrackedDisguise disguise) { + if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) { + return new ClientboundMoveEntityPacket.Rot( + disguise.entity.getBukkitEntity().getEntityId(), + EntityAttachmentHelper.compressAngle(rotPacket.getYRot() + 180), + Mth.packDegrees(rotPacket.getXRot()), + rotPacket.isOnGround() + ); + } + return sendDisguiseForPacket(networkManager, rotPacket, disguise); + } + + + public static ClientboundMoveEntityPacket.PosRot processMoveEntityPosRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.PosRot posRotPacket, DisguiseCommand.TrackedDisguise disguise) { + if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) { + return new ClientboundMoveEntityPacket.PosRot( + disguise.entity.getBukkitEntity().getEntityId(), + posRotPacket.getXa(), + posRotPacket.getYa(), + posRotPacket.getZa(), + EntityAttachmentHelper.compressAngle(posRotPacket.getYRot() + 180), + Mth.packDegrees(posRotPacket.getXRot()), + posRotPacket.isOnGround() + ); + } + return sendDisguiseForPacket(networkManager, posRotPacket, disguise); + } + + public static > T sendDisguiseForPacket(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) { + antiDuplicate = true; + disguise.sendTo(List.of(new PlayerTag(networkManager.player.getUUID()))); + antiDuplicate = false; + return null; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/EntityMetadataPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/EntityMetadataPacketHandlers.java new file mode 100644 index 0000000000..f0b1cf4b7f --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/EntityMetadataPacketHandlers.java @@ -0,0 +1,92 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.helpers.PacketHelperImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.scripts.commands.entity.GlowCommand; +import com.denizenscript.denizen.scripts.commands.entity.InvisibleCommand; +import com.denizenscript.denizen.scripts.commands.entity.RenameCommand; +import com.denizenscript.denizen.scripts.commands.entity.SneakCommand; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.entity.Entity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class EntityMetadataPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, EntityMetadataPacketHandlers::processMetadataChangesForPacket); + } + + public static ClientboundSetEntityDataPacket getModifiedMetadataFor(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket metadataPacket) { + if (!RenameCommand.hasAnyDynamicRenames() && SneakCommand.forceSetSneak.isEmpty() && InvisibleCommand.helper.noOverrides() && GlowCommand.helper.noOverrides()) { + return null; + } + try { + Entity entity = networkManager.player.level().getEntity(metadataPacket.id()); + if (entity == null) { + return null; // If it doesn't exist on-server, it's definitely not relevant, so move on + } + String nameToApply = RenameCommand.getCustomNameFor(entity.getUUID(), networkManager.player.getBukkitEntity(), false); + Boolean forceSneak = SneakCommand.shouldSneak(entity.getUUID(), networkManager.player.getUUID()); + Boolean isInvisible = InvisibleCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true); + Boolean isGlowing = GlowCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true); + boolean shouldModifyFlags = isInvisible != null || forceSneak != null || isGlowing != null; + if (nameToApply == null && !shouldModifyFlags) { + return null; + } + List> data = new ArrayList<>(metadataPacket.packedItems().size()); + Byte currentFlags = null; + for (SynchedEntityData.DataValue dataValue : metadataPacket.packedItems()) { + if (dataValue.id() == 0 && shouldModifyFlags) { // 0: Entity Flags + currentFlags = (Byte) dataValue.value(); + } + else if (nameToApply == null || (dataValue.id() != 2 && dataValue.id() != 3)) { // 2 and 3: Custom name and custom name visible + data.add(dataValue); + } + } + if (shouldModifyFlags) { + byte flags = currentFlags == null ? entity.getEntityData().get(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS) : currentFlags; + flags = applyEntityDataFlag(flags, forceSneak, 0x02); + flags = applyEntityDataFlag(flags, isInvisible, 0x20); + flags = applyEntityDataFlag(flags, isGlowing, 0x40); + data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags)); + } + if (nameToApply != null) { + data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(nameToApply, ChatColor.WHITE))))); + data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true)); + } + return new ClientboundSetEntityDataPacket(metadataPacket.id(), data); + } + catch (Throwable ex) { + Debug.echoError(ex); + return null; + } + } + + public static byte applyEntityDataFlag(byte currentFlags, Boolean value, int flag) { + if (value == null) { + return currentFlags; + } + return (byte) (value ? currentFlags | flag : currentFlags & ~flag); + } + + public static Packet processMetadataChangesForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (!(packet instanceof ClientboundSetEntityDataPacket entityDataPacket)) { + return packet; + } + ClientboundSetEntityDataPacket altPacket = getModifiedMetadataFor(networkManager, entityDataPacket); + if (altPacket == null) { + return packet; + } + return altPacket; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakeBlocksPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakeBlocksPacketHandlers.java new file mode 100644 index 0000000000..0144bcfa08 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakeBlocksPacketHandlers.java @@ -0,0 +1,111 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.FakeBlockHelper; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.utilities.blocks.ChunkCoordinate; +import com.denizenscript.denizen.utilities.blocks.FakeBlock; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.world.level.block.state.BlockState; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; + +public class FakeBlocksPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelChunkWithLightPacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSectionBlocksUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket); + } + + public static Field SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get("sectionPos", SectionPos.class); + public static Field OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get("positions", short[].class); + public static Field BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get("states", BlockState[].class); + + public static Packet processShowFakeForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (FakeBlock.blocks.isEmpty()) { + return packet; + } + try { + if (packet instanceof ClientboundLevelChunkWithLightPacket) { + FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID()); + if (map == null) { + return packet; + } + int chunkX = ((ClientboundLevelChunkWithLightPacket) packet).getX(); + int chunkZ = ((ClientboundLevelChunkWithLightPacket) packet).getZ(); + ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, networkManager.player.level().getWorld().getName()); + List blocks = FakeBlock.getFakeBlocksFor(networkManager.player.getUUID(), chunkCoord); + if (blocks == null || blocks.isEmpty()) { + return packet; + } + ClientboundLevelChunkWithLightPacket newPacket = FakeBlockHelper.handleMapChunkPacket(networkManager.player.getBukkitEntity().getWorld(), (ClientboundLevelChunkWithLightPacket) packet, chunkX, chunkZ, blocks); + return newPacket; + } + else if (packet instanceof ClientboundSectionBlocksUpdatePacket sectionBlocksUpdatePacket) { + FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID()); + if (map == null) { + return sectionBlocksUpdatePacket; + } + SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.get(sectionBlocksUpdatePacket); + ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), networkManager.player.level().getWorld().getName()); + if (!map.byChunk.containsKey(coordinateDenizen)) { + return sectionBlocksUpdatePacket; + } + ClientboundSectionBlocksUpdatePacket newPacket = DenizenNetworkManagerImpl.copyPacket(sectionBlocksUpdatePacket, ClientboundSectionBlocksUpdatePacket.STREAM_CODEC); + LocationTag location = new LocationTag(networkManager.player.level().getWorld(), 0, 0, 0); + short[] originalOffsetArray = (short[])OFFSETARRAY_MULTIBLOCKCHANGE.get(newPacket); + BlockState[] originalDataArray = (BlockState[])BLOCKARRAY_MULTIBLOCKCHANGE.get(newPacket); + short[] offsetArray = Arrays.copyOf(originalOffsetArray, originalOffsetArray.length); + BlockState[] dataArray = Arrays.copyOf(originalDataArray, originalDataArray.length); + OFFSETARRAY_MULTIBLOCKCHANGE.set(newPacket, offsetArray); + BLOCKARRAY_MULTIBLOCKCHANGE.set(newPacket, dataArray); + for (int i = 0; i < offsetArray.length; i++) { + short offset = offsetArray[i]; + BlockPos pos = coord.relativeToBlockPos(offset); + location.setX(pos.getX()); + location.setY(pos.getY()); + location.setZ(pos.getZ()); + FakeBlock block = map.byLocation.get(location); + if (block != null) { + dataArray[i] = FakeBlockHelper.getNMSState(block); + } + } + return newPacket; + } + else if (packet instanceof ClientboundBlockUpdatePacket) { + BlockPos pos = ((ClientboundBlockUpdatePacket) packet).getPos(); + LocationTag loc = new LocationTag(networkManager.player.level().getWorld(), pos.getX(), pos.getY(), pos.getZ()); + FakeBlock block = FakeBlock.getFakeBlockFor(networkManager.player.getUUID(), loc); + if (block != null) { + ClientboundBlockUpdatePacket newPacket = new ClientboundBlockUpdatePacket(((ClientboundBlockUpdatePacket) packet).getPos(), FakeBlockHelper.getNMSState(block)); + return newPacket; + } + } + else if (packet instanceof ClientboundBlockChangedAckPacket) { + // TODO: 1.19: Can no longer determine what block this packet is for. Would have to track separately? Possibly from the inbound packet rather than the outbound one. + /* + ClientboundBlockChangedAckPacket origPack = (ClientboundBlockChangedAckPacket) packet; + BlockPos pos = origPack.pos(); + LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ()); + FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc); + if (block != null) { + ClientboundBlockChangedAckPacket newPacket = new ClientboundBlockChangedAckPacket(origPack.pos(), FakeBlockHelper.getNMSState(block), origPack.action(), false); + oldManager.send(newPacket, genericfuturelistener); + return true; + }*/ + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + return packet; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java new file mode 100644 index 0000000000..9471624704 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java @@ -0,0 +1,138 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand; +import com.mojang.datafixers.util.Pair; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.inventory.CraftItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class FakeEquipmentPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEquipmentPacket.class, FakeEquipmentPacketHandlers::processSetEquipmentPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundEntityEventPacket.class, FakeEquipmentPacketHandlers::processEntityEventPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetContentPacket.class, FakeEquipmentPacketHandlers::processContainerSetContentPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetSlotPacket.class, FakeEquipmentPacketHandlers::processContainerSetSlotPacket); + } + + public static ClientboundSetEquipmentPacket processSetEquipmentPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEquipmentPacket setEquipmentPacket) { + if (FakeEquipCommand.overrides.isEmpty()) { + return setEquipmentPacket; + } + Entity entity = networkManager.player.level().getEntity(setEquipmentPacket.getEntity()); + if (entity == null) { + return setEquipmentPacket; + } + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(entity.getUUID(), networkManager.player.getBukkitEntity()); + if (override == null) { + return setEquipmentPacket; + } + List> equipment = new ArrayList<>(setEquipmentPacket.getSlots()); + for (int i = 0; i < equipment.size(); i++) { + Pair pair = equipment.get(i); + ItemStack use = switch (pair.getFirst()) { + case MAINHAND -> override.hand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.hand.getItemStack()); + case OFFHAND -> override.offhand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.offhand.getItemStack()); + case CHEST -> override.chest == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.chest.getItemStack()); + case HEAD -> override.head == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.head.getItemStack()); + case LEGS -> override.legs == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.legs.getItemStack()); + case FEET -> override.boots == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.boots.getItemStack()); + case BODY -> pair.getSecond(); // TODO: 1.20.6: is this actually used here? do we want to allow overriding it? + case SADDLE -> pair.getSecond(); // TODO: 1.21.5: same as above + }; + equipment.set(i, new Pair<>(pair.getFirst(), use)); + } + return new ClientboundSetEquipmentPacket(setEquipmentPacket.getEntity(), equipment); + } + + public static Packet processEntityEventPacket(DenizenNetworkManagerImpl networkManager, ClientboundEntityEventPacket entityEventPacket) { + if (FakeEquipCommand.overrides.isEmpty()) { + return entityEventPacket; + } + if (entityEventPacket.getEventId() != 55) { + return entityEventPacket; + } + if (!(entityEventPacket.getEntity(networkManager.player.level()) instanceof LivingEntity livingEntity)) { + return entityEventPacket; + } + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(livingEntity.getUUID(), networkManager.player.getBukkitEntity()); + if (override == null || (override.hand == null && override.offhand == null)) { + return entityEventPacket; + } + ItemStack hand = override.hand != null ? CraftItemStack.asNMSCopy(override.hand.getItemStack()) : livingEntity.getMainHandItem(); + ItemStack offhand = override.offhand != null ? CraftItemStack.asNMSCopy(override.offhand.getItemStack()) : livingEntity.getOffhandItem(); + return new ClientboundSetEquipmentPacket(livingEntity.getId(), List.of(new Pair<>(EquipmentSlot.MAINHAND, hand), new Pair<>(EquipmentSlot.OFFHAND, offhand))); + } + + public static ClientboundContainerSetContentPacket processContainerSetContentPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetContentPacket setContentPacket) { + if (FakeEquipCommand.overrides.isEmpty()) { + return setContentPacket; + } + if (setContentPacket.containerId() != 0) { + return setContentPacket; + } + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity()); + if (override == null) { + return setContentPacket; + } + List items = setContentPacket.items(); + if (override.head != null) { + items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack())); + } + if (override.chest != null) { + items.set(6, CraftItemStack.asNMSCopy(override.chest.getItemStack())); + } + if (override.legs != null) { + items.set(7, CraftItemStack.asNMSCopy(override.legs.getItemStack())); + } + if (override.boots != null) { + items.set(8, CraftItemStack.asNMSCopy(override.boots.getItemStack())); + } + if (override.offhand != null) { + items.set(45, CraftItemStack.asNMSCopy(override.offhand.getItemStack())); + } + if (override.hand != null) { + items.set(getMainHandSlot(networkManager.player), CraftItemStack.asNMSCopy(override.hand.getItemStack())); + } + return new ClientboundContainerSetContentPacket(setContentPacket.containerId(), setContentPacket.stateId(), items, setContentPacket.carriedItem()); + } + + public static ClientboundContainerSetSlotPacket processContainerSetSlotPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetSlotPacket setSlotPacket) { + if (FakeEquipCommand.overrides.isEmpty()) { + return setSlotPacket; + } + if (setSlotPacket.getContainerId() != 0) { + return setSlotPacket; + } + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity()); + if (override == null) { + return setSlotPacket; + } + ItemTag item = switch (setSlotPacket.getSlot()) { + case 5 -> override.head; + case 6 -> override.chest; + case 7 -> override.legs; + case 8 -> override.boots; + case 45 -> override.offhand; + default -> setSlotPacket.getSlot() == getMainHandSlot(networkManager.player) ? override.hand : null; + }; + if (item == null) { + return setSlotPacket; + } + return new ClientboundContainerSetSlotPacket(setSlotPacket.getContainerId(), setSlotPacket.getStateId(), setSlotPacket.getSlot(), CraftItemStack.asNMSCopy(item.getItemStack())); + } + + public static int getMainHandSlot(ServerPlayer player) { + return player.getInventory().getSelectedSlot() + 36; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakePlayerPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakePlayerPacketHandlers.java new file mode 100644 index 0000000000..ac5c76ae4f --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakePlayerPacketHandlers.java @@ -0,0 +1,29 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +/* +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v1_20.impl.entities.EntityFakePlayerImpl; +import com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import org.bukkit.Bukkit; + +import java.util.List; +*/ + +public class FakePlayerPacketHandlers { + + public static void registerHandlers() { + // TODO: 1.20.2: Replace this. + //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddPlayerPacket.class, FakePlayerPacketHandlers::processAddPlayerPacket); + } + + /* + public static void processAddPlayerPacket(DenizenNetworkManagerImpl networkManager, ClientboundAddPlayerPacket addPlayerPacket) { + if (networkManager.player.level().getEntity(addPlayerPacket.getEntityId()) instanceof EntityFakePlayerImpl fakePlayer) { + networkManager.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, fakePlayer)); + Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), + () -> networkManager.send(new ClientboundPlayerInfoRemovePacket(List.of(fakePlayer.getUUID()))), 5); + } + }*/ +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java new file mode 100644 index 0000000000..d905b22d07 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java @@ -0,0 +1,69 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.utilities.entity.HideEntitiesHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; + +public class HiddenEntitiesPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddExperienceOrbPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Pos.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + } + + public static boolean isHidden(ServerPlayer player, Entity entity) { + return entity != null && HideEntitiesHelper.playerShouldHide(player.getBukkitEntity().getUniqueId(), entity.getBukkitEntity()); + } + + public static Packet processHiddenEntitiesForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (!HideEntitiesHelper.hasAnyHides()) { + return packet; + } + try { + int ider = -1; + Entity e = null; + if (packet instanceof ClientboundAddEntityPacket) { + ider = ((ClientboundAddEntityPacket) packet).getId(); + } + //TODO: 1.21.5: check this packet list + /* + else if (packet instanceof ClientboundAddExperienceOrbPacket) { + ider = ((ClientboundAddExperienceOrbPacket) packet).getId(); + }*/ + else if (packet instanceof ClientboundMoveEntityPacket) { + e = ((ClientboundMoveEntityPacket) packet).getEntity(networkManager.player.level()); + } + else if (packet instanceof ClientboundSetEntityDataPacket) { + ider = ((ClientboundSetEntityDataPacket) packet).id(); + } + else if (packet instanceof ClientboundSetEntityMotionPacket) { + ider = ((ClientboundSetEntityMotionPacket) packet).id(); + } + else if (packet instanceof ClientboundTeleportEntityPacket) { + ider = ((ClientboundTeleportEntityPacket) packet).id(); + } + if (e == null && ider != -1) { + e = networkManager.player.level().getEntity(ider); + } + if (e != null) { + if (isHidden(networkManager.player, e)) { + return null; + } + } + } + catch (Exception ex) { + Debug.echoError(ex); + } + return packet; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/HideParticlesPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/HideParticlesPacketHandlers.java new file mode 100644 index 0000000000..70c64da27b --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/HideParticlesPacketHandlers.java @@ -0,0 +1,31 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.utilities.packets.HideParticles; +import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; +import org.bukkit.Particle; +import org.bukkit.craftbukkit.CraftParticle; + +import java.util.Set; + +public class HideParticlesPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelParticlesPacket.class, HideParticlesPacketHandlers::processParticlesPacket); + } + + public static ClientboundLevelParticlesPacket processParticlesPacket(DenizenNetworkManagerImpl networkManager, ClientboundLevelParticlesPacket particlesPacket) { + if (HideParticles.hidden.isEmpty()) { + return particlesPacket; + } + Set hidden = HideParticles.hidden.get(networkManager.player.getUUID()); + if (hidden == null) { + return particlesPacket; + } + Particle bukkitParticle = CraftParticle.minecraftToBukkit(particlesPacket.getParticle().getType()); + if (hidden.contains(bukkitParticle)) { + return null; + } + return particlesPacket; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java new file mode 100644 index 0000000000..f26a97ad7e --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java @@ -0,0 +1,39 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.events.player.PlayerHearsSoundScriptEvent; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.world.entity.Entity; +import org.bukkit.Location; + +public class PlayerHearsSoundEventPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundEntityPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket); + } + + public static Packet processSoundPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (!PlayerHearsSoundScriptEvent.instance.eventData.isEnabled) { + return packet; + } + if (packet instanceof ClientboundSoundPacket) { + ClientboundSoundPacket spacket = (ClientboundSoundPacket) packet; + return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().location().getPath(), spacket.getSource().name(), + false, null, new Location(networkManager.player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch()) ? null : packet; + } + else if (packet instanceof ClientboundSoundEntityPacket) { + ClientboundSoundEntityPacket spacket = (ClientboundSoundEntityPacket) packet; + Entity entity = networkManager.player.level().getEntity(spacket.getId()); + if (entity == null) { + return packet; + } + return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().location().getPath(), spacket.getSource().name(), + false, entity.getBukkitEntity(), null, spacket.getVolume(), spacket.getPitch()) ? null : packet; + } + return packet; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java new file mode 100644 index 0000000000..cd436d5d26 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java @@ -0,0 +1,126 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet; + +import com.denizenscript.denizen.events.player.PlayerReceivesTablistUpdateScriptEvent; +import com.denizenscript.denizen.nms.v26_1.Handler; +import com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.base.Joiner; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.world.level.GameType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class TablistUpdateEventPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoRemovePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket); + } + + public static boolean tablistBreakOnlyOnce = false; + + // TODO: properly rebundle the packet instead of splitting it up + public static Packet processTablistPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (!PlayerReceivesTablistUpdateScriptEvent.instance.eventData.isEnabled) { + return packet; + } + if (packet instanceof ClientboundPlayerInfoUpdatePacket) { + ClientboundPlayerInfoUpdatePacket infoPacket = (ClientboundPlayerInfoUpdatePacket) packet; + String mode = ""; + for (ClientboundPlayerInfoUpdatePacket.Action action : infoPacket.actions()) { + switch (action) { + case ADD_PLAYER: + mode = "add"; + break; + case UPDATE_LATENCY: + mode = mode.isEmpty() ? "update_latency" : mode + "|update_latency"; + break; + case UPDATE_GAME_MODE: + mode = mode.isEmpty() ? "update_gamemode" : mode + "|update_gamemode"; + break; + case UPDATE_DISPLAY_NAME: + mode = mode.isEmpty() ? "update_display" : mode + "|update_display"; + break; + case UPDATE_LISTED: + mode = mode.isEmpty() ? "update_listed" : mode + "|update_listed"; + break; + case INITIALIZE_CHAT: + mode = mode.isEmpty() ? "initialize_chat" : mode + "|initialize_chat"; + default: + break; + } + } + if (mode.isEmpty()) { + if (!tablistBreakOnlyOnce) { + tablistBreakOnlyOnce = true; + Debug.echoError("Tablist packet processing failed: unknown action " + Joiner.on(", ").join(infoPacket.actions())); + } + return packet; + } + boolean isOverriding = false; + for (ClientboundPlayerInfoUpdatePacket.Entry update : infoPacket.entries()) { + GameProfile profile = update.profile(); + String texture = null, signature = null; + if (profile.properties().containsKey("textures")) { + Property property = profile.properties().get("textures").stream().findFirst().get(); + texture = property.value(); + signature = property.signature(); + } + String modeText = update.gameMode() == null ? null : update.gameMode().name(); + PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.id(), update.listed(), profile.name(), + update.displayName() == null ? null : FormattedTextHelper.stringify(Handler.componentToSpigot(update.displayName())), modeText, texture, signature, update.latency()); + PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data); + if (data.modified) { + if (!isOverriding) { + isOverriding = true; + for (ClientboundPlayerInfoUpdatePacket.Entry priorUpdate : infoPacket.entries()) { + if (priorUpdate == update) { + break; + } + networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(priorUpdate))); + } + } + if (!data.cancelled) { + GameProfile newProfile = ProfileEditorImpl.createGameProfile(data.id, data.name, data.texture, data.signature); + ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(newProfile.id(), newProfile, data.isListed, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)), + data.display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(data.display, ChatColor.WHITE)), update.showHat(), update.listOrder(), update.chatSession()); + networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(entry))); + } + } + else if (isOverriding) { + networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(update))); + } + } + return isOverriding ? null : packet; + } + else if (packet instanceof ClientboundPlayerInfoRemovePacket) { + ClientboundPlayerInfoRemovePacket removePacket = (ClientboundPlayerInfoRemovePacket) packet; + boolean modified = false; + List altIds = new ArrayList<>(((ClientboundPlayerInfoRemovePacket) packet).profileIds()); + for (UUID id : ((ClientboundPlayerInfoRemovePacket) packet).profileIds()) { + PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData("remove", id, false, null, null, null, null, null, 0); + PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data); + if (data.modified && data.cancelled) { + modified = true; + altIds.remove(id); + } + } + if (modified) { + return new ClientboundPlayerInfoRemovePacket(altIds); + } + } + return packet; + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketInResourcePackStatusImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketInResourcePackStatusImpl.java new file mode 100644 index 0000000000..64a80bad64 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketInResourcePackStatusImpl.java @@ -0,0 +1,18 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.packets; + +import com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus; +import net.minecraft.network.protocol.common.ServerboundResourcePackPacket; + +public class PacketInResourcePackStatusImpl implements PacketInResourcePackStatus { + + private ServerboundResourcePackPacket internal; + + public PacketInResourcePackStatusImpl(ServerboundResourcePackPacket internal) { + this.internal = internal; + } + + @Override + public String getStatus() { + return internal.action().name(); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketInSteerVehicleImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketInSteerVehicleImpl.java new file mode 100644 index 0000000000..fb041134f3 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketInSteerVehicleImpl.java @@ -0,0 +1,33 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.packets; + +import com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle; +import net.minecraft.network.protocol.game.ServerboundPlayerInputPacket; + +public class PacketInSteerVehicleImpl implements PacketInSteerVehicle { + + private ServerboundPlayerInputPacket internal; + + public PacketInSteerVehicleImpl(ServerboundPlayerInputPacket internal) { + this.internal = internal; + } + + @Override + public float getLeftwardInput() { + return internal.input().left() ? 1 : 0; + } + + @Override + public float getForwardInput() { + return internal.input().forward() ? 1 : 0; + } + + @Override + public boolean getJumpInput() { + return internal.input().jump(); + } + + @Override + public boolean getDismountInput() { + return internal.input().shift(); + } +} diff --git a/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketOutChatImpl.java b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketOutChatImpl.java new file mode 100644 index 0000000000..ed5445a201 --- /dev/null +++ b/v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketOutChatImpl.java @@ -0,0 +1,50 @@ +package com.denizenscript.denizen.nms.v26_1.impl.network.packets; + +import com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import net.md_5.bungee.chat.ComponentSerializer; +import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; +import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +public class PacketOutChatImpl extends PacketOutChat { + + public ClientboundPlayerChatPacket playerPacket; + public ClientboundSystemChatPacket systemPacket; + public String message; + public String rawJson; + public boolean isOverlayActionbar; + + public PacketOutChatImpl(ClientboundSystemChatPacket internal) { + systemPacket = internal; + rawJson = CraftChatMessage.toJSON(internal.content()); + message = FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson)); + isOverlayActionbar = internal.overlay(); + } + + public PacketOutChatImpl(ClientboundPlayerChatPacket internal) { + playerPacket = internal; + rawJson = ComponentSerializer.toString(internal.body().content()); + message = FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson)); + } + + @Override + public boolean isSystem() { + return systemPacket != null; + } + + @Override + public boolean isActionbar() { + return isOverlayActionbar; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getRawJson() { + return rawJson; + } +} diff --git a/v26_2/pom.xml b/v26_2/pom.xml new file mode 100644 index 0000000000..6b101c92c8 --- /dev/null +++ b/v26_2/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + com.denizenscript + denizen-v26_2 + 1.3.3-SNAPSHOT + + + + com.denizenscript + denizen + ${project.version} + compile + true + + + org.spigotmc + spigot-api + 26.2-R0.1-SNAPSHOT + provided + + + org.spigotmc + spigot + 26.2-R0.1-SNAPSHOT + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + 16 + 16 + + + + + + diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/Handler.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/Handler.java new file mode 100644 index 0000000000..780a147c80 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/Handler.java @@ -0,0 +1,435 @@ +package com.denizenscript.denizen.nms.v26_2; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.BiomeNMS; +import com.denizenscript.denizen.nms.abstracts.BlockLight; +import com.denizenscript.denizen.nms.abstracts.ProfileEditor; +import com.denizenscript.denizen.nms.abstracts.Sidebar; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_2.helpers.*; +import com.denizenscript.denizen.nms.v26_2.impl.BiomeNMSImpl; +import com.denizenscript.denizen.nms.v26_2.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_2.impl.SidebarImpl; +import com.denizenscript.denizen.nms.v26_2.impl.blocks.BlockLightImpl; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.objects.MaterialTag; +import com.denizenscript.denizen.objects.properties.item.ItemRawNBT; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizen.utilities.PaperAPITools; +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.core.QuaternionTag; +import com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand; +import com.denizenscript.denizencore.utilities.CoreConfiguration; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.denizenscript.denizencore.utilities.debugging.DebugInternals; +import com.google.common.collect.Iterables; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DynamicOps; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.minecraft.SharedConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Rotations; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.BossEvent; +import net.minecraft.world.Container; +import net.minecraft.world.Nameable; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.TagValueInput; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.boss.BossBar; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.boss.CraftBossBar; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftInventoryCustom; +import org.bukkit.craftbukkit.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.legacy.FieldRename; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; +import org.bukkit.craftbukkit.util.ApiVersion; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.persistence.PersistentDataContainer; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.spigotmc.AsyncCatcher; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +public class Handler extends NMSHandler { + + public static BlockPos toBlockPos(Location location) { // TODO: Paper renamed 'CraftLocation#toBlockPosition', switch back once on Paper NMS + return new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + // TODO: Paper renamed some NMS methods, switch back to direct calls once on Paper NMS + public static MethodHandle reflectPaperRenamed(Class clazz, String spigot, String paper, Class... params) { + return ReflectionHelper.getMethodHandle(clazz, Denizen.supportsPaper ? paper : spigot, params); + } + + public Handler() { + advancementHelper = new AdvancementHelperImpl(); + animationHelper = new AnimationHelperImpl(); + blockHelper = new BlockHelperImpl(); + chunkHelper = new ChunkHelperImpl(); + customEntityHelper = new CustomEntityHelperImpl(); + entityHelper = new EntityHelperImpl(); + fishingHelper = new FishingHelperImpl(); + itemHelper = new ItemHelperImpl(); + packetHelper = new PacketHelperImpl(); + playerHelper = new PlayerHelperImpl(); + worldHelper = new WorldHelperImpl(); + enchantmentHelper = new EnchantmentHelperImpl(); + + registerConversion(ItemTag.class, ItemStack.class, item -> CraftItemStack.asNMSCopy(item.getItemStack())); + registerConversion(ElementTag.class, Component.class, element -> componentToNMS(FormattedTextHelper.parse(element.asString(), ChatColor.WHITE))); + registerConversion(MaterialTag.class, BlockState.class, material -> ((CraftBlockData) material.getModernData()).getState()); + registerConversion(LocationTag.class, Rotations.class, location -> new Rotations((float) location.getX(), (float) location.getY(), (float) location.getZ())); + registerConversion(LocationTag.class, BlockPos.class, Handler::toBlockPos); + registerConversion(MapTag.class, CompoundTag.class, map -> { + CompoundBinaryTag compoundTag = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(map, CoreUtilities.noDebugContext, "(item)."); + return compoundTag != null ? NBTAdapter.toNMS(compoundTag) : null; + }); + registerConversion(LocationTag.class, Vector3f.class, location -> new Vector3f((float) location.getX(), (float) location.getY(), (float) location.getZ())); + registerConversion(QuaternionTag.class, Quaternionf.class, quaternion -> new Quaternionf(quaternion.x, quaternion.y, quaternion.z, quaternion.w)); + } + + public static
void registerConversion(Class
denizenType, Class javaType, Function convertor) { + ReflectionSetCommand.typeConverters.put(javaType, objectTag -> { + DT denizenObject = objectTag.asType(denizenType, CoreUtilities.noDebugContext); + return denizenObject != null ? convertor.apply(denizenObject) : null; + }); + } + + private final ProfileEditor profileEditor = new ProfileEditorImpl(); + + private boolean wasAsyncCatcherEnabled; + + @Override + public void disableAsyncCatcher() { + wasAsyncCatcherEnabled = AsyncCatcher.enabled; + AsyncCatcher.enabled = false; + } + + @Override + public void undisableAsyncCatcher() { + AsyncCatcher.enabled = wasAsyncCatcherEnabled; + } + + @Override + public boolean isExactServerVersionMatch() { + return Denizen.supportsPaper ? SharedConstants.getCurrentVersion().id().equals("26.2") : CraftMagicNumbers.INSTANCE.getMappingsVersion().equals("e8ece90188c951d866bd2fffc52c803e"); + } + + @Override + public double[] getRecentTps() { + return ((CraftServer) Bukkit.getServer()).getServer().recentTps; + } + + @Override + public Sidebar createSidebar(Player player) { + return new SidebarImpl(player); + } + + @Override + public BlockLight createBlockLight(Location location, int lightLevel, long ticks) { + return BlockLightImpl.createLight(location, lightLevel, ticks); + } + + @Override + public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) { + if (playerProfile == null) { + return null; + } + if (playerProfile.getName() == null && playerProfile.getUniqueId() == null) { + return playerProfile; // Cannot fill without lookup data + } + if (playerProfile.hasTexture() && playerProfile.hasTextureSignature() && playerProfile.getName() != null && playerProfile.getUniqueId() != null) { + return playerProfile; // Already filled + } + try { + GameProfile profile = null; + MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer(); + if (playerProfile.getUniqueId() != null) { + profile = minecraftServer.services().nameToIdCache().get(playerProfile.getUniqueId()).map(result -> new GameProfile(result.id(), result.name())).orElse(null); + } + if (profile == null && playerProfile.getName() != null) { + profile = minecraftServer.services().nameToIdCache().get(playerProfile.getName()).map(result -> new GameProfile(result.id(), result.name())).orElse(null); + } + if (profile == null) { + profile = ProfileEditorImpl.getGameProfileNoProperties(playerProfile); + } + Property textures = profile.properties().containsKey("textures") ? Iterables.getFirst(profile.properties().get("textures"), null) : null; + if (textures == null || !textures.hasSignature() || profile.name() == null || profile.id() == null) { + profile = minecraftServer.services().profileResolver().fetchById(profile.id()).orElse(null); + if (profile == null) { + return null; + } + textures = profile.properties().containsKey("textures") ? Iterables.getFirst(profile.properties().get("textures"), null) : null; + } + return new PlayerProfile(profile.name(), profile.id(), textures == null ? null : textures.value(), textures == null ? null : textures.signature()); + } + catch (Exception e) { + if (CoreConfiguration.debugVerbose) { + Debug.echoError(e); + } + } + return null; + } + + public static MethodHandle PAPER_INVENTORY_TITLE_GETTER; + + @Override + public String getTitle(Inventory inventory) { + Container nms = ((CraftInventory) inventory).getInventory(); + if (inventory instanceof CraftInventoryCustom && Denizen.supportsPaper) { + try { + if (PAPER_INVENTORY_TITLE_GETTER == null) { + PAPER_INVENTORY_TITLE_GETTER = ReflectionHelper.getMethodHandle(nms.getClass(), "title"); + } + return PaperAPITools.instance.parseComponent(PAPER_INVENTORY_TITLE_GETTER.invoke(nms)); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + if (nms instanceof Nameable) { + return CraftChatMessage.fromComponent(((Nameable) nms).getDisplayName()); + } + else if (MINECRAFT_INVENTORY.isInstance(nms)) { + try { + return (String) INVENTORY_TITLE.get(nms); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + } + return "Chest"; + } + + public static MethodHandle AbstractContainerMenu_title_SETTER = ReflectionHelper.getFinalSetter(AbstractContainerMenu.class, "title"); + + @Override + public void setInventoryTitle(InventoryView view, String title) { + AbstractContainerMenu menu = ((CraftInventoryView) view).getHandle(); + try { + AbstractContainerMenu_title_SETTER.invoke(menu, componentToNMS(FormattedTextHelper.parse(title, ChatColor.DARK_GRAY))); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static final Class MINECRAFT_INVENTORY; + public static final Field INVENTORY_TITLE; + public static final Field ENTITY_BUKKITYENTITY = ReflectionHelper.getFields(Entity.class).get("bukkitEntity"); + + static { + Class minecraftInv = null; + Field title = null; + try { + for (Class clzz : CraftInventoryCustom.class.getDeclaredClasses()) { + if (CoreUtilities.toLowerCase(clzz.getName()).contains("minecraftinventory")) { // MinecraftInventory. + minecraftInv = clzz; + title = clzz.getDeclaredField("title"); + title.setAccessible(true); + break; + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + MINECRAFT_INVENTORY = minecraftInv; + INVENTORY_TITLE = title; + } + + @Override + public PlayerProfile getPlayerProfile(Player player) { + GameProfile gameProfile = ((CraftPlayer) player).getProfile(); + Property property = Iterables.getFirst(gameProfile.properties().get("textures"), null); + return new PlayerProfile(gameProfile.name(), gameProfile.id(), + property != null ? property.value() : null, + property != null ? property.signature() : null); + } + + @Override + public ProfileEditor getProfileEditor() { + return profileEditor; + } + + @Override + public List getBiomes(World world) { + ServerLevel level = ((CraftWorld) world).getHandle(); + ArrayList output = new ArrayList<>(); + for (Identifier key : level.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { + output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key))); + } + return output; + } + + @Override + public BiomeNMS getBiomeNMS(World world, NamespacedKey key) { + BiomeNMSImpl impl = new BiomeNMSImpl(((CraftWorld) world).getHandle(), key); + if (impl.biomeHolder == null) { + return null; + } + return impl; + } + + @Override + public BiomeNMS getBiomeAt(Block block) { + // Based on CraftWorld source + ServerLevel level = ((CraftWorld) block.getWorld()).getHandle(); + Holder biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2); + Identifier key = level.registryAccess().lookupOrThrow(Registries.BIOME).getKey(biome.value()); + return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key)); + } + + @Override + public ArrayList containerListFlags(PersistentDataContainer container, String prefix) { + prefix = "denizen:" + prefix; + ArrayList output = new ArrayList<>(); + for (String key : ((CraftPersistentDataContainer) container).getRaw().keySet()) { + if (key.startsWith(prefix)) { + output.add(key.substring(prefix.length())); + } + } + return output; + } + + @Override + public boolean containerHas(PersistentDataContainer container, String key) { + return ((CraftPersistentDataContainer) container).getRaw().containsKey(key); + } + + @Override + public String containerGetString(PersistentDataContainer container, String key) { + net.minecraft.nbt.Tag base = ((CraftPersistentDataContainer) container).getRaw().get(key); + if (base instanceof StringTag) { + return base.asString().get(); + } + else if (base instanceof ByteArrayTag) { + return new String(((ByteArrayTag) base).getAsByteArray(), StandardCharsets.UTF_8); + } + return null; + } + + @Override + public UUID getBossbarUUID(BossBar bar) { + return ((CraftBossBar) bar).getHandle().getId(); + } + + public static MethodHandle BOSSBAR_ID_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BossEvent.class, UUID.class); + + @Override + public void setBossbarUUID(BossBar bar, UUID id) { + try { + BOSSBAR_ID_SETTER.invoke(((CraftBossBar) bar).getHandle(), id); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static BaseComponent[] componentToSpigot(Component nms) { + if (nms == null) { + return null; + } + return FormattedTextHelper.parseJson(CraftChatMessage.toJSON(nms)); + } + + public static Component componentToNMS(BaseComponent[] spigot) { + if (spigot == null) { + return null; + } + return CraftChatMessage.fromJSONOrNull(FormattedTextHelper.componentToJson(spigot)); + } + + public static final MethodHandle TAG_VALUE_OUTPUT_CONSTRUCTOR = ReflectionHelper.getConstructor(TagValueOutput.class, ProblemReporter.class, DynamicOps.class, CompoundTag.class); + + public static CompoundTag useValueOutput(Consumer handler) { + ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector(); + TagValueOutput nmsValueOutput = TagValueOutput.createWithContext(nmsProblemReporter, CraftRegistry.getMinecraftRegistry()); + handler.accept(nmsValueOutput); + handleProblems(nmsProblemReporter); + return nmsValueOutput.buildResult(); + } + + public static CompoundTag useValueOutput(CompoundTag nmsExistingValue, Consumer handler) { + ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector(); + TagValueOutput nmsValueOutput; + try { + nmsValueOutput = (TagValueOutput) TAG_VALUE_OUTPUT_CONSTRUCTOR.invoke(nmsProblemReporter, CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsExistingValue); + } + catch (Throwable e) { + Debug.echoError(e); + return nmsExistingValue; + } + handler.accept(nmsValueOutput); + handleProblems(nmsProblemReporter); + return nmsValueOutput.buildResult(); + } + + public static void useValueInput(CompoundTag nmsTag, Consumer handler) { + ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector(); + ValueInput nmsValueInput = TagValueInput.create(nmsProblemReporter, CraftRegistry.getMinecraftRegistry(), nmsTag); + handler.accept(nmsValueInput); + handleProblems(nmsProblemReporter); + } + + private static void handleProblems(ProblemReporter.Collector nmsProblemReporter) { + if (!nmsProblemReporter.isEmpty()) { + Debug.echoError(nmsProblemReporter.getTreeReport()); + } + } + + @Override + public String updateLegacyName(Class type, String legacyName) { + return FieldRename.rename(ApiVersion.FIELD_NAME_PARITY, DebugInternals.getFullClassNameOpti(type).replace('.', '/'), legacyName); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/AdvancementHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/AdvancementHelperImpl.java new file mode 100644 index 0000000000..ef82c56eda --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/AdvancementHelperImpl.java @@ -0,0 +1,196 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.interfaces.AdvancementHelper; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.google.common.collect.ImmutableMap; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.advancements.*; +import net.minecraft.advancements.triggers.Criterion; +import net.minecraft.advancements.triggers.ImpossibleTrigger; +import net.minecraft.core.ClientAsset; +import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket; +import net.minecraft.resources.Identifier; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.ServerAdvancementManager; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.Player; + +import java.util.*; + +public class AdvancementHelperImpl extends AdvancementHelper { + + private static final String IMPOSSIBLE_KEY = "impossible"; + private static final Map> IMPOSSIBLE_CRITERIA = Map.of(IMPOSSIBLE_KEY, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance())); + private static final List> IMPOSSIBLE_REQUIREMENTS = List.of(List.of(IMPOSSIBLE_KEY)); + + public static ServerAdvancementManager getNMSAdvancementManager() { + return ((CraftServer) Bukkit.getServer()).getServer().getAdvancements(); + } + + @Override + public void register(com.denizenscript.denizen.nms.util.Advancement advancement) { + if (advancement.temporary || advancement.registered) { + return; + } + AdvancementHolder nmsAdvancementHolder = asNMSCopy(advancement); + Map nmsAdvancements = getNMSAdvancementManager().advancements; + ImmutableMap.Builder mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() + 1); + mapBuilder.putAll(nmsAdvancements); + mapBuilder.put(nmsAdvancementHolder.id(), nmsAdvancementHolder); + getNMSAdvancementManager().advancements = mapBuilder.build(); + + AdvancementTree tree = getNMSAdvancementManager().tree(); + tree.addAll(List.of(nmsAdvancementHolder)); + // recalculate advancement tree from this advancement's root + AdvancementNode node = tree.get(nmsAdvancementHolder.id()); + if (node != null) { + AdvancementNode root = node.root(); + if (root.holder().value().display().isPresent()) { + TreeNodePosition.run(root); + } + } + advancement.registered = true; + if (!advancement.hidden && advancement.parent != null) { + PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancementHolder), Set.of(), Map.of(), false)); + } + } + + @Override + public void unregister(com.denizenscript.denizen.nms.util.Advancement advancement) { + if (advancement.temporary || !advancement.registered) { + return; + } + Identifier nmsKey = CraftNamespacedKey.toMinecraft(advancement.key); + Map nmsAdvancements = getNMSAdvancementManager().advancements; + ImmutableMap.Builder mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() - 1); + for (Map.Entry entry : nmsAdvancements.entrySet()) { + if (!entry.getKey().equals(nmsKey)) { + mapBuilder.put(entry); + } + } + getNMSAdvancementManager().advancements = mapBuilder.build(); + getNMSAdvancementManager().tree().remove(Set.of(nmsKey)); + advancement.registered = false; + PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(nmsKey), Map.of(), false)); + } + + @Override + public void grantPartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) { + if (advancement.length <= 1) { + grant(advancement, player); + return; + } + if (advancement.temporary) { + AdvancementHolder nmsAdvancement = asNMSCopy(advancement); + AdvancementProgress progress = new AdvancementProgress(); + progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS)); + for (int i = 0; i < len; i++) { + progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria + } + PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), false)); + } + else { + AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key)); + for (int i = 0; i < len; i++) { + ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY + i); + } + } + } + + @Override + public void grant(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) { + if (advancement.length > 1) { + grantPartial(advancement, player, advancement.length); + return; + } + if (advancement.temporary) { + AdvancementHolder nmsAdvancement = asNMSCopy(advancement); + AdvancementProgress progress = new AdvancementProgress(); + progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS)); + progress.grantProgress(IMPOSSIBLE_KEY); // complete impossible criteria + PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), true)); + } + else { + AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key)); + ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY); + } + } + + @Override + public void revokePartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) { + if (advancement.length <= 1) { + revoke(advancement, player); + return; + } + if (advancement.temporary) { + AdvancementHolder nmsAdvancement = asNMSCopy(advancement); + AdvancementProgress progress = new AdvancementProgress(); + progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS)); + for (int i = 0; i < len; i++) { + progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria + } + PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), false)); + } + else { + AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key)); + PlayerAdvancements advancements = ((CraftPlayer) player).getHandle().getAdvancements(); + for (int i = len; i < advancement.length; i++) { + advancements.revoke(nmsAdvancement, IMPOSSIBLE_KEY + i); + } + } + } + + @Override + public void revoke(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) { + if (advancement.temporary) { + PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(CraftNamespacedKey.toMinecraft(advancement.key)), Map.of(), false)); + } + else { + AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key)); + PlayerAdvancements advancements = ((CraftPlayer) player).getHandle().getAdvancements(); + for (String criterion : nmsAdvancement.value().criteria().keySet()) { + advancements.revoke(nmsAdvancement, criterion); + } + } + } + + @Override + public void update(Player player) { + // TODO: 1.21.5: should showAdvancements be true? + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + nmsPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(true, List.of(), Set.of(), Map.of(), false)); + PlayerAdvancements data = nmsPlayer.getAdvancements(); + data.save(); // save progress + data.reload(getNMSAdvancementManager()); // clear progress + data.flushDirty(nmsPlayer, false); // load progress and update client + } + + private static AdvancementHolder asNMSCopy(com.denizenscript.denizen.nms.util.Advancement advancement) { + AdvancementHolder parent = advancement.parent != null + ? getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.parent)) + : null; + DisplayInfo display = new DisplayInfo(ItemHelperImpl.asNMSTemplate(advancement.icon), + Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)), + Optional.ofNullable(advancement.background).map(CraftNamespacedKey::toMinecraft).map(ClientAsset.ResourceTexture::new), AdvancementType.valueOf(advancement.frame.name()), + advancement.toast, advancement.announceToChat, advancement.hidden); + display.setLocation(advancement.xOffset, advancement.yOffset); + Map> criteria = IMPOSSIBLE_CRITERIA; + List> requirements = IMPOSSIBLE_REQUIREMENTS; + if (advancement.length > 1) { + criteria = new HashMap<>(); + requirements = new ArrayList<>(advancement.length); + for (int i = 0; i < advancement.length; i++) { + criteria.put(IMPOSSIBLE_KEY + i, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance())); + requirements.add(List.of(IMPOSSIBLE_KEY + i)); + } + } + AdvancementRequirements reqs = new AdvancementRequirements(requirements); + Advancement adv = new Advancement(parent == null ? Optional.empty() : Optional.of(parent.id()), Optional.of(display), AdvancementRewards.EMPTY, criteria, reqs, false); // TODO: 1.20: do we want to ever enable telemetry? + return new AdvancementHolder(CraftNamespacedKey.toMinecraft(advancement.key), adv); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/AnimationHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/AnimationHelperImpl.java new file mode 100644 index 0000000000..6965a1b5d5 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/AnimationHelperImpl.java @@ -0,0 +1,48 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.abstracts.AnimationHelper; +import net.minecraft.world.entity.Entity; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftHorse; +import org.bukkit.craftbukkit.entity.CraftPolarBear; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Horse; +import org.bukkit.entity.IronGolem; + +public class AnimationHelperImpl extends AnimationHelper { + + public AnimationHelperImpl() { + register("POLAR_BEAR_START_STANDING", entity -> { + if (entity.getType() == EntityType.POLAR_BEAR) { + ((CraftPolarBear) entity).getHandle().setStanding(true); + } + }); + register("POLAR_BEAR_STOP_STANDING", entity -> { + if (entity.getType() == EntityType.POLAR_BEAR) { + ((CraftPolarBear) entity).getHandle().setStanding(false); + } + }); + // TODO: 1.21.6: this is a tick duration now, should become a mechanism + register("HORSE_START_STANDING", entity -> { + if (entity instanceof Horse) { + ((CraftHorse) entity).getHandle().setStanding(Integer.MAX_VALUE); + } + }); + register("HORSE_STOP_STANDING", entity -> { + if (entity instanceof Horse) { + ((CraftHorse) entity).getHandle().clearStanding(); + } + }); + register("HORSE_BUCK", entity -> { + if (entity instanceof Horse) { + ((CraftHorse) entity).getHandle().makeMad(); + } + }); + register("IRON_GOLEM_ATTACK", entity -> { + if (entity instanceof IronGolem) { + Entity nmsEntity = ((CraftEntity) entity).getHandle(); + nmsEntity.level().broadcastEntityEvent(nmsEntity, (byte) 4); + } + }); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/BlockHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/BlockHelperImpl.java new file mode 100644 index 0000000000..68455e6c28 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/BlockHelperImpl.java @@ -0,0 +1,271 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.interfaces.BlockHelper; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.impl.ProfileEditorImpl; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.collect.Iterables; +import com.mojang.authlib.GameProfile; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.InclusiveRange; +import net.minecraft.util.random.WeightedList; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.component.ResolvableProfile; +import net.minecraft.world.level.BaseSpawner; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.SpawnData; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.PushReaction; +import org.bukkit.Instrument; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.Skull; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.block.CraftBlockEntityState; +import org.bukkit.craftbukkit.block.CraftCreatureSpawner; +import org.bukkit.craftbukkit.block.CraftSkull; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.Optional; + +public class BlockHelperImpl implements BlockHelper { + + public static final Field craftBlockEntityState_tileEntity; + public static final Field craftBlockEntityState_snapshot = ReflectionHelper.getFields(CraftBlockEntityState.class).get("snapshot"); + public static final Field craftSkull_profile = ReflectionHelper.getFields(CraftSkull.class).get("profile"); + + static { + Field blockEntityField = ReflectionHelper.getFields(CraftBlockEntityState.class).getNoCheck("blockEntity"); + if (blockEntityField == null) { + blockEntityField = ReflectionHelper.getFields(CraftBlockEntityState.class).get("tileEntity"); + } + craftBlockEntityState_tileEntity = blockEntityField; + } + + public static final MethodHandle CRAFT_BLOCK_GET_STATE = Handler.reflectPaperRenamed(CraftBlock.class, "getNMS", "getBlockState"); + + public static BlockState getNMSState(Block block) { + try { + return (BlockState) CRAFT_BLOCK_GET_STATE.invokeExact((CraftBlock) block); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public void applyPhysics(Location location) { + ((CraftWorld) location.getWorld()).getHandle().updateNeighborsAt(Handler.toBlockPos(location), CraftMagicNumbers.getBlock(location.getBlock().getType())); + } + + public static T getTE(CraftBlockEntityState cbs) { + try { + return (T) craftBlockEntityState_tileEntity.get(cbs); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + return null; + } + + @Override + public PlayerProfile getPlayerProfile(Skull skull) { + ResolvableProfile profile = getTE(((CraftSkull) skull)).owner; + if (profile == null) { + return null; + } + com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.partialProfile().properties().get("textures"), null); + return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile), property != null ? property.value() : null); + } + + @Override + public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) { + GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile); + try { + craftSkull_profile.set(skull, ResolvableProfile.createResolved(gameProfile)); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + skull.update(); + } + + public static final MethodHandle CRAFT_BLOCK_GET_LEVEL = Handler.reflectPaperRenamed(CraftBlock.class, "getHandle", "getLevel"); + + public BlockEntity getBlockEntity(Block block) { + CraftBlock craftBlock = ((CraftBlock) block); + try { + return ((LevelAccessor) CRAFT_BLOCK_GET_LEVEL.invokeExact(craftBlock)).getBlockEntity(craftBlock.getPosition()); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public CompoundBinaryTag getNbtData(Block block) { + BlockEntity nmsBlockEntity = getBlockEntity(block); + if (nmsBlockEntity != null) { + CompoundTag compound = nmsBlockEntity.saveWithFullMetadata(CraftRegistry.getMinecraftRegistry()); + return NBTAdapter.toAPI(compound); + } + return null; + } + + @Override + public void setNbtData(Block block, CompoundBinaryTag ctag) { + CompoundTag nmsData = NBTAdapter.toNMS(ctag); + nmsData.putInt("x", block.getX()); + nmsData.putInt("y", block.getY()); + nmsData.putInt("z", block.getZ()); + Handler.useValueInput(nmsData, getBlockEntity(block)::loadWithComponents); + } + + @Override + public boolean setBlockResistance(Material material, float resistance) { + net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material); + if (block == null) { + return false; + } + ReflectionHelper.setFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, "explosionResistance", block, resistance); + return true; + } + + @Override + public float getBlockResistance(Material material) { + net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material); + if (block == null) { + return 0; + } + return ReflectionHelper.getFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, "explosionResistance", block); + } + + public static final MethodHandle MATERIAL_PUSH_REACTION_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BlockBehaviour.BlockStateBase.class, PushReaction.class); + + public static final MethodHandle BLOCK_STRENGTH_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.class, float.class); // destroySpeed + + public net.minecraft.world.level.block.state.BlockState getMaterialBlockState(Material bukkitMaterial) { + net.minecraft.world.level.block.Block nmsBlock = CraftMagicNumbers.getBlock(bukkitMaterial); + return nmsBlock != null ? nmsBlock.defaultBlockState() : null; + } + + @Override + public void setPushReaction(Material mat, PistonPushReaction reaction) { + try { + MATERIAL_PUSH_REACTION_SETTER.invoke(getMaterialBlockState(mat), PushReaction.values()[reaction.ordinal()]); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public float getBlockStrength(Material mat) { + return getMaterialBlockState(mat).destroySpeed; + } + + @Override + public void setBlockStrength(Material mat, float strength) { + try { + BLOCK_STRENGTH_SETTER.invoke(getMaterialBlockState(mat), strength); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void doRandomTick(Location location) { + BlockPos pos = Handler.toBlockPos(location); + ChunkAccess nmsChunk = ((CraftChunk) location.getChunk()).getHandle(ChunkStatus.FULL); + net.minecraft.world.level.block.state.BlockState nmsBlock = nmsChunk.getBlockState(pos); + ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle(); + if (nmsBlock.isRandomlyTicking()) { + nmsBlock.randomTick(nmsWorld, pos, nmsWorld.getRandom()); + } + FluidState fluid = nmsBlock.getFluidState(); + if (fluid.isRandomlyTicking()) { + fluid.animateTick(nmsWorld, pos, nmsWorld.getRandom()); + } + } + + @Override + public Instrument getInstrumentFor(Material mat) { + return Instrument.values()[getMaterialBlockState(mat).instrument().ordinal()]; + } + + @Override + public int getExpDrop(Block block, org.bukkit.inventory.ItemStack item) { + net.minecraft.world.level.block.Block blockType = CraftMagicNumbers.getBlock(block.getType()); + if (blockType == null) { + return 0; + } + return blockType.getExpDrop(getNMSState(block), ((CraftBlock) block).getCraftWorld().getHandle(), ((CraftBlock) block).getPosition(), + item == null ? null : CraftItemStack.asNMSCopy(item), true); + } + + @Override + public void setSpawnerSpawnedType(CreatureSpawner spawner, EntityTag entity) { + spawner.setSpawnedType(entity.getBukkitEntityType()); + if (entity.getWaitingMechanisms() == null || entity.getWaitingMechanisms().size() == 0) { + return; + } + try { + // Wrangle a fake entity + // TODO: 1.21.6: seems to have a bug where the "Pos" value being set prevents it from spawning? + org.bukkit.entity.Entity bukkitEntity = ((CraftWorld) spawner.getWorld()).createEntity(spawner.getLocation(), entity.getBukkitEntityType().getEntityClass()); + Entity nmsEntity = ((CraftEntity) bukkitEntity).getHandle(); + EntityTag entityTag = new EntityTag(bukkitEntity); + entityTag.isFake = true; + entityTag.isFakeValid = true; + for (Mechanism mechanism : entity.getWaitingMechanisms()) { + entityTag.safeAdjustDuplicate(mechanism); + } + nmsEntity.unsetRemoved(); + // Store it into the spawner + SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(spawner); + Handler.useValueOutput(nmsSnapshot.getSpawner().nextSpawnData.getEntityToSpawn(), nmsEntity::saveWithoutId); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void setSpawnerCustomRules(CreatureSpawner spawner, int skyMin, int skyMax, int blockMin, int blockMax) { + try { + CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner; + SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner); + BaseSpawner nmsSpawner = nmsSnapshot.getSpawner(); + SpawnData toSpawn = nmsSpawner.nextSpawnData; + SpawnData.CustomSpawnRules rules = skyMin == -1 ? null : new SpawnData.CustomSpawnRules(new InclusiveRange<>(skyMin, skyMax), new InclusiveRange<>(blockMin, blockMax)); + nmsSpawner.nextSpawnData = new SpawnData(toSpawn.entityToSpawn(), Optional.ofNullable(rules), toSpawn.equipment()); + nmsSpawner.spawnPotentials = WeightedList.of(); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/ChunkHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/ChunkHelperImpl.java new file mode 100644 index 0000000000..ac482bf852 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/ChunkHelperImpl.java @@ -0,0 +1,119 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.abstracts.BiomeNMS; +import com.denizenscript.denizen.nms.interfaces.ChunkHelper; +import com.denizenscript.denizen.nms.v26_2.impl.BiomeNMSImpl; +import com.denizenscript.denizencore.tags.TagManager; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.Holder; +import net.minecraft.core.QuartPos; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.Heightmap; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.CraftWorld; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; + +public class ChunkHelperImpl implements ChunkHelper { + + public final static Field chunkProviderServerThreadField; + public final static MethodHandle chunkProviderServerThreadFieldSetter; + public final static Field worldThreadField; + public final static MethodHandle worldThreadFieldSetter; + + static { + chunkProviderServerThreadField = ReflectionHelper.getFields(ServerChunkCache.class).getFirstOfType(Thread.class); + chunkProviderServerThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(ServerChunkCache.class, Thread.class); + worldThreadField = ReflectionHelper.getFields(net.minecraft.world.level.Level.class).getFirstOfType(Thread.class); + worldThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.Level.class, Thread.class); + } + + public Thread resetServerThread; + + @Override + public void changeChunkServerThread(World world) { + if (TagManager.tagThread == null) { + return; + } + if (resetServerThread != null) { + return; + } + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + ServerChunkCache provider = nmsWorld.getChunkSource(); + try { + resetServerThread = (Thread) chunkProviderServerThreadField.get(provider); + chunkProviderServerThreadFieldSetter.invoke(provider, Thread.currentThread()); + worldThreadFieldSetter.invoke(nmsWorld, Thread.currentThread()); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void restoreServerThread(World world) { + if (TagManager.tagThread == null) { + return; + } + if (resetServerThread == null) { + return; + } + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + ServerChunkCache provider = nmsWorld.getChunkSource(); + try { + chunkProviderServerThreadFieldSetter.invoke(provider, resetServerThread); + worldThreadFieldSetter.invoke(nmsWorld, resetServerThread); + resetServerThread = null; + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public int[] getHeightMap(Chunk chunk) { + Heightmap map = ((CraftChunk) chunk).getHandle(ChunkStatus.FEATURES).heightmaps.get(Heightmap.Types.MOTION_BLOCKING); + int[] outputMap = new int[256]; + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + outputMap[x * 16 + y] = map.getFirstAvailable(x, y); + } + } + return outputMap; + } + + @Override + public void setAllBiomes(Chunk chunk, BiomeNMS biome) { + Holder nmsBiome = ((BiomeNMSImpl) biome).biomeHolder; + ChunkAccess nmsChunk = ((CraftChunk) chunk).getHandle(ChunkStatus.BIOMES); + ChunkPos chunkcoordintpair = nmsChunk.getPos(); + int i = QuartPos.fromBlock(chunkcoordintpair.getMinBlockX()); + int j = QuartPos.fromBlock(chunkcoordintpair.getMinBlockZ()); + LevelHeightAccessor levelheightaccessor = nmsChunk.getHeightAccessorForGeneration(); + for(int k = levelheightaccessor.getMinSectionY(); k < levelheightaccessor.getMaxSectionY(); ++k) { + LevelChunkSection chunksection = nmsChunk.getSection(nmsChunk.getSectionIndexFromSectionY(k)); + PalettedContainer> datapaletteblock = (PalettedContainer>) chunksection.getBiomes(); + datapaletteblock.acquire(); + for(int l = 0; l < 4; ++l) { + for(int i1 = 0; i1 < 4; ++i1) { + for(int j1 = 0; j1 < 4; ++j1) { + datapaletteblock.getAndSetUnchecked(l, i1, j1, nmsBiome); + } + } + } + datapaletteblock.release(); + } + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/CustomEntityHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/CustomEntityHelperImpl.java new file mode 100644 index 0000000000..dc3aa0498b --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/CustomEntityHelperImpl.java @@ -0,0 +1,149 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.interfaces.CustomEntityHelper; +import com.denizenscript.denizen.nms.interfaces.FakeArrow; +import com.denizenscript.denizen.nms.interfaces.FakePlayer; +import com.denizenscript.denizen.nms.interfaces.ItemProjectile; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_2.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_2.impl.entities.CraftFakePlayerImpl; +import com.denizenscript.denizen.nms.v26_2.impl.entities.EntityFakeArrowImpl; +import com.denizenscript.denizen.nms.v26_2.impl.entities.EntityFakePlayerImpl; +import com.denizenscript.denizen.nms.v26_2.impl.entities.EntityItemProjectileImpl; +import com.denizenscript.denizen.utilities.BukkitImplDeprecations; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.scoreboard.Team; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public class CustomEntityHelperImpl implements CustomEntityHelper { + + @Override + public FakeArrow spawnFakeArrow(Location location) { + CraftWorld world = (CraftWorld) location.getWorld(); + EntityFakeArrowImpl arrow = new EntityFakeArrowImpl(world, location); + return arrow.getBukkitEntity(); + } + + @Override + public ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack) { + CraftWorld world = (CraftWorld) location.getWorld(); + EntityItemProjectileImpl entity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack)); + world.getHandle().addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM); + return entity.getBukkitEntity(); + } + + public FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd) throws IllegalArgumentException { + BukkitImplDeprecations.fakePlayer.warn(); + String fullName = name; + String prefix = null; + String suffix = null; + if (name == null) { + Debug.echoError("FAKE_PLAYER: null name, cannot spawn"); + return null; + } + else if (fullName.length() > 16) { + prefix = fullName.substring(0, 16); + if (fullName.length() > 30) { + int len = 30; + name = fullName.substring(16, 30); + if (name.matches(".*[^A-Za-z0-9_].*")) { + if (fullName.length() >= 32) { + len = 32; + name = fullName.substring(16, 32); + } + else if (fullName.length() == 31) { + len = 31; + name = fullName.substring(16, 31); + } + } + else if (name.length() > 46) { + throw new IllegalArgumentException("You must specify a name with no more than 46 characters for FAKE_PLAYER entities!"); + } + else { + name = ChatColor.RESET + name; + } + suffix = fullName.substring(len); + } + else { + name = fullName.substring(16); + if (!name.matches(".*[^A-Za-z0-9_].*")) { + name = ChatColor.RESET + name; + } + if (name.length() > 16) { + suffix = name.substring(16); + name = name.substring(0, 16); + } + } + } + if (skin != null && skin.length() > 16) { + throw new IllegalArgumentException("You must specify a name with no more than 16 characters for FAKE_PLAYER entity skins!"); + } + CraftWorld world = (CraftWorld) location.getWorld(); + ServerLevel worldServer = world.getHandle(); + PlayerProfile playerProfile = new PlayerProfile(name, null); + if (blob != null) { + int sc = blob.indexOf(';'); + if (sc != -1) { + playerProfile.setTexture(blob.substring(0, sc)); + playerProfile.setTextureSignature(blob.substring(sc + 1)); + } + } + else if (skin == null && !name.matches(".*[^A-Za-z0-9_].*")) { + playerProfile = NMSHandler.instance.fillPlayerProfile(playerProfile); + } + if (skin != null) { + PlayerProfile skinProfile = new PlayerProfile(skin, null); + skinProfile = NMSHandler.instance.fillPlayerProfile(skinProfile); + playerProfile.setTexture(skinProfile.getTexture()); + playerProfile.setTextureSignature(skinProfile.getTextureSignature()); + } + UUID uuid = UUID.randomUUID(); + playerProfile.setUniqueId(uuid); + + GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile); + final EntityFakePlayerImpl fakePlayer = new EntityFakePlayerImpl(worldServer.getServer(), worldServer, gameProfile, ClientInformation.createDefault(), doAdd); + + fakePlayer.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), + location.getYaw(), location.getPitch()); + CraftFakePlayerImpl craftFakePlayer = fakePlayer.getBukkitEntity(); + craftFakePlayer.fullName = fullName; + if (prefix != null) { + Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard(); + String teamName = "FAKE_PLAYER_TEAM_" + fullName; + String hash = null; + try { + hash = CoreUtilities.hash_md5(teamName.getBytes(StandardCharsets.UTF_8)).substring(0, 16); + } + catch (Exception e) { + Debug.echoError(e); + } + if (hash != null) { + Team team = scoreboard.getTeam(hash); + if (team == null) { + team = scoreboard.registerNewTeam(hash); + team.setPrefix(prefix); + if (suffix != null) { + team.setSuffix(suffix); + } + } + team.addPlayer(craftFakePlayer); + } + } + return craftFakePlayer; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EnchantmentHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EnchantmentHelperImpl.java new file mode 100644 index 0000000000..3b7b92c38d --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EnchantmentHelperImpl.java @@ -0,0 +1,195 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.interfaces.EnchantmentHelper; + +public class EnchantmentHelperImpl extends EnchantmentHelper { + // TODO: 1.21: Enchantments were entirely reworked, need to update this + /* + public static final Field REGISTRY_FROZEN = ReflectionHelper.getFields(MappedRegistry.class).get("frozen", boolean.class); + public static final Field REGISTRY_INTRUSIVE_HOLDERS = ReflectionHelper.getFields(MappedRegistry.class).get("unregisteredIntrusiveHolders", Map.class); + + @Override + public org.bukkit.enchantments.Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) { + try { + Map holders = (Map) REGISTRY_INTRUSIVE_HOLDERS.get(BuiltInRegistries.ENCHANTMENT); + if (holders == null) { + REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, new IdentityHashMap()); + } + boolean wasFrozen = REGISTRY_FROZEN.getBoolean(BuiltInRegistries.ENCHANTMENT); + REGISTRY_FROZEN.setBoolean(BuiltInRegistries.ENCHANTMENT, false); + EquipmentSlot[] slots = new EquipmentSlot[script.script.slots.size()]; + for (int i = 0; i < slots.length; i++) { + slots[i] = EquipmentSlot.valueOf(CoreUtilities.toUpperCase(script.script.slots.get(i))); + } + // TODO: 1.20.6: rarity is provided as an int, can make our own mirror enum; categories seemed to only over control #canEnchant(ItemStack), so can probably safely phase them out? + net.minecraft.world.item.enchantment.Enchantment.Rarity.valueOf(script.script.rarity), EnchantmentCategory.valueOf(script.script.category), slots + net.minecraft.world.item.enchantment.Enchantment nmsEnchant = new net.minecraft.world.item.enchantment.Enchantment(null) { + // TODO: 1.20.6: methods are final now and the values are provided by EnchantmentDefinition - would probably need to create a new one on reload and modify the existing enchantment + @Override + public int getMinLevel() { + return script.script.minLevel; + } + @Override + public int getMaxLevel() { + return script.script.maxLevel; + } + @Override + public int getMinCost(int level) { + return script.script.getMinCost(level); + } + @Override + public int getMaxCost(int level) { + return script.script.getMaxCost(level); + } + @Override + public int getDamageProtection(int level, DamageSource src) { + return script.script.getDamageProtection(level, src.getMsgId(), src.getEntity() == null ? null : src.getEntity().getBukkitEntity()); + } + // TODO: 1.20.6: Takes an EntityType now, and MobType seems to have been removed in favor of vanilla tags - can probably use these to backsupport & properly pass the entity type + @Override + public float getDamageBonus(int level, EntityType type) { + String typeName = "UNDEFINED"; + if (type == MobType.ARTHROPOD) { + typeName = "ARTHROPOD"; + } + else if (type == MobType.ILLAGER) { + typeName = "ILLAGER"; + } + else if (type == MobType.UNDEAD) { + typeName = "UNDEAD"; + } + else if (type == MobType.WATER) { + typeName = "WATER"; + } + return script.script.getDamageBonus(level, typeName); + } + @Override + protected boolean checkCompatibility(net.minecraft.world.item.enchantment.Enchantment nmsEnchantment) { + ResourceLocation nmsKey = BuiltInRegistries.ENCHANTMENT.getKey(nmsEnchantment); + NamespacedKey bukkitKey = CraftNamespacedKey.fromMinecraft(nmsKey); + org.bukkit.enchantments.Enchantment bukkitEnchant = CraftEnchantment.getByKey(bukkitKey); + return script.script.isCompatible(bukkitEnchant); + } + @Override + protected String getOrCreateDescriptionId() { + return script.script.descriptionId; + } + @Override + public String getDescriptionId() { + return script.script.descriptionId; + } + @Override + public Component getFullname(int level) { + return Handler.componentToNMS(script.script.getFullName(level)); + } + @Override + public boolean canEnchant(net.minecraft.world.item.ItemStack var0) { + return super.canEnchant(var0) && script.script.canEnchant(CraftItemStack.asBukkitCopy(var0)); + } + @Override + public void doPostAttack(LivingEntity attacker, Entity victim, int level) { + script.script.doPostAttack(attacker.getBukkitEntity(), victim.getBukkitEntity(), level); + } + @Override + public void doPostHurt(LivingEntity victim, Entity attacker, int level) { + script.script.doPostHurt(victim.getBukkitEntity(), attacker.getBukkitEntity(), level); + } + @Override + public boolean isTreasureOnly() { + return script.script.isTreasureOnly; + } + @Override + public boolean isCurse() { + return script.script.isCurse; + } + @Override + public boolean isTradeable() { + return script.script.isTradable; + } + @Override + public boolean isDiscoverable() { + return script.script.isDiscoverable; + } + }; + NamespacedKey enchantmentKey = new NamespacedKey(Denizen.getInstance(), script.script.id); + Registry.register(BuiltInRegistries.ENCHANTMENT, enchantmentKey.toString(), nmsEnchant); + String enchName = CoreUtilities.toUpperCase(script.script.id); + CraftEnchantment ench = new CraftEnchantment(enchantmentKey, nmsEnchant) { + @Override + public String getName() { + return enchName; + } + }; + REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, holders); + if (wasFrozen) { + BuiltInRegistries.ENCHANTMENT.freeze(); + } + return ench; + } + catch (Throwable ex) { + Debug.echoError("Failed to register enchantment " + script.script.id); + Debug.echoError(ex); + return null; + } + } + + // TODO: 1.20.6: rarity is just an int now (weight), can deprecate & backsupport by estimating it based on the weight + @Override + public String getRarity(Enchantment enchantment) { + return ((CraftEnchantment) enchantment).getHandle().getRarity().name(); + } + + @Override + public boolean isDiscoverable(Enchantment enchantment) { + return ((CraftEnchantment) enchantment).getHandle().isDiscoverable(); + } + + @Override + public boolean isTradable(Enchantment enchantment) { + return ((CraftEnchantment) enchantment).getHandle().isTradeable(); + } + + @Override + public boolean isCurse(Enchantment enchantment) { + return ((CraftEnchantment) enchantment).getHandle().isCurse(); + } + + @Override + public int getMinCost(Enchantment enchantment, int level) { + return ((CraftEnchantment) enchantment).getHandle().getMinCost(level); + } + + @Override + public int getMaxCost(Enchantment enchantment, int level) { + return ((CraftEnchantment) enchantment).getHandle().getMaxCost(level); + } + + @Override + public String getFullName(Enchantment enchantment, int level) { + return FormattedTextHelper.stringify(Handler.componentToSpigot(((CraftEnchantment) enchantment).getHandle().getFullname(level))); + } + + // TODO: 1.20.6: MobType was removed in favor of using the entity type directly - deprecate + potentially backsupport with vanilla tags + @Override + public float getDamageBonus(Enchantment enchantment, int level, String type) { + MobType mobType = switch (type) { + case "illager" -> MobType.ILLAGER; + case "undead" -> MobType.UNDEAD; + case "water" -> MobType.WATER; + case "arthropod" -> MobType.ARTHROPOD; + default -> MobType.UNDEFINED; + }; + return ((CraftEnchantment) enchantment).getHandle().getDamageBonus(level, mobType); + } + + @Override + public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, org.bukkit.entity.Entity attacker) { + Entity nmsAttacker = attacker == null ? null : ((CraftEntity) attacker).getHandle(); + DamageSource src = EntityHelperImpl.getSourceFor(nmsAttacker, type, nmsAttacker); + if (src instanceof EntityHelperImpl.FakeDamageSrc fakeDamageSrc) { + src = fakeDamageSrc.real; + } + return ((CraftEnchantment) enchantment).getHandle().getDamageProtection(level, src); + } + */ +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EntityDataNameMapper.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EntityDataNameMapper.java new file mode 100644 index 0000000000..d3b0209856 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EntityDataNameMapper.java @@ -0,0 +1,485 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + + +import com.denizenscript.denizencore.objects.ArgumentHelper; +import com.denizenscript.denizencore.objects.core.ElementTag; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ambient.Bat; +import net.minecraft.world.entity.animal.axolotl.Axolotl; +import net.minecraft.world.entity.animal.bee.Bee; +import net.minecraft.world.entity.animal.camel.Camel; +import net.minecraft.world.entity.animal.cow.MushroomCow; +import net.minecraft.world.entity.animal.dolphin.Dolphin; +import net.minecraft.world.entity.animal.equine.AbstractChestedHorse; +import net.minecraft.world.entity.animal.equine.AbstractHorse; +import net.minecraft.world.entity.animal.equine.Horse; +import net.minecraft.world.entity.animal.equine.Llama; +import net.minecraft.world.entity.animal.feline.Cat; +import net.minecraft.world.entity.animal.feline.Ocelot; +import net.minecraft.world.entity.animal.fish.AbstractFish; +import net.minecraft.world.entity.animal.fish.Pufferfish; +import net.minecraft.world.entity.animal.fish.TropicalFish; +import net.minecraft.world.entity.animal.fox.Fox; +import net.minecraft.world.entity.animal.frog.Frog; +import net.minecraft.world.entity.animal.goat.Goat; +import net.minecraft.world.entity.animal.golem.IronGolem; +import net.minecraft.world.entity.animal.golem.SnowGolem; +import net.minecraft.world.entity.animal.panda.Panda; +import net.minecraft.world.entity.animal.parrot.Parrot; +import net.minecraft.world.entity.animal.pig.Pig; +import net.minecraft.world.entity.animal.polarbear.PolarBear; +import net.minecraft.world.entity.animal.rabbit.Rabbit; +import net.minecraft.world.entity.animal.sheep.Sheep; +import net.minecraft.world.entity.animal.sniffer.Sniffer; +import net.minecraft.world.entity.animal.turtle.Turtle; +import net.minecraft.world.entity.animal.wolf.Wolf; +import net.minecraft.world.entity.boss.enderdragon.EndCrystal; +import net.minecraft.world.entity.boss.enderdragon.EnderDragon; +import net.minecraft.world.entity.boss.wither.WitherBoss; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.decoration.ItemFrame; +import net.minecraft.world.entity.decoration.painting.Painting; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.world.entity.monster.*; +import net.minecraft.world.entity.monster.cubemob.Slime; +import net.minecraft.world.entity.monster.hoglin.Hoglin; +import net.minecraft.world.entity.monster.illager.Pillager; +import net.minecraft.world.entity.monster.illager.SpellcasterIllager; +import net.minecraft.world.entity.monster.piglin.AbstractPiglin; +import net.minecraft.world.entity.monster.piglin.Piglin; +import net.minecraft.world.entity.monster.spider.Spider; +import net.minecraft.world.entity.monster.warden.Warden; +import net.minecraft.world.entity.monster.zombie.Zombie; +import net.minecraft.world.entity.monster.zombie.ZombieVillager; +import net.minecraft.world.entity.npc.villager.AbstractVillager; +import net.minecraft.world.entity.npc.villager.Villager; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.EyeOfEnder; +import net.minecraft.world.entity.projectile.FireworkRocketEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.entity.projectile.ThrowableProjectile; +import net.minecraft.world.entity.projectile.arrow.AbstractArrow; +import net.minecraft.world.entity.projectile.arrow.Arrow; +import net.minecraft.world.entity.projectile.arrow.ThrownTrident; +import net.minecraft.world.entity.projectile.hurtingprojectile.Fireball; +import net.minecraft.world.entity.projectile.hurtingprojectile.SmallFireball; +import net.minecraft.world.entity.projectile.hurtingprojectile.WitherSkull; +import net.minecraft.world.entity.raid.Raider; +import net.minecraft.world.entity.vehicle.boat.Boat; +import net.minecraft.world.entity.vehicle.minecart.AbstractMinecart; +import net.minecraft.world.entity.vehicle.minecart.MinecartCommandBlock; +import net.minecraft.world.entity.vehicle.minecart.MinecartFurnace; + +import java.util.HashMap; +import java.util.Map; + +public class EntityDataNameMapper { + + public static final Map, Map> entityDataNames = new HashMap<>(); + + public static void registerDataName(Class entityClass, int id, String name) { + entityDataNames.computeIfAbsent(entityClass, k -> new HashMap<>()).put(name, id); + } + + static { + // Entity + registerDataName(Entity.class, 0, "entity_flags"); + registerDataName(Entity.class, 1, "air_ticks"); + registerDataName(Entity.class, 2, "custom_name"); + registerDataName(Entity.class, 3, "custom_name_visible"); + registerDataName(Entity.class, 4, "silent"); + registerDataName(Entity.class, 5, "no_gravity"); + registerDataName(Entity.class, 6, "pose"); + registerDataName(Entity.class, 7, "frozen_ticks"); + + // Interaction + registerDataName(Interaction.class, 8, "width"); + registerDataName(Interaction.class, 9, "height"); + registerDataName(Interaction.class, 10, "responsive"); + + // Display + registerDataName(Display.class, 8, "transform_interpolation_start"); + registerDataName(Display.class, 9, "transform_interpolation_duration"); + registerDataName(Display.class, 10, "movement_interpolation_duration"); + registerDataName(Display.class, 11, "translation"); + registerDataName(Display.class, 12, "scale"); + registerDataName(Display.class, 13, "left_rotation"); + registerDataName(Display.class, 14, "right_rotation"); + registerDataName(Display.class, 15, "billboard"); + registerDataName(Display.class, 16, "brightness"); + registerDataName(Display.class, 17, "view_range"); + registerDataName(Display.class, 18, "shadow_radius"); + registerDataName(Display.class, 19, "shadow_strength"); + registerDataName(Display.class, 20, "width"); + registerDataName(Display.class, 21, "height"); + registerDataName(Display.class, 22, "glow_color"); + + // Block display + registerDataName(Display.BlockDisplay.class, 23, "material"); + + // Item display + registerDataName(Display.ItemDisplay.class, 23, "item"); + registerDataName(Display.ItemDisplay.class, 24, "model_transform"); + + // Text display + registerDataName(Display.TextDisplay.class, 23, "text"); + registerDataName(Display.TextDisplay.class, 24, "line_width"); + registerDataName(Display.TextDisplay.class, 25, "background_color"); + registerDataName(Display.TextDisplay.class, 26, "text_opacity"); + registerDataName(Display.TextDisplay.class, 27, "text_display_flags"); + + // Thrown item projectile + registerDataName(ThrowableProjectile.class, 8, "item"); + + // Eye of ender + registerDataName(EyeOfEnder.class, 8, "item"); + + // Falling block + registerDataName(FireworkRocketEntity.class, 8, "spawn_position"); + + // Area effect cloud + registerDataName(AreaEffectCloud.class, 8, "radius"); + registerDataName(AreaEffectCloud.class, 9, "color"); + registerDataName(AreaEffectCloud.class, 10, "waiting"); + registerDataName(AreaEffectCloud.class, 11, "particle"); + + // Fishing hook + registerDataName(FishingHook.class, 8, "hooked_entity_id"); + registerDataName(FishingHook.class, 9, "catchable"); + + // Abstract arrow + registerDataName(AbstractArrow.class, 8, "abstract_arrow_flags"); + registerDataName(AbstractArrow.class, 9, "piercing_level"); + + // Arrow + registerDataName(Arrow.class, 10, "color"); + + // Thrown trident + registerDataName(ThrownTrident.class, 10, "loyalty_level"); + registerDataName(ThrownTrident.class, 11, "enchantment_glint"); + + // Boat + registerDataName(Boat.class, 8, "shaking_ticks"); + registerDataName(Boat.class, 9, "shaking_direction"); + registerDataName(Boat.class, 10, "damage_taken"); + registerDataName(Boat.class, 11, "type"); + registerDataName(Boat.class, 12, "left_paddle_moving"); + registerDataName(Boat.class, 13, "right_paddle_moving"); + registerDataName(Boat.class, 14, "bubble_shaking_ticks"); + + // End crystal + registerDataName(EndCrystal.class, 8, "beam_target"); + registerDataName(EndCrystal.class, 9, "showing_bottom"); + + // Small fireball + registerDataName(SmallFireball.class, 8, "item"); + + // Fireball + registerDataName(Fireball.class, 8, "item"); + + // Wither skull + registerDataName(WitherSkull.class, 8, "invulnerable"); + + // Firework rocket + registerDataName(FireworkRocketEntity.class, 8, "item"); + registerDataName(FireworkRocketEntity.class, 9, "shooter_id"); + registerDataName(FireworkRocketEntity.class, 10, "shot_at_angle"); + + // Item frame + registerDataName(ItemFrame.class, 8, "item"); + registerDataName(ItemFrame.class, 9, "rotation"); + + // Painting + registerDataName(Painting.class, 8, "painting_variant"); + + // Living entity + registerDataName(LivingEntity.class, 8, "living_entity_flags"); + registerDataName(LivingEntity.class, 9, "health"); + registerDataName(LivingEntity.class, 10, "potion_effect_color"); + registerDataName(LivingEntity.class, 11, "is_potion_effect_ambient"); + registerDataName(LivingEntity.class, 12, "arrows_in_body"); + registerDataName(LivingEntity.class, 13, "bee_stingers_in_body"); + registerDataName(LivingEntity.class, 14, "bed_location"); + + // Player + registerDataName(Player.class, 15, "additional_hearts"); + registerDataName(Player.class, 16, "score"); + registerDataName(Player.class, 17, "skin_parts"); + registerDataName(Player.class, 18, "main_hand"); + registerDataName(Player.class, 19, "left_shoulder_entity"); + registerDataName(Player.class, 20, "right_shoulder_entity"); + + // Armor stand + registerDataName(ArmorStand.class, 15, "armor_stand_flags"); + registerDataName(ArmorStand.class, 16, "head_rotation"); + registerDataName(ArmorStand.class, 17, "body_rotation"); + registerDataName(ArmorStand.class, 18, "left_arm_rotation"); + registerDataName(ArmorStand.class, 19, "right_arm_rotation"); + registerDataName(ArmorStand.class, 20, "left_leg_rotation"); + registerDataName(ArmorStand.class, 21, "right_leg_rotation"); + + // Mob + registerDataName(Mob.class, 15, "mob_flags"); + + // Bat flags + registerDataName(Bat.class, 16, "bat_flags"); + + // Dolphin + registerDataName(Dolphin.class, 16, "treasure_location"); + registerDataName(Dolphin.class, 17, "has_fish"); + registerDataName(Dolphin.class, 18, "moisture_level"); + + // Abstract Fish + registerDataName(AbstractFish.class, 16, "from_bucket"); + + // PufferFish + registerDataName(Pufferfish.class, 17, "puff_state"); + + // Tropical fish + registerDataName(TropicalFish.class, 17, "variant"); + + // Ageable mob + registerDataName(AgeableMob.class, 16, "is_baby"); + + // Sniffer + registerDataName(Sniffer.class, 17, "sniffer_state"); + registerDataName(Sniffer.class, 18, "finish_dig_time"); + + // Abstract horse + registerDataName(AbstractHorse.class, 17, "horse_flags"); + + // Horse + registerDataName(Horse.class, 18, "variant"); + + // Camel + registerDataName(Camel.class, 18, "is_dashing"); + registerDataName(Camel.class, 19, "last_pose_change"); + + // Chested horse + registerDataName(AbstractChestedHorse.class, 18, "has_chest"); + + // Llama + registerDataName(Llama.class, 19, "strength"); + registerDataName(Llama.class, 20, "carpet_color"); + registerDataName(Llama.class, 21, "variant"); + + // Axolotl + registerDataName(Axolotl.class, 17, "variant"); + registerDataName(Axolotl.class, 18, "playing_dead"); + registerDataName(Axolotl.class, 19, "from_bucket"); + + // Bee + registerDataName(Bee.class, 17, "bee_flags"); + registerDataName(Bee.class, 18, "anger_time"); + + // Fox + registerDataName(Fox.class, 17, "type"); + registerDataName(Fox.class, 18, "fox_flags"); + registerDataName(Fox.class, 19, "first_trusted_uuid"); + registerDataName(Fox.class, 20, "second_trusted_uuid"); + + // Frog + registerDataName(Frog.class, 17, "variant"); + registerDataName(Frog.class, 18, "target_id"); + + // Ocelot + registerDataName(Ocelot.class, 17, "is_trusting"); + + // Panda + registerDataName(Panda.class, 17, "ask_for_bamboo_timer"); + registerDataName(Panda.class, 18, "sneeze_timer"); + registerDataName(Panda.class, 19, "eat_timer"); + registerDataName(Panda.class, 20, "main_gene"); + registerDataName(Panda.class, 21, "hidden_gene"); + registerDataName(Panda.class, 22, "panda_flags"); + + // Pig + registerDataName(Pig.class, 17, "has_saddle"); + registerDataName(Pig.class, 18, "boost_ticks"); + + // Rabbit + registerDataName(Rabbit.class, 17, "type"); + + // Turtle + registerDataName(Turtle.class, 17, "home_location"); + registerDataName(Turtle.class, 18, "has_egg"); + registerDataName(Turtle.class, 19, "laying_egg"); + registerDataName(Turtle.class, 20, "travel_location"); + registerDataName(Turtle.class, 21, "going_home"); + registerDataName(Turtle.class, 20, "traveling"); + + // Polar bear + registerDataName(PolarBear.class, 17, "standing_up"); + + // Hoglin + registerDataName(Hoglin.class, 17, "immune_to_zombification"); + + // Mooshroom + registerDataName(MushroomCow.class, 17, "variant"); + + // Sheep + registerDataName(Sheep.class, 17, "sheep_wool_flags"); + + // Strider + registerDataName(Strider.class, 17, "boost_ticks"); + registerDataName(Strider.class, 18, "shaking"); + registerDataName(Strider.class, 19, "has_saddle"); + + // Tamable animal + registerDataName(TamableAnimal.class, 17, "tamable_animal_flags"); + registerDataName(TamableAnimal.class, 18, "owner"); + + // Cat + registerDataName(Cat.class, 19, "variant"); + registerDataName(Cat.class, 20, "lying"); + registerDataName(Cat.class, 20, "relaxed"); + registerDataName(Cat.class, 21, "collar_color"); + + // Wolf + registerDataName(Wolf.class, 19, "begging"); + registerDataName(Wolf.class, 20, "collar_color"); + registerDataName(Wolf.class, 21, "anger_time"); + + // Parrot + registerDataName(Parrot.class, 19, "variant"); + + // Abstract villager + registerDataName(AbstractVillager.class, 17, "head_shake_ticks"); + + // Villager + registerDataName(Villager.class, 18, "villager_data"); + + // Iron golem + registerDataName(IronGolem.class, 16, "iron_golem_flags"); + + // Snow golem + registerDataName(SnowGolem.class, 16, "snow_golem_pumpkin_flags"); + + // Shulker + registerDataName(Shulker.class, 16, "attach_face"); + registerDataName(Shulker.class, 17, "attachment_location"); + registerDataName(Shulker.class, 18, "peek"); + registerDataName(Shulker.class, 19, "color"); + + // Base piglin + registerDataName(AbstractPiglin.class, 16, "immune_to_zombification"); + + // Piglin + registerDataName(Piglin.class, 17, "is_baby"); + registerDataName(Piglin.class, 18, "charging_crossbow"); + registerDataName(Piglin.class, 19, "dancing"); + + // Blaze + registerDataName(Blaze.class, 16, "blaze_flags"); + + // Creeper + registerDataName(Creeper.class, 16, "state"); + registerDataName(Creeper.class, 17, "charged"); + registerDataName(Creeper.class, 18, "ignited"); + + // Goat + registerDataName(Goat.class, 17, "screaming"); + registerDataName(Goat.class, 18, "has_left_horn"); + registerDataName(Goat.class, 19, "has_right_horn"); + + // Guardian + registerDataName(Guardian.class, 16, "spikes_retracted"); + registerDataName(Guardian.class, 17, "target_id"); + + // Raider + registerDataName(Raider.class, 16, "celebrating"); + + // Pillager + registerDataName(Pillager.class, 17, "charging_crossbow"); + + // Spellcaster illager + registerDataName(SpellcasterIllager.class, 17, "spell"); + + // Witch + registerDataName(Witch.class, 17, "drinking_potion"); + + // Vex + registerDataName(Vex.class, 16, "vex_flags"); + + // Spider + registerDataName(Spider.class, 16, "spider_flags"); + + // Warden + registerDataName(Warden.class, 16, "anger_level"); + + // Wither + registerDataName(WitherBoss.class, 16, "center_head_target"); + registerDataName(WitherBoss.class, 17, "left_head_target"); + registerDataName(WitherBoss.class, 18, "right_head_target"); + registerDataName(WitherBoss.class, 19, "invulnerable_time"); + + // Zoglin + registerDataName(Zoglin.class, 16, "is_baby"); + + // Zombie + registerDataName(Zombie.class, 16, "is_baby"); + registerDataName(Zombie.class, 17, "type"); // Unused + registerDataName(Zombie.class, 18, "converting_in_water"); + + // Zombie villager + registerDataName(ZombieVillager.class, 19, "is_converting"); + registerDataName(ZombieVillager.class, 20, "villager_data"); + + // Enderman + registerDataName(EnderMan.class, 16, "carried_block"); + registerDataName(EnderMan.class, 17, "screaming"); + registerDataName(EnderMan.class, 18, "staring"); + + // Ender dragon + registerDataName(EnderDragon.class, 16, "phase"); + + // Ghast + registerDataName(Ghast.class, 16, "attacking"); + + // Phantom + registerDataName(Phantom.class, 16, "size"); + + // Slime + registerDataName(Slime.class, 16, "size"); + + // Abstract minecart + registerDataName(AbstractMinecart.class, 8, "shaking_ticks"); + registerDataName(AbstractMinecart.class, 9, "shaking_direction"); + registerDataName(AbstractMinecart.class, 10, "damage_taken"); + registerDataName(AbstractMinecart.class, 11, "display_block_id"); + registerDataName(AbstractMinecart.class, 12, "display_block_y"); + registerDataName(AbstractMinecart.class, 13, "show_display_block"); + + // Minecraft furnace + registerDataName(MinecartFurnace.class, 14, "has_fuel"); + + // Minecraft command block + registerDataName(MinecartCommandBlock.class, 14, "command"); + registerDataName(MinecartCommandBlock.class, 15, "last_output"); + + // Primed TNT + registerDataName(PrimedTnt.class, 8, "fuse_ticks"); + } + + public static int getIdForName(Class entityClass, String name) { + Class currentClass = entityClass; + int id = getIdFromClass(currentClass, name); + while (id == -1) { + currentClass = currentClass.getSuperclass(); + if (currentClass == Object.class) { + break; + } + id = getIdFromClass(currentClass, name); + } + return id; + } + + private static int getIdFromClass(Class entityClass, String name) { + Map nameToId = entityDataNames.get(entityClass); + int id = nameToId != null ? nameToId.getOrDefault(name, -1) : -1; + if (id == -1 && ArgumentHelper.matchesInteger(name)) { + id = new ElementTag(name).asInt(); + } + return id; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EntityHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EntityHelperImpl.java new file mode 100644 index 0000000000..0223d4b183 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/EntityHelperImpl.java @@ -0,0 +1,867 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.interfaces.EntityHelper; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.properties.entity.EntityState; +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; +import com.denizenscript.denizencore.objects.ObjectTag; +import com.denizenscript.denizencore.objects.core.MapTag; +import com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.denizenscript.denizencore.utilities.text.StringHolder; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minecraft.commands.arguments.EntityAnchorArgument; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; +import net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.dedicated.DedicatedPlayerList; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.players.PlayerList; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.damagesource.CombatRules; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.damagesource.DamageSources; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MoverType; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.goal.Goal; +import net.minecraft.world.entity.ai.navigation.PathNavigation; +import net.minecraft.world.entity.animal.armadillo.Armadillo; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.world.entity.monster.EnderMan; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.pathfinder.Path; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftCreatureSpawner; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.entity.*; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.*; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.BiConsumer; + +public class EntityHelperImpl extends EntityHelper { + + public static final MethodHandle ENTITY_ONGROUND_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.Entity.class, "onGround", boolean.class); + + public static final EntityDataAccessor ENDERMAN_DATA_ACCESSOR_SCREAMING = ReflectionHelper.getFieldValue(EnderMan.class, "DATA_CREEPY", null); + + @Override + public void setInvisible(Entity entity, boolean invisible) { + ((CraftEntity) entity).getHandle().setInvisible(invisible); + } + + @Override + public boolean isInvisible(Entity entity) { + return ((CraftEntity) entity).getHandle().isInvisible(); + } + + @Override + public void setPose(Entity entity, Pose pose) { + ((CraftEntity) entity).getHandle().setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]); + } + + @Override + public double getDamageTo(LivingEntity attacker, Entity target) { + double damage = 0; + AttributeInstance attrib = attacker.getAttribute(Attribute.ATTACK_DAMAGE); + if (attrib != null) { + damage = attrib.getValue(); + } + if (damage <= 0) { + return 0; + } + if (target == null) { + // Target is required as of MC 1.21, so if unspecified just assume target is equivalent to attacker + target = attacker; + } + DamageSource source; + net.minecraft.world.entity.Entity nmsTarget = ((CraftEntity) target).getHandle(); + ServerLevel nmsWorld = ((CraftWorld) attacker.getWorld()).getHandle(); + if (attacker instanceof CraftPlayer playerAttacker) { + source = nmsTarget.level().damageSources().playerAttack(playerAttacker.getHandle()); + } + else { + source = nmsTarget.level().damageSources().mobAttack(((CraftLivingEntity) attacker).getHandle()); + } + net.minecraft.world.entity.LivingEntity nmsLivingTarget = nmsTarget instanceof net.minecraft.world.entity.LivingEntity living ? living : null; + if (nmsLivingTarget != null ? nmsLivingTarget.isInvulnerableTo(nmsWorld, source) : nmsTarget.isInvulnerableToBase(source)) { + return 0; + } + if (attacker.getEquipment() != null) { + damage = EnchantmentHelper.modifyDamage(nmsWorld, CraftItemStack.asNMSCopy(attacker.getEquipment().getItemInMainHand()), nmsTarget, source, (float) damage); + } + if (nmsLivingTarget == null) { + return damage; + } + damage = CombatRules.getDamageAfterAbsorb(nmsLivingTarget, (float) damage, source, (float) nmsLivingTarget.getArmorValue(), (float) nmsLivingTarget.getAttributeValue(Attributes.ARMOR_TOUGHNESS)); + float enchantDamageModifier = EnchantmentHelper.getDamageProtection(nmsWorld, nmsLivingTarget, source); + if (enchantDamageModifier > 0) { + damage = CombatRules.getDamageAfterMagicAbsorb((float) damage, enchantDamageModifier); + } + return damage; + } + + public static final MethodHandle LIVINGENTITY_AUTOSPINATTACK_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.LivingEntity.class, "autoSpinAttackTicks"); + public static final MethodHandle LIVINGENTITY_SETLIVINGENTITYFLAG = ReflectionHelper.getMethodHandle(net.minecraft.world.entity.LivingEntity.class, "setLivingEntityFlag", int.class, boolean.class); + + @Override + public void setRiptide(Entity entity, boolean state) { + try { + net.minecraft.world.entity.LivingEntity nmsEntity = ((CraftLivingEntity) entity).getHandle(); + LIVINGENTITY_AUTOSPINATTACK_SETTER.invoke(nmsEntity, state ? 0 : 1); + LIVINGENTITY_SETLIVINGENTITYFLAG.invoke(nmsEntity, 4, true); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void forceInteraction(Player player, Location location) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + BlockHelperImpl.getNMSState(location.getBlock()).useItemOn(nmsPlayer.getMainHandItem(), ((CraftWorld) location.getWorld()).getHandle(), + nmsPlayer, InteractionHand.MAIN_HAND, + new BlockHitResult(new Vec3(0, 0, 0), null, Handler.toBlockPos(location), false)); + } + + @Override + public CompoundBinaryTag getNbtData(Entity entity) { + CompoundTag nmsTag = Handler.useValueOutput(((CraftEntity) entity).getHandle()::saveAsPassenger); + return NBTAdapter.toAPI(nmsTag); + } + + @Override + public void setNbtData(Entity entity, CompoundBinaryTag compoundTag) { + Handler.useValueInput(NBTAdapter.toNMS(compoundTag), ((CraftEntity) entity).getHandle()::load); + } + + /* + Entity Movement + */ + + private final static Map followTasks = new HashMap<>(); + + @Override + public void stopFollowing(Entity follower) { + if (follower == null) { + return; + } + UUID uuid = follower.getUniqueId(); + if (followTasks.containsKey(uuid)) { + followTasks.get(uuid).cancel(); + } + } + + @Override + public void stopWalking(Entity entity) { + if (((CraftEntity) entity).getHandle() instanceof Mob nmsMob) { + nmsMob.getNavigation().stop(); + } + } + + @Override + public void follow(final Entity target, final Entity follower, final double speed, final double lead, + final double maxRange, final boolean allowWander, final boolean teleport) { + if (target == null || follower == null) { + return; + } + + final net.minecraft.world.entity.Entity nmsEntityFollower = ((CraftEntity) follower).getHandle(); + if (!(nmsEntityFollower instanceof Mob nmsFollower)) { + return; + } + final PathNavigation followerNavigation = nmsFollower.getNavigation(); + + UUID uuid = follower.getUniqueId(); + + if (followTasks.containsKey(uuid)) { + followTasks.get(uuid).cancel(); + } + + final int locationNearInt = (int) Math.floor(lead); + final boolean hasMax = maxRange > lead; + + followTasks.put(follower.getUniqueId(), new BukkitRunnable() { + + private boolean inRadius = false; + + public void run() { + if (!target.isValid() || !follower.isValid()) { + this.cancel(); + } + followerNavigation.setSpeedModifier(2D); + Location targetLocation = target.getLocation(); + Path path; + + if (hasMax && !Utilities.checkLocation(targetLocation, follower.getLocation(), maxRange) + && !target.isDead() && target.isOnGround()) { + if (!inRadius) { + if (teleport) { + follower.teleport(Utilities.getWalkableLocationNear(targetLocation, locationNearInt)); + } + else { + cancel(); + } + } + else { + inRadius = false; + path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0); + if (path != null) { + followerNavigation.moveTo(path, 1D); + followerNavigation.setSpeedModifier(2D); + } + } + } + else if (!inRadius && !Utilities.checkLocation(targetLocation, follower.getLocation(), lead)) { + path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0); + if (path != null) { + followerNavigation.moveTo(path, 1D); + followerNavigation.setSpeedModifier(2D); + } + } + else { + inRadius = true; + } + if (inRadius && !allowWander) { + followerNavigation.stop(); + } + nmsFollower.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed); + } + }.runTaskTimer(NMSHandler.getJavaPlugin(), 0, 10)); + } + + @Override + public void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback) { + if (entity == null || location == null) { + return; + } + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + if (!(nmsEntity instanceof final Mob nmsMob)) { + return; + } + final PathNavigation entityNavigation = nmsMob.getNavigation(); + final Path path; + final boolean aiDisabled = !entity.hasAI(); + if (aiDisabled) { + entity.setAI(true); + try { + ENTITY_ONGROUND_SETTER.invoke(nmsMob, true); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + path = entityNavigation.createPath(location.getX(), location.getY(), location.getZ(), 1); + if (path != null) { + nmsMob.goalSelector.enableControlFlag(Goal.Flag.MOVE); + entityNavigation.moveTo(path, 1D); + final double oldSpeed = nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue(); + if (speed != null) { + nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed); + } + new BukkitRunnable() { + @Override + public void run() { + if (!entity.isValid()) { + if (callback != null) { + callback.run(); + } + cancel(); + return; + } + if (aiDisabled && entity instanceof Wolf wolf) { + wolf.setAngry(false); + } + if (entityNavigation.isDone() || path.isDone()) { + if (callback != null) { + callback.run(); + } + if (speed != null) { + nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(oldSpeed); + } + if (aiDisabled) { + entity.setAI(false); + } + cancel(); + } + } + }.runTaskTimer(NMSHandler.getJavaPlugin(), 1, 1); + } + //if (!Utilities.checkLocation(location, entity.getLocation(), 20)) { + // TODO: generate waypoints to the target location? + else { + entity.teleport(location); + } + } + + @Override + public void sendAllUpdatePackets(Entity entity) { + ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap; + ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId()); + if (entityTracker == null) { + return; + } + try { + ServerEntity serverEntity = (ServerEntity) PacketHelperImpl.ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker); + serverEntity.sendChanges(); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + /* + Hide Entity + */ + + @Override + public void sendHidePacket(Player pl, Entity entity) { + if (entity instanceof Player player) { + pl.hidePlayer(Denizen.getInstance(), player); + return; + } + ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle(); + if (nmsPlayer.connection != null && !pl.equals(entity)) { + ChunkMap.TrackedEntity entry = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); + if (entry != null) { + entry.removePlayer(nmsPlayer); + } + if (Denizen.supportsPaper) { // Workaround for Paper issue + nmsPlayer.connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId())); + } + } + } + + @Override + public void sendShowPacket(Player pl, Entity entity) { + if (entity instanceof Player player) { + pl.showPlayer(Denizen.getInstance(), player); + return; + } + ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle(); + if (nmsPlayer.connection != null && !pl.equals(entity)) { + ChunkMap.TrackedEntity entry = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); + if (entry != null) { + entry.removePlayer(nmsPlayer); + entry.updatePlayer(nmsPlayer); + } + } + } + + @Override + public void rotate(Entity entity, float yaw, float pitch) { + // If this entity is a real player instead of a player type NPC, + // it will appear to be online + if (entity instanceof Player player && player.isOnline()) { + NetworkInterceptHelper.enable(); + float relYaw = (yaw - entity.getLocation().getYaw()) % 360; + if (relYaw > 180) { + relYaw -= 360; + } + final float actualRelYaw = relYaw; + float relPitch = pitch - entity.getLocation().getPitch(); + NMSHandler.packetHelper.sendRelativeLookPacket(player, actualRelYaw, relPitch); + } + else if (entity instanceof LivingEntity) { + if (entity instanceof EnderDragon) { + yaw = normalizeYaw(yaw - 180); + } + look(entity, yaw, pitch); + } + else { + net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle(); + handle.setYRot(yaw - 360); + handle.setXRot(pitch); + } + } + + @Override + public float getBaseYaw(LivingEntity entity) { + return ((CraftLivingEntity) entity).getHandle().yBodyRot; + } + + @Override + public void look(Entity entity, float yaw, float pitch) { + net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle(); + if (handle == null) { + Debug.echoError("Cannot set look direction for unspawned entity " + entity.getUniqueId()); + return; + } + handle.setYRot(yaw); + if (handle instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) { + while (yaw < -180.0F) { + yaw += 360.0F; + } + while (yaw >= 180.0F) { + yaw -= 360.0F; + } + nmsLivingEntity.yBodyRotO = yaw; + if (!(handle instanceof net.minecraft.world.entity.player.Player)) { + nmsLivingEntity.setYBodyRot(yaw); + } + nmsLivingEntity.setYHeadRot(yaw); + } + handle.setXRot(pitch); + } + + private static HitResult rayTrace(World world, Vector start, Vector end) { + try { + NMSHandler.chunkHelper.changeChunkServerThread(world); + return ((CraftWorld) world).getHandle().clip(new ClipContext(new Vec3(start.getX(), start.getY(), start.getZ()), + new Vec3(end.getX(), end.getY(), end.getZ()), + ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, CollisionContext.empty())); + } + finally { + NMSHandler.chunkHelper.restoreServerThread(world); + } + } + + @Override + public boolean canTrace(World world, Vector start, Vector end) { + HitResult pos = rayTrace(world, start, end); + if (pos == null) { + return true; + } + return pos.getType() == HitResult.Type.MISS; + } + + @Override + public void snapPositionTo(Entity entity, Vector vector) { + ((CraftEntity) entity).getHandle().setPosRaw(vector.getX(), vector.getY(), vector.getZ()); + } + + @Override + public void move(Entity entity, Vector vector) { + ((CraftEntity) entity).getHandle().move(MoverType.SELF, new Vec3(vector.getX(), vector.getY(), vector.getZ())); + } + + @Override + public boolean internalLook(Player player, Location at) { + PacketHelperImpl.send(player, new ClientboundPlayerLookAtPacket(EntityAnchorArgument.Anchor.EYES, at.getX(), at.getY(), at.getZ())); + return true; + } + + public static long entityToPacket(double x) { + return Mth.lfloor(x * 4096.0D); + } + + @Override + public void fakeMove(Entity entity, Vector vector) { + long x = entityToPacket(vector.getX()); + long y = entityToPacket(vector.getY()); + long z = entityToPacket(vector.getZ()); + ClientboundMoveEntityPacket packet = new ClientboundMoveEntityPacket.Pos(entity.getEntityId(), (short) x, (short) y, (short) z, entity.isOnGround()); + for (Player player : getPlayersThatSee(entity)) { + PacketHelperImpl.send(player, packet); + } + } + + @Override + public void fakeTeleport(Entity entity, Location location) { + FriendlyByteBuf packetData = new FriendlyByteBuf(Unpooled.buffer()); + // Referenced from ClientboundTeleportEntityPacket source + packetData.writeVarInt(entity.getEntityId()); + packetData.writeDouble(location.getX()); + packetData.writeDouble(location.getY()); + packetData.writeDouble(location.getZ()); + packetData.writeByte((byte)((int)(location.getYaw() * 256.0F / 360.0F))); + packetData.writeByte((byte)((int)(location.getPitch() * 256.0F / 360.0F))); + packetData.writeBoolean(entity.isOnGround()); + ClientboundTeleportEntityPacket packet = ClientboundTeleportEntityPacket.STREAM_CODEC.decode(packetData); + for (Player player : getPlayersThatSee(entity)) { + PacketHelperImpl.send(player, packet); + } + } + + @Override + public void clientResetLoc(Entity entity) { + ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(entity.getEntityId(), PositionMoveRotation.of(((CraftEntity) entity).getHandle()), Set.of(), entity.isOnGround()); + for (Player player : getPlayersThatSee(entity)) { + PacketHelperImpl.send(player, packet); + } + } + + @Override + public void teleport(Entity entity, Location loc) { + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + nmsEntity.setYRot(loc.getYaw()); + nmsEntity.setXRot(loc.getPitch()); + if (nmsEntity instanceof ServerPlayer) { + nmsEntity.teleportTo(loc.getX(), loc.getY(), loc.getZ()); + } + nmsEntity.setPos(loc.getX(), loc.getY(), loc.getZ()); + } + + @Override + public void setBoundingBox(Entity entity, BoundingBox box) { + ((CraftEntity) entity).getHandle().setBoundingBox(new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ())); + } + + public static final Field EXPERIENCE_ORB_AGE = ReflectionHelper.getFields(net.minecraft.world.entity.ExperienceOrb.class).get("age", int.class); + + @Override + public void setTicksLived(Entity entity, int ticks) { + // Bypass Spigot's must-be-at-least-1-tick requirement, as negative tick counts are useful + ((CraftEntity) entity).getHandle().tickCount = ticks; + if (entity instanceof CraftFallingBlock craftFallingBlock) { + craftFallingBlock.getHandle().time = ticks; + } + else if (entity instanceof CraftItem craftItem) { + ((ItemEntity) craftItem.getHandle()).age = ticks; + } + else if (entity instanceof CraftExperienceOrb craftExperienceOrb) { + try { + EXPERIENCE_ORB_AGE.setInt(craftExperienceOrb.getHandle(), ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + } + + @Override + public void setHeadAngle(LivingEntity entity, float angle) { + ((CraftLivingEntity) entity).getHandle().setYHeadRot(angle); + } + + @Override + public void setEndermanAngry(Enderman enderman, boolean angry) { + ((CraftEnderman) enderman).getHandle().getEntityData().set(ENDERMAN_DATA_ACCESSOR_SCREAMING, angry); + } + + public static class FakeDamageSrc extends DamageSource { public DamageSource real; public FakeDamageSrc(DamageSource src) { super(null); real = src; } } + + public static DamageSources backupDamageSources; + + public static DamageSources getReusableDamageSources() { + if (backupDamageSources == null) { + backupDamageSources = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().damageSources(); + } + return backupDamageSources; + } + + public static DamageSource getSourceFor(net.minecraft.world.entity.Entity nmsSource, EntityDamageEvent.DamageCause cause, net.minecraft.world.entity.Entity nmsSourceProvider) { + DamageSources sources = nmsSourceProvider == null ? getReusableDamageSources() : nmsSourceProvider.level().damageSources(); + DamageSource src = sources.generic(); + if (nmsSource != null) { + if (nmsSource instanceof net.minecraft.world.entity.player.Player nmsPlayer) { + src = nmsSource.level().damageSources().playerAttack(nmsPlayer); + } + else if (nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) { + src = nmsSource.level().damageSources().mobAttack(nmsLivingEntity); + } + } + if (cause == null) { + return src; + } + return switch (cause) { + case CONTACT -> sources.cactus(); + case ENTITY_ATTACK -> sources.mobAttack(nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity ? nmsLivingEntity : null); + case ENTITY_SWEEP_ATTACK -> src != sources.generic() ? src.sweep() : src; + case PROJECTILE -> sources.thrown(nmsSource, nmsSource != null && nmsSource.getBukkitEntity() instanceof Projectile projectile + && projectile.getShooter() instanceof CraftEntity shooter ? shooter.getHandle() : null); + case SUFFOCATION -> sources.inWall(); + case FALL -> sources.fall(); + case FIRE -> sources.inFire(); + case FIRE_TICK -> sources.onFire(); + case MELTING -> sources.melting(); + case LAVA -> sources.lava(); + case DROWNING -> sources.drown(); + case BLOCK_EXPLOSION -> nmsSource instanceof PrimedTnt primedTnt ? sources.explosion(primedTnt, primedTnt.getOwner()) : sources.explosion(null); + case ENTITY_EXPLOSION -> sources.explosion(nmsSource, null); + case VOID -> sources.fellOutOfWorld(); + case LIGHTNING -> sources.lightningBolt(); + case STARVATION -> sources.starve(); + case POISON -> sources.poison(); + case MAGIC -> sources.magic(); + case WITHER -> sources.wither(); + case FALLING_BLOCK -> sources.fallingBlock(nmsSource); + case THORNS -> sources.thorns(nmsSource); + case DRAGON_BREATH -> sources.dragonBreath(); + case CUSTOM -> sources.generic(); + case FLY_INTO_WALL -> sources.flyIntoWall(); + case HOT_FLOOR -> sources.hotFloor(); + case CAMPFIRE -> sources.campfire(); + case CRAMMING -> sources.cramming(); + case DRYOUT -> sources.dryOut(); + case FREEZE -> sources.freeze(); + case SONIC_BOOM -> sources.sonicBoom(nmsSource); + case WORLD_BORDER -> sources.outOfBorder(); + case KILL -> sources.genericKill(); + case SUICIDE -> new FakeDamageSrc(src); + }; + } + + @Override + public void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause) { + if (target == null) { + return; + } + net.minecraft.world.entity.LivingEntity nmsTarget = ((CraftLivingEntity) target).getHandle(); + net.minecraft.world.entity.Entity nmsSource = source == null ? null : ((CraftEntity) source.getBukkitEntity()).getHandle(); + DamageSource src = getSourceFor(nmsSource, cause, nmsTarget); + if (src instanceof FakeDamageSrc fakeDamageSrc) { + src = fakeDamageSrc.real; + if (fireFakeDamageEvent(target, source, sourceLoc, cause, amount).isCancelled()) { + return; + } + } + nmsTarget.hurt(src, amount); + } + + @Override + public void setLastHurtBy(LivingEntity mob, LivingEntity damager) { + ((CraftLivingEntity) mob).getHandle().setLastHurtByMob(((CraftLivingEntity) damager).getHandle()); + } + + public static final Field FALLINGBLOCK_BLOCK_STATE = ReflectionHelper.getFields(FallingBlockEntity.class).getFirstOfType(BlockState.class); + + @Override + public void setFallingBlockType(FallingBlock fallingBlock, BlockData block) { + BlockState state = ((CraftBlockData) block).getState(); + FallingBlockEntity nmsEntity = ((CraftFallingBlock) fallingBlock).getHandle(); + try { + FALLINGBLOCK_BLOCK_STATE.set(nmsEntity, state); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) { + SpawnerBlockEntity nmsSpawner = BlockHelperImpl.getTE((CraftCreatureSpawner) spawner); + ServerLevel level = ((CraftWorld) spawner.getWorld()).getHandle(); + net.minecraft.world.entity.Entity nmsEntity = nmsSpawner.getSpawner().getOrCreateDisplayEntity(level, nmsSpawner.getBlockPos()); + return new EntityTag(nmsEntity.getBukkitEntity()); + } + + public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.zombie.Zombie.class).get("inWaterTime", int.class); + + @Override + public int getInWaterTime(Zombie zombie) { + try { + return ZOMBIE_INWATERTIME.getInt(((CraftZombie) zombie).getHandle()); + } + catch (Throwable ex) { + Debug.echoError(ex); + return 0; + } + } + + @Override + public void setInWaterTime(Zombie zombie, int ticks) { + try { + ZOMBIE_INWATERTIME.setInt(((CraftZombie) zombie).getHandle(), ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static final MethodHandle TRACKING_RANGE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ChunkMap.TrackedEntity.class, int.class); + + @Override + public void setTrackingRange(Entity entity, int range) { + try { + ChunkMap map = ((CraftWorld) entity.getWorld()).getHandle().getChunkSource().chunkMap; + ChunkMap.TrackedEntity entry = map.entityMap.get(entity.getEntityId()); + if (entry != null) { + TRACKING_RANGE_SETTER.invoke(entry, range); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public boolean isAggressive(org.bukkit.entity.Mob mob) { + return ((CraftMob) mob).getHandle().isAggressive(); + } + + @Override + public void setAggressive(org.bukkit.entity.Mob mob, boolean aggressive) { + ((CraftMob) mob).getHandle().setAggressive(aggressive); + } + + // Use reflection because Paper changes the method return type + public static final MethodHandle PLAYERLIST_REMOVE = ReflectionHelper.getMethodHandle(PlayerList.class, "remove", ServerPlayer.class); + + @Override + public void setUUID(Entity entity, UUID id) { + try { + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + nmsEntity.stopRiding(); + nmsEntity.getPassengers().forEach(net.minecraft.world.entity.Entity::stopRiding); + Level level = nmsEntity.level(); + DedicatedPlayerList playerList = ((CraftServer) Bukkit.getServer()).getHandle(); + if (nmsEntity instanceof ServerPlayer nmsPlayer) { + PLAYERLIST_REMOVE.invoke(playerList, nmsPlayer); + } + else { + nmsEntity.remove(net.minecraft.world.entity.Entity.RemovalReason.DISCARDED); + } + nmsEntity.unsetRemoved(); + nmsEntity.setUUID(id); + if (nmsEntity instanceof ServerPlayer nmsPlayer) { + playerList.placeNewPlayer(DenizenNetworkManagerImpl.getConnection(nmsPlayer), nmsPlayer, new CommonListenerCookie(nmsPlayer.getGameProfile(), nmsPlayer.connection.latency(), nmsPlayer.clientInformation(), nmsPlayer.connection.isTransferred())); + } + else { + level.addFreshEntity(nmsEntity); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static final Field SynchedEntityData_itemsById = ReflectionHelper.getFields(SynchedEntityData.class).get("itemsById"); + + public static Int2ObjectMap> getDataItems(Entity entity) { + try { + return (Int2ObjectMap>) SynchedEntityData_itemsById.get(((CraftEntity) entity).getHandle().getEntityData()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); // Stop the code here to avoid NPEs down the road + } + } + + public static void convertToInternalData(Entity entity, MapTag internalData, BiConsumer, Object> processConverted) { + Int2ObjectMap> dataItemsById = getDataItems(entity); + for (Map.Entry entry : internalData.entrySet()) { + int id = EntityDataNameMapper.getIdForName(((CraftEntity) entity).getHandle().getClass(), entry.getKey().low); + if (id == -1) { + Debug.echoError("Invalid internal data key: " + entry.getKey()); + return; + } + SynchedEntityData.DataItem dataItem = dataItemsById.get(id); + if (dataItem == null) { + Debug.echoError("Invalid internal data id '" + id + "': couldn't be matched to any internal data for entity of type '" + entity.getType() + "'."); + return; + } + Object converted = ReflectionSetCommand.convertObjectTypeFor(dataItem.getValue().getClass(), entry.getValue()); + if (converted != null) { + processConverted.accept(dataItem, converted); + } + } + } + + @Override + public List convertInternalEntityDataValues(Entity entity, MapTag internalData) { + List dataValues = new ArrayList<>(internalData.size()); + convertToInternalData(entity, internalData, (dataItem, converted) -> dataValues.add(PacketHelperImpl.createEntityData(dataItem.getAccessor(), converted))); + return dataValues; + } + + @Override + public void modifyInternalEntityData(Entity entity, MapTag internalData) { + SynchedEntityData nmsEntityData = ((CraftEntity) entity).getHandle().getEntityData(); + convertToInternalData(entity, internalData, (dataItem, converted) -> nmsEntityData.set(dataItem.getAccessor(), converted)); + } + + @Override + public void startUsingItem(LivingEntity entity, EquipmentSlot hand) { + ((CraftLivingEntity) entity).getHandle().startUsingItem(hand == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND); + } + + @Override + public void stopUsingItem(LivingEntity entity) { + ((CraftLivingEntity) entity).getHandle().stopUsingItem(); + } + + @Override + public void openHorseInventory(Player player, AbstractHorse horse) { + net.minecraft.world.entity.animal.equine.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle(); + ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory); + } + + private CompoundTag getRawEntityNBT(net.minecraft.world.entity.Entity entity) { + return Handler.useValueOutput(entity::saveWithoutId); + } + + @Override + public CompoundBinaryTag getRawNBT(Entity entity) { + return NBTAdapter.toAPI(getRawEntityNBT(((CraftEntity) entity).getHandle())); + } + + @Override + public void modifyRawNBT(Entity entity, CompoundBinaryTag tag) { + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + CompoundTag nmsTag = NBTAdapter.toNMS(tag); + CompoundTag nmsMergedTag = getRawEntityNBT(nmsEntity).merge(nmsTag); + UUID uuid = nmsEntity.getUUID(); + Handler.useValueInput(nmsMergedTag, nmsEntity::load); + nmsEntity.setUUID(uuid); + } + + @Override + public EntityState.ArmadilloState getArmadilloState(org.bukkit.entity.Armadillo entity) { + Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle(); + return switch (armadillo.getState()) { + case IDLE -> EntityState.ArmadilloState.IDLE; + case ROLLING -> EntityState.ArmadilloState.ROLLING; + case SCARED -> EntityState.ArmadilloState.SCARED; + case UNROLLING -> EntityState.ArmadilloState.UNROLLING; + }; + } + + @Override + public void setArmadilloState(org.bukkit.entity.Armadillo entity, EntityState.ArmadilloState state) { + Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle(); + armadillo.switchToState(switch (state) { + case IDLE -> Armadillo.ArmadilloState.IDLE; + case ROLLING -> Armadillo.ArmadilloState.ROLLING; + case SCARED -> Armadillo.ArmadilloState.SCARED; + case UNROLLING -> Armadillo.ArmadilloState.UNROLLING; + }); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/FishingHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/FishingHelperImpl.java new file mode 100644 index 0000000000..acb94b9f09 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/FishingHelperImpl.java @@ -0,0 +1,156 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.interfaces.FishingHelper; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.storage.loot.BuiltInLootTables; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftFishHook; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent; + +import java.lang.reflect.Field; +import java.util.List; + +public class FishingHelperImpl implements FishingHelper { + + @Override + public org.bukkit.inventory.ItemStack getResult(FishHook fishHook, CatchType catchType) { + FishingHook nmsHook = ((CraftFishHook) fishHook).getHandle(); + ItemStack result = switch (catchType) { + case DEFAULT -> { + ServerLevel nmsWorld = ((CraftWorld) fishHook.getWorld()).getHandle(); + ItemStack nmsFishingRod = nmsHook.getPlayerOwner().getMainHandItem(); + float f = nmsWorld.getRandom().nextFloat(); + float i = EnchantmentHelper.getFishingTimeReduction(nmsWorld, nmsFishingRod, nmsHook.getPlayerOwner()); + int j = EnchantmentHelper.getFishingLuckBonus(nmsWorld, nmsFishingRod, nmsHook.getPlayerOwner()); + float f1 = 0.1F - i * 0.025F - (float) j * 0.01F; + float f2 = 0.05F + i * 0.01F - (float) j * 0.01F; + + f1 = Mth.clamp(f1, 0.0F, 1.0F); + f2 = Mth.clamp(f2, 0.0F, 1.0F); + if (f < f1) { + yield catchRandomJunk(nmsHook); + } + else { + f -= f1; + if (f < f2) { + yield catchRandomTreasure(nmsHook); + } + else { + yield catchRandomFish(nmsHook); + } + } + } + case JUNK -> catchRandomJunk(nmsHook); + case TREASURE -> catchRandomTreasure(nmsHook); + case FISH -> catchRandomFish(nmsHook); + default -> null; + }; + return result != null ? ItemHelperImpl.asBukkitCopy(result) : null; + } + + public ItemStack getRandomReward(FishingHook nmsHook, ResourceKey key) { + ServerLevel nmsWorld = (ServerLevel) nmsHook.level(); + LootParams nmsLootParams = new LootParams.Builder(nmsWorld) + .withParameter(LootContextParams.ORIGIN, new Vec3(nmsHook.getX(), nmsHook.getY(), nmsHook.getZ())) + .withParameter(LootContextParams.TOOL, new ItemStack(Items.FISHING_ROD)) + .create(LootContextParamSets.FISHING); + List nmsItems = nmsWorld.getServer().reloadableRegistries().getLootTable(key).getRandomItems(nmsLootParams); + return nmsItems.get(nmsWorld.getRandom().nextInt(nmsItems.size())); + } + + @Override + public FishHook spawnHook(Location location, Player player) { + ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle(); + FishingHook hook = new FishingHook(((CraftPlayer) player).getHandle(), nmsWorld, 0, 0); + nmsWorld.addFreshEntity(hook, CreatureSpawnEvent.SpawnReason.CUSTOM); + return (FishHook) hook.getBukkitEntity(); + } + + private ItemStack catchRandomJunk(FishingHook fishHook) { + return getRandomReward(fishHook, BuiltInLootTables.FISHING_JUNK); + } + + private ItemStack catchRandomTreasure(FishingHook fishHook) { + return getRandomReward(fishHook, BuiltInLootTables.FISHING_TREASURE); + } + + private ItemStack catchRandomFish(FishingHook fishHook) { + return getRandomReward(fishHook, BuiltInLootTables.FISHING_FISH); + } + + public static final Field FISHING_HOOK_NIBBLE = ReflectionHelper.getFields(FishingHook.class).get("nibble", int.class); + public static final Field FISHING_HOOK_LURE_TIME = ReflectionHelper.getFields(FishingHook.class).get("timeUntilLured", int.class); + public static final Field FISHING_HOOK_HOOK_TIME = ReflectionHelper.getFields(FishingHook.class).get("timeUntilHooked", int.class); + + @Override + public FishHook getHookFrom(Player player) { + FishingHook nmsHook = ((CraftPlayer) player).getHandle().fishing; + if (nmsHook == null) { + return null; + } + return (FishHook) nmsHook.getBukkitEntity(); + } + + @Override + public void setNibble(FishHook hook, int ticks) { + FishingHook nmsHook = ((CraftFishHook) hook).getHandle(); + try { + FISHING_HOOK_NIBBLE.setInt(nmsHook, ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void setHookTime(FishHook hook, int ticks) { + FishingHook nmsHook = ((CraftFishHook) hook).getHandle(); + try { + FISHING_HOOK_HOOK_TIME.setInt(nmsHook, ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public int getLureTime(FishHook hook) { + FishingHook nmsHook = ((CraftFishHook) hook).getHandle(); + try { + return FISHING_HOOK_LURE_TIME.getInt(nmsHook); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + return -1; + } + + @Override + public void setLureTime(FishHook hook, int ticks) { + FishingHook nmsHook = ((CraftFishHook) hook).getHandle(); + try { + FISHING_HOOK_LURE_TIME.setInt(nmsHook, ticks); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/ItemHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/ItemHelperImpl.java new file mode 100644 index 0000000000..0e851f7076 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/ItemHelperImpl.java @@ -0,0 +1,807 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.interfaces.ItemHelper; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.impl.ProfileEditorImpl; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.properties.item.ItemComponentsPatch; +import com.denizenscript.denizen.objects.properties.item.ItemRawNBT; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizen.utilities.PaperAPITools; +import com.denizenscript.denizencore.objects.core.ElementTag; +import com.denizenscript.denizencore.objects.core.MapTag; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.collect.*; +import com.google.gson.JsonObject; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.JsonOps; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.advancements.predicates.BlockPredicate; +import net.minecraft.advancements.predicates.DataComponentMatchers; +import net.minecraft.core.*; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.fixes.References; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.item.AdventureModePredicate; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStackTemplate; +import net.minecraft.world.item.alchemy.PotionBrewing; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.item.component.ItemLore; +import net.minecraft.world.item.component.ResolvableProfile; +import net.minecraft.world.item.component.TypedEntityData; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.item.crafting.BlastingRecipe; +import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.minecraft.world.item.crafting.SmithingTransformRecipe; +import net.minecraft.world.item.crafting.SmokingRecipe; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.saveddata.maps.MapId; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.entity.CraftEntityType; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.*; +import org.bukkit.craftbukkit.map.CraftMapView; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.*; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.SmithingTrimRecipe; +import org.bukkit.inventory.TransmuteRecipe; +import org.bukkit.map.MapView; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class ItemHelperImpl extends ItemHelper { + + public static final Recipe.CommonInfo BASE_RECIPE_INFO = new Recipe.CommonInfo(true); + + public static net.minecraft.world.item.crafting.RecipeHolder getNMSRecipe(NamespacedKey key) { + ResourceKey> nmsKey = ResourceKey.create(Registries.RECIPE, CraftNamespacedKey.toMinecraft(key)); + return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().byKey(nmsKey).orElse(null); + } + + public static final MethodHandle CRAFT_ITEM_STACK_AS_TEMPLATE = Handler.reflectPaperRenamed(CraftItemStack.class, "asNMSTemplate", "asTemplate", ItemStack.class); + + public static ItemStackTemplate asNMSTemplate(ItemStack item) { + try { + return (ItemStackTemplate) CRAFT_ITEM_STACK_AS_TEMPLATE.invokeExact(item); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static final MethodHandle CRAFT_ITEM_STACK_AS_BUKKIT_COPY = ReflectionHelper.getMethodHandle(CraftItemStack.class, "asBukkitCopy", net.minecraft.world.item.ItemStack.class); + + public static ItemStack asBukkitCopy(net.minecraft.world.item.ItemStack nmsItem) { + try { + return (ItemStack) CRAFT_ITEM_STACK_AS_BUKKIT_COPY.invokeExact(nmsItem); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static final Field RecipeManager_featureFlagSet = ReflectionHelper.getFields(RecipeManager.class).getFirstOfType(FeatureFlagSet.class); + + public void setMaxStackSize(Material material, int size) { + try { + ReflectionHelper.getFinalSetter(Material.class, "maxStack").invoke(material, size); + Holder.Reference nmsItemHolder = BuiltInRegistries.ITEM.get(CraftNamespacedKey.toMinecraft(material.getKey())).orElseThrow(); + nmsItemHolder.bindComponents(DataComponentMap.composite(nmsItemHolder.components(), DataComponentMap.builder().set(DataComponents.MAX_STACK_SIZE, size).build())); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static RecipeManager getRecipeManager() { + return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager(); + } + + public static CompoundTag serializeNmsItem(net.minecraft.world.item.ItemStack nmsItem) { + return (CompoundTag) net.minecraft.world.item.ItemStack.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsItem).getOrThrow(); + } + + public static net.minecraft.world.item.ItemStack parseNmsItem(CompoundTag nmsTag) { + return net.minecraft.world.item.ItemStack.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsTag).getOrThrow(); + } + + public Object recipeManagerFeatureFlagSetCache = null; + + @Override + public void blockRecipeFinalization() { + try { + RecipeManager manager = getRecipeManager(); + Object flags = RecipeManager_featureFlagSet.get(manager); + if (flags != null) { + recipeManagerFeatureFlagSetCache = flags; + RecipeManager_featureFlagSet.set(manager, null); + + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void restoreRecipeFinalization() { + try { + RecipeManager manager = getRecipeManager(); + if (recipeManagerFeatureFlagSetCache != null) { + RecipeManager_featureFlagSet.set(manager, recipeManagerFeatureFlagSetCache); + manager.finalizeRecipeLoading(); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void removeRecipes(List keys) { + blockRecipeFinalization(); + RecipeManager manager = getRecipeManager(); + for (NamespacedKey key: keys) { + ResourceKey> nmsKey = ResourceKey.create(Registries.RECIPE, CraftNamespacedKey.toMinecraft(key)); + manager.removeRecipe(nmsKey); + } + restoreRecipeFinalization(); + } + + @Override + public Integer burnTime(Material material) { + return MinecraftServer.getServer().fuelValues().burnDuration(new net.minecraft.world.item.ItemStack(CraftMagicNumbers.getItem(material))); + } + + @Override + public void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact) { + if (item.length == 1 && item[0].getType() == Material.AIR) { + recipe.setIngredient(c, new RecipeChoice.MaterialChoice(Material.AIR)); + } + else if (exact) { + recipe.setIngredient(c, new RecipeChoice.ExactChoice(item)); + } + else { + Material[] mats = new Material[item.length]; + for (int i = 0; i < item.length; i++) { + mats[i] = item[i].getType(); + } + recipe.setIngredient(c, new RecipeChoice.MaterialChoice(mats)); + } + } + + // TODO: Recipe registration should be moved to the API + public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) { + if (!exact) { + return Ingredient.of(Arrays.stream(items).map(item -> CraftMagicNumbers.getItem(item.getType()))); + } + return Ingredient.ofStacks(Arrays.stream(items).map(CraftItemStack::asNMSCopy).toList()); + } + + public static ResourceKey> createRecipeKey(String name) { + return ResourceKey.create(Registries.RECIPE, Identifier.fromNamespaceAndPath("denizen", name)); + } + + @Override + public void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category) { + ResourceKey> key = createRecipeKey(keyName); + Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact); + AbstractCookingRecipe recipe; + AbstractCookingRecipe.CookingBookInfo bookInfo = new AbstractCookingRecipe.CookingBookInfo(category == null ? CookingBookCategory.MISC : CookingBookCategory.valueOf(CoreUtilities.toUpperCase(category)), group); + if (type.equalsIgnoreCase("smoker")) { + recipe = new SmokingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time); + } + else if (type.equalsIgnoreCase("blast")) { + recipe = new BlastingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time); + } + else if (type.equalsIgnoreCase("campfire")) { + recipe = new CampfireCookingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time); + } + else { + recipe = new SmeltingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time); + } + RecipeHolder holder = new RecipeHolder<>(key, recipe); + getRecipeManager().addRecipe(holder); + } + + @Override + public void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact) { + ResourceKey> key = createRecipeKey(keyName); + Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact); + StonecutterRecipe recipe = new StonecutterRecipe(BASE_RECIPE_INFO, itemRecipe, asNMSTemplate(result)); + RecipeHolder holder = new RecipeHolder<>(key, recipe); + getRecipeManager().addRecipe(holder); + } + + @Override + public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) { + ResourceKey> key = createRecipeKey(keyName); + Ingredient templateItemRecipe = templateItem.length == 0 ? null : itemArrayToRecipe(templateItem, templateExact); + Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact); + Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact); + SmithingTransformRecipe recipe = new SmithingTransformRecipe(BASE_RECIPE_INFO, Optional.ofNullable(templateItemRecipe), baseItemRecipe, Optional.of(upgradeItemRecipe), asNMSTemplate(result)); + RecipeHolder holder = new RecipeHolder<>(key, recipe); + getRecipeManager().addRecipe(holder); + } + + @Override + public void registerShapelessRecipe(String keyName, String group, ItemStack result, List ingredients, boolean[] exact, String category) { + ResourceKey> key = createRecipeKey(keyName); + ArrayList ingredientList = new ArrayList<>(); + CraftingBookCategory categoryValue = category == null ? CraftingBookCategory.MISC : CraftingBookCategory.valueOf(CoreUtilities.toUpperCase(category)); + for (int i = 0; i < ingredients.size(); i++) { + ingredientList.add(itemArrayToRecipe(ingredients.get(i), exact[i])); + } + ShapelessRecipe recipe = new ShapelessRecipe(BASE_RECIPE_INFO, new CraftingRecipe.CraftingBookInfo(categoryValue, group), asNMSTemplate(result), NonNullList.of(null, ingredientList.toArray(new Ingredient[0]))); + RecipeHolder holder = new RecipeHolder<>(key, recipe); + getRecipeManager().addRecipe(holder); + } + + public static final MethodHandle CRAFT_RECIPE_ADD_TO_MANAGER = Handler.reflectPaperRenamed(CraftRecipe.class, "addToCraftingManager", "addToRecipeManager"); + + @Override + public void registerOtherRecipe(org.bukkit.inventory.Recipe recipe) { + // This method copied from Bukkit CraftServer source, just to bypass unwanted paper patch + CraftRecipe toAdd; + if (recipe instanceof CraftRecipe craft) { + toAdd = craft; + } + else if (recipe instanceof ShapedRecipe) { + toAdd = CraftShapedRecipe.fromBukkitRecipe((ShapedRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.ShapelessRecipe) { + toAdd = CraftShapelessRecipe.fromBukkitRecipe((org.bukkit.inventory.ShapelessRecipe)recipe); + } + else if (recipe instanceof FurnaceRecipe) { + toAdd = CraftFurnaceRecipe.fromBukkitRecipe((FurnaceRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.BlastingRecipe) { + toAdd = CraftBlastingRecipe.fromBukkitRecipe((org.bukkit.inventory.BlastingRecipe)recipe); + } + else if (recipe instanceof CampfireRecipe) { + toAdd = CraftCampfireRecipe.fromBukkitRecipe((CampfireRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.SmokingRecipe) { + toAdd = CraftSmokingRecipe.fromBukkitRecipe((org.bukkit.inventory.SmokingRecipe)recipe); + } + else if (recipe instanceof StonecuttingRecipe) { + toAdd = CraftStonecuttingRecipe.fromBukkitRecipe((StonecuttingRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.SmithingTransformRecipe) { + toAdd = CraftSmithingTransformRecipe.fromBukkitRecipe((org.bukkit.inventory.SmithingTransformRecipe)recipe); + } + else if (recipe instanceof org.bukkit.inventory.SmithingTrimRecipe) { + toAdd = CraftSmithingTrimRecipe.fromBukkitRecipe((SmithingTrimRecipe)recipe); + } + else { + if (!(recipe instanceof org.bukkit.inventory.TransmuteRecipe)) { + if (recipe instanceof ComplexRecipe) { + throw new UnsupportedOperationException("Cannot add custom complex recipe"); + } + return; + } + toAdd = CraftTransmuteRecipe.fromBukkitRecipe((TransmuteRecipe)recipe); + } + try { + CRAFT_RECIPE_ADD_TO_MANAGER.invokeExact(toAdd); + } + catch (Throwable e) { + Debug.echoError(e); + } + } + + @Override + public String getJsonString(ItemStack itemStack) { + String json = CraftItemStack.asNMSCopy(itemStack).getDisplayName().getStyle().toString().replace("\\", "\\\\").replace("\"", "\\\""); + return json.substring(176, json.length() - 185); + } + + @Override + public JsonObject getRawHoverComponentsJson(ItemStack item) { + DataComponentPatch nmsComponents = CraftItemStack.asNMSCopy(item).getComponentsPatch(); + if (nmsComponents.isEmpty()) { + return null; + } + return DataComponentPatch.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), nmsComponents).getOrThrow().getAsJsonObject(); + } + + @Override + public ItemStack applyRawHoverComponentsJson(ItemStack item, JsonObject components) { + return DataComponentPatch.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), components).mapOrElse( + nmsComponents -> { + net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); + nmsItem.applyComponents(nmsComponents); + return CraftItemStack.asCraftMirror(nmsItem); + }, + error -> { + Debug.echoError("Invalid hover item data '" + components + "': " + error.message()); + return item; + }); + } + + @Override + public PlayerProfile getSkullSkin(ItemStack is) { + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is); + ResolvableProfile profile = itemStack.get(DataComponents.PROFILE); + if (profile != null) { + Property property = Iterables.getFirst(profile.partialProfile().properties().get("textures"), null); + return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile), + property != null ? property.value() : null, + property != null ? property.signature() : null); + } + return null; + } + + @Override + public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) { + GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile); + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + nmsItemStack.set(DataComponents.PROFILE, ResolvableProfile.createResolved(gameProfile)); + return asBukkitCopy(nmsItemStack); + } + + @Override + public ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + nmsItemStack.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, customData -> customData.update(nmsCompoundTag -> nmsCompoundTag.put(key, NBTAdapter.toNMS(value)))); + return asBukkitCopy(nmsItemStack); + } + + // TODO: 1.20.6: this now needs to serialize components into NBT every single time, should probably only return custom NBT data with specialized methods for other usages + // TODO: 1.20.6: NBT structure is different basically everywhere, usages of this will need an update + @Override + public CompoundBinaryTag getNbtData(ItemStack itemStack) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + if (nmsItemStack != null && !nmsItemStack.isEmpty()) { + return NBTAdapter.toAPI(serializeNmsItem(nmsItemStack)); + } + return CompoundBinaryTag.empty(); + } + + // TODO: 1.20.6: same as getNbtData, ideally needs to only set custom NBT data and have specialized methods for other usages + @Override + public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) { + return asBukkitCopy(parseNmsItem(NBTAdapter.toNMS(compoundTag))); + } + + @Override + public CompoundBinaryTag getCustomData(ItemStack item) { + CustomData customData = CraftItemStack.asNMSCopy(item).get(DataComponents.CUSTOM_DATA); + return customData != null ? NBTAdapter.toAPI(customData.copyTag()) : null; + } + + @Override + public ItemStack setCustomData(ItemStack item, CompoundBinaryTag data) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + if (data == null) { + nmsItemStack.remove(DataComponents.CUSTOM_DATA); + } + else { + nmsItemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(NBTAdapter.toNMS(data))); + } + return asBukkitCopy(nmsItemStack); + } + + public static final int DATA_VERSION_1_20_4 = 3700; + + @Override + public ItemStack setPartialOldNbt(ItemStack item, CompoundBinaryTag oldTag) { + int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion(); + CompoundTag nmsOldTag = new CompoundTag(); + nmsOldTag.putString("id", item.getType().getKey().toString()); + nmsOldTag.putByte("Count", (byte) item.getAmount()); + nmsOldTag.put("tag", NBTAdapter.toNMS(oldTag)); + CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().getFixerUpper().update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, nmsOldTag), DATA_VERSION_1_20_4, currentDataVersion).getValue(); + CompoundTag nmsCurrentTag = serializeNmsItem(CraftItemStack.asNMSCopy(item)); + CompoundTag nmsMergedTag = nmsCurrentTag.merge(nmsUpdatedTag); + return asBukkitCopy(parseNmsItem(nmsMergedTag)); + } + + @Override + public CompoundBinaryTag getEntityData(ItemStack item) { + TypedEntityData> entityData = CraftItemStack.asNMSCopy(item).get(DataComponents.ENTITY_DATA); + return entityData != null ? NBTAdapter.toAPI(entityData.getUnsafe()) : null; + } + + @Override + public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + if (entityNbt == null || entityNbt.isEmpty() || (entityNbt.size() == 1 && entityNbt.contains("id"))) { + nmsItemStack.remove(DataComponents.ENTITY_DATA); + } + else { + CompoundTag nmsEntityNbt = NBTAdapter.toNMS(entityNbt); + nmsEntityNbt.remove("id"); + nmsItemStack.set(DataComponents.ENTITY_DATA, TypedEntityData.of(CraftEntityType.bukkitToMinecraft(entityType), nmsEntityNbt)); + } + return asBukkitCopy(nmsItemStack); + } + + @Override + public MapTag getRawComponentsPatch(ItemStack item, boolean excludeHandled) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + DataComponentPatch patch = nmsItemStack.getComponentsPatch(); + if (excludeHandled) { + patch = patch.forget(componentType -> { + Identifier componentId = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(componentType); + return ItemComponentsPatch.propertyHandledComponents.contains(componentId.toString()); + }); + } + if (patch.isEmpty()) { + return new MapTag(); + } + RegistryOps registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE); + CompoundTag nmsPatch = (CompoundTag) DataComponentPatch.CODEC.encodeStart(registryOps, patch).getOrThrow(); + if (excludeHandled && Denizen.supportsPaper) { + nmsPatch.keySet().removeIf(s -> s.charAt(0) == '!'); + if (nmsPatch.isEmpty()) { + return new MapTag(); + } + } + MapTag rawComponents = (MapTag) ItemRawNBT.nbtTagToObject(NBTAdapter.toAPI(nmsPatch)); + rawComponents.putObject(ItemComponentsPatch.DATA_VERSION_KEY, new ElementTag(CraftMagicNumbers.INSTANCE.getDataVersion())); + return rawComponents; + } + + @Override + public ItemStack setRawComponentsPatch(ItemStack item, MapTag rawComponentsMap, int dataVersion, Consumer errorHandler) { + int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion(); + CompoundBinaryTag rawComponents = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(rawComponentsMap, CoreUtilities.errorButNoDebugContext, ""); + CompoundTag nmsRawComponents = NBTAdapter.toNMS(rawComponents); + RegistryOps registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE); + if (dataVersion < currentDataVersion) { + CompoundTag legacyItemData = new CompoundTag(); + legacyItemData.putString("id", item.getType().getKey().toString()); + legacyItemData.putInt("count", item.getAmount()); + legacyItemData.put("components", nmsRawComponents); + CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().getFixerUpper().update(References.ITEM_STACK, new Dynamic<>(registryOps, legacyItemData), dataVersion, currentDataVersion).getValue(); + nmsRawComponents = nmsUpdatedTag.getCompound("components").orElseGet(CompoundTag::new); + } + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + DataComponentPatch.CODEC.parse(registryOps, nmsRawComponents) + .ifError(error -> errorHandler.accept(error.message())) + .ifSuccess(nmsItemStack::applyComponents); + return asBukkitCopy(nmsItemStack); + } + + public static final Field AdventureModePredicate_predicates = ReflectionHelper.getFields(AdventureModePredicate.class).get("predicates"); + + @Override + public List getCanPlaceOn(ItemStack item) { + return getAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON); + } + + @Override + public ItemStack setCanPlaceOn(ItemStack item, List canPlaceOn) { + return setAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON, canPlaceOn); + } + + @Override + public List getCanBreak(ItemStack item) { + return getAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK); + } + + @Override + public ItemStack setCanBreak(ItemStack item, List canBreak) { + return setAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK, canBreak); + } + + private List getAdventureModePredicateMaterials(ItemStack item, DataComponentType nmsComponent) { + AdventureModePredicate nmsAdventurePredicate = CraftItemStack.asNMSCopy(item).get(nmsComponent); + if (nmsAdventurePredicate == null) { + return null; + } + List nmsPredicates; + try { + nmsPredicates = (List) AdventureModePredicate_predicates.get(nmsAdventurePredicate); + } + catch (Throwable e) { + Debug.echoError(e); + return null; + } + List materials = new ArrayList<>(); + for (BlockPredicate nmsPredicate : nmsPredicates) { + nmsPredicate.blocks().ifPresent(nmsHolderSet -> { + for (Holder nmsHolder : nmsHolderSet) { + materials.add(CraftMagicNumbers.getMaterial(nmsHolder.value())); + } + }); + } + return materials; + } + + private ItemStack setAdventureModePredicateMaterials(ItemStack item, DataComponentType nmsComponent, List materials) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); + AdventureModePredicate nmsAdventurePredicate = nmsItemStack.get(nmsComponent); + if (materials == null) { + if (nmsAdventurePredicate == null) { + return item; + } + nmsItemStack.remove(nmsComponent); + return asBukkitCopy(nmsItemStack); + } + BlockPredicate nmsPredicate = new BlockPredicate(Optional.of( + HolderSet.direct(material -> BuiltInRegistries.BLOCK.get(CraftNamespacedKey.toMinecraft(material.getKey())).orElseThrow(), materials) + ), Optional.empty(), Optional.empty(), DataComponentMatchers.ANY); + nmsItemStack.set(nmsComponent, new AdventureModePredicate(List.of(nmsPredicate))); + return asBukkitCopy(nmsItemStack); + } + + @Override + public void setInventoryItem(Inventory inventory, ItemStack item, int slot) { + if (inventory instanceof CraftInventoryPlayer && ((CraftInventoryPlayer) inventory).getInventory().player == null) { + ((CraftInventoryPlayer) inventory).getInventory().setItem(slot, CraftItemStack.asNMSCopy(item)); + } + else { + inventory.setItem(slot, item); + } + } + + @Override + public String getDisplayName(ItemTag item) { + if (!item.getItemMeta().hasDisplayName()) { + return null; + } + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack()); + Component nmsDisplayName = nmsItemStack.get(DataComponents.CUSTOM_NAME); + return FormattedTextHelper.stringify(Handler.componentToSpigot(nmsDisplayName)); + } + + @Override + public List getLore(ItemTag item) { + if (!item.getItemMeta().hasLore()) { + return null; + } + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack()); + ItemLore nmsLore = nmsItemStack.get(DataComponents.LORE); + List outList = new ArrayList<>(nmsLore.lines().size()); + for (Component nmsLoreLine : nmsLore.lines()) { + outList.add(FormattedTextHelper.stringify(Handler.componentToSpigot(nmsLoreLine))); + } + return outList; + } + + @Override + public void setDisplayName(ItemTag item, String name) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack()); + if (name == null || name.isEmpty()) { + nmsItemStack.remove(DataComponents.CUSTOM_NAME); + } + else { + nmsItemStack.set(DataComponents.CUSTOM_NAME, Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE))); + } + item.setItemStack(asBukkitCopy(nmsItemStack)); + } + + @Override + public void setLore(ItemTag item, List lore) { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack()); + if (lore == null || lore.isEmpty()) { + nmsItemStack.remove(DataComponents.LORE); + } + else { + List nmsLore = new ArrayList<>(lore.size()); + for (String loreLine : lore) { + nmsLore.add(Handler.componentToNMS(FormattedTextHelper.parse(loreLine, ChatColor.WHITE))); + } + nmsItemStack.set(DataComponents.LORE, new ItemLore(nmsLore)); + } + item.setItemStack(asBukkitCopy(nmsItemStack)); + } + + /** + * Copied from MapItem.getCorrectStateForFluidBlock. + */ + public static BlockState getCorrectStateForFluidBlock(Level world, BlockState blockState, BlockPos blockPos) { + FluidState fluid = blockState.getFluidState(); + return !fluid.isEmpty() && !blockState.isFaceSturdy(world, blockPos, Direction.UP) ? fluid.createLegacyBlock() : blockState; + } + + /** + * Copied from MapItem.update, redesigned slightly to render totally rather than just relative to a player. + * Some variables manually renamed for readability. + */ + public static void renderFullMap(MapItemSavedData worldmap, int xMin, int zMin, int xMax, int zMax) { + Level world = ((CraftWorld) worldmap.mapView.getWorld()).getHandle(); + int scale = 1 << worldmap.scale; + int mapX = worldmap.centerX; + int mapZ = worldmap.centerZ; + for (int x = xMin; x < xMax; x++) { + double d0 = 0.0D; + for (int z = zMin; z < zMax; z++) { + int k2 = (mapX / scale + x - 64) * scale; + int l2 = (mapZ / scale + z - 64) * scale; + Multiset multiset = LinkedHashMultiset.create(); + LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2)); + if (!chunk.isEmpty()) { + ChunkPos chunkcoordintpair = chunk.getPos(); + int i3 = k2 & 15; + int j3 = l2 & 15; + int k3 = 0; + double d1 = 0.0D; + if (world.dimensionType().hasCeiling()) { + int l3 = k2 + l2 * 231871; + l3 = l3 * l3 * 31287121 + l3 * 11; + if ((l3 >> 20 & 1) == 0) { + multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(world, BlockPos.ZERO), 10); + } + else { + multiset.add(Blocks.STONE.defaultBlockState().getMapColor(world, BlockPos.ZERO), 100); + } + + d1 = 100.0D; + } + else { + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + BlockPos.MutableBlockPos blockposition_mutableblockposition1 = new BlockPos.MutableBlockPos(); + for (int i4 = 0; i4 < scale; ++i4) { + for (int j4 = 0; j4 < scale; ++j4) { + int k4 = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i4 + i3, j4 + j3) + 1; + BlockState iblockdata; + if (k4 <= world.getMinY() + 1) { + iblockdata = Blocks.BEDROCK.defaultBlockState(); + } + else { + do { + --k4; + blockposition_mutableblockposition.set(chunkcoordintpair.getMinBlockX() + i4 + i3, k4, chunkcoordintpair.getMinBlockZ() + j4 + j3); + iblockdata = chunk.getBlockState(blockposition_mutableblockposition); + } while (iblockdata.getMapColor(world, blockposition_mutableblockposition) == MapColor.NONE && k4 > world.getMinY()); + if (k4 > world.getMinY() && !iblockdata.getFluidState().isEmpty()) { + int l4 = k4 - 1; + blockposition_mutableblockposition1.set(blockposition_mutableblockposition); + + BlockState iblockdata1; + do { + blockposition_mutableblockposition1.setY(l4--); + iblockdata1 = chunk.getBlockState(blockposition_mutableblockposition1); + k3++; + } while (l4 > world.getMinY() && !iblockdata1.getFluidState().isEmpty()); + iblockdata = getCorrectStateForFluidBlock(world, iblockdata, blockposition_mutableblockposition); + } + } + worldmap.checkBanners(world, chunkcoordintpair.getMinBlockX() + i4 + i3, chunkcoordintpair.getMinBlockZ() + j4 + j3); + d1 += (double) k4 / (double) (scale * scale); + multiset.add(iblockdata.getMapColor(world, blockposition_mutableblockposition)); + } + } + } + k3 /= scale * scale; + double d2 = (d1 - d0) * 4.0D / (double) (scale + 4) + ((double) (x + z & 1) - 0.5D) * 0.4D; + byte b0 = 1; + if (d2 > 0.6D) { + b0 = 2; + } + if (d2 < -0.6D) { + b0 = 0; + } + MapColor materialmapcolor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MapColor.NONE); + if (materialmapcolor == MapColor.WATER) { + d2 = (double) k3 * 0.1D + (double) (x + z & 1) * 0.2D; + b0 = 1; + if (d2 < 0.5D) { + b0 = 2; + } + if (d2 > 0.9D) { + b0 = 0; + } + } + d0 = d1; + worldmap.updateColor(x, z, (byte) (materialmapcolor.id * 4 + b0)); + } + } + } + } + + @Override + public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) { + MapItemSavedData worldmap = ((CraftServer) Bukkit.getServer()).getServer().getLevel(net.minecraft.world.level.Level.OVERWORLD).getMapData(new MapId(mapId)); + if (worldmap == null) { + return false; + } + renderFullMap(worldmap, xMin, zMin, xMax, zMax); + return true; + } + + @Override + public BlockData getPlacedBlock(Material material) { + Item nmsItem = BuiltInRegistries.ITEM.getOptional(CraftNamespacedKey.toMinecraft(material.getKey())).orElse(null); + if (nmsItem instanceof BlockItem) { + Block block = ((BlockItem) nmsItem).getBlock(); + return CraftBlockData.fromData(block.defaultBlockState()); + } + return null; + } + + @Override + public boolean isValidMix(ItemStack input, ItemStack ingredient) { + net.minecraft.world.item.ItemStack nmsInput = CraftItemStack.asNMSCopy(input); + net.minecraft.world.item.ItemStack nmsIngredient = CraftItemStack.asNMSCopy(ingredient); + return MinecraftServer.getServer().potionBrewing().hasMix(nmsInput, nmsIngredient); + } + + public static Class PaperPotionMix_CLASS = null; + public static Map customBrewingRecipes = null; + + @Override + public Map getCustomBrewingRecipes() { + if (customBrewingRecipes == null) { + customBrewingRecipes = Maps.transformValues((Map) ReflectionHelper.getFieldValue(PotionBrewing.class, "customMixes", MinecraftServer.getServer().potionBrewing()), paperMix -> { + if (PaperPotionMix_CLASS == null) { + PaperPotionMix_CLASS = paperMix.getClass(); + } + RecipeChoice ingredient = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, "ingredient", paperMix)); + RecipeChoice input = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, "input", paperMix)); + ItemStack result = asBukkitCopy(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, "result", paperMix)); + return new BrewingRecipe(input, ingredient, result); + }); + } + return customBrewingRecipes; + } + + private RecipeChoice convertChoice(Predicate nmsPredicate) { + // Not an instance of net.minecraft.world.item.crafting.Ingredient = a predicate recipe choice + if (nmsPredicate instanceof Ingredient ingredient) { + return CraftRecipe.toBukkit(ingredient); + } + return PaperAPITools.instance.createPredicateRecipeChoice(item -> nmsPredicate.test(CraftItemStack.asNMSCopy(item))); + } + + @Override + public byte[] renderMap(MapView mapView, Player player) { + return ((CraftMapView) mapView).render((CraftPlayer) player).buffer; + } + + @Override + public int getFoodPoints(Material itemType) { + return CraftMagicNumbers.getItem(itemType).components().get(DataComponents.FOOD).nutrition(); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/NBTAdapter.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/NBTAdapter.java new file mode 100644 index 0000000000..41f35f52ac --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/NBTAdapter.java @@ -0,0 +1,143 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import net.kyori.adventure.nbt.*; +import net.minecraft.nbt.*; + +import java.lang.invoke.MethodHandle; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class NBTAdapter { + + public static final MethodHandle COMPOUND_TAG_MAP_CONSTRUCTOR = ReflectionHelper.getConstructor(CompoundTag.class, Map.class); + + public static Tag toNMS(BinaryTag tag) { + if (tag instanceof ByteBinaryTag byteTag) { + return switch (byteTag.value()) { + case 0 -> ByteTag.ZERO; + case 1 -> ByteTag.ONE; + default -> ByteTag.valueOf(byteTag.value()); + }; + } + else if (tag instanceof ShortBinaryTag shortTag) { + return ShortTag.valueOf(shortTag.value()); + } + else if (tag instanceof IntBinaryTag intTag) { + return IntTag.valueOf(intTag.value()); + } + else if (tag instanceof LongBinaryTag longTag) { + return LongTag.valueOf(longTag.value()); + } + else if (tag instanceof FloatBinaryTag floatTag) { + return FloatTag.valueOf(floatTag.value()); + } + else if (tag instanceof DoubleBinaryTag doubleTag) { + return DoubleTag.valueOf(doubleTag.value()); + } + else if (tag instanceof ByteArrayBinaryTag byteArrayTag) { + return new ByteArrayTag(byteArrayTag.value()); + } + else if (tag instanceof IntArrayBinaryTag intArrayTag) { + return new IntArrayTag(intArrayTag.value()); + } + else if (tag instanceof LongArrayBinaryTag longArrayTag) { + return new LongArrayTag(longArrayTag.value()); + } + else if (tag instanceof StringBinaryTag stringTag) { + return StringTag.valueOf(stringTag.value()); + } + else if (tag instanceof ListBinaryTag listTag) { + return toNMS(listTag); + } + else if (tag instanceof CompoundBinaryTag compoundTag) { + return toNMS(compoundTag); + } + else if (tag instanceof EndBinaryTag) { + return EndTag.INSTANCE; + } + throw new IllegalStateException("Unrecognized API tag of type '" + tag.type() + "': " + tag); + } + + public static BinaryTag toAPI(Tag nmsTag) { + if (nmsTag instanceof ByteTag nmsByteTag) { + return ByteBinaryTag.byteBinaryTag(nmsByteTag.value()); + } + else if (nmsTag instanceof ShortTag nmsShortTag) { + return ShortBinaryTag.shortBinaryTag(nmsShortTag.value()); + } + else if (nmsTag instanceof IntTag nmsIntTag) { + return IntBinaryTag.intBinaryTag(nmsIntTag.value()); + } + else if (nmsTag instanceof LongTag nmsLongTag) { + return LongBinaryTag.longBinaryTag(nmsLongTag.value()); + } + else if (nmsTag instanceof FloatTag nmsFloatTag) { + return FloatBinaryTag.floatBinaryTag(nmsFloatTag.value()); + } + else if (nmsTag instanceof DoubleTag nmsDoubleTag) { + return DoubleBinaryTag.doubleBinaryTag(nmsDoubleTag.value()); + } + else if (nmsTag instanceof ByteArrayTag nmsByteArrayTag) { + return ByteArrayBinaryTag.byteArrayBinaryTag(nmsByteArrayTag.getAsByteArray()); + } + else if (nmsTag instanceof IntArrayTag nmsIntArrayTag) { + return IntArrayBinaryTag.intArrayBinaryTag(nmsIntArrayTag.getAsIntArray()); + } + else if (nmsTag instanceof LongArrayTag nmsLongArrayTag) { + return LongArrayBinaryTag.longArrayBinaryTag(nmsLongArrayTag.getAsLongArray()); + } + else if (nmsTag instanceof StringTag nmsStringTag) { + return StringBinaryTag.stringBinaryTag(nmsStringTag.value()); + } + else if (nmsTag instanceof ListTag nmsListTag) { + return toAPI(nmsListTag); + } + else if (nmsTag instanceof CompoundTag nmsCompoundTag) { + return toAPI(nmsCompoundTag); + } + else if (nmsTag instanceof EndTag) { + return EndBinaryTag.endBinaryTag(); + } + throw new IllegalStateException("Unrecognized NMS tag of type '" + nmsTag.getClass().getName() + '/' + nmsTag.getType().getName() + "': " + nmsTag); + } + + public static ListBinaryTag toAPI(ListTag nmsListTag) { + ListBinaryTag.Builder builder = ListBinaryTag.heterogeneousListBinaryTag(nmsListTag.size()); + for (Tag nmsValue : nmsListTag) { + builder.add(toAPI(nmsValue)); + } + return builder.build(); + } + + public static ListTag toNMS(ListBinaryTag listTag) { + List nmsTags = new ArrayList<>(listTag.size()); + for (BinaryTag value : listTag) { + nmsTags.add(toNMS(value)); + } + return new ListTag(nmsTags); + } + + public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size()); + for (Map.Entry nmsEntry : nmsCompoundTag.entrySet()) { + builder.put(nmsEntry.getKey(), toAPI(nmsEntry.getValue())); + } + return builder.build(); + } + + public static CompoundTag toNMS(CompoundBinaryTag compoundTag) { + Map nmsTags = new HashMap<>(compoundTag.size()); + for (Map.Entry entry : compoundTag) { + nmsTags.put(entry.getKey(), toNMS(entry.getValue())); + } + try { + return (CompoundTag) COMPOUND_TAG_MAP_CONSTRUCTOR.invokeExact(nmsTags); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/PacketHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/PacketHelperImpl.java new file mode 100644 index 0000000000..78e80f13b4 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/PacketHelperImpl.java @@ -0,0 +1,434 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.interfaces.PacketHelper; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.impl.SidebarImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.scripts.commands.entity.TeleportCommand; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizen.utilities.Utilities; +import com.denizenscript.denizen.utilities.maps.MapImage; +import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper; +import com.denizenscript.denizencore.objects.core.ColorTag; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.BrandPayload; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.Relative; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.monster.Creeper; +import net.minecraft.world.entity.monster.EnderMan; +import net.minecraft.world.entity.monster.spider.CaveSpider; +import net.minecraft.world.entity.monster.spider.Spider; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Team; +import org.bukkit.Bukkit; +import org.bukkit.EntityEffect; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; +import org.bukkit.block.sign.SignSide; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.map.CraftMapCanvas; +import org.bukkit.craftbukkit.map.CraftMapView; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.map.MapCanvas; +import org.bukkit.map.MapPalette; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; + +public class PacketHelperImpl implements PacketHelper { + + public static final EntityDataAccessor PLAYER_DATA_ACCESSOR_ABSORPTION = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, "DATA_PLAYER_ABSORPTION_ID", null); + + public static final EntityDataAccessor ENTITY_DATA_ACCESSOR_FLAGS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, "DATA_SHARED_FLAGS_ID", null); + + public static final MethodHandle ABILITIES_PACKET_FOV_SETTER = ReflectionHelper.getFinalSetter(ClientboundPlayerAbilitiesPacket.class, "walkingSpeed"); + + public static final Field ENTITY_TRACKER_ENTRY_GETTER = ReflectionHelper.getFields(ChunkMap.TrackedEntity.class).getFirstOfType(ServerEntity.class); + + public static final MethodHandle CANVAS_GET_BUFFER = ReflectionHelper.getMethodHandle(CraftMapCanvas.class, "getBuffer"); + public static final Field MAPVIEW_WORLDMAP = ReflectionHelper.getFields(CraftMapView.class).get("worldMap"); + + public static final EntityDataAccessor> ENTITY_DATA_ACCESSOR_CUSTOM_NAME = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, "DATA_CUSTOM_NAME", null); + public static final EntityDataAccessor ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, "DATA_CUSTOM_NAME_VISIBLE", null); + + @Override + public void setFakeAbsorption(Player player, float value) { + send(player, new ClientboundSetEntityDataPacket(player.getEntityId(), List.of(createEntityData(PLAYER_DATA_ACCESSOR_ABSORPTION, value)))); + } + + @Override + public void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly) { + AbstractContainerMenu menu = ((CraftPlayer) player).getHandle().containerMenu; + int windowId = playerOnly ? 0 : menu.containerId; + send(player, new ClientboundContainerSetSlotPacket(windowId, menu.incrementStateId(), slot, CraftItemStack.asNMSCopy(itemStack))); + } + + @Override + public void setFieldOfView(Player player, float fov) { + ClientboundPlayerAbilitiesPacket packet = new ClientboundPlayerAbilitiesPacket(((CraftPlayer) player).getHandle().getAbilities()); + if (!Float.isNaN(fov)) { + try { + ABILITIES_PACKET_FOV_SETTER.invoke(packet, fov); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + send(player, packet); + } + + @Override + public void respawn(Player player) { + ((CraftPlayer) player).getHandle().connection.handleClientCommand(new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN)); + } + + @Override + public void setVision(Player player, EntityType entityType) { + final net.minecraft.world.entity.LivingEntity entity; + if (entityType == EntityType.CREEPER) { + entity = new Creeper(EntityTypes.CREEPER, ((CraftWorld) player.getWorld()).getHandle()); + } + else if (entityType == EntityType.SPIDER) { + entity = new Spider(EntityTypes.SPIDER, ((CraftWorld) player.getWorld()).getHandle()); + } + else if (entityType == EntityType.CAVE_SPIDER) { + entity = new CaveSpider(EntityTypes.CAVE_SPIDER, ((CraftWorld) player.getWorld()).getHandle()); + } + else if (entityType == EntityType.ENDERMAN) { + entity = new EnderMan(EntityTypes.ENDERMAN, ((CraftWorld) player.getWorld()).getHandle()); + } + else { + return; + } + + // Spectating an entity then immediately respawning the player prevents a client shader update, + // allowing the player to retain whatever vision the mob they spectated had. + send(player, new ClientboundAddEntityPacket(entity, 0, BlockPos.ZERO)); + send(player, new ClientboundSetCameraPacket(entity)); + NMSHandler.playerHelper.refreshPlayer(player); + send(player, new ClientboundRemoveEntitiesPacket(entity.getId())); + } + + @Override + public void showBlockAction(Player player, Location location, int action, int state) { + BlockPos position = Handler.toBlockPos(location); + Block block = ((CraftWorld) location.getWorld()).getHandle().getBlockState(position).getBlock(); + send(player, new ClientboundBlockEventPacket(position, block, action, state)); + } + + @Override + public void showTabListHeaderFooter(Player player, String header, String footer) { + Component cHeader = Handler.componentToNMS(FormattedTextHelper.parse(header, ChatColor.WHITE)); + Component cFooter = Handler.componentToNMS(FormattedTextHelper.parse(footer, ChatColor.WHITE)); + send(player, new ClientboundTabListPacket(cHeader, cFooter)); + } + + @Override + public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { + send(player, new ClientboundBundlePacket(List.of( + new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks), + new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))), + new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE))) + ))); + } + + @Override + public void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth) { + AttributeInstance attr = new AttributeInstance(Attributes.MAX_HEALTH, (a) -> {}); + attr.setBaseValue(maxHealth); + send(player, new ClientboundUpdateAttributesPacket(mob.getEntityId(), List.of(attr))); + send(player, new ClientboundSetEntityDataPacket(mob.getEntityId(), List.of(createEntityData(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) health)))); + } + + @Override + public void showSignEditor(Player player, Location location) { // TODO: MC 1.18: once 1.18 is removed, remove 'location' arg + NetworkInterceptHelper.enable(); + Sign sign = null; + BlockPos toOpen = null; + // It actually allows 8 blocks of distance, but we limit to 7 because the client doesn't properly round down + for (int i = 0; i < 8; i++) { + Location toCheck = player.getLocation(); + toCheck.setY(toCheck.getY() - i); + if (toCheck.getBlock().getState() instanceof Sign foundSign) { + sign = foundSign; + } + else { + sign = null; + toOpen = Handler.toBlockPos(toCheck); + break; + } + } + if (sign != null) { + toOpen = Handler.toBlockPos(sign.getLocation()); + SignSide front = sign.getSide(Side.FRONT); + for (int line = 0; line < 4; line++) { + front.setLine(line, ""); + } + player.sendBlockUpdate(sign.getLocation(), sign); + } + else { + player.sendBlockChange(new LocationTag(player.getWorld(), toOpen.getX(), toOpen.getY(), toOpen.getZ()), Material.OAK_WALL_SIGN.createBlockData()); + } + DenizenNetworkManagerImpl.getNetworkManager(player).packetListener.fakeSignExpected = toOpen; + send(player, new ClientboundOpenSignEditorPacket(toOpen, true)); + } + + @Override + public void forceSpectate(Player player, Entity entity) { + send(player, new ClientboundSetCameraPacket(((CraftEntity) entity).getHandle())); + } + + public static void forceRespawnPlayerEntity(Entity entity, Player viewer) { + ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap; + ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId()); + if (entityTracker != null) { + try { + ServerEntity entry = (ServerEntity) ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker); + if (entry != null) { + entry.removePairing(((CraftPlayer) viewer).getHandle()); + entry.addPairing(((CraftPlayer) viewer).getHandle()); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + } + + @Override + public void sendRename(Player player, Entity entity, String name, boolean listMode) { + try { + if (entity.getType() == EntityType.PLAYER) { + if (listMode) { + send(player, new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ((CraftPlayer) player).getHandle())); + } + else { + // For player entities, force a respawn packet and let the dynamic intercept correct the details + forceRespawnPlayerEntity(entity, player); + } + return; + } + List> list = List.of( + createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)))), + createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true) + ); + send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), list)); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static HashMap> noCollideTeamMap = new HashMap<>(); + + @Override + public void generateNoCollideTeam(Player player, UUID noCollide) { + removeNoCollideTeam(player, noCollide); + PlayerTeam team = new PlayerTeam(SidebarImpl.dummyScoreboard, Utilities.generateRandomColors(8)); + team.getPlayers().add(noCollide.toString()); + team.setCollisionRule(Team.CollisionRule.NEVER); + HashMap map = noCollideTeamMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()); + map.put(noCollide, team); + send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); + } + + @Override + public void removeNoCollideTeam(Player player, UUID noCollide) { + if (noCollide == null || !player.isOnline()) { + noCollideTeamMap.remove(player.getUniqueId()); + return; + } + HashMap map = noCollideTeamMap.get(player.getUniqueId()); + if (map == null) { + return; + } + PlayerTeam team = map.remove(noCollide); + if (team != null) { + send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team)); + } + if (map.isEmpty()) { + noCollideTeamMap.remove(player.getUniqueId()); + } + } + + @Override + public void sendEntityMetadataFlagsUpdate(Player player, Entity entity) { + byte flags = ((CraftEntity) entity).getHandle().getEntityData().get(ENTITY_DATA_ACCESSOR_FLAGS); + send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), List.of(createEntityData(ENTITY_DATA_ACCESSOR_FLAGS, flags)))); + } + + @Override + public void sendEntityEffect(Player player, Entity entity, EntityEffect effect) { + send(player, new ClientboundEntityEventPacket(((CraftEntity) entity).getHandle(), effect.getData())); + } + + @Override + public int getPacketStats(Player player, boolean sent) { + DenizenNetworkManagerImpl netMan = DenizenNetworkManagerImpl.getNetworkManager(player); + return sent ? netMan.packetsSent : netMan.packetsReceived; + } + + @Override + public void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) { + if (x > 127 || y > 127) { + return; + } + int width = Math.min(image.width, 128 - x), + height = Math.min(image.height, 128 - y); + if (x + width <= 0 || y + height <= 0) { + return; + } + try { + boolean anyChanged = false; + byte[] buffer = (byte[]) CANVAS_GET_BUFFER.invoke(canvas); + for (int x2 = x < 0 ? -x : 0; x2 < width; ++x2) { + for (int y2 = y < 0 ? -y : 0; y2 < height; ++y2) { + byte p = bytes[y2 * image.width + x2]; + if (p != MapPalette.TRANSPARENT) { + int index = (y2 + y) * 128 + (x2 + x); + if (buffer[index] != p) { + buffer[index] = p; + anyChanged = true; + } + } + } + } + if (anyChanged) { + // Flag the whole image as dirty + MapItemSavedData map = (MapItemSavedData) MAPVIEW_WORLDMAP.get(canvas.getMapView()); + map.setColorsDirty(Math.max(x, 0), Math.max(y, 0)); + map.setColorsDirty(width + x - 1, height + y - 1); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void setNetworkManagerFor(Player player) { + DenizenNetworkManagerImpl.setNetworkManager(player); + } + + @Override + public void enableNetworkManager() { + DenizenNetworkManagerImpl.enableNetworkManager(); + } + + @Override + public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) { + BlockPos nmsPos = Handler.toBlockPos(location); + LocationTag displayPos = !name.isEmpty() ? LocationTag.valueOf(name, CoreUtilities.noDebugContext) : null; + send(player, new ClientboundGameTestHighlightPosPacket(nmsPos, displayPos != null ? Handler.toBlockPos(displayPos) : nmsPos)); + } + + @Override + public void clearDebugTestMarker(Player player) { + } + + @Override + public void sendBrand(Player player, String brand) { + BrandPayload payload = new BrandPayload(brand); + send(player, new ClientboundCustomPayloadPacket(payload)); + } + + @Override + public void sendCollectItemEntity(Player player, Entity taker, Entity item, int amount) { + send(player, new ClientboundTakeItemEntityPacket(item.getEntityId(), taker.getEntityId(), amount)); + } + + public Relative toNmsRelativeMovement(TeleportCommand.Relative relative) { + // TODO: 1.21.3: There seem to be more relative movement types now + return switch (relative) { + case X -> Relative.X; + case Y -> Relative.Y; + case Z -> Relative.Z; + case YAW -> Relative.Y_ROT; + case PITCH -> Relative.X_ROT; + }; + } + + @Override + public void sendRelativePositionPacket(Player player, double x, double y, double z, float yaw, float pitch, List relativeAxis) { + Set relativeMovements; + if (relativeAxis == null) { + relativeMovements = Relative.ALL; + } + else { + relativeMovements = EnumSet.noneOf(Relative.class); + for (TeleportCommand.Relative relative : relativeAxis) { + relativeMovements.add(toNmsRelativeMovement(relative)); + } + } + ClientboundPlayerPositionPacket packet = new ClientboundPlayerPositionPacket(0, new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), relativeMovements); + sendAsyncSafe(player, packet); + } + + @Override + public void sendRelativeLookPacket(Player player, float yaw, float pitch) { + sendRelativePositionPacket(player, 0, 0, 0, yaw, pitch, null); + } + + @Override + public void sendEntityDataPacket(List players, Entity entity, List data) { + ClientboundSetEntityDataPacket setEntityDataPacket = new ClientboundSetEntityDataPacket(entity.getEntityId(), (List>) (Object) data); + Iterator playerIterator = players.iterator(); + while (playerIterator.hasNext()) { + Player player = playerIterator.next(); + if (!DenizenNetworkManagerImpl.getConnection(player).isConnected()) { + playerIterator.remove(); + continue; + } + sendAsyncSafe(player, setEntityDataPacket); + } + } + + public static void send(Player player, Packet packet) { + ((CraftPlayer) player).getHandle().connection.send(packet); + } + + public static void broadcast(Packet packet) { + ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(packet); + } + + public static void sendAsyncSafe(Player player, Packet packet) { + DenizenNetworkManagerImpl.getConnection(player).channel.writeAndFlush(packet); + } + + public static SynchedEntityData.DataValue createEntityData(EntityDataAccessor accessor, T value) { + return new SynchedEntityData.DataValue<>(accessor.id(), accessor.serializer(), value); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/PlayerHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/PlayerHelperImpl.java new file mode 100644 index 0000000000..5f86a70362 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/PlayerHelperImpl.java @@ -0,0 +1,508 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer; +import com.denizenscript.denizen.nms.enums.CustomEntityType; +import com.denizenscript.denizen.nms.interfaces.PlayerHelper; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.impl.ImprovedOfflinePlayerImpl; +import com.denizenscript.denizen.nms.v26_2.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_2.impl.entities.CraftFakePlayerImpl; +import com.denizenscript.denizen.nms.v26_2.impl.entities.EntityItemProjectileImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.AbstractListenerPlayInImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.EntityTag; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizen.utilities.entity.DenizenEntityType; +import com.denizenscript.denizen.utilities.entity.FakeEntity; +import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import it.unimi.dsi.fastutil.ints.IntList; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket; +import net.minecraft.network.protocol.game.*; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.NameAndId; +import net.minecraft.server.players.PlayerList; +import net.minecraft.server.players.ServerOpList; +import net.minecraft.server.players.ServerOpListEntry; +import net.minecraft.stats.ServerRecipeBook; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagNetworkSerialization; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Avatar; +import net.minecraft.world.entity.Leashable; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.item.ItemCooldowns; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.LevelData; +import net.minecraft.world.phys.AABB; +import org.bukkit.*; +import org.bukkit.boss.BossBar; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.boss.CraftBossBar; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.Predicate; + +public class PlayerHelperImpl extends PlayerHelper { + + public static final Field ATTACK_COOLDOWN_TICKS = ReflectionHelper.getFields(LivingEntity.class).get("attackStrengthTicker", int.class); + + public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("aboveGroundTickCount", int.class); + public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("aboveGroundVehicleTickCount", int.class); + public static final Field PASSENGERS_PACKET_PASSENGERS = ReflectionHelper.getFields(ClientboundSetPassengersPacket.class).get("passengers", int[].class); + public static final MethodHandle PLAYER_RESPAWNCONFIG_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, "respawnConfig", ServerPlayer.RespawnConfig.class); + public static final MethodHandle SERVER_RECIPE_BOOK_ADD_HIGHLIGHT = ReflectionHelper.getMethodHandle(ServerRecipeBook.class, "addHighlight", ResourceKey.class); + + @Override + public void stopSound(Player player, NamespacedKey sound, SoundCategory category) { + ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(sound == null ? null : CraftNamespacedKey.toMinecraft(sound), null)); + } + + @Override + public void deTrackEntity(Player player, Entity entity) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + ChunkMap.TrackedEntity tracker = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); + if (tracker == null) { + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Failed to de-track entity " + entity.getEntityId() + " for " + player.getName() + ": tracker null"); + } + return; + } + sendEntityDestroy(player, entity); + tracker.removePlayer(nmsPlayer); + } + + public record TrackerData(PlayerTag player, ServerEntity tracker) {} + + @Override + public void addFakePassenger(List players, Entity vehicle, FakeEntity fakePassenger) { + ClientboundSetPassengersPacket packet = new ClientboundSetPassengersPacket(((CraftEntity) vehicle).getHandle()); + int[] newPassengers = Arrays.copyOf(packet.getPassengers(), packet.getPassengers().length + 1); + newPassengers[packet.getPassengers().length] = fakePassenger.id; + try { + PASSENGERS_PACKET_PASSENGERS.set(packet, newPassengers); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + for (PlayerTag player : players) { + PacketHelperImpl.send(player.getPlayerEntity(), packet); + } + } + + public record FakeEntitySynchronizer(ServerGamePacketListenerImpl target) implements ServerEntity.Synchronizer { + + @Override + public void sendToTrackingPlayers(Packet packet) { + target.send(packet); + } + + @Override + public void sendToTrackingPlayersAndSelf(Packet packet) { + sendToTrackingPlayers(packet); + } + + @Override + public void sendToTrackingPlayersFiltered(Packet packet, Predicate predicate) { + if (predicate.test(target.getPlayer())) { + sendToTrackingPlayers(packet); + } + } + + @Override + public void sendToTrackingPlayersFilteredAndSelf(Packet packet, Predicate predicate) { + sendToTrackingPlayersFiltered(packet, predicate); + } + } + + @Override + public FakeEntity sendEntitySpawn(List players, DenizenEntityType entityType, LocationTag location, ArrayList mechanisms, int customId, UUID customUUID, boolean autoTrack) { + CraftWorld world = ((CraftWorld) location.getWorld()); + net.minecraft.world.entity.Entity nmsEntity; + if (entityType.isCustom()) { + if (entityType.customEntityType == CustomEntityType.ITEM_PROJECTILE) { + ItemStack itemStack = new ItemStack(Material.STONE); + for (Mechanism mechanism : mechanisms) { + if (mechanism.matches("item") && mechanism.requireObject(ItemTag.class)) { + itemStack = mechanism.valueAsType(ItemTag.class).getItemStack(); + } + } + nmsEntity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack)); + } + else if (entityType.customEntityType == CustomEntityType.FAKE_PLAYER) { + String name = null; + String skin = null; + String blob = null; + for (Mechanism mechanism : new ArrayList<>(mechanisms)) { + if (mechanism.matches("name")) { + name = mechanism.getValue().asString(); + mechanisms.remove(mechanism); + } + else if (mechanism.matches("skin")) { + skin = mechanism.getValue().asString(); + mechanisms.remove(mechanism); + } + else if (mechanism.matches("skin_blob")) { + blob = mechanism.getValue().asString(); + mechanisms.remove(mechanism); + } + if (name != null && (skin != null || blob != null)) { + break; + } + } + nmsEntity = ((CraftFakePlayerImpl) NMSHandler.customEntityHelper.spawnFakePlayer(location, name, skin, blob, false)).getHandle(); + } + else { + throw new IllegalArgumentException("entityType"); + } + } + else { + org.bukkit.entity.Entity entity = world.createEntity(location, entityType.getBukkitEntityType().getEntityClass()); + nmsEntity = ((CraftEntity) entity).getHandle(); + } + if (customUUID != null) { + nmsEntity.setId(customId); + nmsEntity.setUUID(customUUID); + } + EntityTag entity = new EntityTag(nmsEntity.getBukkitEntity()); + entity.isFake = true; + entity.isFakeValid = true; + for (Mechanism mechanism : mechanisms) { + entity.safeAdjustDuplicate(mechanism); + } + nmsEntity.unsetRemoved(); + FakeEntity fake = new FakeEntity(players, location, entity.getBukkitEntity().getEntityId()); + fake.entity = new EntityTag(entity.getBukkitEntity()); + fake.entity.isFake = true; + fake.entity.isFakeValid = true; + List trackers = new ArrayList<>(); + fake.triggerSpawnPacket = (player) -> { + ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle(); + ServerGamePacketListenerImpl conn = nmsPlayer.connection; + final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, new FakeEntitySynchronizer(conn), Set.of(conn)); + tracker.addPairing(nmsPlayer); + final TrackerData data = new TrackerData(player, tracker); + trackers.add(data); + if (autoTrack) { + new BukkitRunnable() { + boolean wasOnline = true; + @Override + public void run() { + if (!fake.entity.isFakeValid) { + cancel(); + return; + } + if (player.isOnline()) { + if (!wasOnline) { + tracker.addPairing(((CraftPlayer) player.getPlayerEntity()).getHandle()); + wasOnline = true; + } + tracker.sendChanges(); + } + else if (wasOnline) { + wasOnline = false; + } + } + }.runTaskTimer(Denizen.getInstance(), 1, 1); + } + }; + for (PlayerTag player : players) { + fake.triggerSpawnPacket.accept(player); + } + fake.triggerUpdatePacket = () -> { + for (TrackerData tracker : trackers) { + if (tracker.player.isOnline()) { + tracker.tracker.sendChanges(); + } + } + }; + fake.triggerDestroyPacket = () -> { + for (TrackerData tracker : trackers) { + if (tracker.player.isOnline()) { + tracker.tracker.removePairing(((CraftPlayer) tracker.player.getPlayerEntity()).getHandle()); + } + } + trackers.clear(); + }; + return fake; + } + + @Override + public void sendEntityDestroy(Player player, Entity entity) { + ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId())); + } + + @Override + public int getFlyKickCooldown(Player player) { + ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection; + if (conn instanceof AbstractListenerPlayInImpl denizenListener) { + conn = denizenListener.oldListener; + } + try { + return Math.max(80 - Math.max(FLY_TICKS.getInt(conn), VEHICLE_FLY_TICKS.getInt(conn)), 0); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + return 80; + } + + @Override + public void setFlyKickCooldown(Player player, int ticks) { + ticks = 80 - ticks; + ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection; + if (conn instanceof AbstractListenerPlayInImpl denizenListener) { + conn = denizenListener.oldListener; + } + try { + FLY_TICKS.setInt(conn, ticks); + VEHICLE_FLY_TICKS.setInt(conn, ticks); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + } + + @Override + public int ticksPassedDuringCooldown(Player player) { + try { + return ATTACK_COOLDOWN_TICKS.getInt(((CraftPlayer) player).getHandle()); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + return -1; + } + + @Override + public float getMaxAttackCooldownTicks(Player player) { + return ((CraftPlayer) player).getHandle().getCurrentItemAttackStrengthDelay() + 3; + } + + @Override + public void setAttackCooldown(Player player, int ticks) { + try { + ATTACK_COOLDOWN_TICKS.setInt(((CraftPlayer) player).getHandle(), ticks); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + + } + + @Override + public boolean hasChunkLoaded(Player player, Chunk chunk) { + return ((CraftWorld) chunk.getWorld()).getHandle().getChunkSource().chunkMap + .getPlayers(new ChunkPos(chunk.getX(), chunk.getZ()), false).stream() + .anyMatch(entityPlayer -> entityPlayer.getUUID().equals(player.getUniqueId())); + } + + @Override + public void setTemporaryOp(Player player, boolean op) { + MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); + NameAndId nameAndId = new NameAndId(((CraftPlayer) player).getProfile()); + ServerOpList opList = server.getPlayerList().getOps(); + if (op) { + opList.add(new ServerOpListEntry(nameAndId, server.operatorUserPermissions(), opList.canBypassPlayerLimit(nameAndId))); + } + else { + opList.remove(nameAndId); + } + player.recalculatePermissions(); + } + + @Override + public void showEndCredits(Player player) { + ((CraftPlayer) player).getHandle().wonGame = true; + ((CraftPlayer) player).getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1f)); + } + + @Override + public ImprovedOfflinePlayer getOfflineData(UUID uuid) { + return new ImprovedOfflinePlayerImpl(uuid); + } + + @Override + public void resendRecipeDetails(Player player) { + RecipeManager recipeManager = ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager(); + ClientboundUpdateRecipesPacket updatePacket = new ClientboundUpdateRecipesPacket(recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes()); + ((CraftPlayer) player).getHandle().connection.send(updatePacket); + } + + @Override + public void resendDiscoveredRecipes(Player player) { + ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook(); + recipeBook.sendInitialRecipeBook(((CraftPlayer) player).getHandle()); + } + + @Override + public void quietlyAddRecipe(Player player, NamespacedKey key) { + ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook(); + RecipeHolder recipe = ItemHelperImpl.getNMSRecipe(key); + if (recipe == null) { + Debug.echoError("Cannot add recipe '" + key + "': it does not exist."); + return; + } + recipeBook.add(recipe.id()); + try { + SERVER_RECIPE_BOOK_ADD_HIGHLIGHT.invoke(recipeBook, recipe.id()); + } + catch (Throwable e) { + Debug.echoError(e); + } + } + + @Override + public byte getSkinLayers(Player player) { + return ((CraftPlayer) player).getHandle().getEntityData().get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION); + } + + @Override + public void setSkinLayers(Player player, byte flags) { + ((CraftPlayer) player).getHandle().getEntityData().set(Avatar.DATA_PLAYER_MODE_CUSTOMISATION, flags); + } + + @Override + public void setBossBarTitle(BossBar bar, String title) { + ((CraftBossBar) bar).getHandle().name = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE)); + ((CraftBossBar) bar).getHandle().broadcast(ClientboundBossEventPacket::createUpdateNamePacket); + } + + @Override + public boolean getSpawnForced(Player player) { + return ((CraftPlayer) player).getHandle().getRespawnConfig().forced(); + } + + @Override + public void setSpawnForced(Player player, boolean forced) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + try { + ServerPlayer.RespawnConfig config = nmsPlayer.getRespawnConfig(); + config = new ServerPlayer.RespawnConfig(config.respawnData(), forced); + PLAYER_RESPAWNCONFIG_SETTER.invoke(nmsPlayer, config); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public Location getBedSpawnLocation(Player player) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + ServerPlayer.RespawnConfig nmsRespawnConfig = nmsPlayer.getRespawnConfig(); + if (nmsRespawnConfig == null) { + return null; + } + LevelData.RespawnData nmsRespawnData = nmsRespawnConfig.respawnData(); + Level nmsWorld = MinecraftServer.getServer().getLevel(nmsRespawnData.dimension()); + return nmsWorld != null ? new Location(nmsWorld.getWorld(), nmsRespawnData.pos().getX(), nmsRespawnData.pos().getY(), nmsRespawnData.pos().getZ(), nmsRespawnData.yaw(), nmsRespawnData.pitch()) : null; + } + + @Override + public long getLastActionTime(Player player) { + return ((CraftPlayer) player).getHandle().getLastActionTime(); + } + + @Override + public void sendPlayerInfoAddPacket(Player player, EnumSet editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) { + EnumSet actions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class); + for (ProfileEditMode editMode : editModes) { + actions.add(switch (editMode) { + case ADD -> ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER; + case UPDATE_DISPLAY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME; + case UPDATE_LATENCY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY; + case UPDATE_GAME_MODE -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE; + case UPDATE_LISTED -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED; + }); + } + GameProfile profile = ProfileEditorImpl.createGameProfile(id, name, texture, signature); + // TODO: 1.21.3: Player list order and hat visibility support + ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), true, player.getPlayerListOrder(), null); + PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(actions, List.of(entry))); + } + + @Override + public void sendPlayerInfoRemovePacket(Player player, UUID id) { + PacketHelperImpl.send(player, new ClientboundPlayerInfoRemovePacket(List.of(id))); + } + + @Override + public void sendClimbableMaterials(Player player, List materials) { + Map>, TagNetworkSerialization.NetworkPayload> packetInput = TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries()); + Map tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, "tags", packetInput.get(BuiltInRegistries.BLOCK.key())); + IntList climbableBlocks = tags.get(BlockTags.CLIMBABLE.location()); + climbableBlocks.clear(); + for (Material material : materials) { + climbableBlocks.add(BuiltInRegistries.BLOCK.getId(CraftMagicNumbers.getBlock(material))); + } + PacketHelperImpl.send(player, new ClientboundUpdateTagsPacket(packetInput)); + } + + @Override + public void refreshPlayer(Player player) { + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + ServerLevel nmsWorld = nmsPlayer.level(); + nmsPlayer.connection.send(new ClientboundRespawnPacket(nmsPlayer.createCommonSpawnInfo(nmsWorld), ClientboundRespawnPacket.KEEP_ALL_DATA)); + nmsPlayer.connection.internalTeleport(PositionMoveRotation.of(nmsPlayer), Set.of()); + if (nmsPlayer.isPassenger()) { + nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer.getVehicle())); + } + if (nmsPlayer.isVehicle()) { + nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer)); + } + AABB boundingBox = AABB.ofSize(nmsPlayer.getBoundingBox().getCenter(), 32, 32, 32); + for (net.minecraft.world.entity.Entity nmsEntity : nmsWorld.getEntitiesOfClass(net.minecraft.world.entity.Entity.class, boundingBox, nmsEntity -> nmsEntity instanceof Leashable nmsLeashable && nmsPlayer.equals(nmsLeashable.getLeashHolder()))) { + nmsPlayer.connection.send(new ClientboundSetEntityLinkPacket(nmsEntity, nmsPlayer)); + } + if (!nmsPlayer.getCooldowns().cooldowns.isEmpty()) { + int tickCount = nmsPlayer.getCooldowns().tickCount; + for (Map.Entry entry : nmsPlayer.getCooldowns().cooldowns.entrySet()) { + nmsPlayer.connection.send(new ClientboundCooldownPacket(entry.getKey(), entry.getValue().endTime - tickCount)); + } + } + nmsPlayer.connection.send(new ClientboundSetExperiencePacket(nmsPlayer.experienceProgress, nmsPlayer.totalExperience, nmsPlayer.experienceLevel)); + for (MobEffectInstance nmsEffect : nmsPlayer.getActiveEffects()) { + nmsPlayer.connection.send(new ClientboundUpdateMobEffectPacket(nmsPlayer.getId(), nmsEffect, false)); + } + nmsPlayer.onUpdateAbilities(); + PlayerList nmsPlayerList = MinecraftServer.getServer().getPlayerList(); + nmsPlayerList.sendPlayerPermissionLevel(nmsPlayer); + nmsPlayerList.sendLevelInfo(nmsPlayer, nmsWorld); + nmsPlayerList.sendAllPlayerInfo(nmsPlayer); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/WorldHelperImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/WorldHelperImpl.java new file mode 100644 index 0000000000..9cf7b262d9 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/helpers/WorldHelperImpl.java @@ -0,0 +1,103 @@ +package com.denizenscript.denizen.nms.v26_2.helpers; + +import com.denizenscript.denizen.nms.interfaces.WorldHelper; +import com.denizenscript.denizen.nms.v26_2.impl.BiomeNMSImpl; +import com.denizenscript.denizen.objects.BiomeTag; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.players.SleepStatus; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.storage.PrimaryLevelData; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.event.world.TimeSkipEvent; + +public class WorldHelperImpl implements WorldHelper { + + @Override + public boolean isStatic(World world) { + return ((CraftWorld) world).getHandle().isClientSide(); + } + + @Override + public void setStatic(World world, boolean isStatic) { + ServerLevel worldServer = ((CraftWorld) world).getHandle(); + ReflectionHelper.setFieldValue(net.minecraft.world.level.Level.class, "isClientSide", worldServer, isStatic); + } + + @Override + public float getLocalDifficulty(Location location) { + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + DifficultyInstance scaler = ((CraftWorld) location.getWorld()).getHandle().getCurrentDifficultyAt(pos); + return scaler.getEffectiveDifficulty(); + } + + @Override + public Location getNearestBiomeLocation(Location start, BiomeTag biome) { + Pair> result = ((CraftWorld) start.getWorld()).getHandle() + .findClosestBiome3d(b -> b.is(((BiomeNMSImpl) biome.getBiome()).biomeHolder.unwrapKey().get()), new BlockPos(start.getBlockX(), start.getBlockY(), start.getBlockZ()), 6400, 32, 64); + if (result == null || result.getFirst() == null) { + return null; + } + return new Location(start.getWorld(), result.getFirst().getX(), result.getFirst().getY(), result.getFirst().getZ()); + } + + @Override + public boolean areEnoughSleeping(World world, int percentage) { + SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, "sleepStatus", ((CraftWorld) world).getHandle()); + return status.areEnoughSleeping(percentage); + } + + @Override + public boolean areEnoughDeepSleeping(World world, int percentage) { + ServerLevel level = ((CraftWorld) world).getHandle(); + SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, "sleepStatus", level); + return status.areEnoughDeepSleeping(percentage, level.players()); + } + + @Override + public int getSkyDarken(World world) { + return ((CraftWorld) world).getHandle().getSkyDarken(); + } + + @Override + public boolean isDay(World world) { + return ((CraftWorld) world).getHandle().isBrightOutside(); + } + + @Override + public boolean isNight(World world) { + return ((CraftWorld) world).getHandle().isDarkOutside(); + } + + @Override + public void setDayTime(World world, long time) { + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + nmsWorld.dimensionType().defaultClock().ifPresent((clock) -> nmsWorld.clockManager().setTotalTicks(clock, time, TimeSkipEvent.SkipReason.CUSTOM)); + } + + @Override + public void setGameTime(World world, long time) { + ((PrimaryLevelData) ((CraftWorld) world).getHandle().levelData).setGameTime(time); + } + + // net.minecraft.server.level.ServerLevel#wakeUpAllPlayers() + @Override + public void wakeUpAllPlayers(World world) { + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, "sleepStatus", nmsWorld); + status.removeAllSleepers(); + nmsWorld.getPlayers(LivingEntity::isSleeping).forEach((player) -> player.stopSleepInBed(false, false)); + } + + @Override + public void clearWeather(World world) { + ((CraftWorld) world).getHandle().resetWeatherCycle(); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/BiomeNMSImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/BiomeNMSImpl.java new file mode 100644 index 0000000000..c08d4c03f4 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/BiomeNMSImpl.java @@ -0,0 +1,241 @@ +package com.denizenscript.denizen.nms.v26_2.impl; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.BiomeNMS; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.RegistrationInfo; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.random.Weighted; +import net.minecraft.util.random.WeightedList; +import net.minecraft.world.attribute.EnvironmentAttribute; +import net.minecraft.world.attribute.EnvironmentAttributeMap; +import net.minecraft.world.attribute.EnvironmentAttributes; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; +import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftEntityType; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.EntityType; + +import java.lang.invoke.MethodHandle; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class BiomeNMSImpl extends BiomeNMS { + + public static final MethodHandle BIOME_CLIMATESETTINGS_CONSTRUCTOR = ReflectionHelper.getConstructor(Biome.ClimateSettings.class, boolean.class, float.class, Biome.TemperatureModifier.class, float.class); + public static final MethodHandle MAPPED_REGISTRY_REGISTRATION_INFOS = ReflectionHelper.getFields(MappedRegistry.class).getGetter("registrationInfos"); + public static final MethodHandle BIOME_ATTRIBUTES_SETTER = ReflectionHelper.getFields(Biome.class).getSetter("attributes"); + + public Holder.Reference biomeHolder; + public ServerLevel world; + + public BiomeNMSImpl(ServerLevel world, NamespacedKey key) { + super(world.getWorld(), key); + this.world = world; + this.biomeHolder = getBiomeRegistry().get(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(key))).orElse(null); + } + + private MappedRegistry getBiomeRegistry() { + return (MappedRegistry) world.registryAccess().lookupOrThrow(Registries.BIOME); + } + + @Override + public DownfallType getDownfallTypeAt(Location location) { + Biome.Precipitation precipitation = biomeHolder.value().getPrecipitationAt(Handler.toBlockPos(location), world.getSeaLevel()); + return switch (precipitation) { + case RAIN -> DownfallType.RAIN; + case SNOW -> DownfallType.SNOW; + case NONE -> DownfallType.NONE; + }; + } + + @Override + public float getHumidity() { + return biomeHolder.value().climateSettings.downfall(); + } + + @Override + public float getBaseTemperature() { + return biomeHolder.value().getBaseTemperature(); + } + + @Override + public float getTemperatureAt(Location location) { + return biomeHolder.value().getTemperature(Handler.toBlockPos(location), world.getSeaLevel()); + } + + @Override + public boolean hasDownfall() { + return biomeHolder.value().hasPrecipitation(); + } + + @Override + public List getAmbientEntities() { + return getSpawnableEntities(MobCategory.AMBIENT); + } + + @Override + public List getCreatureEntities() { + return getSpawnableEntities(MobCategory.CREATURE); + } + + @Override + public List getMonsterEntities() { + return getSpawnableEntities(MobCategory.MONSTER); + } + + @Override + public List getWaterEntities() { + return getSpawnableEntities(MobCategory.WATER_CREATURE); + } + + @Override + public int getFoliageColor() { + // Check if the biome already has a default color + if (biomeHolder.value().getFoliageColor() != 0) { + return biomeHolder.value().getFoliageColor(); + } + // Based on net.minecraft.world.level.biome.Biome#getFoliageColorFromTexture() + float temperature = clampColor(getBaseTemperature()); + float humidity = clampColor(getHumidity()); + // Based on net.minecraft.world.level.FoliageColor#get() + humidity *= temperature; + int humidityValue = (int)((1.0f - humidity) * 255.0f); + int temperatureValue = (int)((1.0f - temperature) * 255.0f); + int index = temperatureValue << 8 | humidityValue; + return index >= 65536 ? 4764952 : getColor(index / 256, index % 256).asRGB(); + } + + public void setClimate(boolean hasPrecipitation, float temperature, Biome.TemperatureModifier temperatureModifier, float downfall) { + try { + Object newClimate = BIOME_CLIMATESETTINGS_CONSTRUCTOR.invoke(hasPrecipitation, temperature, temperatureModifier, downfall); + ReflectionHelper.setFieldValue(Biome.class, "climateSettings", biomeHolder.value(), newClimate); + setNetworkedRegistrationInfo(); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public void setHumidity(float humidity) { + setClimate(hasDownfall(), getBaseTemperature(), getTemperatureModifier(), humidity); + } + + @Override + public void setBaseTemperature(float baseTemperature) { + setClimate(hasDownfall(), baseTemperature, getTemperatureModifier(), getHumidity()); + } + + @Override + public void setHasDownfall(boolean hasDownfall) { + setClimate(hasDownfall, getBaseTemperature(), getTemperatureModifier(), getHumidity()); + } + + @Override + public void setFoliageColor(int color) { + BiomeSpecialEffects nmsCurrEffects = biomeHolder.value().getSpecialEffects(); + BiomeSpecialEffects nmsNewEffects = new BiomeSpecialEffects( + nmsCurrEffects.waterColor(), Optional.of(color), nmsCurrEffects.dryFoliageColorOverride(), nmsCurrEffects.grassColorOverride(), nmsCurrEffects.grassColorModifier() + ); + ReflectionHelper.setFieldValue(Biome.class, "specialEffects", biomeHolder.value(), nmsNewEffects); + setNetworkedRegistrationInfo(); + } + + @Override + public int getFogColor() { + return getEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR); + } + + @Override + public void setFogColor(int color) { + setEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR, color); + } + + @Override + public int getWaterFogColor() { + return getEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR); + } + + @Override + public void setWaterFogColor(int color) { + setEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR, color); + } + + public T getEnvironmentAttribute(EnvironmentAttribute attribute) { + return biomeHolder.value().getAttributes().applyModifier(attribute, attribute.defaultValue()); + } + + public void setEnvironmentAttribute(EnvironmentAttribute attribute, T value) { + Biome nmsBiome = biomeHolder.value(); + EnvironmentAttributeMap newAttributeMap = EnvironmentAttributeMap.builder().putAll(nmsBiome.getAttributes()).set(attribute, value).build(); + try { + BIOME_ATTRIBUTES_SETTER.invokeExact(nmsBiome, newAttributeMap); + } + catch (Throwable e) { + Debug.echoError(e); + } + setNetworkedRegistrationInfo(); + } + + private List getSpawnableEntities(MobCategory creatureType) { + MobSpawnSettings mobs = biomeHolder.value().getMobSettings(); + WeightedList typeSettingList = mobs.getMobs(creatureType); + List entityTypes = new ArrayList<>(); + if (typeSettingList == null) { + return entityTypes; + } + for (Weighted meta : typeSettingList.unwrap()) { + entityTypes.add(CraftEntityType.minecraftToBukkit(meta.value().type())); + } + return entityTypes; + } + + @Override + public void setTo(Block block) { + if (((CraftWorld) block.getWorld()).getHandle() != this.world) { + NMSHandler.instance.getBiomeNMS(block.getWorld(), getKey()).setTo(block); + return; + } + // Based on CraftWorld source + BlockPos pos = new BlockPos(block.getX(), 0, block.getZ()); + if (world.hasChunkAt(pos)) { + LevelChunk chunk = world.getChunkAt(pos); + if (chunk != null) { + chunk.setBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2, biomeHolder); + chunk.markUnsaved(); + } + } + } + + public Biome.TemperatureModifier getTemperatureModifier() { + return biomeHolder.value().climateSettings.temperatureModifier(); + } + + private void setNetworkedRegistrationInfo() { + try { + Map, RegistrationInfo> registrationInfos = (Map, RegistrationInfo>) MAPPED_REGISTRY_REGISTRATION_INFOS.invokeExact(getBiomeRegistry()); + registrationInfos.put(biomeHolder.key(), RegistrationInfo.BUILT_IN); + } + catch (Throwable e) { + Debug.echoError("Failed to set biome registration info, changes may not be synced correctly."); + Debug.echoError(e); + } + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/ImprovedOfflinePlayerImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/ImprovedOfflinePlayerImpl.java new file mode 100644 index 0000000000..4826347c16 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/ImprovedOfflinePlayerImpl.java @@ -0,0 +1,170 @@ +package com.denizenscript.denizen.nms.v26_2.impl; + +import com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.helpers.NBTAdapter; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ParticleStatus; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.ItemStackWithSlot; +import net.minecraft.world.entity.EntityEquipment; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeMap; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.attributes.DefaultAttributes; +import net.minecraft.world.entity.player.ChatVisiblity; +import net.minecraft.world.inventory.PlayerEnderChestContainer; +import net.minecraft.world.level.storage.ValueOutput; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftInventoryPlayer; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.UUID; +import java.util.function.Consumer; + +public class ImprovedOfflinePlayerImpl extends ImprovedOfflinePlayer { + + public ImprovedOfflinePlayerImpl(UUID playeruuid) { + super(playeruuid); + } + + public static class OfflinePlayerInventory extends net.minecraft.world.entity.player.Inventory { + + public OfflinePlayerInventory(net.minecraft.world.entity.player.Player entityhuman) { + super(entityhuman, new EntityEquipment()); // TODO: 1.21.5: is the new Equipment right here? + } + + @Override + public InventoryHolder getOwner() { + return null; + } + } + + public static class OfflineCraftInventoryPlayer extends CraftInventoryPlayer { + + public OfflineCraftInventoryPlayer(net.minecraft.world.entity.player.Inventory inventory) { + super(inventory); + } + + @Override + public HumanEntity getHolder() { + return null; + } + } + + public static ServerPlayer fakeNmsPlayer; + + public static ServerPlayer getFakeNmsPlayer() { + if (fakeNmsPlayer == null) { + MinecraftServer server = ((CraftServer)Bukkit.getServer()).getServer(); + World world = Bukkit.getWorlds().getFirst(); + GameProfile fakeProfile = new GameProfile(new UUID(0, 0xABC123), "fakeplayer"); + ClientInformation fakeClientInfo = new ClientInformation("en", 0, ChatVisiblity.HIDDEN, false, 0, HumanoidArm.LEFT, true, false, ParticleStatus.MINIMAL); + fakeNmsPlayer = new ServerPlayer(server, ((CraftWorld) world).getHandle(), fakeProfile, fakeClientInfo); + } + return fakeNmsPlayer; + } + + public void editData(Consumer editor) { + this.compound = NBTAdapter.toAPI(Handler.useValueOutput(NBTAdapter.toNMS(this.compound), editor)); + markModified(); + } + + @Override + public org.bukkit.inventory.PlayerInventory getInventory() { + if (inventory == null) { + net.minecraft.world.entity.player.Inventory newInv = new OfflinePlayerInventory(getFakeNmsPlayer()); + Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> newInv.load(valueInput.listOrEmpty("Inventory", ItemStackWithSlot.CODEC))); + inventory = new OfflineCraftInventoryPlayer(newInv); + } + return inventory; + } + + @Override + public void setInventory(org.bukkit.inventory.PlayerInventory inventory) { + CraftInventoryPlayer inv = (CraftInventoryPlayer) inventory; + editData(valueOutput -> inv.getInventory().save(valueOutput.list("Inventory", ItemStackWithSlot.CODEC))); + } + + @Override + public Inventory getEnderChest() { + if (enderchest == null) { + PlayerEnderChestContainer nmsEnderChest = new PlayerEnderChestContainer(getFakeNmsPlayer()); + Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> nmsEnderChest.fromSlots(valueInput.listOrEmpty("EnderItems", ItemStackWithSlot.CODEC))); + enderchest = new CraftInventory(nmsEnderChest); + } + return enderchest; + } + + @Override + public void setEnderChest(Inventory inventory) { + editData(valueOutput -> ((PlayerEnderChestContainer) ((CraftInventory) inventory).getInventory()).storeAsSlots(valueOutput.list("EnderItems", ItemStackWithSlot.CODEC))); + } + + @Override + public double getMaxHealth() { + AttributeInstance maxHealth = getAttributes().getInstance(Attributes.MAX_HEALTH); + return maxHealth == null ? Attributes.MAX_HEALTH.value().getDefaultValue() : maxHealth.getValue(); + } + + @Override + public void setMaxHealth(double input) { + AttributeMap attributes = getAttributes(); + AttributeInstance maxHealth = attributes.getInstance(Attributes.MAX_HEALTH); + maxHealth.setBaseValue(input); + setAttributes(attributes); + } + + private AttributeMap getAttributes() { + AttributeMap amb = new AttributeMap(DefaultAttributes.getSupplier(EntityTypes.PLAYER)); + Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> valueInput.read("attributes", AttributeInstance.Packed.LIST_CODEC).ifPresent(amb::apply)); + return amb; + } + + public void setAttributes(AttributeMap attributes) { + editData(valueOutput -> valueOutput.store("attributes", AttributeInstance.Packed.LIST_CODEC, attributes.pack())); + } + + @Override + protected boolean loadPlayerData(UUID uuid) { + try { + this.player = uuid; + this.file = new File(MinecraftServer.getServer().getPlayerList().playerIo.getPlayerDir(), this.player + ".dat"); + if (this.file.exists()) { + this.compound = NBTAdapter.toAPI(NbtIo.readCompressed(new FileInputStream(this.file), NbtAccounter.unlimitedHeap())); + return true; + } + } + catch (Exception e) { + Debug.echoError(e); + } + return false; + } + + @Override + public void saveInternal(CompoundBinaryTag compound) { + try { + NbtIo.writeCompressed(NBTAdapter.toNMS(compound), new FileOutputStream(this.file)); + } + catch (Exception e) { + Debug.echoError(e); + } + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/ProfileEditorImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/ProfileEditorImpl.java new file mode 100644 index 0000000000..881c6df7ff --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/ProfileEditorImpl.java @@ -0,0 +1,168 @@ +package com.denizenscript.denizen.nms.v26_2.impl; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.ProfileEditor; +import com.denizenscript.denizen.nms.util.PlayerProfile; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.helpers.PacketHelperImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.scripts.commands.entity.RenameCommand; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.collect.ImmutableMultimap; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.datafixers.util.Either; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.component.ResolvableProfile; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerRespawnEvent; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; + +public class ProfileEditorImpl extends ProfileEditor { + + @Override + protected void updatePlayer(final Player player, final boolean isSkinChanging) { + final ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + final UUID uuid = player.getUniqueId(); + ClientboundPlayerInfoRemovePacket removePlayerInfoPacket = new ClientboundPlayerInfoRemovePacket(List.of(uuid)); + ClientboundPlayerInfoUpdatePacket addPlayerInfoPacket = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(nmsPlayer)); + for (Player otherPlayer : Bukkit.getServer().getOnlinePlayers()) { + PacketHelperImpl.send(otherPlayer, removePlayerInfoPacket); + PacketHelperImpl.send(otherPlayer, addPlayerInfoPacket); + } + for (Player otherPlayer : NMSHandler.entityHelper.getPlayersThatSee(player)) { + if (!otherPlayer.getUniqueId().equals(uuid)) { + PacketHelperImpl.forceRespawnPlayerEntity(player, otherPlayer); + } + } + if (isSkinChanging) { + ((CraftServer) Bukkit.getServer()).getHandle().respawn(nmsPlayer, true, Entity.RemovalReason.CHANGED_DIMENSION, PlayerRespawnEvent.RespawnReason.PLUGIN); + } + else { + NMSHandler.playerHelper.refreshPlayer(player); + } + player.updateInventory(); + } + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, ProfileEditorImpl::processPlayerInfoUpdatePacket); + } + + public static ClientboundPlayerInfoUpdatePacket processPlayerInfoUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket) { + if (ProfileEditor.mirrorUUIDs.isEmpty() && !RenameCommand.hasAnyDynamicRenames() && fakeProfiles.isEmpty()) { + return playerInfoUpdatePacket; + } + EnumSet actions = playerInfoUpdatePacket.actions(); + if (!actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) && !actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) { + return playerInfoUpdatePacket; + } + boolean any = false; + for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) { + if (shouldChange(entry)) { + any = true; + break; + } + } + if (!any) { + return playerInfoUpdatePacket; + } + GameProfile ownProfile = networkManager.player.getGameProfile(); + List modifiedEntries = new ArrayList<>(playerInfoUpdatePacket.entries().size()); + for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) { + if (!shouldChange(entry)) { + modifiedEntries.add(entry); + continue; + } + String rename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), false); + GameProfile baseProfile = fakeProfiles.containsKey(entry.profileId()) ? getGameProfile(fakeProfiles.get(entry.profileId())) : entry.profile(); + PropertyMap modifiedProperties; + if (ProfileEditor.mirrorUUIDs.contains(entry.profileId())) { + modifiedProperties = ownProfile.properties(); + } + else { + // On Paper 1.19+, we use Paper's PlayerProfile API instead of this system + modifiedProperties = Denizen.supportsPaper ? entry.profile().properties() : baseProfile.properties(); + } + GameProfile modifiedProfile = new GameProfile(baseProfile.id(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : baseProfile.name(), modifiedProperties); + String listRename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), true); + Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : entry.displayName(); + ClientboundPlayerInfoUpdatePacket.Entry modifiedEntry = new ClientboundPlayerInfoUpdatePacket.Entry(entry.profileId(), modifiedProfile, entry.listed(), entry.latency(), entry.gameMode(), displayName, entry.showHat(), entry.listOrder(), entry.chatSession()); + modifiedEntries.add(modifiedEntry); + } + return createInfoPacket(actions, modifiedEntries); + } + + public static boolean shouldChange(ClientboundPlayerInfoUpdatePacket.Entry entry) { + return ProfileEditor.mirrorUUIDs.contains(entry.profileId()) || RenameCommand.customNames.containsKey(entry.profileId()) || fakeProfiles.containsKey(entry.profileId()); + } + + public static final Field ClientboundPlayerInfoUpdatePacket_entries = ReflectionHelper.getFields(ClientboundPlayerInfoUpdatePacket.class).getFirstOfType(List.class); + + public static ClientboundPlayerInfoUpdatePacket createInfoPacket(EnumSet actions, List entries) { + ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket = new ClientboundPlayerInfoUpdatePacket(actions, List.of()); + try { + ClientboundPlayerInfoUpdatePacket_entries.set(playerInfoUpdatePacket, entries); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + return playerInfoUpdatePacket; + } + + public static GameProfile createGameProfile(UUID uuid, String name, String texture, String signature) { + return new GameProfile( + uuid != null ? uuid : NIL_UUID, name != null ? name : EMPTY_NAME, + texture != null ? new PropertyMap(ImmutableMultimap.of("textures", new Property("textures", texture, signature))) : PropertyMap.EMPTY + ); + } + + public static GameProfile getGameProfileNoProperties(PlayerProfile playerProfile) { + return createGameProfile(playerProfile.getUniqueId(), playerProfile.getName(), null, null); + } + + public static GameProfile getGameProfile(PlayerProfile playerProfile) { + return createGameProfile(playerProfile.getUniqueId(), playerProfile.getName(), playerProfile.getTexture(), playerProfile.getTextureSignature()); + } + + public static final MethodHandle RESOLVABLEPROFILE_UNPACK = ReflectionHelper.getMethodHandle(ResolvableProfile.class, "unpack"); + public static final MethodHandle RESOLVABLEPROFILE_PARTIAL_ID = ReflectionHelper + .getFields(ReflectionHelper.getClassOrThrow("net.minecraft.world.item.component.ResolvableProfile$Partial")) + .getGetter("id", Optional.class); + + public static UUID getUUID(ResolvableProfile resolvableProfile) { + try { + Either unpacked = (Either) RESOLVABLEPROFILE_UNPACK.invokeExact(resolvableProfile); + return unpacked.map(GameProfile::id, + partial -> { + try { + Optional uuid = (Optional) RESOLVABLEPROFILE_PARTIAL_ID.invoke(partial); + return uuid.orElse(null); + } + catch (Throwable e) { + Debug.echoError(e); + return null; + } + } + ); + } + catch (Throwable e) { + Debug.echoError(e); + return null; + } + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/SidebarImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/SidebarImpl.java new file mode 100644 index 0000000000..69f2257017 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/SidebarImpl.java @@ -0,0 +1,103 @@ +package com.denizenscript.denizen.nms.v26_2.impl; + +import com.denizenscript.denizen.nms.abstracts.Sidebar; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.helpers.PacketHelperImpl; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.numbers.StyledFormat; +import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket; +import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket; +import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; +import net.minecraft.network.protocol.game.ClientboundSetScorePacket; +import net.minecraft.world.scores.DisplaySlot; +import net.minecraft.world.scores.Objective; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.scores.criteria.ObjectiveCriteria; +import org.bukkit.entity.Player; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class SidebarImpl extends Sidebar { + + // TODO: 1.20.3: the scoreboard system saw significant changes, there is likely a better way to do this now + + public static Scoreboard dummyScoreboard = new Scoreboard(); + public static ObjectiveCriteria dummyCriteria; + + static { + try { + Constructor constructor = ObjectiveCriteria.class.getDeclaredConstructor(String.class); + constructor.setAccessible(true); + dummyCriteria = constructor.newInstance("dummy"); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public Objective obj1; + public Objective obj2; + + public SidebarImpl(Player player) { + super(player); + Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE)); + this.obj1 = new Objective(dummyScoreboard, "dummy_1", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT); + this.obj2 = new Objective(dummyScoreboard, "dummy_2", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT); + } + + @Override + protected void setDisplayName(String title) { + if (this.obj1 != null) { + Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE)); + this.obj1.setDisplayName(chatComponentTitle); + this.obj2.setDisplayName(chatComponentTitle); + } + } + + public List generatedTeams = new ArrayList<>(); + + @Override + public void sendUpdate() { + List oldTeams = generatedTeams; + generatedTeams = new ArrayList<>(); + PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj1, 0)); + String[] ids = getIds(); + for (int i = 0; i < this.lines.length; i++) { + String line = this.lines[i]; + if (line == null) { + break; + } + String lineId = ids[i]; + PlayerTeam team = new PlayerTeam(dummyScoreboard, lineId); + team.getPlayers().add(lineId); + team.setPlayerPrefix(Handler.componentToNMS(FormattedTextHelper.parse(line, ChatColor.WHITE))); + generatedTeams.add(team); + PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); + PacketHelperImpl.send(player, new ClientboundSetScorePacket(lineId, obj1.getName(), this.scores[i], Optional.empty(), Optional.of(StyledFormat.SIDEBAR_DEFAULT))); + } + PacketHelperImpl.send(player, new ClientboundSetDisplayObjectivePacket(DisplaySlot.SIDEBAR, this.obj1)); + PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1)); + Objective temp = this.obj2; + this.obj2 = this.obj1; + this.obj1 = temp; + for (PlayerTeam team : oldTeams) { + PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team)); + } + } + + @Override + public void remove() { + for (PlayerTeam team : generatedTeams) { + PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team)); + } + generatedTeams.clear(); + PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1)); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/blocks/BlockLightImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/blocks/BlockLightImpl.java new file mode 100644 index 0000000000..b81f0a6bde --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/blocks/BlockLightImpl.java @@ -0,0 +1,263 @@ +package com.denizenscript.denizen.nms.v26_2.impl.blocks; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.abstracts.BlockLight; +import com.denizenscript.denizen.utilities.blocks.ChunkCoordinate; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; +import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.DataLayer; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.lighting.LayerLightEventListener; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.util.Vector; + +import java.lang.invoke.MethodHandle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +public class BlockLightImpl extends BlockLight { + + public static final Class LIGHTENGINETHREADED_TASKTYPE = Arrays.stream(ThreadedLevelLightEngine.class.getDeclaredClasses()).filter(c -> c.isEnum()).findFirst().get(); // TaskType + public static final Object LIGHTENGINETHREADED_TASKTYPE_PRE; + + static { + Object preObj = null; + try { + preObj = ReflectionHelper.getFields(LIGHTENGINETHREADED_TASKTYPE).get("PRE_UPDATE").get(null); + } + catch (Throwable ex) { + ex.printStackTrace(); + } + LIGHTENGINETHREADED_TASKTYPE_PRE = preObj; + } + + public static final MethodHandle LIGHTENGINETHREADED_QUEUERUNNABLE = ReflectionHelper.getMethodHandle(ThreadedLevelLightEngine.class, "addTask", + int.class, int.class, LIGHTENGINETHREADED_TASKTYPE, Runnable.class); + + public static void enqueueRunnable(LevelChunk chunk, Runnable runnable) { + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + if (lightEngine instanceof ThreadedLevelLightEngine) { + ChunkPos coord = chunk.getPos(); + try { + LIGHTENGINETHREADED_QUEUERUNNABLE.invoke(lightEngine, coord.x(), coord.z(), LIGHTENGINETHREADED_TASKTYPE_PRE, runnable); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + else { + runnable.run(); + } + } + + private BlockLightImpl(Location location, long ticks) { + super(location, ticks); + } + + public static BlockLight createLight(Location location, int lightLevel, long ticks) { + location = location.getBlock().getLocation(); + BlockLight blockLight; + if (lightsByLocation.containsKey(location)) { + blockLight = lightsByLocation.get(location); + if (blockLight.removeTask != null) { + blockLight.removeTask.cancel(); + blockLight.removeTask = null; + } + if (blockLight.updateTask != null) { + blockLight.updateTask.cancel(); + blockLight.updateTask = null; + } + blockLight.removeLater(ticks); + } + else { + blockLight = new BlockLightImpl(location, ticks); + lightsByLocation.put(location, blockLight); + if (!lightsByChunk.containsKey(blockLight.chunkCoord)) { + lightsByChunk.put(blockLight.chunkCoord, new ArrayList<>()); + } + lightsByChunk.get(blockLight.chunkCoord).add(blockLight); + } + blockLight.intendedLevel = lightLevel; + blockLight.update(lightLevel, true); + return blockLight; + } + + public static void checkIfLightsBrokenByPacket(ClientboundBlockUpdatePacket packet, Level world) { + try { + BlockPos pos = packet.getPos(); + int chunkX = pos.getX() >> 4; + int chunkZ = pos.getZ() >> 4; + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> { + LevelChunk chunk = world.getChunk(chunkX, chunkZ); + boolean any = false; + for (Vector vec : RELATIVE_CHUNKS) { + ChunkAccess other = world.getChunk(chunkX + vec.getBlockX(), chunkZ + vec.getBlockZ(), ChunkStatus.FULL, false); + if (other instanceof LevelChunk) { + List lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) other))); + if (lights != null) { + any = true; + for (BlockLight light : lights) { + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1); + } + } + } + } + if (any) { + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates(chunk), 3); + } + }, 1); + } + catch (Exception ex) { + Debug.echoError(ex); + } + } + + public static void checkIfLightsBrokenByPacket(ClientboundLightUpdatePacket packet, Level world) { + if (doNotCheck) { + return; + } + try { + int cX = packet.getX(); + int cZ = packet.getZ(); + BitSet bitMask = packet.getLightData().getBlockYMask(); + List blockData = packet.getLightData().getBlockUpdates(); + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> { + ChunkAccess chk = world.getChunk(cX, cZ, ChunkStatus.FULL, false); + if (!(chk instanceof LevelChunk)) { + return; + } + List lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) chk))); + if (lights == null) { + return; + } + boolean any = false; + for (BlockLight light : lights) { + if (((BlockLightImpl) light).checkIfChangedBy(bitMask, blockData)) { + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1); + any = true; + } + } + if (any) { + Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates((LevelChunk) chk), 3); + } + }, 1); + } + catch (Exception ex) { + Debug.echoError(ex); + } + } + + public static boolean doNotCheck = false; + + public boolean checkIfChangedBy(BitSet bitmask, List data) { + Location blockLoc = block.getLocation(); + int layer = (blockLoc.getBlockY() >> 4) + 1; + if (!bitmask.get(layer)) { + return false; + } + int found = 0; + for (int i = 0; i < 16; i++) { + if (bitmask.get(i)) { + if (i == layer) { + byte[] blocks = data.get(found); + DataLayer arr = new DataLayer(blocks); + int x = blockLoc.getBlockX() - (chunkCoord.x << 4); + int y = blockLoc.getBlockY() % 16; + int z = blockLoc.getBlockZ() - (chunkCoord.z << 4); + int level = arr.get(x, y, z); + return intendedLevel != level; + } + found++; + } + } + return false; + } + + public static void runResetFor(final LevelChunk chunk, final BlockPos pos) { + Runnable runnable = () -> { + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK); + engineBlock.checkBlock(pos); + }; + enqueueRunnable(chunk, runnable); + } + + public static void runSetFor(final LevelChunk chunk, final BlockPos pos, final int level) { + Runnable runnable = () -> { + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK); + // engineBlock.onBlockEmissionIncrease(pos, level); // TODO: 1.20: ? + }; + enqueueRunnable(chunk, runnable); + } + + @Override + public void reset(boolean updateChunk) { + runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition()); + if (updateChunk) { + // This runnable cast is necessary despite what your IDE may claim + updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1); + } + } + + @Override + public void update(int lightLevel, boolean updateChunk) { + runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition()); + updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), () -> { + updateTask = null; + runSetFor((LevelChunk) ((CraftChunk) chunk).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition(), lightLevel); + if (updateChunk) { + // This runnable cast is necessary despite what your IDE may claim + updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1); + } + }, 1); + } + + public static final Vector[] RELATIVE_CHUNKS = new Vector[] { + new Vector(0, 0, 0), + new Vector(-1, 0, 0), new Vector(1, 0, 0), new Vector(0, 0, -1), new Vector(0, 0, 1), + new Vector(-1, 0, -1), new Vector(-1, 0, 1), new Vector(1, 0, -1), new Vector(1, 0, 1) + }; + + public void sendNearbyChunkUpdates() { + sendNearbyChunkUpdates((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL)); + } + + public static void sendNearbyChunkUpdates(LevelChunk chunk) { + ChunkPos pos = chunk.getPos(); + for (Vector vec : RELATIVE_CHUNKS) { + ChunkAccess other = chunk.getLevel().getChunk(pos.x() + vec.getBlockX(), pos.z() + vec.getBlockZ(), ChunkStatus.FULL, false); + if (other instanceof LevelChunk) { + sendSingleChunkUpdate((LevelChunk) other); + } + } + } + + public static void sendSingleChunkUpdate(LevelChunk chunk) { + // TODO: 1.20: ? + /* + doNotCheck = true; + LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); + ChunkPos pos = chunk.getPos(); + //ClientboundLightUpdatePacket packet = new ClientboundLightUpdatePacket(pos, lightEngine, null, null, true); // TODO: 1.16: should 'trust edges' be true here? + ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(pos, false).forEach((player) -> { + player.connection.send(packet); + }); + doNotCheck = false;*/ + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftFakeArrowImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftFakeArrowImpl.java new file mode 100644 index 0000000000..7d622d3984 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftFakeArrowImpl.java @@ -0,0 +1,26 @@ +package com.denizenscript.denizen.nms.v26_2.impl.entities; + +import com.denizenscript.denizen.nms.interfaces.FakeArrow; +import net.minecraft.world.entity.projectile.arrow.AbstractArrow; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftAbstractArrow; + +public class CraftFakeArrowImpl extends CraftAbstractArrow implements FakeArrow { + + public CraftFakeArrowImpl(CraftServer craftServer, AbstractArrow entityArrow) { + super(craftServer, entityArrow); + } + + @Override + public void remove() { + if (getPassenger() != null) { + return; + } + super.remove(); + } + + @Override + public String getEntityTypeName() { + return "FAKE_ARROW"; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftFakePlayerImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftFakePlayerImpl.java new file mode 100644 index 0000000000..7effccb9b1 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftFakePlayerImpl.java @@ -0,0 +1,66 @@ +package com.denizenscript.denizen.nms.v26_2.impl.entities; + +import com.denizenscript.denizen.nms.interfaces.FakePlayer; +import com.denizenscript.denizen.nms.NMSHandler; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; + +import java.util.List; +import java.util.Set; + +public class CraftFakePlayerImpl extends CraftPlayer implements FakePlayer { + + private final CraftServer server; + public String fullName; + + public CraftFakePlayerImpl(CraftServer server, EntityFakePlayerImpl entity) { + super(server, entity); + this.server = server; + setMetadata("NPC", new FixedMetadataValue(NMSHandler.getJavaPlugin(), true)); + } + + @Override + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue); + } + + @Override + public List getMetadata(String metadataKey) { + return this.server.getEntityMetadata().getMetadata(this, metadataKey); + } + + @Override + public boolean hasMetadata(String metadataKey) { + return this.server.getEntityMetadata().hasMetadata(this, metadataKey); + } + + @Override + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + this.server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin); + } + + @Override + public String getEntityTypeName() { + return "FAKE_PLAYER"; + } + + @Override + public String getFullName() { + return fullName; + } + + @Override + public Block getTargetBlock(Set transparent, int maxDistance) { + return null; + } + + @Override + public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { + return null; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftItemProjectileImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftItemProjectileImpl.java new file mode 100644 index 0000000000..624eefbf70 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/CraftItemProjectileImpl.java @@ -0,0 +1,118 @@ +package com.denizenscript.denizen.nms.v26_2.impl.entities; + +import com.denizenscript.denizen.nms.interfaces.ItemProjectile; +import com.denizenscript.denizen.nms.v26_2.helpers.ItemHelperImpl; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.projectiles.ProjectileSource; + +import java.lang.invoke.MethodHandle; +import java.util.UUID; + +public class CraftItemProjectileImpl extends CraftEntity implements ItemProjectile { + + private boolean doesBounce; + + public CraftItemProjectileImpl(CraftServer server, EntityItemProjectileImpl entity) { + super(server, entity); + MethodHandle handle = ReflectionHelper.getFinalSetterForFirstOfType(CraftEntity.class, EntityType.class); + if (handle != null) { + try { + handle.invoke(this, EntityType.ITEM); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + } + + @Override + public EntityItemProjectileImpl getHandle() { + return (EntityItemProjectileImpl) super.getHandle(); + } + + @Override + public String getEntityTypeName() { + return getType().name(); + } + + @Override + public ItemStack getItemStack() { + return ItemHelperImpl.asBukkitCopy(getHandle().getItemStack()); + } + + @Override + public void setItemStack(ItemStack itemStack) { + getHandle().setItemStack(CraftItemStack.asNMSCopy(itemStack)); + } + + @Override + public int getPickupDelay() { + return 0; + } + + @Override + public void setPickupDelay(int i) { + // Do nothing + } + + @Override + public void setUnlimitedLifetime(boolean b) { + // Do nothing + } + + @Override + public boolean isUnlimitedLifetime() { + return false; + } + + @Override + public void setOwner(UUID uuid) { + // Do nothing + } + + @Override + public UUID getOwner() { + return null; + } + + @Override + public void setThrower(UUID uuid) { + // Do nothing + } + + @Override + public UUID getThrower() { + return null; + } + + @Override + public ProjectileSource getShooter() { + return getHandle().projectileSource; + } + + @Override + public void setShooter(ProjectileSource projectileSource) { + if (projectileSource instanceof CraftEntity) { + getHandle().setOwner(((CraftEntity) projectileSource).getHandle()); + } + else { + getHandle().projectileSource = projectileSource; + } + } + + @Override + public boolean doesBounce() { + return doesBounce; + } + + @Override + public void setBounce(boolean doesBounce) { + this.doesBounce = doesBounce; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityFakeArrowImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityFakeArrowImpl.java new file mode 100644 index 0000000000..8d40d1ace4 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityFakeArrowImpl.java @@ -0,0 +1,44 @@ +package com.denizenscript.denizen.nms.v26_2.impl.entities; + +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.entity.projectile.arrow.SpectralArrow; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.event.entity.CreatureSpawnEvent; + +public class EntityFakeArrowImpl extends SpectralArrow { + + public EntityFakeArrowImpl(CraftWorld craftWorld, Location location) { + super(EntityTypes.SPECTRAL_ARROW, craftWorld.getHandle()); + try { + Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakeArrowImpl((CraftServer) Bukkit.getServer(), this)); + } + catch (Exception ex) { + Debug.echoError(ex); + } + setPosRaw(location.getX(), location.getY(), location.getZ()); + setRot(location.getYaw(), location.getPitch()); + level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM); + } + + @Override + public void tick() { + // Do nothing + } + + @Override + protected ItemStack getPickupItem() { + return new ItemStack(Items.ARROW); + } + + @Override + public CraftFakeArrowImpl getBukkitEntity() { + return (CraftFakeArrowImpl) super.getBukkitEntity(); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityFakePlayerImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityFakePlayerImpl.java new file mode 100644 index 0000000000..84332b3107 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityFakePlayerImpl.java @@ -0,0 +1,43 @@ +package com.denizenscript.denizen.nms.v26_2.impl.entities; + +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.impl.network.fakes.FakeNetworkManagerImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.fakes.FakePlayerConnectionImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.world.entity.player.Player; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.event.entity.CreatureSpawnEvent; + +public class EntityFakePlayerImpl extends ServerPlayer { + + public EntityFakePlayerImpl(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, ClientInformation clientInfo, boolean doAdd) { + super(minecraftserver, worldserver, gameprofile, clientInfo); + try { + Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakePlayerImpl((CraftServer) Bukkit.getServer(), this)); + net.minecraft.network.Connection networkManager = new FakeNetworkManagerImpl(PacketFlow.CLIENTBOUND); + connection = new FakePlayerConnectionImpl(minecraftserver, networkManager, this, new CommonListenerCookie(gameprofile, 0, clientInfo, false)); + DenizenNetworkManagerImpl.Connection_packetListener.set(networkManager, connection); + } + catch (Exception ex) { + Debug.echoError(ex); + } + getEntityData().set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127); + if (doAdd) { + worldserver.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM); + } + } + + @Override + public CraftFakePlayerImpl getBukkitEntity() { + return (CraftFakePlayerImpl) super.getBukkitEntity(); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityItemProjectileImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityItemProjectileImpl.java new file mode 100644 index 0000000000..8771ed2bda --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/entities/EntityItemProjectileImpl.java @@ -0,0 +1,98 @@ +package com.denizenscript.denizen.nms.v26_2.impl.entities; + +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.base.Preconditions; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.ThrowableProjectile; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.BlockHitResult; +import org.bukkit.Location; + +import java.lang.invoke.MethodHandle; + +public class EntityItemProjectileImpl extends ThrowableProjectile { + + public static MethodHandle setBukkitEntityMethod = ReflectionHelper.getFinalSetter(Entity.class, "bukkitEntity"); + + public static final EntityDataAccessor ITEM; + + static { + EntityDataAccessor watcher = null; + try { + watcher = (EntityDataAccessor) ReflectionHelper.getFields(ItemEntity.class).get("DATA_ITEM").get(null); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + ITEM = watcher; + } + + public EntityItemProjectileImpl(Level world, Location location, ItemStack item) { + super((net.minecraft.world.entity.EntityType) EntityTypes.ITEM, world); + try { + setBukkitEntityMethod.invoke(this, new CraftItemProjectileImpl(((ServerLevel) world).getServer().server, this)); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + setPosRaw(location.getX(), location.getY(), location.getZ()); + setRot(location.getYaw(), location.getPitch()); + setItemStack(item); + } + + @Override + protected void defineSynchedData(SynchedEntityData.Builder builder) { + builder.define(ITEM, ItemStack.EMPTY); + } + + public ItemStack getItemStack() { + return this.getEntityData().get(ITEM); + } + + public void setItemStack(ItemStack itemstack) { + Preconditions.checkArgument(!itemstack.isEmpty(), "Cannot drop air"); + this.getEntityData().set(ITEM, itemstack); + this.getEntityData().markDirty(ITEM); + } + + @Override + protected void onHitBlock(BlockHitResult movingobjectpositionblock) { + super.onHitBlock(movingobjectpositionblock); + remove(RemovalReason.KILLED); + } + + @Override + public boolean save(ValueOutput nmsValueOutput) { + if (!this.getItemStack().isEmpty()) { + nmsValueOutput.store("Item", ItemStack.CODEC, this.getItemStack()); + } + super.save(nmsValueOutput); + return true; + } + + @Override + public void load(ValueInput nmsValueInput) { + ItemStack nmsItemStack = nmsValueInput.read("Item", ItemStack.CODEC).orElse(ItemStack.EMPTY); + if (nmsItemStack.isEmpty()) { + this.remove(RemovalReason.KILLED); + } + else { + this.setItemStack(nmsItemStack); + } + super.load(nmsValueInput); + } + + @Override + public CraftItemProjectileImpl getBukkitEntity() { + return (CraftItemProjectileImpl) super.getBukkitEntity(); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakeChannelImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakeChannelImpl.java new file mode 100644 index 0000000000..9c14ea7dd4 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakeChannelImpl.java @@ -0,0 +1,80 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.fakes; + +import io.netty.channel.*; + +import java.net.SocketAddress; + +public class FakeChannelImpl extends AbstractChannel { + + private final ChannelConfig config = new DefaultChannelConfig(this); + + protected FakeChannelImpl(Channel parent) { + super(parent); + } + + @Override + public ChannelConfig config() { + config.setAutoRead(true); + return config; + } + + @Override + protected AbstractUnsafe newUnsafe() { + return null; + } + + @Override + protected boolean isCompatible(EventLoop eventLoop) { + return false; + } + + @Override + protected SocketAddress localAddress0() { + return null; + } + + @Override + protected SocketAddress remoteAddress0() { + return null; + } + + @Override + protected void doBind(SocketAddress socketAddress) throws Exception { + + } + + @Override + protected void doDisconnect() throws Exception { + + } + + @Override + protected void doClose() throws Exception { + + } + + @Override + protected void doBeginRead() throws Exception { + + } + + @Override + protected void doWrite(ChannelOutboundBuffer channelOutboundBuffer) throws Exception { + + } + + @Override + public boolean isOpen() { + return false; + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public ChannelMetadata metadata() { + return new ChannelMetadata(true); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakeNetworkManagerImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakeNetworkManagerImpl.java new file mode 100644 index 0000000000..10b17690fb --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakeNetworkManagerImpl.java @@ -0,0 +1,16 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.fakes; + +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.PacketFlow; + +import java.net.SocketAddress; + +public class FakeNetworkManagerImpl extends Connection { + + public FakeNetworkManagerImpl(PacketFlow enumprotocoldirection) { + super(enumprotocoldirection); + channel = new FakeChannelImpl(null); + address = new SocketAddress() { + }; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakePlayerConnectionImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakePlayerConnectionImpl.java new file mode 100644 index 0000000000..0804cca68d --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/fakes/FakePlayerConnectionImpl.java @@ -0,0 +1,20 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.fakes; + +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.Packet; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerGamePacketListenerImpl; + +public class FakePlayerConnectionImpl extends ServerGamePacketListenerImpl { + + public FakePlayerConnectionImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer, CommonListenerCookie cookie) { + super(minecraftserver, networkmanager, entityplayer, cookie); + } + + @Override + public void send(Packet packet) { + // Do nothing + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/AbstractListenerPlayInImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/AbstractListenerPlayInImpl.java new file mode 100644 index 0000000000..97d6182d45 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/AbstractListenerPlayInImpl.java @@ -0,0 +1,670 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers; + +import com.denizenscript.denizen.events.player.PlayerSendPacketScriptEvent; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.mojang.authlib.GameProfile; +import io.netty.channel.ChannelFutureListener; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.DisconnectionDetails; +import net.minecraft.network.chat.ChatType; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.*; +import net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.protocol.ping.ServerboundPingRequestPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerCommonPacketListenerImpl; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.Relative; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.event.player.PlayerTeleportEvent; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.net.SocketAddress; +import java.util.Set; + +public class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl { + + public static final Field ServerGamePacketListenerImpl_chunkSender = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("chunkSender"); + + public static final MethodHandle SERVER_COMMON_PACKET_LISTENER_IMPL_CREATE_COOKIE = ReflectionHelper.getMethodHandle(ServerCommonPacketListenerImpl.class, "createCookie", ClientInformation.class); + + public static CommonListenerCookie createCookie(ServerPlayer nmsPlayer) { + try { + return (CommonListenerCookie) SERVER_COMMON_PACKET_LISTENER_IMPL_CREATE_COOKIE.invoke(nmsPlayer.connection, nmsPlayer.clientInformation()); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public final ServerGamePacketListenerImpl oldListener; + public final DenizenNetworkManagerImpl denizenNetworkManager; + + public AbstractListenerPlayInImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer, ServerGamePacketListenerImpl oldListener) { + super(MinecraftServer.getServer(), networkManager, entityPlayer, createCookie(entityPlayer)); + this.oldListener = oldListener; + this.denizenNetworkManager = networkManager; + try { + ServerGamePacketListenerImpl_chunkSender.set(this, oldListener.chunkSender); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + } + } + + @Override + public void disconnect(Component ichatbasecomponent) { + oldListener.disconnect(ichatbasecomponent); + } + + @Override + public void disconnect(DisconnectionDetails disconnectiondetails) { + oldListener.disconnect(disconnectiondetails); + } + + @Override + public void kickPlayer(Component reason) { + oldListener.kickPlayer(reason); + } + + @Override + public void teleport(double d0, double d1, double d2, float f, float f1) { + oldListener.teleport(d0, d1, d2, f, f1); + } + + @Override + public boolean teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { + return oldListener.teleport(d0, d1, d2, f, f1, cause); + } + + @Override + public void teleport(PositionMoveRotation positionmoverotation, Set set) { + oldListener.teleport(positionmoverotation, set); + } + + @Override + public boolean teleport(PositionMoveRotation positionmoverotation, Set set, PlayerTeleportEvent.TeleportCause cause) { + return oldListener.teleport(positionmoverotation, set, cause); + } + + @Override + public void teleport(Location dest) { + oldListener.teleport(dest); + } + + @Override + public void internalTeleport(PositionMoveRotation positionmoverotation, Set set) { + oldListener.internalTeleport(positionmoverotation, set); + } + + @Override + public CraftPlayer getCraftPlayer() { + return oldListener.getCraftPlayer(); + } + + @Override + public void tick() { + oldListener.tick(); + } + + @Override + public void resetPosition() { + oldListener.resetPosition(); + } + + @Override + public boolean isAcceptingMessages() { + return oldListener.isAcceptingMessages(); + } + + @Override + public boolean shouldHandleMessage(Packet packet) { + return oldListener.shouldHandleMessage(packet); + } + + @Override + public void fillCrashReport(CrashReport var0) { + oldListener.fillCrashReport(var0); + } + + @Override + public void fillListenerSpecificCrashDetails(CrashReport var0, CrashReportCategory var1) { + oldListener.fillListenerSpecificCrashDetails(var0, var1); + } + + @Override + public GameProfile getOwner() { + return oldListener.getOwner(); + } + + @Override + public int latency() { + return oldListener.latency(); + } + + @Override + public void onDisconnect(DisconnectionDetails disconnectionDetails) { + oldListener.onDisconnect(disconnectionDetails); + } + + @Override + public void ackBlockChangesUpTo(int i) { + oldListener.ackBlockChangesUpTo(i); + } + + @Override + public void send(Packet packet) { + oldListener.send(packet); + } + + @Override + public void send(Packet packet, ChannelFutureListener channelfuturelistener) { + oldListener.send(packet, channelfuturelistener); + } + + public static Field AWAITING_POS_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("awaitingPositionFromClient", Vec3.class); + public static Field AWAITING_TELEPORT_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get("awaitingTeleport", int.class); + + public void debugPacketOutput(Packet packet) { + try { + if (packet instanceof ServerboundMovePlayerPacket) { + ServerboundMovePlayerPacket movePacket = (ServerboundMovePlayerPacket) packet; + DenizenNetworkManagerImpl.doPacketOutput("Packet ServerboundMovePlayerPacket sent from " + player.getScoreboardName() + " with XYZ=" + + movePacket.x + ", " + movePacket.y + ", " + movePacket.z + ", yRot=" + movePacket.yRot + ", xRot=" + movePacket.xRot + + ", onGround=" + movePacket.isOnGround() + ", hasPos=" + movePacket.hasPos + ", hasRot=" + movePacket.hasRot); + } + else if (packet instanceof ServerboundAcceptTeleportationPacket) { + Vec3 awaitPos = (Vec3) AWAITING_POS_FIELD.get(oldListener); + int awaitTeleportId = AWAITING_TELEPORT_FIELD.getInt(oldListener); + ServerboundAcceptTeleportationPacket acceptPacket = (ServerboundAcceptTeleportationPacket) packet; + DenizenNetworkManagerImpl.doPacketOutput("Packet ServerboundAcceptTeleportationPacket sent from " + player.getScoreboardName() + + " with ID=" + acceptPacket.getId() + ", awaitingTeleport=" + awaitTeleportId + ", awaitPos=" + awaitPos); + } + else { + DenizenNetworkManagerImpl.doPacketOutput("Packet: " + packet.getClass().getCanonicalName() + " sent from " + player.getScoreboardName()); + } + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public boolean handlePacketIn(Packet packet) { + denizenNetworkManager.packetsReceived++; + if (NMSHandler.debugPackets) { + debugPacketOutput(packet); + } + if (PlayerSendPacketScriptEvent.instance.eventData.isEnabled) { + if (PlayerSendPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) { + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Denied packet-in " + packet.getClass().getCanonicalName() + " from " + player.getScoreboardName() + " due to event"); + } + return true; + } + } + return false; + } + + @Override + public void handleChatAck(ServerboundChatAckPacket serverboundchatackpacket) { + oldListener.handleChatAck(serverboundchatackpacket); + } + + @Override + public void handlePlayerInput(ServerboundPlayerInputPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlayerInput(packet); + } + + @Override + public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleMoveVehicle(packet); + } + + @Override + public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleAcceptTeleportPacket(packet); + } + + @Override + public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleRecipeBookSeenRecipePacket(packet); + } + + @Override + public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleRecipeBookChangeSettingsPacket(packet); + } + + @Override + public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSeenAdvancements(packet); + } + + @Override + public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleCustomCommandSuggestions(packet); + } + + @Override + public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetCommandBlock(packet); + } + + @Override + public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetCommandMinecart(packet); + } + + @Override + public void handleAcceptPlayerLoad(ServerboundPlayerLoadedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleAcceptPlayerLoad(packet); + } + + @Override + public void handlePickItemFromBlock(ServerboundPickItemFromBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePickItemFromBlock(packet); + } + + @Override + public void handlePickItemFromEntity(ServerboundPickItemFromEntityPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePickItemFromEntity(packet); + } + + @Override + public void handleRenameItem(ServerboundRenameItemPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleRenameItem(packet); + } + + @Override + public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetBeaconPacket(packet); + } + + @Override + public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetStructureBlock(packet); + } + + @Override + public void handleSetTestBlock(ServerboundSetTestBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetTestBlock(packet); + } + + @Override + public void handleTestInstanceBlockAction(ServerboundTestInstanceBlockActionPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleTestInstanceBlockAction(packet); + } + + @Override + public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetJigsawBlock(packet); + } + + @Override + public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleJigsawGenerate(packet); + } + + @Override + public void handleSelectTrade(ServerboundSelectTradePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSelectTrade(packet); + } + + @Override + public void handleEditBook(ServerboundEditBookPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleEditBook(packet); + } + + @Override + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleMovePlayer(packet); + } + + @Override + public void handlePlayerAction(ServerboundPlayerActionPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlayerAction(packet); + } + + @Override + public void handleUseItemOn(ServerboundUseItemOnPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleUseItemOn(packet); + } + + @Override + public void handleUseItem(ServerboundUseItemPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleUseItem(packet); + } + + @Override + public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleTeleportToEntityPacket(packet); + } + + @Override + public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleResourcePackResponse(packet); + } + + @Override + public void suspendFlushing() { + oldListener.suspendFlushing(); + } + + @Override + public void resumeFlushing() { + oldListener.resumeFlushing(); + } + + @Override + public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePaddleBoat(packet); + } + + @Override + public void handlePong(ServerboundPongPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePong(packet); + } + + @Override + public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetCarriedItem(packet); + } + + @Override + public void handleChat(ServerboundChatPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChat(packet); + } + + @Override + public void handleChatCommand(ServerboundChatCommandPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChatCommand(packet); + } + + @Override + public void chat(String s, PlayerChatMessage original, boolean async) { + oldListener.chat(s, original, async); + } + + @Override + public ConnectionProtocol protocol() { + return oldListener == null ? ConnectionProtocol.PLAY : oldListener.protocol(); + } + + @Override + public void handleAnimate(ServerboundSwingPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleAnimate(packet); + } + + @Override + public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlayerCommand(packet); + } + + @Override + public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatType.Bound chatmessagetype_a) { + oldListener.sendPlayerChatMessage(playerchatmessage, chatmessagetype_a); + } + + @Override + public void sendDisguisedChatMessage(Component ichatbasecomponent, ChatType.Bound chatmessagetype_a) { + oldListener.sendDisguisedChatMessage(ichatbasecomponent, chatmessagetype_a); + } + + @Override + public SocketAddress getRemoteAddress() { + return oldListener.getRemoteAddress(); + } + + @Override + public SocketAddress getRawAddress() { + return oldListener.getRawAddress(); + } + + @Override + public void switchToConfig() { + oldListener.switchToConfig(); + } + + @Override + public void handleInteract(ServerboundInteractPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleInteract(packet); + } + + @Override + public void handleClientCommand(ServerboundClientCommandPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleClientCommand(packet); + } + + @Override + public void handleContainerClose(ServerboundContainerClosePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleContainerClose(packet); + } + + @Override + public void handleContainerClick(ServerboundContainerClickPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleContainerClick(packet); + } + + @Override + public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlaceRecipe(packet); + } + + @Override + public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleContainerButtonClick(packet); + } + + @Override + public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSetCreativeModeSlot(packet); + } + + @Override + public void handleSignUpdate(ServerboundSignUpdatePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSignUpdate(packet); + } + + @Override + public void handleKeepAlive(ServerboundKeepAlivePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleKeepAlive(packet); + } + + @Override + public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePlayerAbilities(packet); + } + + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleClientInformation(packet); + } + + @Override + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleCustomPayload(packet); + } + + @Override + public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChangeDifficulty(packet); + } + + @Override + public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleLockDifficulty(packet); + } + + @Override + public void handleChatSessionUpdate(ServerboundChatSessionUpdatePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChatSessionUpdate(packet); + } + + @Override + public void handleConfigurationAcknowledged(ServerboundConfigurationAcknowledgedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleConfigurationAcknowledged(packet); + } + + @Override + public void handleChunkBatchReceived(ServerboundChunkBatchReceivedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChunkBatchReceived(packet); + } + + @Override + public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQueryPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleBlockEntityTagQuery(packet); + } + + @Override + public void handleBundleItemSelectedPacket(ServerboundSelectBundleItemPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleBundleItemSelectedPacket(packet); + } + + @Override + public void handleClientTickEnd(ServerboundClientTickEndPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleClientTickEnd(packet); + } + + @Override + public boolean hasInfiniteMaterials() { + return oldListener.hasInfiniteMaterials(); + } + + @Override + public void handleContainerSlotStateChanged(ServerboundContainerSlotStateChangedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleContainerSlotStateChanged(packet); + } + + @Override + public void handleDebugSubscriptionRequest(ServerboundDebugSubscriptionRequestPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleDebugSubscriptionRequest(packet); + } + + @Override + public void handleEntityTagQuery(ServerboundEntityTagQueryPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleEntityTagQuery(packet); + } + + @Override + public void handlePingRequest(ServerboundPingRequestPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handlePingRequest(packet); + } + + @Override + public void handleSignedChatCommand(ServerboundChatCommandSignedPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleSignedChatCommand(packet); + } + + @Override + public void handleChangeGameMode(ServerboundChangeGameModePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleChangeGameMode(packet); + } + + @Override + public void handleCustomClickAction(ServerboundCustomClickActionPacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleCustomClickAction(packet); + } + + @Override + public void handleCookieResponse(ServerboundCookieResponsePacket packet) { + if (handlePacketIn(packet)) { return; } + oldListener.handleCookieResponse(packet); + } + + @Override + public ServerPlayer getPlayer() { + return oldListener.getPlayer(); + } + + @Override + public PacketFlow flow() { + return oldListener == null ? PacketFlow.SERVERBOUND : oldListener.flow(); + } + + @Override + public void onPacketError(Packet var0, Exception var1) throws ReportedException { + oldListener.onPacketError(var0, var1); + } + + @Override + public DisconnectionDetails createDisconnectionInfo(Component var0, Throwable var1) { + return oldListener.createDisconnectionInfo(var0, var1); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/DenizenNetworkManagerImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/DenizenNetworkManagerImpl.java new file mode 100644 index 0000000000..4689bbe78d --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/DenizenNetworkManagerImpl.java @@ -0,0 +1,581 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers; + +import com.denizenscript.denizen.events.player.PlayerReceivesPacketScriptEvent; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v26_2.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet.*; +import com.denizenscript.denizen.utilities.Settings; +import com.denizenscript.denizen.utilities.packets.NetworkInterceptCodeGen; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import net.minecraft.network.*; +import net.minecraft.network.chat.Component; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.protocol.login.ClientLoginPacketListener; +import net.minecraft.network.protocol.status.ClientStatusPacketListener; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerCommonPacketListenerImpl; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.network.ServerPlayerConnection; +import net.minecraft.util.debugchart.LocalSampleLogger; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import javax.annotation.Nullable; +import javax.crypto.Cipher; +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.net.SocketAddress; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class DenizenNetworkManagerImpl extends Connection { + + public static > T copyPacket(T original, StreamCodec packetCodec) { + try { + RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry()); + packetCodec.encode(copier, original); + return packetCodec.decode(copier); + } + catch (Throwable ex) { + Debug.echoError(ex); + return null; + } + } + + @FunctionalInterface + public interface PacketHandler> { + Packet handlePacket(DenizenNetworkManagerImpl networkManager, T packet) throws Throwable; + } + + public static final Map>, List>> packetHandlers = new HashMap<>(); + + public static > void registerPacketHandler(Class packetClass, PacketHandler handler) { + packetHandlers.computeIfAbsent(packetClass, k -> new ArrayList<>()).add(handler); + } + + public static > void registerPacketHandler(Class packetClass, BiConsumer handler) { + registerPacketHandler(packetClass, (networkManager, packet) -> { + handler.accept(networkManager, packet); + return packet; + }); + } + + public final Connection oldManager; + public final DenizenPacketListenerImpl packetListener; + public final ServerPlayer player; + public int packetsSent, packetsReceived; + + public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManager) { + super(getProtocolDirection(oldManager)); + this.oldManager = oldManager; + this.channel = oldManager.channel; + this.player = entityPlayer; + packetListener = (DenizenPacketListenerImpl) NetworkInterceptCodeGen.generateAppropriateInterceptor(this, entityPlayer, DenizenPacketListenerImpl.class, AbstractListenerPlayInImpl.class, ServerGamePacketListenerImpl.class); + if (!(oldManager.getPacketListener() instanceof ServerConfigurationPacketListener)) { + setListener(packetListener); + } + } + + public void setListener(PacketListener listener) { + try { + Connection_packetListener.set(oldManager, listener); + } + catch (IllegalAccessException e) { + Debug.echoError(e); + throw new RuntimeException("Failed to set packet listener due to reflection error", e); + } + } + + public static Connection getConnection(ServerPlayer player) { + try { + return (Connection) ServerGamePacketListener_ConnectionField.get(player.connection); + } + catch (Throwable ex) { + Debug.echoError(ex); + throw new RuntimeException("Failed to get connection from player due to reflection error", ex); + } + } + + public static Connection getConnection(Player player) { + return getConnection(((CraftPlayer) player).getHandle()); + } + + public static DenizenNetworkManagerImpl getNetworkManager(ServerPlayer player) { + return (DenizenNetworkManagerImpl) getConnection(player); + } + + public static DenizenNetworkManagerImpl getNetworkManager(Player player) { + return getNetworkManager(((CraftPlayer) player).getHandle()); + } + + public static void setNetworkManager(Player player) { + ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle(); + ServerGamePacketListenerImpl playerConnection = entityPlayer.connection; + setNetworkManager(playerConnection, new DenizenNetworkManagerImpl(entityPlayer, getConnection(entityPlayer))); + } + + public static void enableNetworkManager() { + for (World w : Bukkit.getWorlds()) { + for (ChunkMap.TrackedEntity tracker : ((CraftWorld) w).getHandle().getChunkSource().chunkMap.entityMap.values()) { + ArrayList connections = new ArrayList<>(tracker.seenBy); + tracker.seenBy.clear(); + for (ServerPlayerConnection connection : connections) { + tracker.seenBy.add(connection.getPlayer().connection); + } + } + } + } + + @Override + public int hashCode() { + return oldManager.hashCode(); + } + + @Override + public boolean equals(Object c2) { + return oldManager.equals(c2); + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + oldManager.channelRegistered(ctx); + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + oldManager.channelUnregistered(ctx); + } + + @Override + public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception { + oldManager.channelActive(channelhandlercontext); + } + + @Override + public void channelInactive(ChannelHandlerContext channelhandlercontext) { + oldManager.channelInactive(channelhandlercontext); + } + + @Override + public boolean isSharable() { + return oldManager.isSharable(); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + oldManager.handlerAdded(ctx); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + oldManager.handlerRemoved(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) { + oldManager.exceptionCaught(channelhandlercontext, throwable); + } + + @Override + public void setupInboundProtocol(ProtocolInfo protocolinfo, T t0) { + oldManager.setupInboundProtocol(protocolinfo, t0); + } + + @Override + public void setupOutboundProtocol(ProtocolInfo protocolinfo) { + oldManager.setupOutboundProtocol(protocolinfo); + } + + @Override + protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { + if (oldManager.channel.isOpen()) { + try { + packet.handle(this.packetListener); + } + catch (Exception e) { + // Do nothing + } + } + } + + @Override + public void setListenerForServerboundHandshake(PacketListener packetlistener) { + oldManager.setListenerForServerboundHandshake(packetlistener); + } + + @Override + public void initiateServerboundStatusConnection(String s, int i, ClientStatusPacketListener packetstatusoutlistener) { + oldManager.initiateServerboundStatusConnection(s, i, packetstatusoutlistener); + } + + @Override + public void initiateServerboundPlayConnection(String s, int i, ClientLoginPacketListener packetloginoutlistener) { + oldManager.initiateServerboundPlayConnection(s, i, packetloginoutlistener); + } + + @Override + public void initiateServerboundPlayConnection(String s, int i, ProtocolInfo protocolinfo, ProtocolInfo protocolinfo1, C c0, boolean flag) { + oldManager.initiateServerboundPlayConnection(s, i, protocolinfo, protocolinfo1, c0, flag); + } + + @Override + public void send(Packet packet) { + send(packet, null); + } + + public static void doPacketOutput(String text) { + if (!NMSHandler.debugPackets) { + return; + } + if (NMSHandler.debugPacketFilter == null || NMSHandler.debugPacketFilter.trim().isEmpty() + || CoreUtilities.toLowerCase(text).contains(NMSHandler.debugPacketFilter)) { + Debug.log(text); + } + } + + public void debugOutputPacket(Packet packet) { + if (packet instanceof ClientboundSetEntityDataPacket) { + StringBuilder output = new StringBuilder(128); + output.append("Packet: ClientboundSetEntityDataPacket sent to ").append(player.getScoreboardName()).append(" for entity ID: ").append(((ClientboundSetEntityDataPacket) packet).id()).append(": "); + List> list = ((ClientboundSetEntityDataPacket) packet).packedItems(); + if (list == null) { + output.append("None"); + } + else { + for (SynchedEntityData.DataValue data : list) { + output.append('[').append(data.id()).append(": ").append(data.value()).append("], "); + } + } + doPacketOutput(output.toString()); + } + else if (packet instanceof ClientboundSetEntityMotionPacket) { + ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet; + doPacketOutput("Packet: ClientboundSetEntityMotionPacket sent to " + player.getScoreboardName() + " for entity ID: " + velPacket.id() + ": " + velPacket.movement()); + } + else if (packet instanceof ClientboundAddEntityPacket) { + ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet; + doPacketOutput("Packet: ClientboundAddEntityPacket sent to " + player.getScoreboardName() + " for entity ID: " + addEntityPacket.getId() + ": " + "uuid: " + addEntityPacket.getUUID() + + ", type: " + addEntityPacket.getType() + ", at: " + addEntityPacket.getX() + "," + addEntityPacket.getY() + "," + addEntityPacket.getZ() + ", data: " + addEntityPacket.getData()); + } + else if (packet instanceof ClientboundMapItemDataPacket) { + ClientboundMapItemDataPacket mapPacket = (ClientboundMapItemDataPacket) packet; + doPacketOutput("Packet: ClientboundMapItemDataPacket sent to " + player.getScoreboardName() + " for map ID: " + mapPacket.mapId() + ", scale: " + mapPacket.scale() + ", locked: " + mapPacket.locked()); + } + else if (packet instanceof ClientboundRemoveEntitiesPacket) { + ClientboundRemoveEntitiesPacket removePacket = (ClientboundRemoveEntitiesPacket) packet; + doPacketOutput("Packet: ClientboundRemoveEntitiesPacket sent to " + player.getScoreboardName() + " for entities: " + removePacket.getEntityIds().stream().map(Object::toString).collect(Collectors.joining(", "))); + } + else if (packet instanceof ClientboundPlayerInfoUpdatePacket) { + ClientboundPlayerInfoUpdatePacket playerInfoPacket = (ClientboundPlayerInfoUpdatePacket) packet; + doPacketOutput("Packet: ClientboundPlayerInfoPacket sent to " + player.getScoreboardName() + " of types " + playerInfoPacket.actions() + " for player profiles: " + + playerInfoPacket.entries().stream().map(p -> "mode=" + p.gameMode() + "/latency=" + p.latency() + "/display=" + p.displayName() + "/name=" + p.profile().name() + "/id=" + p.profile().id() + "/" + + p.profile().properties().asMap().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue().stream().map(v -> v.value() + ";" + v.signature()).collect(Collectors.joining(";;;"))).collect(Collectors.joining("/"))).collect(Collectors.joining(", "))); + } + else { + doPacketOutput("Packet: " + packet.getClass().getCanonicalName() + " sent to " + player.getScoreboardName()); + } + } + + @Override + public void send(Packet packet, ChannelFutureListener channelFutureListener) { + send(packet, channelFutureListener, true); + } + + @Override + public void send(Packet packet, @Nullable ChannelFutureListener channelFutureListener, boolean flush) { + if (!Bukkit.isPrimaryThread()) { + if (Settings.cache_warnOnAsyncPackets + && !(packet instanceof ClientboundSystemChatPacket) && !(packet instanceof ClientboundPlayerChatPacket) // Vanilla supports an async chat system, though it's normally disabled, some plugins use this as justification for sending messages async + && !(packet instanceof ClientboundCommandSuggestionsPacket)) { // Async tab complete is wholly unsupported in Spigot (and will cause an exception), however Paper explicitly adds async support (for unclear reasons), so let it through too + Debug.echoError("Warning: packet sent off main thread! This is completely unsupported behavior! Denizen network interceptor ignoring packet to avoid crash. Packet class: " + + packet.getClass().getCanonicalName() + " sent to " + player.getScoreboardName() + " identify the sender of the packet from the stack trace:"); + try { + throw new RuntimeException("Trace"); + } + catch (Exception ex) { + Debug.echoError(ex); + } + } + oldManager.send(packet, channelFutureListener, flush); + return; + } + if (NMSHandler.debugPackets) { + debugOutputPacket(packet); + } + packetsSent++; + if (packet instanceof ClientboundBundlePacket bundlePacket) { + List> processedPackets = new ArrayList<>(); + boolean anyChange = false; + for (Packet _subPacket : bundlePacket.subPackets()) { + // Bundle packets with non-game packets shouldn't ever be sent while the Denizen interceptor is active + Packet subPacket = (Packet) _subPacket; + Packet processed = processPacketHandlersFor(subPacket); + anyChange = anyChange || processed != subPacket; + if (processed != null) { + processedPackets.add(processed); + } + } + if (processedPackets.isEmpty()) { + return; + } + if (anyChange) { + packet = new ClientboundBundlePacket(processedPackets); + } + } + else { + Packet processed = processPacketHandlersFor((Packet) packet); + if (processed == null) { + return; + } + packet = processed; + } + oldManager.send(packet, channelFutureListener, flush); + } + + @Override + public void runOnceConnected(Consumer consumer) { + oldManager.runOnceConnected(consumer); + } + + @Override + public void flushChannel() { + oldManager.flushChannel(); + } + + public Packet processPacketHandlersFor(Packet packet) { + if (packet == null) { + return null; + } + List> packetHandlers = DenizenNetworkManagerImpl.packetHandlers.get(packet.getClass()); + if (packetHandlers != null) { + for (PacketHandler _packetHandler : packetHandlers) { + PacketHandler> packetHandler = (PacketHandler>) _packetHandler; + Packet processed; + try { + processed = packetHandler.handlePacket(this, packet); + } + catch (Throwable ex) { + Debug.echoError("Packet handler for " + packet.getClass().getCanonicalName() + " threw an exception:"); + Debug.echoError(ex); + continue; + } + if (processed == null) { + if (NMSHandler.debugPackets) { + doPacketOutput("DENIED PACKET - " + packet.getClass().getCanonicalName() + " DENIED FROM SEND TO " + player.getScoreboardName()); + } + return null; + } + packet = processed; + } + } + if (PlayerReceivesPacketScriptEvent.instance.eventData.isEnabled & PlayerReceivesPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) { + if (NMSHandler.debugPackets) { + doPacketOutput("DENIED PACKET - " + packet.getClass().getCanonicalName() + " DENIED FROM SEND TO " + player.getScoreboardName() + " due to event"); + } + return null; + } + return packet; + } + + static { + ActionBarEventPacketHandlers.registerHandlers(); + AttachPacketHandlers.registerHandlers(); + BlockLightPacketHandlers.registerHandlers(); + DenizenPacketHandlerPacketHandlers.registerHandlers(); + EntityMetadataPacketHandlers.registerHandlers(); + DisguisePacketHandlers.registerHandlers(); + FakeBlocksPacketHandlers.registerHandlers(); + FakeEquipmentPacketHandlers.registerHandlers(); + FakePlayerPacketHandlers.registerHandlers(); + HiddenEntitiesPacketHandlers.registerHandlers(); + HideParticlesPacketHandlers.registerHandlers(); + PlayerHearsSoundEventPacketHandlers.registerHandlers(); + ProfileEditorImpl.registerHandlers(); + TablistUpdateEventPacketHandlers.registerHandlers(); + } + + @Override + public void tick() { + oldManager.tick(); + } + + @Override + public SocketAddress getRemoteAddress() { + return oldManager.getRemoteAddress(); + } + + @Override + public String getLoggableAddress(boolean flag) { + return oldManager.getLoggableAddress(flag); + } + + @Override + public void disconnect(Component ichatbasecomponent) { + if (!player.getBukkitEntity().isOnline()) { // Workaround Paper duplicate quit event issue + return; + } + oldManager.disconnect(ichatbasecomponent); + } + + @Override + public void disconnect(DisconnectionDetails disconnectiondetails) { + oldManager.disconnect(disconnectiondetails); + } + + @Override + public boolean isMemoryConnection() { + return oldManager != null && oldManager.isMemoryConnection(); + } + + @Override + public PacketFlow getReceiving() { + return oldManager.getReceiving(); + } + + @Override + public PacketFlow getSending() { + return oldManager.getSending(); + } + + @Override + public void configurePacketHandler(ChannelPipeline channelpipeline) { + oldManager.configurePacketHandler(channelpipeline); + } + + @Override + public void setEncryptionKey(Cipher cipher, Cipher cipher1) { + oldManager.setEncryptionKey(cipher, cipher1); + } + + @Override + public void setIntendedProfileId(UUID profileId) { + oldManager.setIntendedProfileId(profileId); + } + + @Override + public UUID getIntendedProfileId() { + return oldManager.getIntendedProfileId(); + } + + @Override + public boolean isConnected() { + return oldManager.isConnected(); + } + + @Override + public boolean isConnecting() { + return oldManager.isConnecting(); + } + + @Override + public PacketListener getPacketListener() { + return oldManager.getPacketListener(); + } + + @Override + public DisconnectionDetails getDisconnectionDetails() { + return oldManager.getDisconnectionDetails(); + } + + @Override + public void setReadOnly() { + oldManager.setReadOnly(); + } + + @Override + public void setupCompression(int i, boolean b) { + oldManager.setupCompression(i, b); + } + + @Override + public void handleDisconnection() { + oldManager.handleDisconnection(); + } + + @Override + public float getAverageReceivedPackets() { + return oldManager.getAverageReceivedPackets(); + } + + @Override + public float getAverageSentPackets() { + return oldManager.getAverageSentPackets(); + } + + @Override + public void setBandwidthLogger(LocalSampleLogger localsamplelogger) { + oldManager.setBandwidthLogger(localsamplelogger); + } + + ////////////////////////////////// + //// Reflection Methods/Fields + /////////// + + private static final Field protocolDirectionField = ReflectionHelper.getFields(Connection.class).get("receiving", PacketFlow.class); + public static final Field Connection_packetListener = ReflectionHelper.getFields(Connection.class).get("packetListener", PacketListener.class); + private static final Field ServerGamePacketListener_ConnectionField = ReflectionHelper.getFields(ServerCommonPacketListenerImpl.class).get("connection"); + private static final MethodHandle ServerGamePacketListener_ConnectionSetter = ReflectionHelper.getFinalSetter(ServerCommonPacketListenerImpl.class, "connection"); + + private static PacketFlow getProtocolDirection(Connection networkManager) { + PacketFlow direction = null; + try { + direction = (PacketFlow) protocolDirectionField.get(networkManager); + } + catch (Exception e) { + Debug.echoError(e); + } + return direction; + } + + private static void setNetworkManager(ServerGamePacketListenerImpl playerConnection, Connection networkManager) { + try { + ServerGamePacketListener_ConnectionSetter.invoke(playerConnection, networkManager); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + @Override + public boolean acceptInboundMessage(Object msg) throws Exception { + return oldManager.acceptInboundMessage(msg); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + oldManager.channelRead(ctx, msg); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + oldManager.channelReadComplete(ctx); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + oldManager.userEventTriggered(ctx, evt); + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + oldManager.channelWritabilityChanged(ctx); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/DenizenPacketListenerImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/DenizenPacketListenerImpl.java new file mode 100644 index 0000000000..b69819fd14 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/DenizenPacketListenerImpl.java @@ -0,0 +1,121 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers; + +import com.denizenscript.denizen.events.player.PlayerChangesSignScriptEvent; +import com.denizenscript.denizen.events.player.PlayerSteersEntityScriptEvent; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v26_2.impl.network.packets.PacketInResourcePackStatusImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.packets.PacketInSteerVehicleImpl; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.objects.MaterialTag; +import com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand; +import com.denizenscript.denizen.utilities.packets.DenizenPacketHandler; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ServerboundResourcePackPacket; +import net.minecraft.network.protocol.game.*; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.event.block.SignChangeEvent; + +public class DenizenPacketListenerImpl extends AbstractListenerPlayInImpl { + + public BlockPos fakeSignExpected; + + public DenizenPacketListenerImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer) { + super(networkManager, entityPlayer, entityPlayer.connection); + } + + @Override + public void handlePlayerInput(final ServerboundPlayerInputPacket packet) { + if (!PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) { + super.handlePlayerInput(packet); + return; + } + DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInSteerVehicleImpl(packet), () -> super.handlePlayerInput(packet)); + } + + @Override + public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { + DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInResourcePackStatusImpl(packet)); + super.handleResourcePackResponse(packet); + } + + @Override + public void handleUseItem(ServerboundUseItemPacket packet) { + DenizenPacketHandler.instance.receivePlacePacket(player.getBukkitEntity()); + super.handleUseItem(packet); + } + + @Override + public void handlePlayerAction(ServerboundPlayerActionPacket packet) { + DenizenPacketHandler.instance.receiveDigPacket(player.getBukkitEntity()); + super.handlePlayerAction(packet); + } + + @Override + public void handleAnimate(ServerboundSwingPacket packet) { + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer()); + if (override != null && (override.hand != null || override.offhand != null)) { + player.getBukkitEntity().updateInventory(); + } + super.handleAnimate(packet); + } + + @Override + public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) { + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer()); + if (override != null && override.hand != null) { + Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 2); + } + super.handleSetCarriedItem(packet); + } + + @Override + public void handleContainerClick(ServerboundContainerClickPacket packet) { + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer()); + if (override != null && packet.containerId() == 0) { + Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 1); + } + super.handleContainerClick(packet); + } + + @Override + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { + if (NMSHandler.debugPackets) { + Debug.log("Custom packet payload: " + packet.payload().type().id().toString() + " sent from " + player.getScoreboardName()); + } + super.handleCustomPayload(packet); + } + + @Override + public void handleSignUpdate(ServerboundSignUpdatePacket packet) { + if (fakeSignExpected != null && packet.getPos().equals(fakeSignExpected)) { + LocationTag loc = new LocationTag(player.getBukkitEntity().getWorld(), fakeSignExpected.getX(), fakeSignExpected.getY(), fakeSignExpected.getZ()); + this.connection.send(new ClientboundBlockUpdatePacket(player.level(), fakeSignExpected)); + PlayerChangesSignScriptEvent evt = (PlayerChangesSignScriptEvent) PlayerChangesSignScriptEvent.instance.clone(); + evt.material = new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN); + evt.location = new LocationTag(player.getBukkitEntity().getLocation()); + evt.event = new SignChangeEvent(CraftBlock.at(player.level(), fakeSignExpected), player.getBukkitEntity(), packet.getLines()); + fakeSignExpected = null; + evt.fire(evt.event); + } + super.handleSignUpdate(packet); + } + + @Override + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + if (DenizenPacketHandler.forceNoclip.contains(player.getUUID())) { + player.noPhysics = true; + } + super.handleMovePlayer(packet); + } + + // For compatibility with other plugins using Reflection weirdly... + @Override + public void send(Packet packet) { + super.send(packet); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/FakeBlockHelper.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/FakeBlockHelper.java new file mode 100644 index 0000000000..5b1abd82b5 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/FakeBlockHelper.java @@ -0,0 +1,291 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers; + +import com.denizenscript.denizen.Denizen; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.utilities.blocks.ChunkCoordinate; +import com.denizenscript.denizen.utilities.blocks.FakeBlock; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.shorts.ShortArraySet; +import it.unimi.dsi.fastutil.shorts.ShortObjectPair; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LightBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.DataLayer; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.Strategy; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.block.data.CraftBlockData; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.*; + +public class FakeBlockHelper { + + public static final MethodHandle CHUNKDATA_BLOCK_ENTITIES = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getGetter("blockEntitiesData", List.class); + public static final MethodHandle CHUNKDATA_BUFFER_SETTER = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getSetter("buffer", byte[].class); + public static final Class CHUNKDATA_BLOCKENTITYINFO_CLASS = ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0]; + public static final MethodHandle CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).getGetter("packedXZ"); + public static final MethodHandle CHUNKDATA_BLOCKENTITYINFO_Y = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).getGetter("y"); + + public static final PalettedContainer EMPTY_BLOCKS_CONTAINER = new PalettedContainer<>(Blocks.AIR.defaultBlockState(), Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY)); + public static final BlockState MAX_LIGHT_BLOCK = Blocks.LIGHT.defaultBlockState().setValue(LightBlock.LEVEL, 15), AIR = Blocks.AIR.defaultBlockState(); + public static final byte[] MAX_LIGHT_SECTION = new DataLayer(15).getData(), MIN_LIGHT_SECTION = new DataLayer(0).getData(); + + public static BlockState getNMSState(FakeBlock block) { + return ((CraftBlockData) block.material.getModernData()).getState(); + } + + public static Field PAPER_CHUNK_READY; + public static boolean tryPaperPatch = true; + + public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket) { + if (!Denizen.supportsPaper || !tryPaperPatch) { + return; + } + try { + if (PAPER_CHUNK_READY == null) { + PAPER_CHUNK_READY = ReflectionHelper.getFields(ClientboundLevelChunkWithLightPacket.class).get("ready"); + } + } + catch (Throwable ex) { + tryPaperPatch = false; + Debug.echoError("Paper packet patch failed:"); + Debug.echoError(ex); + return; + } + try { + PAPER_CHUNK_READY.setBoolean(newPacket, true); + } + catch (Throwable ex) { + Debug.echoError(ex); + } + } + + public static boolean hasLight(DataLayer lightData) { + if (lightData == null || lightData.isEmpty()) { + return false; + } + // Sometimes Mojang sends "0" for every block instead of an empty layer?? + // Reproduction: fully underground chunk section, place a single torch & fake block, reload chunk + for (byte value : lightData.getData()) { + if (value != 0) { + return true; + } + } + return false; + } + + public static Packet handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalChunkPacket, int chunkX, int chunkZ, Int2ObjectMap> blocksBySection, FakeBlock.FakeBlockMap fakeBlockMap) throws Throwable { + ClientboundLevelChunkWithLightPacket copiedChunkPacket = DenizenNetworkManagerImpl.copyPacket(originalChunkPacket, ClientboundLevelChunkWithLightPacket.STREAM_CODEC); + copyPacketPaperPatch(copiedChunkPacket); + // TODO pass coord? + boolean isNaturalLoad = !FakeBlock.scheduled.containsKey(new ChunkCoordinate(chunkX, chunkZ, world.getName())); + // A list of ClientboundLevelChunkPacketData$BlockEntityInfo, every block entity in the chunk + List blockEntities = (List) CHUNKDATA_BLOCK_ENTITIES.invokeExact(copiedChunkPacket.getChunkData()); + LocationTag location = new LocationTag(world, 0, 0, 0); + ListIterator blockEntitiesIterator = blockEntities.listIterator(); + // Clear every block entity being replaced by a fake block + while (blockEntitiesIterator.hasNext()) { + Object blockEnt = blockEntitiesIterator.next(); + int xz = (int) CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.invoke(blockEnt); + int y = (int) CHUNKDATA_BLOCKENTITYINFO_Y.invoke(blockEnt); + int relativeX = SectionPos.sectionRelative(xz >> 4); + int relativeZ = SectionPos.sectionRelative(xz); + int x = SectionPos.sectionToBlockCoord(chunkX, relativeX); + int z = SectionPos.sectionToBlockCoord(chunkZ, relativeZ); + location.setX(x); + location.setY(y); + location.setZ(z); + if (fakeBlockMap.byLocation.containsKey(location)) { + blockEntitiesIterator.remove(); + } + } + // This list will contain at least the (modified) chunk packet, and one "patch" packet for every chunk section + List> packets = new ArrayList<>(blocksBySection.size() + 1); + packets.add(copiedChunkPacket); + + // Get the original chunk data to read, and a new buffer of the same size to write + FriendlyByteBuf rawChunkData = originalChunkPacket.getChunkData().getReadBuffer(); + FriendlyByteBuf newChunkData = new FriendlyByteBuf(Unpooled.buffer(rawChunkData.readableBytes())); + final int minChunkY = SectionPos.blockToSectionCoord(world.getMinHeight()); + final int maxChunkY = SectionPos.blockToSectionCoord(world.getMaxHeight()); + Registry biomeRegistry = CraftRegistry.getMinecraftRegistry(Registries.BIOME); + ClientboundLightUpdatePacketData lightData = copiedChunkPacket.getLightData(); + BitSet blockLightMask = lightData.getBlockYMask(), blockNoLightMask = lightData.getEmptyBlockYMask(), skyLightMask = lightData.getSkyYMask(), skyNoLightMask = lightData.getEmptySkyYMask(); + // The list of light data per section only has entries for sections with lighting, need to know how many sections had lighting to know what index to remove + int blockLitSections = 0, skyLitSections = 0; + boolean hadFirstSkyLight = false; + for (int chunkY = minChunkY; chunkY < maxChunkY; chunkY++) { + int blockCount = rawChunkData.readShort(); + int fluidCount = rawChunkData.readShort(); + PalettedContainer states = new PalettedContainer<>(Blocks.AIR.defaultBlockState(), Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY)); + states.read(rawChunkData); + PalettedContainer> biomes = new PalettedContainer<>(biomeRegistry.getOrThrow(Biomes.PLAINS), Strategy.createForBiomes(biomeRegistry.asHolderIdMap())); + biomes.read(rawChunkData); + List fakeBlocksInSection = blocksBySection.get(chunkY); + // The light data counts up from 0 instead of minChunkY, and has a buffer of 1 extra section above and below the world (hence + 1) + int lightingIndex = chunkY - minChunkY + 1; + boolean hasBlockLight = blockLightMask.get(lightingIndex), hasSkyLight = skyLightMask.get(lightingIndex); + boolean hasEffectiveSkyLight = false; + if (fakeBlocksInSection != null) { + List> lightPatch = new ArrayList<>(); + DataLayer blockLayer = null, skyLayer = null; + Debug.log("--------------"); + Debug.log("Has block light: " + hasBlockLight); + Debug.log("Has sky light: " + hasSkyLight); + // Chunk packets schedule light updates for the next render tick, delete all lighting data for these sections so that they don't get scheduled and override the lighting from the fake block patches + // Clears both the "has light" mask and the "has no light" mask to make this section effectively non-existent + if (hasSkyLight) { + skyLightMask.clear(lightingIndex); + skyLayer = new DataLayer(lightData.getSkyUpdates().remove(skyLitSections)); + hasSkyLight = false; + if (!hadFirstSkyLight) { + Debug.log(">>> Found first sky light"); + } + hasEffectiveSkyLight = hasLight(skyLayer); + if (hasEffectiveSkyLight) { + hadFirstSkyLight = true; + } + } + else { + skyNoLightMask.clear(lightingIndex); + } + if (hasBlockLight) { + blockLightMask.clear(lightingIndex); + blockLayer = new DataLayer(lightData.getBlockUpdates().remove(blockLitSections)); + hasBlockLight = false; + } + else { + blockNoLightMask.clear(lightingIndex); + } + // On natural loads the client is receiving the chunk for the first time and has no lighting data for the world around it, making generating lighting for the fake block patches impossible + // Provide best-effort dark/bright lights on first load based on whether we've reached the first sky lit section + if (isNaturalLoad) { + Debug.log(">>> Adding " + (hadFirstSkyLight ? "bright" : "dark") + " sky light"); + byte[] newLightData = hadFirstSkyLight ? MAX_LIGHT_SECTION : MIN_LIGHT_SECTION; + lightData.getSkyUpdates().add(skyLitSections, newLightData); + skyLightMask.set(lightingIndex); + hasSkyLight = true; + hasEffectiveSkyLight = hadFirstSkyLight; + skyLayer = new DataLayer(newLightData); + } + Debug.log("Post has block light: " + blockLightMask.get(lightingIndex)); + Debug.log("Post has sky light: " + skyLightMask.get(lightingIndex)); + Debug.log("--------------"); + // We're removing lighting data to allow lighting from the fake block patches, but that means we need to make the client update lighting for the real blocks in the section too (where needed) + if (hasLight(blockLayer) || hasEffectiveSkyLight) { + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + BlockState state = states.get(x, y, z); + boolean isAir = state.isAir(); + // Air blocks only need an update if they have non-air blocks below them + if (isAir && (y == 0 || states.get(x, y - 1, z).isAir())) { + continue; + } + // Solid blocks only need an update if they had any sort of lighting in the first place + if (!isAir && (!hasLight(blockLayer) || blockLayer.get(x, y, z) == 0)) { + continue; + } + // Based on SectionPos#sectionRelativePos + short offset = (short) (x << 8 | z << 4 | y << 0); + // "Toggle" the block to update it + lightPatch.add(ShortObjectPair.of(offset, isAir ? MAX_LIGHT_BLOCK : AIR)); + lightPatch.add(ShortObjectPair.of(offset, state)); + if (!isAir) { + if (state.getFluidState().isEmpty()) { + blockCount--; + } + else { + fluidCount--; + } + // No need to send the block in the chunk packet as it is sent in the lighting patch + states.set(x, y, z, AIR); + } + } + } + } + } + int fakesCount = fakeBlocksInSection.size(); + short[] offsets = new short[fakesCount]; + BlockState[] statesArr = new BlockState[fakesCount]; + for (int blockIndex = 0; blockIndex < fakesCount; blockIndex++) { + FakeBlock fakeBlock = fakeBlocksInSection.get(blockIndex); + int relativeX = SectionPos.sectionRelative(fakeBlock.location.getBlockX()); + int relativeY = SectionPos.sectionRelative(fakeBlock.location.getBlockY()); + int relativeZ = SectionPos.sectionRelative(fakeBlock.location.getBlockZ()); + BlockState oldState = states.get(relativeX, relativeY, relativeZ); + BlockState newState = ((CraftBlockData) fakeBlock.material.getModernData()).getState(); + short sectionRelativePos = SectionPos.sectionRelativePos(Handler.toBlockPos(fakeBlock.location)); + offsets[blockIndex] = sectionRelativePos; + statesArr[blockIndex] = newState; +// int maxLight = Math.max( +// skyLayer != null ? skyLayer.get(relativeX, relativeY, relativeZ) : 0, +// blockLayer != null ? blockLayer.get(relativeX, relativeY, relativeZ) : 0 +// ); + if (!oldState.isAir()) { + if (oldState.getFluidState().isEmpty()) { + blockCount--; + } + else { + fluidCount--; + } + states.set(relativeX, relativeY, relativeZ, AIR); +// if (maxLight <= 0 && !newState.isSolidRender()) { +// lightPatch.add(ShortObjectPair.of(sectionRelativePos, MAX_LIGHT_BLOCK)); +// } + } + } + SectionPos sectionPos = SectionPos.of(chunkX, chunkY, chunkZ); + if (!lightPatch.isEmpty()) { + int lightCount = lightPatch.size(); + short[] lightOffsets = new short[lightCount]; + BlockState[] lightStates = new BlockState[lightCount]; + for (int i = 0; i < lightCount; i++) { + ShortObjectPair pair = lightPatch.get(i); + lightOffsets[i] = pair.leftShort(); + lightStates[i] = pair.right(); + } + packets.add(new ClientboundSectionBlocksUpdatePacket(sectionPos, new ShortArraySet(lightOffsets), lightStates)); + } + packets.add(new ClientboundSectionBlocksUpdatePacket(sectionPos, new ShortArraySet(offsets), statesArr)); + } + // Now that we have the final decision on the section having light or not, update the counters + if (hasBlockLight) { + blockLitSections++; + } + if (hasSkyLight) { + skyLitSections++; + } + newChunkData.writeShort(blockCount); + newChunkData.writeShort(fluidCount); + // A section that has been cleared out seemingly carries more data than a section that never had anything (SimpleBitStorage vs ZeroBitStorage) + if (blockCount > 0 || fluidCount > 0) { + states.write(newChunkData); + } + else { + EMPTY_BLOCKS_CONTAINER.write(newChunkData); + } + biomes.write(newChunkData); + } + // The instance is already in the list, just update the underlying data + CHUNKDATA_BUFFER_SETTER.invokeExact(copiedChunkPacket.getChunkData(), newChunkData.array()); + return new ClientboundBundlePacket(packets); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/ActionBarEventPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/ActionBarEventPacketHandlers.java new file mode 100644 index 0000000000..5ddec205f5 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/ActionBarEventPacketHandlers.java @@ -0,0 +1,39 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.events.player.PlayerReceivesActionbarScriptEvent; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.objects.core.ElementTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +public class ActionBarEventPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetActionBarTextPacket.class, ActionBarEventPacketHandlers::processActionbarPacket); + } + + public static ClientboundSetActionBarTextPacket processActionbarPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetActionBarTextPacket actionbarPacket) { + PlayerReceivesActionbarScriptEvent event = PlayerReceivesActionbarScriptEvent.instance; + if (!event.loaded) { + return actionbarPacket; + } + event.reset(); + Component actionbarText = actionbarPacket.text(); + event.message = new ElementTag(FormattedTextHelper.stringify(Handler.componentToSpigot(actionbarText)), true); + event.rawJson = new ElementTag(CraftChatMessage.toJSON(actionbarText), true); + event.system = new ElementTag(false); + event.player = PlayerTag.mirrorBukkitPlayer(networkManager.player.getBukkitEntity()); + event = (PlayerReceivesActionbarScriptEvent) event.triggerNow(); + if (event.cancelled) { + return null; + } + if (event.modified) { + return new ClientboundSetActionBarTextPacket(Handler.componentToNMS(event.altMessageDetermination)); + } + return actionbarPacket; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/AttachPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/AttachPacketHandlers.java new file mode 100644 index 0000000000..96e3bad4fa --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/AttachPacketHandlers.java @@ -0,0 +1,307 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper; +import com.denizenscript.denizencore.utilities.CoreConfiguration; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.phys.Vec3; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.util.CraftVector; +import org.bukkit.util.Vector; + +import java.lang.reflect.Field; +import java.util.Set; + +public class AttachPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.class, AttachPacketHandlers::processAttachToForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRotateHeadPacket.class, AttachPacketHandlers::processAttachToForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, AttachPacketHandlers::processAttachToForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, AttachPacketHandlers::processAttachToForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRemoveEntitiesPacket.class, AttachPacketHandlers::processAttachToForPacket); + } + + public static Field POS_X_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("xa", short.class); + public static Field POS_Y_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("ya", short.class); + public static Field POS_Z_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("za", short.class); + public static Field YAW_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("yRot", byte.class); + public static Field PITCH_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get("xRot", byte.class); + + public static Vector VECTOR_ZERO = new Vector(0, 0, 0); + + public static void tryProcessMovePacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket packet, Entity e) throws IllegalAccessException { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + ClientboundMoveEntityPacket pNew; + int newId = att.attached.getBukkitEntity().getEntityId(); + if (packet instanceof ClientboundMoveEntityPacket.Pos) { + pNew = new ClientboundMoveEntityPacket.Pos(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.isOnGround()); + } + else if (packet instanceof ClientboundMoveEntityPacket.Rot) { + pNew = new ClientboundMoveEntityPacket.Rot(newId, Mth.packDegrees(packet.getYRot()), Mth.packDegrees(packet.getXRot()), packet.isOnGround()); + } + else if (packet instanceof ClientboundMoveEntityPacket.PosRot) { + pNew = new ClientboundMoveEntityPacket.PosRot(newId, packet.getXa(), packet.getYa(), packet.getZa(), Mth.packDegrees(packet.getYRot()), Mth.packDegrees(packet.getXRot()), packet.isOnGround()); + } + else { + if (CoreConfiguration.debugVerbose) { + Debug.echoError("Impossible move-entity packet class: " + packet.getClass().getCanonicalName()); + } + return; + } + if (att.positionalOffset != null) { + boolean isRotate = packet instanceof ClientboundMoveEntityPacket.PosRot || packet instanceof ClientboundMoveEntityPacket.Rot; + float yaw, pitch; + if (att.noRotate) { + Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle(); + yaw = attachedEntity.getYRot(); + pitch = attachedEntity.getXRot(); + } + else if (isRotate) { + yaw = packet.getYRot(); + pitch = packet.getXRot(); + } + else { + yaw = e.getYRot(); + pitch = e.getXRot(); + } + if (att.noPitch) { + pitch = ((CraftEntity) att.attached.getBukkitEntity()).getHandle().getXRot(); + } + float newYaw = yaw; + if (isRotate) { + newYaw = EntityAttachmentHelper.normalizeAngle(newYaw + att.positionalOffset.getYaw()); + pitch = EntityAttachmentHelper.normalizeAngle(pitch + att.positionalOffset.getPitch()); + } + Vector goalPosition = att.fixedForOffset(new Vector(e.getX(), e.getY(), e.getZ()), e.getYRot(), e.getXRot()); + Vector oldPos = att.visiblePositions.get(networkManager.player.getUUID()); + boolean forceTele = false; + if (oldPos == null) { + oldPos = att.attached.getLocation().toVector(); + forceTele = true; + } + Vector moveNeeded = goalPosition.clone().subtract(oldPos); + att.visiblePositions.put(networkManager.player.getUUID(), goalPosition.clone()); + int offX = (int) (moveNeeded.getX() * (32 * 128)); + int offY = (int) (moveNeeded.getY() * (32 * 128)); + int offZ = (int) (moveNeeded.getZ() * (32 * 128)); + if ((isRotate && att.offsetRelative) || forceTele || offX < Short.MIN_VALUE || offX > Short.MAX_VALUE + || offY < Short.MIN_VALUE || offY > Short.MAX_VALUE + || offZ < Short.MIN_VALUE || offZ > Short.MAX_VALUE) { + ClientboundTeleportEntityPacket newTeleportPacket = new ClientboundTeleportEntityPacket( + att.attached.getBukkitEntity().getEntityId(), + new PositionMoveRotation(CraftVector.toNMS(goalPosition), Vec3.ZERO, newYaw, pitch), + Set.of(), + e.onGround() + ); + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Move-Tele Packet: " + newTeleportPacket.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName() + " with original yaw " + yaw + " adapted to " + newYaw); + } + networkManager.oldManager.send(newTeleportPacket); + } + else { + POS_X_PACKENT.setShort(pNew, (short) Mth.clamp(offX, Short.MIN_VALUE, Short.MAX_VALUE)); + POS_Y_PACKENT.setShort(pNew, (short) Mth.clamp(offY, Short.MIN_VALUE, Short.MAX_VALUE)); + POS_Z_PACKENT.setShort(pNew, (short) Mth.clamp(offZ, Short.MIN_VALUE, Short.MAX_VALUE)); + if (isRotate) { + YAW_PACKENT.setByte(pNew, EntityAttachmentHelper.compressAngle(yaw)); + PITCH_PACKENT.setByte(pNew, EntityAttachmentHelper.compressAngle(pitch)); + } + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Move Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName() + " with original yaw " + yaw + " adapted to " + newYaw); + } + networkManager.oldManager.send(pNew); + } + } + else { + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Replica-Move Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName()); + } + networkManager.oldManager.send(pNew); + } + } + } + } + if (e.passengers != null && !e.passengers.isEmpty()) { + for (Entity ent : e.passengers) { + tryProcessMovePacketForAttach(networkManager, packet, ent); + } + } + } + + public static void tryProcessRotateHeadPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundRotateHeadPacket packet, Entity e) throws IllegalAccessException { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + float yaw = packet.getYHeadRot(); + Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle(); + if (att.positionalOffset != null) { + if (att.noRotate) { + yaw = attachedEntity.getYRot(); + } + yaw = EntityAttachmentHelper.normalizeAngle(yaw + att.positionalOffset.getYaw()); + } + ClientboundRotateHeadPacket pNew = new ClientboundRotateHeadPacket(attachedEntity, EntityAttachmentHelper.compressAngle(yaw)); + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Head Rotation Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName()); + } + networkManager.oldManager.send(pNew); + } + } + } + if (e.passengers != null && !e.passengers.isEmpty()) { + for (Entity ent : e.passengers) { + tryProcessRotateHeadPacketForAttach(networkManager, packet, ent); + } + } + } + + public static void tryProcessVelocityPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityMotionPacket packet, Entity e) { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + ClientboundSetEntityMotionPacket pNew = new ClientboundSetEntityMotionPacket(att.attached.getBukkitEntity().getEntityId(), packet.movement()); + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Velocity Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + " sent to " + networkManager.player.getScoreboardName()); + } + networkManager.oldManager.send(pNew); + } + } + } + if (e.passengers != null && !e.passengers.isEmpty()) { + for (Entity ent : e.passengers) { + tryProcessVelocityPacketForAttach(networkManager, packet, ent); + } + } + } + + public static void tryProcessTeleportPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket packet, Entity e, Vector relative) throws IllegalAccessException { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + ClientboundTeleportEntityPacket pNew; + Vector resultPos = CraftVector.toBukkit(packet.change().position()).add(relative); + if (att.positionalOffset != null) { + resultPos = att.fixedForOffset(resultPos, e.getYRot(), e.getXRot()); + float yaw, pitch; + if (att.noRotate) { + Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle(); + yaw = attachedEntity.getYRot(); + pitch = attachedEntity.getXRot(); + } + else { + yaw = packet.change().yRot(); + pitch = packet.change().xRot(); + } + if (att.noPitch) { + pitch = ((CraftEntity) att.attached.getBukkitEntity()).getHandle().getXRot(); + } + float newYaw = EntityAttachmentHelper.normalizeAngle(yaw + att.positionalOffset.getYaw()); + pitch = EntityAttachmentHelper.normalizeAngle(pitch + att.positionalOffset.getPitch()); + pNew = new ClientboundTeleportEntityPacket( + att.attached.getBukkitEntity().getEntityId(), + new PositionMoveRotation(CraftVector.toNMS(resultPos), packet.change().deltaMovement(), newYaw, pitch), + packet.relatives(), + packet.onGround() + ); + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("Attach Teleport Packet: " + pNew.getClass().getCanonicalName() + " for " + att.attached.getUUID() + + " sent to " + networkManager.player.getScoreboardName() + " with raw yaw " + yaw + " adapted to " + newYaw); + } + } + else { + pNew = new ClientboundTeleportEntityPacket(att.attached.getBukkitEntity().getEntityId(), packet.change(), packet.relatives(), packet.onGround()); + } + att.visiblePositions.put(networkManager.player.getUUID(), resultPos.clone()); + networkManager.oldManager.send(pNew); + } + } + } + if (e.passengers != null && !e.passengers.isEmpty()) { + for (Entity ent : e.passengers) { + tryProcessTeleportPacketForAttach(networkManager, packet, ent, new Vector(ent.getX() - e.getX(), ent.getY() - e.getY(), ent.getZ() - e.getZ())); + } + } + } + + public static Packet processAttachToForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (EntityAttachmentHelper.toEntityToData.isEmpty()) { + return packet; + } + try { + if (packet instanceof ClientboundMoveEntityPacket moveEntityPacket) { + Entity e = moveEntityPacket.getEntity(networkManager.player.level()); + if (e == null) { + return packet; + } + if (!e.isPassenger()) { + tryProcessMovePacketForAttach(networkManager, moveEntityPacket, e); + } + return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet; + } + else if (packet instanceof ClientboundRotateHeadPacket rotateHeadPacket) { + Entity e = rotateHeadPacket.getEntity(networkManager.player.level()); + if (e == null) { + return packet; + } + tryProcessRotateHeadPacketForAttach(networkManager, rotateHeadPacket, e); + return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet; + } + else if (packet instanceof ClientboundSetEntityMotionPacket setEntityMotionPacket) { + int ider = setEntityMotionPacket.id(); + Entity e = networkManager.player.level().getEntity(ider); + if (e == null) { + return packet; + } + tryProcessVelocityPacketForAttach(networkManager, setEntityMotionPacket, e); + return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet; + } + else if (packet instanceof ClientboundTeleportEntityPacket teleportEntityPacket) { + int ider = teleportEntityPacket.id(); + Entity e = networkManager.player.level().getEntity(ider); + if (e == null) { + return packet; + } + tryProcessTeleportPacketForAttach(networkManager, teleportEntityPacket, e, VECTOR_ZERO); + return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet; + } + else if (packet instanceof ClientboundRemoveEntitiesPacket removeEntitiesPacket) { + for (int id : removeEntitiesPacket.getEntityIds()) { + Entity e = networkManager.player.level().getEntity(id); + if (e != null) { + EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID()); + if (attList != null) { + for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) { + EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID()); + if (attMap.attached.isValid() && att != null) { + att.visiblePositions.remove(networkManager.player.getUUID()); + } + } + } + } + } + } + } + catch (Exception ex) { + Debug.echoError(ex); + } + return packet; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/BlockLightPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/BlockLightPacketHandlers.java new file mode 100644 index 0000000000..3ce4b72fb0 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/BlockLightPacketHandlers.java @@ -0,0 +1,27 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.abstracts.BlockLight; +import com.denizenscript.denizen.nms.v26_2.impl.blocks.BlockLightImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; + +public class BlockLightPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLightUpdatePacket.class, BlockLightPacketHandlers::processLightUpdatePacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, BlockLightPacketHandlers::processBlockUpdatePacket); + } + + public static void processLightUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundLightUpdatePacket lightUpdatePacket) { + if (!BlockLight.lightsByChunk.isEmpty()) { + BlockLightImpl.checkIfLightsBrokenByPacket(lightUpdatePacket, networkManager.player.level()); + } + } + + public static void processBlockUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockUpdatePacket blockUpdatePacket) { + if (!BlockLight.lightsByChunk.isEmpty()) { + BlockLightImpl.checkIfLightsBrokenByPacket(blockUpdatePacket, networkManager.player.level()); + } + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java new file mode 100644 index 0000000000..255a57fed6 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java @@ -0,0 +1,47 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.events.player.PlayerReceivesMessageScriptEvent; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.packets.PacketOutChatImpl; +import com.denizenscript.denizen.utilities.packets.DenizenPacketHandler; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; +import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; + +public class DenizenPacketHandlerPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSystemChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket); + } + + public static Packet processPacketHandlerForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (DenizenPacketHandler.instance.shouldInterceptChatPacket()) { + PacketOutChatImpl packetHelper = null; + boolean isActionbar = false; + if (packet instanceof ClientboundSystemChatPacket chatPacket) { + isActionbar = chatPacket.overlay(); + packetHelper = new PacketOutChatImpl(chatPacket); + if (packetHelper.rawJson == null) { // Makes no sense but this can be null in weird edge cases + return packet; + } + } + else if (packet instanceof ClientboundPlayerChatPacket playerChatPacket) { + packetHelper = new PacketOutChatImpl(playerChatPacket); + } + if (packetHelper != null) { + PlayerReceivesMessageScriptEvent result = DenizenPacketHandler.instance.sendPacket(networkManager.player.getBukkitEntity(), packetHelper); + if (result != null) { + if (result.cancelled) { + return null; + } + if (result.modified) { + return new ClientboundSystemChatPacket(result.altMessageDetermination, isActionbar); + } + } + } + } + return packet; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/DisguisePacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/DisguisePacketHandlers.java new file mode 100644 index 0000000000..c89e8fb819 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/DisguisePacketHandlers.java @@ -0,0 +1,160 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v26_2.helpers.PacketHelperImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.PlayerTag; +import com.denizenscript.denizen.scripts.commands.player.DisguiseCommand; +import com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper; +import com.denizenscript.denizen.utilities.entity.FakeEntity; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.level.Level; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.ToIntFunction; + +public class DisguisePacketHandlers { + + public static void registerHandlers() { + registerPacketHandler(ClientboundSetEntityDataPacket.class, ClientboundSetEntityDataPacket::id, DisguisePacketHandlers::processEntityDataPacket); + registerPacketHandler(ClientboundUpdateAttributesPacket.class, ClientboundUpdateAttributesPacket::getEntityId, DisguisePacketHandlers::processAttributesPacket); + registerPacketHandler(ClientboundAddEntityPacket.class, ClientboundAddEntityPacket::getId, DisguisePacketHandlers::sendDisguiseForPacket); + registerPacketHandler(ClientboundTeleportEntityPacket.class, ClientboundTeleportEntityPacket::id, DisguisePacketHandlers::processTeleportPacket); + registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityRotPacket); + registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityPosRotPacket); + } + + private static boolean antiDuplicate = false; + + public static > void registerPacketHandler(Class packetType, ToIntFunction idGetter, DisguisePacketHandler handler) { + registerPacketHandler(packetType, (packet, level) -> level.getEntity(idGetter.applyAsInt(packet)), handler); + } + + public static > void registerPacketHandler(Class packetType, BiFunction entityGetter, DisguisePacketHandler handler) { + DenizenNetworkManagerImpl.registerPacketHandler(packetType, (networkManager, packet) -> { + if (DisguiseCommand.disguises.isEmpty() || antiDuplicate) { + return packet; + } + Entity entity = entityGetter.apply(packet, networkManager.player.level()); + if (entity == null) { + return packet; + } + Map playerMap = DisguiseCommand.disguises.get(entity.getUUID()); + if (playerMap == null) { + return packet; + } + DisguiseCommand.TrackedDisguise disguise = playerMap.get(networkManager.player.getUUID()); + if (disguise == null) { + disguise = playerMap.get(null); + } + if (disguise == null || !disguise.isActive) { + return packet; + } + if (NMSHandler.debugPackets) { + DenizenNetworkManagerImpl.doPacketOutput("DISGUISED packet " + packet.getClass().getName() + " for entity " + entity.getId() + " to player " + networkManager.player.getScoreboardName()); + } + try { + return handler.handle(networkManager, packet, disguise); + } + catch (Exception e) { + antiDuplicate = false; + throw e; // "pass it" to the generic exception handling + } + }); + } + + @FunctionalInterface + public interface DisguisePacketHandler> { + + T handle(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) throws Exception; + } + + public static ClientboundSetEntityDataPacket processEntityDataPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket entityDataPacket, DisguiseCommand.TrackedDisguise disguise) { + if (entityDataPacket.id() == networkManager.player.getId()) { + if (!disguise.shouldFake) { + return entityDataPacket; + } + for (SynchedEntityData.DataValue dataValue : entityDataPacket.packedItems()) { + if (dataValue.id() == 0) { // Entity flags + List> newData = new ArrayList<>(entityDataPacket.packedItems()); + newData.remove(dataValue); + byte flags = (byte) dataValue.value(); + flags |= 0x20; // Invisible flag + newData.add(PacketHelperImpl.createEntityData(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags)); + return new ClientboundSetEntityDataPacket(entityDataPacket.id(), newData); + } + } + } + else { + List> data = ((CraftEntity) disguise.toOthers.entity.entity).getHandle().getEntityData().getNonDefaultValues(); + return data != null ? new ClientboundSetEntityDataPacket(entityDataPacket.id(), data) : null; + } + return entityDataPacket; + } + + public static ClientboundUpdateAttributesPacket processAttributesPacket(DenizenNetworkManagerImpl networkManager, ClientboundUpdateAttributesPacket attributesPacket, DisguiseCommand.TrackedDisguise disguise) { + FakeEntity fake = attributesPacket.getEntityId() == networkManager.player.getId() ? disguise.fakeToSelf : disguise.toOthers; + return fake == null || fake.entity.entity instanceof LivingEntity ? attributesPacket : null; // Non-living entities don't have attributes + } + + public static ClientboundTeleportEntityPacket processTeleportPacket(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket teleportEntityPacket, DisguiseCommand.TrackedDisguise disguise) throws IllegalAccessException { + if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) { + PositionMoveRotation oldChange = teleportEntityPacket.change(); + return new ClientboundTeleportEntityPacket( + teleportEntityPacket.id(), + new PositionMoveRotation(oldChange.position(), oldChange.deltaMovement(), EntityAttachmentHelper.normalizeAngle(oldChange.yRot() + 180), oldChange.xRot()), + teleportEntityPacket.relatives(), + teleportEntityPacket.onGround() + ); + } + return sendDisguiseForPacket(networkManager, teleportEntityPacket, disguise); + } + + + public static ClientboundMoveEntityPacket.Rot processMoveEntityRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.Rot rotPacket, DisguiseCommand.TrackedDisguise disguise) { + if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) { + return new ClientboundMoveEntityPacket.Rot( + disguise.entity.getBukkitEntity().getEntityId(), + EntityAttachmentHelper.compressAngle(rotPacket.getYRot() + 180), + Mth.packDegrees(rotPacket.getXRot()), + rotPacket.isOnGround() + ); + } + return sendDisguiseForPacket(networkManager, rotPacket, disguise); + } + + + public static ClientboundMoveEntityPacket.PosRot processMoveEntityPosRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.PosRot posRotPacket, DisguiseCommand.TrackedDisguise disguise) { + if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) { + return new ClientboundMoveEntityPacket.PosRot( + disguise.entity.getBukkitEntity().getEntityId(), + posRotPacket.getXa(), + posRotPacket.getYa(), + posRotPacket.getZa(), + EntityAttachmentHelper.compressAngle(posRotPacket.getYRot() + 180), + Mth.packDegrees(posRotPacket.getXRot()), + posRotPacket.isOnGround() + ); + } + return sendDisguiseForPacket(networkManager, posRotPacket, disguise); + } + + public static > T sendDisguiseForPacket(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) { + antiDuplicate = true; + disguise.sendTo(List.of(new PlayerTag(networkManager.player.getUUID()))); + antiDuplicate = false; + return null; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/EntityMetadataPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/EntityMetadataPacketHandlers.java new file mode 100644 index 0000000000..a039e0f218 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/EntityMetadataPacketHandlers.java @@ -0,0 +1,92 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.helpers.PacketHelperImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.scripts.commands.entity.GlowCommand; +import com.denizenscript.denizen.scripts.commands.entity.InvisibleCommand; +import com.denizenscript.denizen.scripts.commands.entity.RenameCommand; +import com.denizenscript.denizen.scripts.commands.entity.SneakCommand; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.entity.Entity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class EntityMetadataPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, EntityMetadataPacketHandlers::processMetadataChangesForPacket); + } + + public static ClientboundSetEntityDataPacket getModifiedMetadataFor(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket metadataPacket) { + if (!RenameCommand.hasAnyDynamicRenames() && SneakCommand.forceSetSneak.isEmpty() && InvisibleCommand.helper.noOverrides() && GlowCommand.helper.noOverrides()) { + return null; + } + try { + Entity entity = networkManager.player.level().getEntity(metadataPacket.id()); + if (entity == null) { + return null; // If it doesn't exist on-server, it's definitely not relevant, so move on + } + String nameToApply = RenameCommand.getCustomNameFor(entity.getUUID(), networkManager.player.getBukkitEntity(), false); + Boolean forceSneak = SneakCommand.shouldSneak(entity.getUUID(), networkManager.player.getUUID()); + Boolean isInvisible = InvisibleCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true); + Boolean isGlowing = GlowCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true); + boolean shouldModifyFlags = isInvisible != null || forceSneak != null || isGlowing != null; + if (nameToApply == null && !shouldModifyFlags) { + return null; + } + List> data = new ArrayList<>(metadataPacket.packedItems().size()); + Byte currentFlags = null; + for (SynchedEntityData.DataValue dataValue : metadataPacket.packedItems()) { + if (dataValue.id() == 0 && shouldModifyFlags) { // 0: Entity Flags + currentFlags = (Byte) dataValue.value(); + } + else if (nameToApply == null || (dataValue.id() != 2 && dataValue.id() != 3)) { // 2 and 3: Custom name and custom name visible + data.add(dataValue); + } + } + if (shouldModifyFlags) { + byte flags = currentFlags == null ? entity.getEntityData().get(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS) : currentFlags; + flags = applyEntityDataFlag(flags, forceSneak, 0x02); + flags = applyEntityDataFlag(flags, isInvisible, 0x20); + flags = applyEntityDataFlag(flags, isGlowing, 0x40); + data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags)); + } + if (nameToApply != null) { + data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(nameToApply, ChatColor.WHITE))))); + data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true)); + } + return new ClientboundSetEntityDataPacket(metadataPacket.id(), data); + } + catch (Throwable ex) { + Debug.echoError(ex); + return null; + } + } + + public static byte applyEntityDataFlag(byte currentFlags, Boolean value, int flag) { + if (value == null) { + return currentFlags; + } + return (byte) (value ? currentFlags | flag : currentFlags & ~flag); + } + + public static Packet processMetadataChangesForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (!(packet instanceof ClientboundSetEntityDataPacket entityDataPacket)) { + return packet; + } + ClientboundSetEntityDataPacket altPacket = getModifiedMetadataFor(networkManager, entityDataPacket); + if (altPacket == null) { + return packet; + } + return altPacket; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakeBlocksPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakeBlocksPacketHandlers.java new file mode 100644 index 0000000000..245fcc155c --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakeBlocksPacketHandlers.java @@ -0,0 +1,120 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.FakeBlockHelper; +import com.denizenscript.denizen.objects.LocationTag; +import com.denizenscript.denizen.utilities.blocks.ChunkCoordinate; +import com.denizenscript.denizen.utilities.blocks.FakeBlock; +import com.denizenscript.denizencore.utilities.ReflectionHelper; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.shorts.ShortArraySet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.World; + +import java.lang.invoke.MethodHandle; +import java.util.Arrays; +import java.util.List; + +public class FakeBlocksPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelChunkWithLightPacket.class, FakeBlocksPacketHandlers::processLevelChunkWithLightPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSectionBlocksUpdatePacket.class, FakeBlocksPacketHandlers::processSectionBlocksUpdatePacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, FakeBlocksPacketHandlers::processBlockUpdatePacket); + } + + public static final MethodHandle SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).getGetter("sectionPos", SectionPos.class); + public static final MethodHandle OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).getGetter("positions", short[].class); + public static final MethodHandle BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).getGetter("states", BlockState[].class); + + public static Packet processLevelChunkWithLightPacket(DenizenNetworkManagerImpl networkManager, ClientboundLevelChunkWithLightPacket chunkPacket) throws Throwable { + if (FakeBlock.blocks.isEmpty()) { + return chunkPacket; + } + FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID()); + if (map == null) { + return chunkPacket; + } + int chunkX = chunkPacket.getX(); + int chunkZ = chunkPacket.getZ(); + World world = networkManager.player.level().getWorld(); + ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, world.getName()); + Int2ObjectMap> blocksBySection = map.byChunk.get(chunkCoord); + if (blocksBySection == null || blocksBySection.isEmpty()) { + return chunkPacket; + } + return FakeBlockHelper.handleMapChunkPacket(networkManager.player.getBukkitEntity().getWorld(), chunkPacket, chunkX, chunkZ, blocksBySection, map); + } + + public static ClientboundSectionBlocksUpdatePacket processSectionBlocksUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundSectionBlocksUpdatePacket sectionUpdatePacket) throws Throwable { + if (FakeBlock.blocks.isEmpty()) { + return sectionUpdatePacket; + } + FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID()); + if (map == null) { + return sectionUpdatePacket; + } + SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.invokeExact(sectionUpdatePacket); + ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), networkManager.player.level().getWorld().getName()); + if (!map.byChunk.containsKey(coordinateDenizen)) { + return sectionUpdatePacket; + } + short[] originalOffsetArray = (short[]) OFFSETARRAY_MULTIBLOCKCHANGE.invokeExact(sectionUpdatePacket); + BlockState[] originalStatesArray = (BlockState[]) BLOCKARRAY_MULTIBLOCKCHANGE.invokeExact(sectionUpdatePacket); + BlockState[] statesArray = Arrays.copyOf(originalStatesArray, originalStatesArray.length); + LocationTag location = new LocationTag(networkManager.player.level().getWorld(), 0, 0, 0); + boolean hasAny = false; + for (int i = 0; i < originalOffsetArray.length; i++) { + short offset = originalOffsetArray[i]; + location.setX(coord.relativeToBlockX(offset)); + location.setY(coord.relativeToBlockY(offset)); + location.setZ(coord.relativeToBlockZ(offset)); + FakeBlock block = map.byLocation.get(location); + if (block != null) { + statesArray[i] = FakeBlockHelper.getNMSState(block); + hasAny = true; + } + } + if (!hasAny) { + return sectionUpdatePacket; + } + return new ClientboundSectionBlocksUpdatePacket(coord, new ShortArraySet(originalOffsetArray), statesArray); + } + + public static ClientboundBlockUpdatePacket processBlockUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockUpdatePacket blockUpdatePacket) { + if (FakeBlock.blocks.isEmpty()) { + return blockUpdatePacket; + } + BlockPos pos = blockUpdatePacket.getPos(); + LocationTag loc = new LocationTag(networkManager.player.level().getWorld(), pos.getX(), pos.getY(), pos.getZ()); + FakeBlock block = FakeBlock.getFakeBlockFor(networkManager.player.getUUID(), loc); + if (block != null) { + return new ClientboundBlockUpdatePacket(blockUpdatePacket.getPos(), FakeBlockHelper.getNMSState(block)); + } + return blockUpdatePacket; + } + + // TODO: 1.19: Can no longer determine what block this packet is for. Would have to track separately? Possibly from the inbound packet rather than the outbound one. + /* + public static ClientboundBlockChangedAckPacket processBlockChangedAckPacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockChangedAckPacket blockChangedAckPacket) { + if (FakeBlock.blocks.isEmpty()) { + return blockChangedAckPacket; + } + BlockPos pos = blockChangedAckPacket.pos(); + LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ()); + FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc); + if (block != null) { + ClientboundBlockChangedAckPacket newPacket = new ClientboundBlockChangedAckPacket(blockChangedAckPacket.pos(), FakeBlockHelper.getNMSState(block), blockChangedAckPacket.action(), false); + oldManager.send(newPacket, genericfuturelistener); + return true; + } + } + */ +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java new file mode 100644 index 0000000000..7f5547eaf6 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java @@ -0,0 +1,138 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.objects.ItemTag; +import com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand; +import com.mojang.datafixers.util.Pair; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.inventory.CraftItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class FakeEquipmentPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEquipmentPacket.class, FakeEquipmentPacketHandlers::processSetEquipmentPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundEntityEventPacket.class, FakeEquipmentPacketHandlers::processEntityEventPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetContentPacket.class, FakeEquipmentPacketHandlers::processContainerSetContentPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetSlotPacket.class, FakeEquipmentPacketHandlers::processContainerSetSlotPacket); + } + + public static ClientboundSetEquipmentPacket processSetEquipmentPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEquipmentPacket setEquipmentPacket) { + if (FakeEquipCommand.overrides.isEmpty()) { + return setEquipmentPacket; + } + Entity entity = networkManager.player.level().getEntity(setEquipmentPacket.getEntity()); + if (entity == null) { + return setEquipmentPacket; + } + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(entity.getUUID(), networkManager.player.getBukkitEntity()); + if (override == null) { + return setEquipmentPacket; + } + List> equipment = new ArrayList<>(setEquipmentPacket.getSlots()); + for (int i = 0; i < equipment.size(); i++) { + Pair pair = equipment.get(i); + ItemStack use = switch (pair.getFirst()) { + case MAINHAND -> override.hand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.hand.getItemStack()); + case OFFHAND -> override.offhand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.offhand.getItemStack()); + case CHEST -> override.chest == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.chest.getItemStack()); + case HEAD -> override.head == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.head.getItemStack()); + case LEGS -> override.legs == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.legs.getItemStack()); + case FEET -> override.boots == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.boots.getItemStack()); + case BODY -> pair.getSecond(); // TODO: 1.20.6: is this actually used here? do we want to allow overriding it? + case SADDLE -> pair.getSecond(); // TODO: 1.21.5: same as above + }; + equipment.set(i, new Pair<>(pair.getFirst(), use)); + } + return new ClientboundSetEquipmentPacket(setEquipmentPacket.getEntity(), equipment); + } + + public static Packet processEntityEventPacket(DenizenNetworkManagerImpl networkManager, ClientboundEntityEventPacket entityEventPacket) { + if (FakeEquipCommand.overrides.isEmpty()) { + return entityEventPacket; + } + if (entityEventPacket.getEventId() != 55) { + return entityEventPacket; + } + if (!(entityEventPacket.getEntity(networkManager.player.level()) instanceof LivingEntity livingEntity)) { + return entityEventPacket; + } + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(livingEntity.getUUID(), networkManager.player.getBukkitEntity()); + if (override == null || (override.hand == null && override.offhand == null)) { + return entityEventPacket; + } + ItemStack hand = override.hand != null ? CraftItemStack.asNMSCopy(override.hand.getItemStack()) : livingEntity.getMainHandItem(); + ItemStack offhand = override.offhand != null ? CraftItemStack.asNMSCopy(override.offhand.getItemStack()) : livingEntity.getOffhandItem(); + return new ClientboundSetEquipmentPacket(livingEntity.getId(), List.of(new Pair<>(EquipmentSlot.MAINHAND, hand), new Pair<>(EquipmentSlot.OFFHAND, offhand))); + } + + public static ClientboundContainerSetContentPacket processContainerSetContentPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetContentPacket setContentPacket) { + if (FakeEquipCommand.overrides.isEmpty()) { + return setContentPacket; + } + if (setContentPacket.containerId() != 0) { + return setContentPacket; + } + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity()); + if (override == null) { + return setContentPacket; + } + List items = setContentPacket.items(); + if (override.head != null) { + items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack())); + } + if (override.chest != null) { + items.set(6, CraftItemStack.asNMSCopy(override.chest.getItemStack())); + } + if (override.legs != null) { + items.set(7, CraftItemStack.asNMSCopy(override.legs.getItemStack())); + } + if (override.boots != null) { + items.set(8, CraftItemStack.asNMSCopy(override.boots.getItemStack())); + } + if (override.offhand != null) { + items.set(45, CraftItemStack.asNMSCopy(override.offhand.getItemStack())); + } + if (override.hand != null) { + items.set(getMainHandSlot(networkManager.player), CraftItemStack.asNMSCopy(override.hand.getItemStack())); + } + return new ClientboundContainerSetContentPacket(setContentPacket.containerId(), setContentPacket.stateId(), items, setContentPacket.carriedItem()); + } + + public static ClientboundContainerSetSlotPacket processContainerSetSlotPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetSlotPacket setSlotPacket) { + if (FakeEquipCommand.overrides.isEmpty()) { + return setSlotPacket; + } + if (setSlotPacket.getContainerId() != 0) { + return setSlotPacket; + } + FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity()); + if (override == null) { + return setSlotPacket; + } + ItemTag item = switch (setSlotPacket.getSlot()) { + case 5 -> override.head; + case 6 -> override.chest; + case 7 -> override.legs; + case 8 -> override.boots; + case 45 -> override.offhand; + default -> setSlotPacket.getSlot() == getMainHandSlot(networkManager.player) ? override.hand : null; + }; + if (item == null) { + return setSlotPacket; + } + return new ClientboundContainerSetSlotPacket(setSlotPacket.getContainerId(), setSlotPacket.getStateId(), setSlotPacket.getSlot(), CraftItemStack.asNMSCopy(item.getItemStack())); + } + + public static int getMainHandSlot(ServerPlayer player) { + return player.getInventory().getSelectedSlot() + 36; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakePlayerPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakePlayerPacketHandlers.java new file mode 100644 index 0000000000..9b5f0b2c05 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/FakePlayerPacketHandlers.java @@ -0,0 +1,29 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +/* +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.v1_20.impl.entities.EntityFakePlayerImpl; +import com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import org.bukkit.Bukkit; + +import java.util.List; +*/ + +public class FakePlayerPacketHandlers { + + public static void registerHandlers() { + // TODO: 1.20.2: Replace this. + //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddPlayerPacket.class, FakePlayerPacketHandlers::processAddPlayerPacket); + } + + /* + public static void processAddPlayerPacket(DenizenNetworkManagerImpl networkManager, ClientboundAddPlayerPacket addPlayerPacket) { + if (networkManager.player.level().getEntity(addPlayerPacket.getEntityId()) instanceof EntityFakePlayerImpl fakePlayer) { + networkManager.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, fakePlayer)); + Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), + () -> networkManager.send(new ClientboundPlayerInfoRemovePacket(List.of(fakePlayer.getUUID()))), 5); + } + }*/ +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java new file mode 100644 index 0000000000..46425d983e --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java @@ -0,0 +1,69 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.utilities.entity.HideEntitiesHelper; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; + +public class HiddenEntitiesPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddExperienceOrbPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Pos.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket); + } + + public static boolean isHidden(ServerPlayer player, Entity entity) { + return entity != null && HideEntitiesHelper.playerShouldHide(player.getBukkitEntity().getUniqueId(), entity.getBukkitEntity()); + } + + public static Packet processHiddenEntitiesForPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (!HideEntitiesHelper.hasAnyHides()) { + return packet; + } + try { + int ider = -1; + Entity e = null; + if (packet instanceof ClientboundAddEntityPacket) { + ider = ((ClientboundAddEntityPacket) packet).getId(); + } + //TODO: 1.21.5: check this packet list + /* + else if (packet instanceof ClientboundAddExperienceOrbPacket) { + ider = ((ClientboundAddExperienceOrbPacket) packet).getId(); + }*/ + else if (packet instanceof ClientboundMoveEntityPacket) { + e = ((ClientboundMoveEntityPacket) packet).getEntity(networkManager.player.level()); + } + else if (packet instanceof ClientboundSetEntityDataPacket) { + ider = ((ClientboundSetEntityDataPacket) packet).id(); + } + else if (packet instanceof ClientboundSetEntityMotionPacket) { + ider = ((ClientboundSetEntityMotionPacket) packet).id(); + } + else if (packet instanceof ClientboundTeleportEntityPacket) { + ider = ((ClientboundTeleportEntityPacket) packet).id(); + } + if (e == null && ider != -1) { + e = networkManager.player.level().getEntity(ider); + } + if (e != null) { + if (isHidden(networkManager.player, e)) { + return null; + } + } + } + catch (Exception ex) { + Debug.echoError(ex); + } + return packet; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/HideParticlesPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/HideParticlesPacketHandlers.java new file mode 100644 index 0000000000..12ceca2b78 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/HideParticlesPacketHandlers.java @@ -0,0 +1,31 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.utilities.packets.HideParticles; +import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; +import org.bukkit.Particle; +import org.bukkit.craftbukkit.CraftParticle; + +import java.util.Set; + +public class HideParticlesPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelParticlesPacket.class, HideParticlesPacketHandlers::processParticlesPacket); + } + + public static ClientboundLevelParticlesPacket processParticlesPacket(DenizenNetworkManagerImpl networkManager, ClientboundLevelParticlesPacket particlesPacket) { + if (HideParticles.hidden.isEmpty()) { + return particlesPacket; + } + Set hidden = HideParticles.hidden.get(networkManager.player.getUUID()); + if (hidden == null) { + return particlesPacket; + } + Particle bukkitParticle = CraftParticle.minecraftToBukkit(particlesPacket.getParticle().getType()); + if (hidden.contains(bukkitParticle)) { + return null; + } + return particlesPacket; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java new file mode 100644 index 0000000000..60ac9388e8 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java @@ -0,0 +1,39 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.events.player.PlayerHearsSoundScriptEvent; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.world.entity.Entity; +import org.bukkit.Location; + +public class PlayerHearsSoundEventPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundEntityPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket); + } + + public static Packet processSoundPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (!PlayerHearsSoundScriptEvent.instance.eventData.isEnabled) { + return packet; + } + if (packet instanceof ClientboundSoundPacket) { + ClientboundSoundPacket spacket = (ClientboundSoundPacket) packet; + return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().location().getPath(), spacket.getSource().name(), + false, null, new Location(networkManager.player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch()) ? null : packet; + } + else if (packet instanceof ClientboundSoundEntityPacket) { + ClientboundSoundEntityPacket spacket = (ClientboundSoundEntityPacket) packet; + Entity entity = networkManager.player.level().getEntity(spacket.getId()); + if (entity == null) { + return packet; + } + return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().location().getPath(), spacket.getSource().name(), + false, entity.getBukkitEntity(), null, spacket.getVolume(), spacket.getPitch()) ? null : packet; + } + return packet; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java new file mode 100644 index 0000000000..95cb8f5189 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java @@ -0,0 +1,126 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.handlers.packet; + +import com.denizenscript.denizen.events.player.PlayerReceivesTablistUpdateScriptEvent; +import com.denizenscript.denizen.nms.v26_2.Handler; +import com.denizenscript.denizen.nms.v26_2.impl.ProfileEditorImpl; +import com.denizenscript.denizen.nms.v26_2.impl.network.handlers.DenizenNetworkManagerImpl; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.Debug; +import com.google.common.base.Joiner; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.world.level.GameType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class TablistUpdateEventPacketHandlers { + + public static void registerHandlers() { + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket); + DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoRemovePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket); + } + + public static boolean tablistBreakOnlyOnce = false; + + // TODO: properly rebundle the packet instead of splitting it up + public static Packet processTablistPacket(DenizenNetworkManagerImpl networkManager, Packet packet) { + if (!PlayerReceivesTablistUpdateScriptEvent.instance.eventData.isEnabled) { + return packet; + } + if (packet instanceof ClientboundPlayerInfoUpdatePacket) { + ClientboundPlayerInfoUpdatePacket infoPacket = (ClientboundPlayerInfoUpdatePacket) packet; + String mode = ""; + for (ClientboundPlayerInfoUpdatePacket.Action action : infoPacket.actions()) { + switch (action) { + case ADD_PLAYER: + mode = "add"; + break; + case UPDATE_LATENCY: + mode = mode.isEmpty() ? "update_latency" : mode + "|update_latency"; + break; + case UPDATE_GAME_MODE: + mode = mode.isEmpty() ? "update_gamemode" : mode + "|update_gamemode"; + break; + case UPDATE_DISPLAY_NAME: + mode = mode.isEmpty() ? "update_display" : mode + "|update_display"; + break; + case UPDATE_LISTED: + mode = mode.isEmpty() ? "update_listed" : mode + "|update_listed"; + break; + case INITIALIZE_CHAT: + mode = mode.isEmpty() ? "initialize_chat" : mode + "|initialize_chat"; + default: + break; + } + } + if (mode.isEmpty()) { + if (!tablistBreakOnlyOnce) { + tablistBreakOnlyOnce = true; + Debug.echoError("Tablist packet processing failed: unknown action " + Joiner.on(", ").join(infoPacket.actions())); + } + return packet; + } + boolean isOverriding = false; + for (ClientboundPlayerInfoUpdatePacket.Entry update : infoPacket.entries()) { + GameProfile profile = update.profile(); + String texture = null, signature = null; + if (profile.properties().containsKey("textures")) { + Property property = profile.properties().get("textures").stream().findFirst().get(); + texture = property.value(); + signature = property.signature(); + } + String modeText = update.gameMode() == null ? null : update.gameMode().name(); + PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.id(), update.listed(), profile.name(), + update.displayName() == null ? null : FormattedTextHelper.stringify(Handler.componentToSpigot(update.displayName())), modeText, texture, signature, update.latency()); + PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data); + if (data.modified) { + if (!isOverriding) { + isOverriding = true; + for (ClientboundPlayerInfoUpdatePacket.Entry priorUpdate : infoPacket.entries()) { + if (priorUpdate == update) { + break; + } + networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(priorUpdate))); + } + } + if (!data.cancelled) { + GameProfile newProfile = ProfileEditorImpl.createGameProfile(data.id, data.name, data.texture, data.signature); + ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(newProfile.id(), newProfile, data.isListed, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)), + data.display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(data.display, ChatColor.WHITE)), update.showHat(), update.listOrder(), update.chatSession()); + networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(entry))); + } + } + else if (isOverriding) { + networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(update))); + } + } + return isOverriding ? null : packet; + } + else if (packet instanceof ClientboundPlayerInfoRemovePacket) { + ClientboundPlayerInfoRemovePacket removePacket = (ClientboundPlayerInfoRemovePacket) packet; + boolean modified = false; + List altIds = new ArrayList<>(((ClientboundPlayerInfoRemovePacket) packet).profileIds()); + for (UUID id : ((ClientboundPlayerInfoRemovePacket) packet).profileIds()) { + PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData("remove", id, false, null, null, null, null, null, 0); + PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data); + if (data.modified && data.cancelled) { + modified = true; + altIds.remove(id); + } + } + if (modified) { + return new ClientboundPlayerInfoRemovePacket(altIds); + } + } + return packet; + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketInResourcePackStatusImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketInResourcePackStatusImpl.java new file mode 100644 index 0000000000..ddcf59c7b3 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketInResourcePackStatusImpl.java @@ -0,0 +1,18 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.packets; + +import com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus; +import net.minecraft.network.protocol.common.ServerboundResourcePackPacket; + +public class PacketInResourcePackStatusImpl implements PacketInResourcePackStatus { + + private ServerboundResourcePackPacket internal; + + public PacketInResourcePackStatusImpl(ServerboundResourcePackPacket internal) { + this.internal = internal; + } + + @Override + public String getStatus() { + return internal.action().name(); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketInSteerVehicleImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketInSteerVehicleImpl.java new file mode 100644 index 0000000000..401b1d89c4 --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketInSteerVehicleImpl.java @@ -0,0 +1,33 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.packets; + +import com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle; +import net.minecraft.network.protocol.game.ServerboundPlayerInputPacket; + +public class PacketInSteerVehicleImpl implements PacketInSteerVehicle { + + private ServerboundPlayerInputPacket internal; + + public PacketInSteerVehicleImpl(ServerboundPlayerInputPacket internal) { + this.internal = internal; + } + + @Override + public float getLeftwardInput() { + return internal.input().left() ? 1 : 0; + } + + @Override + public float getForwardInput() { + return internal.input().forward() ? 1 : 0; + } + + @Override + public boolean getJumpInput() { + return internal.input().jump(); + } + + @Override + public boolean getDismountInput() { + return internal.input().shift(); + } +} diff --git a/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketOutChatImpl.java b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketOutChatImpl.java new file mode 100644 index 0000000000..4bb78f2e3e --- /dev/null +++ b/v26_2/src/main/java/com/denizenscript/denizen/nms/v26_2/impl/network/packets/PacketOutChatImpl.java @@ -0,0 +1,50 @@ +package com.denizenscript.denizen.nms.v26_2.impl.network.packets; + +import com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat; +import com.denizenscript.denizen.utilities.FormattedTextHelper; +import net.md_5.bungee.chat.ComponentSerializer; +import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; +import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +public class PacketOutChatImpl extends PacketOutChat { + + public ClientboundPlayerChatPacket playerPacket; + public ClientboundSystemChatPacket systemPacket; + public String message; + public String rawJson; + public boolean isOverlayActionbar; + + public PacketOutChatImpl(ClientboundSystemChatPacket internal) { + systemPacket = internal; + rawJson = CraftChatMessage.toJSON(internal.content()); + message = FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson)); + isOverlayActionbar = internal.overlay(); + } + + public PacketOutChatImpl(ClientboundPlayerChatPacket internal) { + playerPacket = internal; + rawJson = ComponentSerializer.toString(internal.body().content()); + message = FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson)); + } + + @Override + public boolean isSystem() { + return systemPacket != null; + } + + @Override + public boolean isActionbar() { + return isOverlayActionbar; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getRawJson() { + return rawJson; + } +}