Skip to content

Commit

Permalink
Add support for custom tags in item meta (#1366)
Browse files Browse the repository at this point in the history
This supports tags added by plugins using Bukkit's PersistantDataContainer.
  • Loading branch information
PseudoKnight authored Oct 4, 2023
1 parent 6a7749c commit 1f6deb1
Show file tree
Hide file tree
Showing 14 changed files with 544 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/main/java/com/laytonsmith/abstraction/Convertor.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,16 @@ public interface Convertor {
* @return The username
*/
String GetUser(Environment env);

/**
* Returns a Minecraft namespaced key object from a string.
* The key can only alphanumeric characters, dots, underscores, and dashes.
* A preceding namespace can be delimited with a single colon, which can also have forward slashes.
* Example: "path/commandhelper:my_tag".
* If no namespace is given, it will default to "commandhelper".
*
* @param key a string formatted key
* @return a key object
*/
MCNamespacedKey GetNamespacedKey(String key);
}
4 changes: 4 additions & 0 deletions src/main/java/com/laytonsmith/abstraction/MCItemMeta.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,8 @@ public interface MCItemMeta extends AbstractionObject {
List<MCAttributeModifier> getAttributeModifiers();

void setAttributeModifiers(List<MCAttributeModifier> modifiers);

boolean hasCustomTags();

MCTagContainer getCustomTags();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.laytonsmith.abstraction;

public interface MCNamespacedKey extends AbstractionObject {
}
65 changes: 65 additions & 0 deletions src/main/java/com/laytonsmith/abstraction/MCTagContainer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.laytonsmith.abstraction;

import com.laytonsmith.abstraction.enums.MCTagType;

import java.util.Set;

/**
* Minecraft NBT containers that can be used to read and modify tags in supported game objects.
* This includes item meta, entities, block entities, chunks, worlds, etc.
*/
public interface MCTagContainer extends AbstractionObject {

/**
* Returns whether the tag container does not contain any tags.
* @return whether container is empty
*/
boolean isEmpty();

/**
* Gets a set of namespaced keys for each tag that exists in this container. (e.g. "namespace:key")
* @return a set of keys
*/
Set<MCNamespacedKey> getKeys();

/**
* Returns the tag type with the given key.
* MCTagType can be used to convert tags to and from MethodScript constructs.
* Returns null if a tag with that key does not exist.
* @param key the tag key
* @return the type for the tag
*/
MCTagType getType(MCNamespacedKey key);

/**
* Returns the tag value with the given key and tag type.
* Returns null if a tag with that key and type does not exist.
* @param key the tag key
* @param type the tag type
* @return the value for the tag
*/
Object get(MCNamespacedKey key, MCTagType type);

/**
* Sets the tag value with the given key and tag type.
* Throws an IllegalArgumentException if the type and value do not match.
* @param key the tag key
* @param type the tag type
* @param value the tag value
*/
void set(MCNamespacedKey key, MCTagType type, Object value);

/**
* Deletes the tag with the given key from this container.
* @param key the tag key
*/
void remove(MCNamespacedKey key);

/**
* Creates a new tag container from this container context.
* This can then be used to nest a tag container with the {@link #set} method.
* @return a new tag container
*/
MCTagContainer newContainer();

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.laytonsmith.abstraction.MCItemStack;
import com.laytonsmith.abstraction.MCLocation;
import com.laytonsmith.abstraction.MCMetadataValue;
import com.laytonsmith.abstraction.MCNamespacedKey;
import com.laytonsmith.abstraction.MCNote;
import com.laytonsmith.abstraction.MCPattern;
import com.laytonsmith.abstraction.MCPlugin;
Expand Down Expand Up @@ -871,4 +872,9 @@ public String GetUser(Environment env) {
return name;
}
}

@Override
public MCNamespacedKey GetNamespacedKey(String key) {
return new BukkitMCNamespacedKey(NamespacedKey.fromString(key, CommandHelperPlugin.self));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.laytonsmith.abstraction.MCAttributeModifier;
import com.laytonsmith.abstraction.MCEnchantment;
import com.laytonsmith.abstraction.MCItemMeta;
import com.laytonsmith.abstraction.MCTagContainer;
import com.laytonsmith.abstraction.blocks.MCBlockData;
import com.laytonsmith.abstraction.blocks.MCMaterial;
import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockData;
Expand Down Expand Up @@ -225,4 +226,13 @@ public void setAttributeModifiers(List<MCAttributeModifier> modifiers) {
}
im.setAttributeModifiers(map);
}

@Override
public boolean hasCustomTags() {
return !im.getPersistentDataContainer().isEmpty();
}

public MCTagContainer getCustomTags() {
return new BukkitMCTagContainer(im.getPersistentDataContainer());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.laytonsmith.abstraction.bukkit;

import com.laytonsmith.abstraction.MCNamespacedKey;
import org.bukkit.NamespacedKey;

public class BukkitMCNamespacedKey implements MCNamespacedKey {

NamespacedKey nsk;

public BukkitMCNamespacedKey(NamespacedKey nsk) {
this.nsk = nsk;
}

@Override
public Object getHandle() {
return this.nsk;
}

@Override
public String toString() {
return this.nsk.toString();
}

@Override
public boolean equals(Object obj) {
return obj instanceof MCNamespacedKey && this.nsk.equals(((MCNamespacedKey) obj).getHandle());
}

@Override
public int hashCode() {
return this.nsk.hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.laytonsmith.abstraction.bukkit;

import com.laytonsmith.abstraction.MCNamespacedKey;
import com.laytonsmith.abstraction.MCTagContainer;
import com.laytonsmith.abstraction.enums.MCTagType;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;

import java.util.HashSet;
import java.util.Set;

public class BukkitMCTagContainer implements MCTagContainer {

PersistentDataContainer pdc;

public BukkitMCTagContainer(PersistentDataContainer pdc) {
this.pdc = pdc;
}

@Override
public MCTagContainer newContainer() {
return new BukkitMCTagContainer(this.pdc.getAdapterContext().newPersistentDataContainer());
}

@Override
public boolean isEmpty() {
return this.pdc.isEmpty();
}

@Override
public Set<MCNamespacedKey> getKeys() {
Set<MCNamespacedKey> keys = new HashSet<>();
for(NamespacedKey key : this.pdc.getKeys()) {
keys.add(new BukkitMCNamespacedKey(key));
}
return keys;
}

@Override
public MCTagType getType(MCNamespacedKey key) {
NamespacedKey namespacedKey = (NamespacedKey) key.getHandle();
// Check tag types in order of most frequently used
if(this.pdc.has(namespacedKey, PersistentDataType.STRING)) {
return MCTagType.STRING;
} else if(this.pdc.has(namespacedKey, PersistentDataType.INTEGER)) {
return MCTagType.INTEGER;
} else if(this.pdc.has(namespacedKey, PersistentDataType.BYTE)) {
return MCTagType.BYTE;
} else if(this.pdc.has(namespacedKey, PersistentDataType.DOUBLE)) {
return MCTagType.DOUBLE;
} else if(this.pdc.has(namespacedKey, PersistentDataType.LONG)) {
return MCTagType.LONG;
} else if(this.pdc.has(namespacedKey, PersistentDataType.FLOAT)) {
return MCTagType.FLOAT;
} else if(this.pdc.has(namespacedKey, PersistentDataType.TAG_CONTAINER)) {
return MCTagType.TAG_CONTAINER;
} else if(this.pdc.has(namespacedKey, PersistentDataType.BYTE_ARRAY)) {
return MCTagType.BYTE_ARRAY;
} else if(this.pdc.has(namespacedKey, PersistentDataType.SHORT)) {
return MCTagType.SHORT;
} else if(this.pdc.has(namespacedKey, PersistentDataType.INTEGER_ARRAY)) {
return MCTagType.INTEGER_ARRAY;
} else if(this.pdc.has(namespacedKey, PersistentDataType.LONG_ARRAY)) {
return MCTagType.LONG_ARRAY;
} else if(this.pdc.has(namespacedKey, PersistentDataType.TAG_CONTAINER_ARRAY)) {
return MCTagType.TAG_CONTAINER_ARRAY;
}
return null;
}

@Override
public Object get(MCNamespacedKey key, MCTagType type) {
PersistentDataType bukkitType = GetPersistentDataType(type);
Object value = this.pdc.get((NamespacedKey) key.getHandle(), bukkitType);
if(value instanceof PersistentDataContainer) {
return new BukkitMCTagContainer((PersistentDataContainer) value);
} else if(value instanceof PersistentDataContainer[] concreteContainers) {
MCTagContainer[] abstractContainers = new MCTagContainer[concreteContainers.length];
for(int i = 0; i < concreteContainers.length; i++) {
abstractContainers[i] = new BukkitMCTagContainer(concreteContainers[i]);
}
return abstractContainers;
}
return value;
}

@Override
public void set(MCNamespacedKey key, MCTagType type, Object value) {
PersistentDataType bukkitType = GetPersistentDataType(type);
if(value instanceof MCTagContainer) {
value = ((MCTagContainer) value).getHandle();
} else if(value instanceof MCTagContainer[] abstractContainers) {
PersistentDataContainer[] concreteContainers = new PersistentDataContainer[abstractContainers.length];
for(int i = 0; i < abstractContainers.length; i++) {
concreteContainers[i] = (PersistentDataContainer) abstractContainers[i].getHandle();
}
value = concreteContainers;
}
this.pdc.set((NamespacedKey) key.getHandle(), bukkitType, value);
}

@Override
public void remove(MCNamespacedKey key) {
this.pdc.remove((NamespacedKey) key.getHandle());
}

private static PersistentDataType GetPersistentDataType(MCTagType type) {
switch(type) {
case BYTE:
return PersistentDataType.BYTE;
case BYTE_ARRAY:
return PersistentDataType.BYTE_ARRAY;
case DOUBLE:
return PersistentDataType.DOUBLE;
case FLOAT:
return PersistentDataType.FLOAT;
case INTEGER:
return PersistentDataType.INTEGER;
case INTEGER_ARRAY:
return PersistentDataType.INTEGER_ARRAY;
case LONG:
return PersistentDataType.LONG;
case LONG_ARRAY:
return PersistentDataType.LONG_ARRAY;
case SHORT:
return PersistentDataType.SHORT;
case STRING:
return PersistentDataType.STRING;
case TAG_CONTAINER:
return PersistentDataType.TAG_CONTAINER;
case TAG_CONTAINER_ARRAY:
return PersistentDataType.TAG_CONTAINER_ARRAY;
}
throw new IllegalArgumentException("Invalid persistent data type: " + type.name());
}

@Override
public Object getHandle() {
return this.pdc;
}

@Override
public boolean equals(Object o) {
return o instanceof MCTagContainer && this.pdc.equals(((MCTagContainer) o).getHandle());
}

@Override
public int hashCode() {
return this.pdc.hashCode();
}

@Override
public String toString() {
return this.pdc.toString();
}
}
Loading

0 comments on commit 1f6deb1

Please sign in to comment.