Configuring Metadata in Towny objects. - TownyAdvanced/Towny GitHub Wiki

The Towny API has a list of properties already hooked to towny objects that are editable by Towny plugin developers. While a lot of these are useful in the context of Towny, as an independent plugin developer, you may want to store other custom data within some towny objects. This is the purpose of metadata.

Why use Metadata as opposed to storing data within your plugin?

  • Data not stored in the Towny plugin is decentralized which makes it hard for other devs to see and manipulate your stored data, this is especially problematic if another plugin depends on data from the plugin you are developing. Towny metadata is centralized thus allowing for easy access via documentation, and key-names.
  • If your plugin requires external data on different towny objects developing a backend to deal with it, can be cumbersome and time intensive.
  • Metadata allows for custom fields in towny objects like the ones already included, ie: pvp, explosions, residents.
  • Plugins do not have to worry about towny objects like towns, nations, or residents changing names and losing data associated with them.

Objects which can hold Metadata:

All TownyObjects can use hold MetaData and these include:

  • Towns
  • Nations
  • Residents
  • TownyWorlds
  • TownBlocks
  • PlotGroups

Example plugins using Metadata

Check out Plugins using Metadata

How to use the Metadata within the TownyAPI

The API uses a data field system for storing metadata, which essentially works a key-value storage. Plugins will create their own unique keys, and store a persistent value. Towny comes equipped with several primitive data fields already that work out of the box. However, Towny also provides an API to create and store custom data fields that allow storing more than just primitive values.

List of Towny-Provided Data Fields

  • CustomDataField (Base Class)
    • IntegerDataField
    • StringDataField
    • BooleanDataField
    • DecimalDataField
    • LongDataField
    • ByteDataField
    • LocationDataField
    • ListDataField

Each of the data fields hold a specific type of data that is properly deserialized when Towny loads.

Towny provides a MetaDataUtil which can save you a bit of time with getting and setting metadata for these basic types.

Custom Data Fields

In the case that none of the Towny-provided data fields satisfy your use case, you can create a custom data field class detailed on this page.

Parts of a DataField

