User Tools

Site Tools


customspellcreation

Custom Spell Creation

This is a guide that will teach you how to create your own custom spells. This guide assumes that you already know the basics of the Java programming language and the Bukkit API. If you would like to make spells but do not know Java or have not become familiar with the Bukkit API, there are many tutorials out there to help you.

This guide also assumes you are using Eclipse, but you can obviously make spells in whatever editor you want (even a basic text editor and javac if you wish).

Resources

These resources will be useful while creating spells.

Getting Started

First, let's create a new project in Eclipse. It doesn't matter what you call it, but I'm going to assume you've called it “MyCustomSpells”. The first thing you'll need to do is add MagicSpells and Bukkit to your class path.

Now we need to create a package to put our spells in. Again, it's up to you what you name it, you can even just use the default package if you want. For the purposes of this tutorial, I'm going to name the package “my.custom.spells”.

Now, we're ready to create our first spell.

The Hello World Spell

We're going to create a “Hello World” spell. It's boring, I know, but it's a good place to start and learn the basics of how spells work. First, we're going to create a new class called “HelloSpell”, and it will extend the “InstantSpell” class from MagicSpells. We'll go over what the different spell types are later, for now, just use InstantSpell. Eclipse will now complain about how you're missing a constructor and some required methods, so go ahead and just let it auto-generate those for you. Now we have something like this:

package my.custom.spells;
 
import org.bukkit.entity.Player;
 
import com.nisovin.magicspells.spells.InstantSpell;
import com.nisovin.magicspells.util.MagicConfig;
 
public class HelloSpell extends InstantSpell {
 
	public HelloSpell(MagicConfig config, String spellName) {
		super(config, spellName);
		// TODO Auto-generated constructor stub
	}
 
	@Override
	public PostCastAction castSpell(Player arg0, SpellCastState arg1,
			float arg2, String[] arg3) {
		// TODO Auto-generated method stub
		return null;
	}
 
}

Now, I'm just going to do some cleanup, and add the spell's code. All this spell will do is send a message to the caster that says “Hello World!” Here's our modified code:

package my.custom.spells;
 
import org.bukkit.entity.Player;
 
import com.nisovin.magicspells.spells.InstantSpell;
import com.nisovin.magicspells.util.MagicConfig;
 
public class HelloSpell extends InstantSpell {
 
	public HelloSpell(MagicConfig config, String spellName) {
		super(config, spellName);
	}
 
	@Override
	public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) {
		if (state == SpellCastState.NORMAL) {
			sendMessage(player, "Hello World!");
		}
		return PostCastAction.HANDLE_NORMALLY;
	}
 
}

There are three things to point out in this code.

First, I'm checking to make sure that the state is SpellCastState.NORMAL. You will almost always want to do this first. If the spell cast state is not NORMAL, then it usually should not be cast, because it is on cooldown, or the player is missing reagents, or something similar. There are certain situations where you might want to do something anyway (like toggle off a buff spell), but for the most part you should just always check for SpellCastState.NORMAL.

Second, I'm using the sendMessage method of the Spell class rather than Bukkit's player.sendMessage method. There are a few reasons to use this method:

  • It will automatically use the MagicSpells color from the configuration
  • It will turn ampersand codes into colors
  • It will properly handle line breaks (\n) by sending multiple messages
  • It will ignore the command if the message is empty

Third, the castSpell method returns PostCastAction.HANDLE_NORMALLY. This tells MagicSpells to do any normal post-cast actions, like starting the cooldown, charging reagents, and sending messages. We'll go into this in more detail later.

Now, we need to do two things to get this spell working. First, we need to export our project into a jar file. You can call the jar file whatever you want. Once you have it, just drop it into the plugins/MagicSpells folder. Second, we need to add the spell to the config.yml file. Open up that file, and add this to the bottom:

    hello:
        spell-class: "my.custom.spells.HelloSpell"
        name: hello
        description: Hello, world!

Note that the spell-class option must specify the full package and class name of your custom spell class. Now you can just start up the server and try out your new spell!

A Better Spell

That spell is pretty lame, so for our second spell we'll do something a bit more interesting. This spell will launch our target high into the sky! This is much more exciting than the last spell. So, let's make a new class, and call it LaunchSpell.

Spell Types

