Placeholders

Creating a new Placeholder

Step 1 - Create your Placeholder Class inside of a specific package

package your.plugin.compat.mythicmobs.skills.placeholders.all;

public class ExamplePlaceholder {

}

Step 2 - Extend GenericPlaceholder<T>

package your.plugin.compat.mythicmobs.skills.placeholders.all;

import io.lumine.mythic.core.skills.placeholders.types.GenericPlaceholder;

public class ExamplePlaceholder extends GenericPlaceholder<String> {

}
  • a string? use extends GenericPlaceholder<String>.
  • a location? Use extends GenericPlaceholder<AbstractLocation>.
  • an integer? Use extends GenericPlaceholder<Integer>.
  • ...

Step 3 - Create a Constructor matching the Superclass

package your.plugin.compat.mythicmobs.skills.placeholders.all;

import io.lumine.mythic.core.skills.placeholders.types.GenericPlaceholder;

public class ExamplePlaceholder extends GenericPlaceholder<String> {
    
    public ExamplePlaceholder(GenericPlaceholderArguments metaContext) {
        super(metaContext);
    }

}

Step 4 - Implement abstract methods

There will (normally) be a couple of different methods to implement:

  • some apply... method, that will be in charge of parsing the placeholder
  • a convertType method, to convert the T object to a String
  • a getParsedValueClass method, to return the Class<T> object of the used T type
package your.plugin.compat.mythicmobs.skills.placeholders.all;

import io.lumine.mythic.core.skills.placeholders.types.GenericPlaceholder;
import io.lumine.mythic.core.skills.placeholders.types.GenericPlaceholderTypes.StringPlaceholder;

import io.lumine.mythic.core.skills.placeholders.PlaceholderContext;
import javax.annotation.Nullable;

public class ExamplePlaceholder extends GenericPlaceholder<String> implements StringPlaceholder {
    
    public ExamplePlaceholder(GenericPlaceholderArguments metaContext) {
        super(metaContext);
    }

    @Nullable
    @Override
    public String applyWithMetaKeywords(PlaceholderContext placeholderContext) {
        return "hello world";
    }
}

Step 5 - Use the @MythicPlaceholder annotation

This annotation will allow the placeholder to be registered by MythicMobs later

package your.plugin.compat.mythicmobs.skills.placeholders.all;

import io.lumine.mythic.core.skills.placeholders.types.GenericPlaceholder;
import io.lumine.mythic.core.skills.placeholders.types.GenericPlaceholderTypes.StringPlaceholder;
import io.lumine.mythic.core.utils.annotations.MythicPlaceholder;

import io.lumine.mythic.core.skills.placeholders.PlaceholderContext;
import javax.annotation.Nullable;

@MythicPlaceholder(placeholder="example.placeholder")
public class ExamplePlaceholder extends GenericPlaceholder<String> implements StringPlaceholder {
    
    public ExamplePlaceholder(GenericPlaceholderArguments metaContext) {
        super(metaContext);
    }

    @Nullable
    @Override
    public String applyWithMetaKeywords(PlaceholderContext placeholderContext) {
        return "hello world";
    }
}

Fetching Placeholder Arguments and Attributes

You can, optionally, fetch placeholder arguments (<example.placeholder.argument1.argument.2> or <example.placeholder{argument1=hello;argument2=world}>) using one of the following methods

  • GenericPlaceholder#getResolver - The most complete one, but clunkier to use
  • GenericPlaceholder#getPlaceholder - A less versatile, but more concise version
  • GenericPlaceholder#getPlaceholderString - a very fast version made only for PlaceholderStrings

Fetching an Attribute from the main MythicLineConfig

If you only need to fetch an attribute from the main configuration (and not from the one of a specific placeholder segment) you can just do the following

    public NumericEntityScopedPlaceholder(EntityScopedPlaceholderArguments context, int rounding) {
        super(context);
        if(context.config == null) {
            return;
        }
        this.rounding = context.config.getInteger(new String[] {"rounding", "round", "r"}, rounding);
    }

Simple Placeholder Argument