Each data field will essentially have three member variables: key, value, and label.

  • Key: A unique non-empty string that identifies metadata property. Metadata is not shared between towny objects, so keys only need to be unique against other metadata keys for that specific towny object. However, a good general key should prefix the plugin name before the keyname e.g. towny_explosion.
  • Value: The value stored in the metadata. This depends on the specific data field chosen.
  • Label: An optional string used to display the metadata on the towny object. For example, a metadata field with the label Kills associated with a specific town will show up when the town status of that town is shown (via /t or /t [townname]. Do not set labels if you want hidden metadata.

Prerequisites

  1. Make sure that your plugin depends on Towny, you can do this by adding this to your plugin.yml:
    depends: [Towny]
    
  2. Add Towny as a dependency to Maven as described here.

Registering global metadata with your plugin.

If a plugin registers global metadata, it provides a way for server administrators to add that metadata via in-game commands. This step is not necessary, if a plugin plans on having full control the metadata and does not want admin interaction with the metadata.

The metadata will be registered on load.

public class Plugin extends JavaPlugin {
    // Parts of a datafield
    private static String keyname = "plugin_intfield"; // This key must be unique to your plugin.
    private static int defaultVal = 0; // This is the default value your data field will have whenever its added to an object.
    private static String label = "Super Duper Int Field"; // Label that will be displayed when the towny object's status is shown.
    // Use those parts to create a new data field to store an integer
    private static IntegerDataField myCustomIntegerField = new IntegerDataField(keyname, defaultVal, label);

    // Called when the plugin first loads.
    @Override
    public void onLoad() {
        // (Optional) Try to globally register the data field.
        // Globally registering data fields allow them to be modified in-game by administrators.
        try {
            TownyAPI.getInstance().registerCustomDataField(myCustomIntegerField);
        } catch (KeyAlreadyRegisteredException e) {
            getLogger().warning(e.getMessage()); // A flag with the same key name already exists try again
        }

        getLogger().info("Custom data field successfully registered!");
    }
    
    public static IntegerDataField getMyCustomIntegerField() {
        return myCustomIntegerField;
    }
}

Attaching metadata to Towny objects

// Create a new instance of the data-field passing in a unique key, and a default value.
IntegerDataField idf = new IntegerDataField("plugin_intfield", 10);

// For townblock objects
townblock.addMetaData(idf);

// For town objects
town.addMetaData(idf);

// For nation objects
nation.addMetaData(idf);

// Same for the rest of the Towny objects

Alternatively, if you want your metadata available to all towns by default you could loop through all of the objects on initialization and add the custom data.

Retrieving and modifying custom data fields

private IntegerDataField idf = new IntegerDataField("plugin_idf", 10);

public void manipulateData(TownBlock townBlock) {
    // Check if the townblock has metadata matching they key.
    if (townBlock.hasMeta(idf.getKey())) {
        // Get the generic data field from the townblock.
        // In later versions, this process can be simplified via TownyObject#getMeta(key, IntegerDataField.class) 
        // which will fetch the data field matching the key as an IntegerDataField.
        CustomDataField cdf = townBlock.getMeta(idf.getKey());
        // Make sure that the datafield is an integer data field which is what 'idf' is.
        if (cdf instanceof IntegerDataField) {
            // Cast the data field to an integer data field.
            IntegerDataField myfield = (IntegerDataField) cdf;
            // Set the value
            myfield.setValue(10);
        }
    } 
}

With that you should be able to edit, add and change metadata.

Full Example Demonstrating CRUD Operations

This example file will demonstrate how to create, read, update, and delete metadata.

public class Plugin extends JavaPlugin {

    private static String keyName = "plugin_key"; // A key name unique to your plugin.
    private static int defaultVal = 0; // Default value of your data field.
    private static String label = "Super Cool Int Field"; // Label for the metadata that will be displayed when status is called for that towny object.
    private static IntegerDataField myCustomIntField = new IntegerDataField(keyName, defaultVal, label);
    
    // Register the metadata globally for admins to add the metadata to objects via in-game commands. 
    @Override
    public void onLoad() {
        // Try to register the data field.
        try {
            TownyAPI.getInstance().registerCustomDataField(myCustomIntField);
        } catch (KeyAlreadyRegisteredException e) {
            getLogger().warning(e.getMessage()); // A flag with the same key name already exists try again
        }

        getLogger().info("Flag successfully registered!");
    }

    // Add metadata (Create)
    public void addMetadataToTown(Town t) {
        // Simply clone the metadata and add it to the town.
        // If the object is not cloned, then every time you change the value for the field,
        // it will change the value across all towny objects that have the metadata.
        
        t.addMetaData(myCustomIntField.clone());
    }

    // Fetch metadata (Read)
    public int getMetaDataFromTown(Town t) {
        // Check that the town has the metadata key.
        if (t.hasMeta(myCustomIntField.getKey())) {
            // Get the metadata from the town using the key.
            CustomDataField cdf = t.getMetaData(myCustomIntField.getKey());
            // Check that it's an IntegerDataField
            if (cdf instanceof IntegerDataField) {
                // Cast to IntegerDataField
                IntegerDataField idf = (IntegerDataField) cdf;

                // Return the read value which is an integer.
                return idf.getValue();
            }
        }

        // Return a default value
        return -1;
    }

    // Update metadata (Update)
    public void updateMetaDataFromTown(Town t, int updatedVal) {
        // Check that the town has the metadata key.
        if (t.hasMeta(myCustomIntField.getKey())) {
            // Get the metadata from the town using the key.
            CustomDataField cdf = t.getMetaData(myCustomIntField.getKey());
            // Check that it's an IntegerDataField
            if (cdf instanceof IntegerDataField) {
                // Cast to IntegerDataField
                IntegerDataField idf = (IntegerDataField) cdf;

                // Update the value
                idf.setValue(updatedVal);
            }
        }
    }

    // Remove metadata (Delete)
    public void removeMetaDataFromTown(Town t) {
        t.removeMetaData(myCustomIntField);
    }
}