Now we need to decide what type of spell it is. There are several built in spell types, which are classes that you can use as the base class for your spell.

  • CommandSpell - A spell that must be cast as a command, and generally accepts arguments
  • InstantSpell - A generic spell whose effects happen immediately
    • TargetedSpell - This is a special type of instant spell that targets either an entity (monster or player) or a location (block)
      • TargetedEntitySpell - A spell that targets an entity (a monster or player)
      • TargetedLocationSpell - A spell that targets a location (a block)
  • BuffSpell - A spell that adds a lasting effect to the casting player
  • ChanneledSpell - A spell that requires multiple players casting together to work

If you don't think any of these fit, you can also just extend the Spell class, or even create your own type of spell by creating a class that extends the Spell class, then extend that class. For this spell, we're going to use TargetedEntitySpell, since we want this spell to target an entity.

After having Eclipse generate some code and doing some cleanup, our spell code currently looks like this:

package my.custom.spells;
 
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
 
import com.nisovin.magicspells.spells.TargetedEntitySpell;
import com.nisovin.magicspells.util.MagicConfig;
 
public class LaunchSpell extends TargetedEntitySpell {
 
	public LaunchSpell(MagicConfig config, String spellName) {
		super(config, spellName);
	}
 
	@Override
	public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) {
		return null;
	}
 
	@Override
	public boolean castAtEntity(Player player, LivingEntity target, float power) {
		return false;
	}
 
}

Configuration Options

We want this spell to be configurable, so other server owners can customize how the spell works. There are three values we want to be able to configure: the range of the spell, how hard the target is thrown upward, and the error message to display if no target is found. However, the TargetedSpell will already get a range for us. So, we'll create a private int and a String in our class called “force” and “strNoTarget”.

    private int force;
    private String strNoTarget;

Now in the constructor, we'll get the values of those options from the config file. The spell class already has some built-in methods for getting values from the config, so we'll use those.

    public LaunchSpell(MagicConfig config, String spellName) {
        super(config, spellName);
 
        this.force = getConfigInt("force", 5);
        this.strNoTarget = getConfigString("str-no-target", "No target found.");
    }

As you can see, the getConfigInt and getConfigString methods allows you to specify a config key name and a default value.

Getting a Target

The TargetedSpell class has a useful method (getTargetedEntity) for getting the target of your spell, so we'll use it. It will return null if no target was found, so we'll want to account for that when writing the spell. If no target is found, we'll send our error message and not do anything else. The getTargetedEntity method has some parameters for whether it can target players, and whether it should obey line-of-sight restrictions. You would normally want to add config options for those, but for now we'll just hard-code in our options.

The Launch Spell

So, let's just write up the code for the spell.

package my.custom.spells;
 
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
 
import com.nisovin.magicspells.spells.TargetedEntitySpell;
import com.nisovin.magicspells.util.MagicConfig;
 
public class LaunchSpell extends TargetedEntitySpell {
 
	private int force;
	private String strNoTarget;
 
	public LaunchSpell(MagicConfig config, String spellName) {
		super(config, spellName);
 
		this.force = getConfigInt("force", 5);
		this.strNoTarget = getConfigString("str-no-target", "No target found.");
	}
 
	@Override
	public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) {
		if (state == SpellCastState.NORMAL) {
			LivingEntity target = getTargetedEntity(player, range, false, true);
			if (target == null) {
				sendMessage(player, strNoTarget);
				return PostCastAction.ALREADY_HANDLED;
			} else {
				target.setVelocity(new Vector(0, force, 0));
			}
		}
		return PostCastAction.HANDLE_NORMALLY;
	}
 
	@Override
	public boolean castAtEntity(Player player, LivingEntity target, float power) {
		return false;
	}
 
}

Post Cast Actions

Now is a good time to discuss what happens after the castSpell method is finished. MagicSpells can handle several things for you so you don't have to worry about it. These things are:

  • Begin the spell's cooldown timer
  • Charge the spell's reagents
  • Send the cast messages to the player and to nearby players (str-cast-self and str-cast-others)