package your.plugin.compat.mythicmobs.skills.placeholders.all;

import io.lumine.mythic.core.skills.placeholders.types.GenericPlaceholder;
import io.lumine.mythic.core.skills.placeholders.types.GenericPlaceholderTypes.StringPlaceholder;
import io.lumine.mythic.core.utils.annotations.MythicPlaceholder;

import io.lumine.mythic.core.skills.placeholders.PlaceholderContext;
import javax.annotation.Nullable;

@MythicPlaceholder(placeholder="example.placeholder",usedPlaceholderArguments = 1)
public class ExamplePlaceholder extends GenericPlaceholder<String> implements StringPlaceholder {
    
    private final ResolvedPlaceholderSegment<PlaceholderString> argument;

    public ExamplePlaceholder(GenericPlaceholderArguments metaContext) {
        super(metaContext);
        this.argument = ...
    }

    @Nullable
    @Override
    public String applyWithMetaKeywords(PlaceholderContext placeholderContext) {
        return this.argument.getOrDefault(placeholderContext, PlaceholderString::get, null);
    }
}

ResolvedPlaceholderSegment can be fetched in all of the following ways>

                this.<String>getResolver()
                        .segIndex(0)
                        .segValueParser(PlaceholderString::of)
                        .build()
                        .get();
                this.<String>getPlaceholder(0, SegmentSource.EXTRA_ARGS, null, PlaceholderString::of)
                this.getPlaceholderString(0)

Dynamic Amount of Used Arguments

If

  • The amount of arguments you will use is dynamic ("if there is an argument matching those conditions use it. Otherwise, default it to something else")
  • You wish to use the MythicLineConfig syntax (<example.placeholder{argument1=hello;argument2=world}>)

You need to

  • set usedPlaceholderArguments to a negative value (-1 works fine)
  • call the #initializeMetaKeywords method at the end of the constructor
@MythicPlaceholder(placeholder="utils.epoch",version="5.9", description="Returns the current epoch time in seconds", usedPlaceholderArguments = -1)
public class EpochPlaceholder extends GenericPlaceholder<Long> implements LongPlaceholder {

    private final ResolvedPlaceholderSegment<PlaceholderString> type;

    public EpochPlaceholder(GenericPlaceholderArguments metaContext) {
        super(metaContext);
        this.type = getPlaceholderString(0);
        initializeMetaKeywords();
    }

    @Nullable
    @Override
    public Long applyWithMetaKeywords(PlaceholderContext placeholderContext) {
        if (type.value() == null) {
            return Instant.now().getEpochSecond();
        }
        return switch (this.type.getOrDefault(placeholderContext, PlaceholderString::get, "seconds")) {
            case "millis" -> Instant.now().getLong(java.time.temporal.ChronoField.MILLI_OF_SECOND);
            case "ticks" -> Instant.now().getEpochSecond() * 20 + (Instant.now().getLong(java.time.temporal.ChronoField.MILLI_OF_SECOND) / 50);
            case "timestamp" -> Instant.now().toEpochMilli();
            default -> Instant.now().getEpochSecond();
        };
    }
}

Using Wildcards Arguments

The placeholder name itself can have wildcard arguments. You can use and fetch those wildcard arguments in the following way

@MythicPlaceholder(placeholder="skill.*.cooldown",usedPlaceholderArguments = -1)
public class SkillCooldownPlaceholder extends EntityScopedPlaceholder<Double> implements DoublePlaceholder {

    private final ResolvedPlaceholderSegment<PlaceholderString> skill;
    private final Types type;

    public SkillCooldownPlaceholder(EntityScopedPlaceholderArguments context) {
        super(context);
        this.skill = getPlaceholderString(0, SegmentSource.WILDCARD_ARGS);
        this.type =
                this.<Types>getResolver()
                        .mlcGetter(mythicLineConfig ->
                                mythicLineConfig.getEnum(new String[]{"type"}, Types.class, null)
                        )
                        .segIndex(0)
                        .segValueParser(s -> Types.valueOf(s.toUpperCase()))
                        .segValueChecker(s ->
                                Arrays.stream(Types.values()).anyMatch(b -> b.name().equals(s.toUpperCase()))
                        )
                        .defaultValue(Types.SECONDS)
                        .build()
                        .get()
                        .value();
        initializeMetaKeywords();
    }

