Create a new quest

Since version 2.3 it is possible to create your own quest types using the plugin API. Here's how to do.

The structure

The structure of how quests work is relatively simple.

The basis of the quest is contained in an AbstractQuest class, which contains all the information common to all quests: the name, the description, the item menu... Each quest class inherits from the AbstractQuest class and implements its own settings. The quest class also adds a canProgress() method, which returns a boolean and performs any necessary checks depending on the quest parameters.

Each type of quest is associated with a game event. For example, BREAK type quests are associated with the BlockBreakEvent event.

The class that listens the event inherits from a PlayerProgressor class, which:

  • checks if the player has an active quest of the same type,

  • if yes, calls the quest's canProgress() method,

  • if the method returns true, make the quest progress.

Finally, quests are saved in the QuestTypeRegistry.

How to create your quest

Here, you'll learn step-by-step how to create your new quest type. For this example, we'll use the PyroFishingPro API to create a quest of type PYRO_FISH.

Step 1: Create a quest object

To begin with, you need to create a class to represent your quest object. This class must inherit from the AbstractQuest class.

public class PyroFishQuest extends AbstractQuest {}

Now you need to implement the constructor to instantiate the AbstractQuest, passing as parameter a BasicQuest object, which represents a quest with no prerequisites.

protected PyroFishQuest(BasicQuest basicQuest) { super(basicQuest); }

Now you need to implement the three methods of the IQuest interface.

The getType() method

This method simply returns the quest type as a String.

@Override
public String getType() {
    return "PYRO_FISH";
}

The loadParameters() method

This method takes the quest's section, file and index as parameters and loads your quest's attributes. For example, for a quest of type PYRO_FISH, we need two attributes: an integer representing the item number, and a String for the tier.

private String tier;
private int id;

Let's suppose we have the following quest:

1:
  name: "Get a custom fish"
  menu_item: COD
  description:
    - "&6Get a custom fish."
    - "&6Win &b5000 &6$."
  quest_type: PYRO_FISH
  pyro_fish_tier: "Bronze"
  pyro_fish_id: 2
  required_amount: 1
  reward:
    reward_type: MONEY
    amount: 5000

Here, we have a PYRO_FISH quest that takes two new parameters: pyro_fish_tier and pyro_fish_id. We now need to load them into our loadParameters() method. To do this, we have the section parameter in the method, which corresponds exactly to the quest. We can then check for the presence of the required parameters in the quest configuration, and trigger an error if this is not the case, using the PluginLogger#configurationError() method.

Here is the result. Of course, you can add additional checks, such as checking the parameter type.

Returning false when the parameters are not correct is important, as it tells the plugin that the quest is invalid and won't be added to the list, thus avoiding errors.

@Override
public boolean loadParameters(ConfigurationSection section, String file, int index) {
    if (!section.contains("pyro_fish_tier")) {
        PluginLogger.configurationError(file, index, "pyro_fish_tier", "You must specify the tier of the Pyro Fish.");
        return false;
    }

    if (!section.contains("pyro_fish_id")) {
        PluginLogger.configurationError(file, index, "pyro_fish_id", "You must specify the id of the Pyro Fish.");
        return false;
    }

    this.tier = section.getString("pyro_fish_tier");
    this.id = section.getInt("pyro_fish_id");

    return true;
}

You don't need to call the method, the plugin will do it automatically!

The canProgress() method

Finally, we need to implement a method for checking that the conditions are met for the quest to progress. This method takes an Event object as parameter. The first step is to check that the event corresponds to the one used by the quest, in order to retrieve its instance. Then we can check our parameters.

@Override
public boolean canProgress(Event provided) {
    if (provided instanceof PyroFishCatchEvent event) {
        return event.getTier().equals(this.tier) && event.getFishNumber() == this.id;
    }

    return false;
}

Here, we're using the PyroFishCatchEvent and its associated methods. It's as simple as that!

You don't need to call the method, the plugin will do it automatically!

You don't need to check for yourself whether the player is in a forbidden world or an unauthorized region. If the parameter is present in your config.yml file, the plugin already checks this at the time of progression!

After implementing all the methods, here's the complete result.

PyroFishQuest class
package com.ordwen.odailyquests.quests.types.custom.items;

import com.ordwen.odailyquests.quests.types.AbstractQuest;
import com.ordwen.odailyquests.quests.types.shared.BasicQuest;
import com.ordwen.odailyquests.tools.PluginLogger;
import me.arsmagica.API.PyroFishCatchEvent;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.Event;

public class PyroFishQuest extends AbstractQuest {

    private String tier;
    private int id;

    protected PyroFishQuest(BasicQuest basicQuest) {
        super(basicQuest);
    }

    @Override
    public String getType() {
        return "PYRO_FISH";
    }

    @Override
    public boolean canProgress(Event provided) {
        if (provided instanceof PyroFishCatchEvent event) {
            return event.getTier().equals(this.tier) && event.getFishNumber() == this.id;
        }

        return false;
    }

    @Override
    public boolean loadParameters(ConfigurationSection section, String file, int index) {
        if (!section.contains("pyro_fish_tier")) {
            PluginLogger.configurationError(file, index, "pyro_fish_tier", "You must specify the tier of the Pyro Fish.");
            return false;
        }

        if (!section.contains("pyro_fish_id")) {
            PluginLogger.configurationError(file, index, "pyro_fish_id", "You must specify the id of the Pyro Fish.");
            return false;
        }

        this.tier = section.getString("pyro_fish_tier");
        this.id = section.getInt("pyro_fish_id");

        return true;
    }
}

Now that our quest object is ready, we can move on to the next step.

Step 2: Create the listener

To trigger quest progression, we need to create a class that inherits from PlayerProgressor and implements Listener. Then define the method that listens for the desired event.

When the event is triggered, simply call PlayerProgressor's setPlayerQuestProgression() method, which takes as its parameters the event in question, the player involved, the amount to be added to the progression, and the type of quest.

package com.ordwen.odailyquests.events.listeners.item.custom;

import com.ordwen.odailyquests.quests.player.progression.PlayerProgressor;
import me.arsmagica.API.PyroFishCatchEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public class PyroFishCatchListener extends PlayerProgressor implements Listener {

    @EventHandler(ignoreCancelled = true)
    public void onPyroFishCatch(PyroFishCatchEvent event) {
        setPlayerQuestProgression(event, event.a(), 1, "PYRO_FISH");
    }
}

For the PyroFishCatchEvent, the method used to retrieve the player is a() (strange name but that's how it is), and as the player receives only one item per event, the amount is 1. Then we specify the PYRO_FISH quest type.

Of course, you'll need to adapt to the needs of the event you're listening to.

You can add to this method any pre-checks that would not be taken into account by your quest. For example, if the API of the target plugin allows it, the type of ocean, the distance from the shore, or a defined region...

Step 3: Register quest and event

Now all you have to do is register the quest in the QuestTypeRegistry and the event, so that it can be listened to.

An event must be registered in the same way as any other event, as with any other plugin.

Bukkit.getPluginManager().registerEvents(new PyroFishCatchListener(), oDailyQuests);

The quest type must be registered in the onLoad() method of your main class.

@Override
public void onLoad() {
    ODailyQuestsAPI.registerQuestType("PYRO_FISH", PyroFishQuest.class);
}

And that's it! Now all you have to do is compile and add your plugin to your server. When it starts up, O'DailyQuests should write you logs showing that your quests are loading correctly.

Last updated