The return value of the castSpell method determines which of these things occur. There are several options:

  • HANDLE_NORMALLY: All of the actions will occur (unless the state wasn't NORMAL, in which case it will send appropriate error messages).
  • ALREADY_HANDLED: None of the actions will occur. This should be used if there is some kind of error (like if the spell could not find a target).
  • NO_MESSAGES: This will make the cooldown and begin and the reagents be charged, but the messages will not be sent. This is useful if you want to handle the message sending yourself.
  • NO_COOLDOWN
  • NO_REAGENTS
  • COOLDOWN_ONLY
  • MESSAGES_ONLY
  • REAGENTS_ONLY

The first three are the ones you will use most of the time. The rest are just there for completion. In our launch spell, ALREADY_HANDLED is used when a target was not found, and HANDLE_NORMALLY is used otherwise.

Test the Spell

Now, let's make our jar again, and replace the old one in the plugins/MagicSpells folder. And again, we'll need to add this new spell to our config file, so we'll add this to the bottom:

    launch:
        spell-class: "my.custom.spells.LaunchSpell"
        name: launch
        description: Launch your target into the air!
        range: 10
        force: 5
        str-no-target: No target found.
        str-cast-self: You have launched your target into the air!

Now load up your server, look at a sheep, and type /cast launch, and watch that sheep fly into the sky!

Targeted Messages

The TargetedSpell class has a special method to send messages. This method will replace %t with the target's name, and will also attempt to send a message to the target if the target is a player. Since we want to be able to use %t, we'll use this method instead, which means we'll need to change our return value to NO_MESSAGES. Here's what our castSpell method looks like now:

	@Override
	public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) {
		if (state == SpellCastState.NORMAL) {
			LivingEntity target = getTargetedEntity(player, range, false, true);
			if (target == null) {
				sendMessage(player, strNoTarget);
				return PostCastAction.ALREADY_HANDLED;
			} else {
				target.setVelocity(new Vector(0, force, 0));
				sendMessages(player, target);
				return PostCastAction.NO_MESSAGES;
			}
		}
		return PostCastAction.HANDLE_NORMALLY;
	}

Spell Power

Sometimes spells can be cast with a power modifier. Currently, the empower spell is the only way this happens, but in the future there may be other ways this could change. This is what the power parameter of the castSpell method is for. A power of 1.0 is normal. Since we want our spell to be compatible with the empower spell, we'll multiply the force by the power.

target.setVelocity(new Vector(0, force*power, 0));

A Final Change

You may have noticed the castAtEntity method. The projectile spell (and perhaps other spells in the future) use this method to force a targeted spell's action to occur without actually needing to cast the spell. Since we want our spell to support the projectile spell, we'll need to implement this method. To do this, we'll pull the actual spell code out of the castSpell method and into a new method, then call this method from both castSpell and castAtEntity. Here's what our spell looks like now:

package my.custom.spells;
 
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
 
import com.nisovin.magicspells.spells.TargetedEntitySpell;
import com.nisovin.magicspells.util.MagicConfig;
 
public class LaunchSpell extends TargetedEntitySpell {
 
	private int force;
	private String strNoTarget;
 
	public LaunchSpell(MagicConfig config, String spellName) {
		super(config, spellName);
 
		this.force = getConfigInt("force", 5);
		this.strNoTarget = getConfigString("str-no-target", "No target found.");
	}
 
	@Override
	public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) {
		if (state == SpellCastState.NORMAL) {
			LivingEntity target = getTargetedEntity(player, range, false, true);
			if (target == null) {
				sendMessage(player, strNoTarget);
				return PostCastAction.ALREADY_HANDLED;
			} else {
				launch(target, power);
				sendMessages(player, target);
				return PostCastAction.NO_MESSAGES;
			}
		}
		return PostCastAction.HANDLE_NORMALLY;
	}
 
	private void launch(LivingEntity target, float power) {
		target.setVelocity(new Vector(0, force*power, 0));
	}
 
	@Override
	public boolean castAtEntity(Player player, LivingEntity target, float power) {
		launch(target, power);
		return true;
	}
 
}

Obviously, creating a separate method with only one line may not be necessary, but for more complex spells it would make things easier.

Conclusion

This was just a quick tutorial on the basics of creating a spell. There is a lot of functionality you can access in the MagicSpells plugin to use with your spells. Be sure to keep the MagicSpells javadocs and the Bukkit javadocs handy while writing your spells. Looking at the MagicSpells Source Code can also be a way to learn how to make spells.

If you'd like to download the source code and the compiled jar for this tutorial, click here.

Submitting a Spell

If you create a spell you think is just amazing, and you wish to share it with everyone, please let me know! Send me the source code either in a ticket or in a PM and I'll look into adding it to the main plugin. Please keep in mind that if you do choose to submit a spell to me, I reserve the right to make any changes I think are necessary.

customspellcreation.txt · Last modified: 2012/03/11 20:03 by nisovin