    enum Types {
        MILLIS,
        SECONDS
    }

    @Nullable
    @Override
    public Double applyToScope(PlaceholderContext placeholderContext) {
        if (this.type == null) {
            MythicLogger.debug(MythicLogger.DebugLevel.SKILL_INFO, "Placeholder " + this.instanceData.unparsedPlaceholder + " is invalid");
            return null;
        }

        final String skillName = this.skill.getOrDefault(placeholderContext, PlaceholderString::get, null);
        var maybeSkill = this.manager.getPlugin().getSkillManager().getSkill(skillName);

        if(maybeSkill.isEmpty()) {
            MythicLogger.debug(MythicLogger.DebugLevel.SKILL_INFO, "Placeholder " + this.instanceData.unparsedPlaceholder + " found no skill " + skillName);
            return null;
        }

        var skill = (MetaSkill) maybeSkill.get();

        SkillCaster entity = this.manager.getPlugin().getSkillManager().getCaster(this.getEntity.get(placeholderContext));

        return switch (type) {
            case MILLIS -> (skill.getCooldown(entity));
            default -> Numbers.round(skill.getCooldown(entity), 2);
        };
    }
}

Other Kinds of GenericPlaceholder

Other than GenericPlaceholder, you can also inherit from

  • EntityScopedPlaceholder - automatically prepends caster., target. etc. etc. to the placeholder name
  • NumericEntityScopedPlaceholder - extends EntityScopedPlaceholder, while also automatically returning a Double value with a rounding attribute

EntityScopedPlaceholder

This class allows you access to two very useful functions

  • getEntity.get(PlaceholderContext) returns the AbstractEntity against which to parse the placeholder
  • getLocation.get(PlaceholderContext) returns the AbstractLocation against which to parse the placeholder
@MythicPlaceholder(placeholder="stance")
public class StancePlaceholder extends EntityScopedPlaceholder<String> implements StringPlaceholder {

    public StancePlaceholder(EntityScopedPlaceholderArguments arguments) {
        super(arguments);
    }

    @Nullable
    @Override
    public String applyToScope(PlaceholderContext placeholderContext) {
        if (this.getEntity.get(placeholderContext) instanceof ActiveMob am) {
            return am.getStance();
        }
        return null;
    }
}
@MythicPlaceholder(placeholder="distance")
public class DistancePlaceholder extends NumericEntityScopedPlaceholder {


    public DistancePlaceholder(EntityScopedPlaceholderArguments context) {
        super(context);
    }

    @Nullable
    @Override
    public Number applyToScopeWithNumericFormatting(PlaceholderContext placeholderContext) {
        var caster = placeholderContext.meta().getCaster().getEntity().getLocation();
        var target = getLocation.get(placeholderContext);
        if (caster.getWorld().equals(target.getWorld())) {
            return caster.distance(target);
        }
        return null;
    }
}

ScopedVariablePlaceholder

The placeholder type to inherit if you are fetching a variable's value from somewhere. The variable name will be assumed to be the first extra placeholder argument found

@MythicPlaceholder(placeholder="caster.var")
public class CasterVariablePlaceholder extends ScopedVariablePlaceholder {

    public CasterVariablePlaceholder(GenericPlaceholderArguments metaContext) {
        super(metaContext);
    }

    @Nullable
    @Override
    protected VariableRegistry getRegistry(PlaceholderContext context) {
        return manager.getPlugin().getVariableManager().getRegistry(VariableScope.CASTER, context.meta(), context.entity());
    }
}

Registering the placeholders

import io.lumine.mythic.core.skills.CustomComponentRegistry;

...

        this.componentRegistry = new CustomComponentRegistry(this, Lists.newArrayList())
                .registerCustomComponent(CustomComponentRegistry.MythicComponentType.PLACEHOLDER, "package.path");
Updated Aug 19, 2026