_Tutorial Coding Components - Eniripsa96/SkillAPI GitHub Wiki

Step 1 - Extend CustomEffectComponent

To begin, you need to make a java class that extends CustomEffectComponent. This is the base class provided by SkillAPI and defines the needed methods to work as a dynamic component.

public class MyMechanic extends CustomEffectComponent

Step 2 - Implement Methods

Now that you're implementing the interface, you need to add all the required functions. Your IDE may auto-generate these for you. The methods needed are:

/**
 * @return unique key for your component (what is used in skill .yml files)
 */
String getKey();

/**
 * There are three options you should choose between here. While this is mostly
 * for organization, there's also slight behavior differences for mechanics in particular.
 *   CONDITION - meant for components checking whether or not a target meets certain criteria
 *   MECHANIC - meant for components doing something to the current target(s)
 *   TARGET - meant for components that are to select new targets
 *
 * Mechanics are the only type by default to not allow for child components. To override this,
 * implement the method {@link CustomComponent#isContainer()}.
 *
 * @return type of the component, describing it's general purpose
 * @see ComponentType
 */
ComponentType getType();

/**
 * @return A description for your trigger that's shown in the editor
 */
String getDescription();

/**
 * Defines all the user-configurable options in the editor. You can use
 * any of these option types:
 *   - NUMBER > a numeric value with options for base and per level increments
 *   - STRING > a freeform bit of text the user can enter
 *   - DROPDOWN > allows the user to select one item from a predefined list of options
 *   - LIST > allows the user to select one or more items from a predefined list of options 
 * 
 * More details can be seen in the JavaDocs <a href="http://eniripsa96.github.io/SkillAPI/javadoc/?com.sucy.skill.dynamic.custom:EditorOption">here</a>
 *
 * @return settings to show in the editor
 */
List<EditorOption> getOptions();

/**
 * Applies the effects of the component, whether that's filtering out targets
 * as a condition, determining new targets as a targeting component, or 
 * doing something to the current targets as a mechanic component. 
 *
 * The return value is for determining if the skill ran. If the condition
 * cannot run for any reason, you should return false. Otherwise, return true. 
 *
 * @param caster  caster of the skill
 * @param level   level of the skill
 * @param targets targets to execute on
 *
 * @return true if executed, false if conditions not met
 */
public boolean execute(LivingEntity caster, int level, List<LivingEntity> targets);

Step 3 - Include the component in your SkillPlugin method

See Coding Plugins

Step 4 - Test

Start up your server after finishing the above and your component should be loaded into SkillAPI. Check the file at plugins/SkillAPI/tool-config.json and your component along with its settings should be in the file. Drop that JSON file into the dynamic editor and your component should now be an option in the tool. Make a skill with your trigger and try it out!

Examples

Here is a recreation of the Skill Damage trigger with all of its options:

import com.google.common.collect.ImmutableList;
import com.sucy.skill.dynamic.ComponentType;
import com.sucy.skill.dynamic.custom.CustomEffectComponent;
import com.sucy.skill.dynamic.custom.EditorOption;
import org.bukkit.Location;
import org.bukkit.entity.LivingEntity;

import java.util.List;

public class CustomExplosionMechanic extends CustomEffectComponent {
    private static final String POWER  = "power";
    private static final String DAMAGE = "damage";
    private static final String FIRE   = "fire";

    @Override
    public String getKey() {
        return "custom explosion";
    }

    @Override
    public ComponentType getType() {
        return ComponentType.MECHANIC
    }

    @Override
    public String getDescription() {
        return "Causes an explosion at the current target\'s position";
    }

    @Override
    public List<EditorOption> getOptions() {
        return ImmutableList.of(
                EditorOption.number(
                        "power",
                        "Power",
                        "The strength of the explosion",
                        3,
                        0),
                EditorOption.dropdown(
                        "damage",
                        "Damage Blocks",
                        "Whether or not to damage blocks with the explosion",
                        ImmutableList.of("False", "True")),
                EditorOption.dropdown(
                        "fire",
                        "Fire",
                        "Whether or not to set affected blocks on fire",
                        ImmutableList.of("False", "True"))
        );
    }

    @Override
    public boolean execute(LivingEntity caster, int level, List<LivingEntity> targets) {
        if (targets.size() == 0) {
            return false;
        }
        final double power = parseValues(caster, POWER, level, 4);
        final boolean fire = settings.getBool(FIRE, false);
        final boolean damage = settings.getBool(DAMAGE, false);
        for (final LivingEntity target : targets) {
            final Location loc = target.getLocation();
            target.getWorld().createExplosion(loc.getX(), loc.getY(), loc.getZ(), (float) power, fire, damage);
        }
        return true;
    }
}

And here's a recreation of the Chance condition:

import com.google.common.collect.ImmutableList;
import com.sucy.skill.dynamic.ComponentType;
import com.sucy.skill.dynamic.custom.CustomEffectComponent;
import com.sucy.skill.dynamic.custom.EditorOption;
import org.bukkit.entity.LivingEntity;

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

public class CustomChanceCondition extends CustomEffectComponent
{
    private static final String CHANCE = "chance";
    private static final Random random = new Random();

    @Override
    public String getKey() {
        return "custom chance";
    }

    @Override
    public ComponentType getType() {
        return ComponentType.CONDITION;
    }

    @Override
    public String getDescription() {
        return "Rolls a change to apply child components";
    }

    @Override
    public List<EditorOption> getOptions() {
        return ImmutableList.of(
                EditorOption.number(
                        "chance",
                        "Chance",
                        "The change to execute children as a percentage. \"25\" would be 25%.",
                        25,
                        0)
        );
    }

    @Override
    public boolean execute(final LivingEntity caster, final int level, final List<LivingEntity> targets) {
        final double chance = parseValues(caster, CHANCE, level, 25) / 100.0;
        final List<LivingEntity> passed = targets.stream()
                .filter(target -> random.nextDouble() < chance)
                .collect(Collectors.toList());

        return !passed.isEmpty() && executeChildren(caster, level, passed);
    }
}