- Notifications
You must be signed in to change notification settings - Fork 72
Dev API
On this page, we will document how to add custom spells, modifier conditions, passive listeners, etc. to MagicSpells.
Jump to a section:
- Adding the dependency
- Custom spell classes
- Expressions
- Custom modifier conditions, passive listeners, variables, and spell effects
- Custom No Magic Zone types
- Custom CleanseSpell cleansers
- Custom MobGoalEditSpell goals
MagicSpells uses JitPack as its repository service. You can find guides on how to add MagicSpells as a dependency for your build environment here or use the examples below.
repositories {
maven {url "https://jitpack.io"}
}
dependencies {
implementation("com.github.TheComputerGeek2.MagicSpells:core:main-SNAPSHOT") {transitive =false}
}<repository>
<id>jitpack-repo</id>
<url>https://jitpack.io</url>
</repository><dependency>
<groupId>com.github.TheComputerGeek2.MagicSpells</groupId>
<artifactId>core</artifactId>
<version>main-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>- Custom spell classes can be placed in the root folder of the plugin or in any folder in the root starting with
"classes". - MagicSpells load custom spell classes if a spell uses the classified path in its
spell-classproperty. If the spell is in thecom.nisovin.magicspells.spellspackage, it may be left out (e.g.spell-class: ".MultiSpell"). - All you have to do is to extend either the Spell, CommandSpell, InstantSpell, TargetedSpell, or BuffSpell classes.
TargetedSpellclasses may implement the following interfaces: TargetedEntitySpell, TargetedEntityFromLocationSpell, or TargetedLocationSpell.- Since 4.0 Beta 14. Spells may be annotated with
@DependsOnwhich contains a plugin or an array of them which the spell class will depend on being enabled before being loaded.
This example displays a basic instant spell.
packagecom.example.instant;
importorg.bukkit.entity.LivingEntity;
importcom.nisovin.magicspells.util.MagicConfig;
importcom.nisovin.magicspells.spells.InstantSpell;
importcom.nisovin.magicspells.spelleffects.EffectPosition;
publicclassHelloWorldSpellextendsInstantSpell {
publicHelloWorldSpell(MagicConfigconfig, StringspellName) {
super(config, spellName);
}
@OverridepublicPostCastActioncastSpell(LivingEntitycaster, SpellCastStatestate, floatpower, String[] args) {
if (state == SpellCastState.NORMAL) {
caster.sendMessage("Hello World!");
// We should always play the correct effects in the spell.playSpellEffects(EffectPosition.CASTER, caster);
}
returnPostCastAction.HANDLE_NORMALLY;
}
}hello_world:
spell-class: "com.example.instant.HelloWorldSpell"Here's how that code should look like since 4.0 Beta 13.
packagecom.example.instant;
importorg.bukkit.entity.Player;
importcom.nisovin.magicspells.util.SpellData;
importcom.nisovin.magicspells.util.CastResult;
importcom.nisovin.magicspells.util.MagicConfig;
importcom.nisovin.magicspells.spells.InstantSpell;
publicclassHelloWorldSpellextendsInstantSpell {
publicHelloWorldSpell(MagicConfigconfig, StringspellName) {
super(config, spellName);
}
@OverridepublicCastResultcast(SpellDatadata) {
// Fail if the caster isn't a player.// In our case the message is only relevant if it was sent to a player.if (!(data.caster() instanceofPlayercaster)) returnnewCastResult(PostCastAction.ALREADY_HANDLED, data);
caster.sendMessage("Hello World!");
playSpellEffects(data);
returnnewCastResult(PostCastAction.HANDLE_NORMALLY, data);
}
}This example displays a basic targeted spell, and how to handle targets and spell effects in targeted spells.
packagecom.example.targeted;
importorg.bukkit.entity.LivingEntity;
importcom.nisovin.magicspells.util.TargetInfo;
importcom.nisovin.magicspells.util.MagicConfig;
importcom.nisovin.magicspells.spells.TargetedSpell;
importcom.nisovin.magicspells.spells.TargetedEntitySpell;
importcom.nisovin.magicspells.spelleffects.EffectPosition;
publicclassHelloWorldSpellextendsTargetedSpellimplementsTargetedEntitySpell {
publicHelloWorldSpell(MagicConfigconfig, StringspellName) {
super(config, spellName);
}
@OverridepublicPostCastActioncastSpell(LivingEntitycaster, SpellCastStatestate, floatpower, String[] args) {
if (state == SpellCastState.NORMAL) {
TargetInfo<LivingEntity> targetInfo = getTargetedEntity(caster, power);
if (targetInfo == null) returnnoTarget(caster);
LivingEntitytarget = targetInfo.getTarget();
hello(caster, target);
sendMessages(caster, target);
returnPostCastAction.NO_MESSAGES;
}
returnPostCastAction.HANDLE_NORMALLY;
}
@OverridepublicbooleancastAtEntity(LivingEntitycaster, LivingEntitytarget, floatpower) {
returnhello(caster, target);
}
@OverridepublicbooleancastAtEntity(LivingEntitytarget, floatpower) {
returnhello(null, target);
}
privatebooleanhello(LivingEntitycaster, LivingEntitytarget) {
target.sendMessage("Hello World!");
if (caster == null) playSpellEffects(EffectPosition.TARGET, target);
elseplaySpellEffects(caster, target);
returntrue;
}
}hello_world_targeted:
spell-class: "com.example.targeted.HelloWorldSpell"Here's how that code should look like since 4.0 Beta 13.
packagecom.example.targeted;
importorg.bukkit.entity.LivingEntity;
importorg.bukkit.entity.Player;
importcom.nisovin.magicspells.util.CastResult;
importcom.nisovin.magicspells.util.SpellData;
importcom.nisovin.magicspells.util.TargetInfo;
importcom.nisovin.magicspells.util.MagicConfig;
importcom.nisovin.magicspells.spells.TargetedSpell;
importcom.nisovin.magicspells.spells.TargetedEntitySpell;
publicclassHelloWorldSpellextendsTargetedSpellimplementsTargetedEntitySpell {
publicHelloWorldSpell(MagicConfigconfig, StringspellName) {
super(config, spellName);
}
@OverridepublicCastResultcast(SpellDatadata) {
TargetInfo<LivingEntity> info = getTargetedEntity(data);
if (info.noTarget()) returnnoTarget(info);
returnhelloWorld(info.spellData());
}
@OverridepublicCastResultcastAtEntity(SpellDatadata) {
returnhelloWorld(data);
}
privateCastResulthelloWorld(SpellDatadata) {
if (!(data.target() instanceofPlayertarget)) returnnoTarget(data);
target.sendMessage("Hello World!");
playSpellEffects(data);
returnnewCastResult(PostCastAction.HANDLE_NORMALLY, data);
}
}This example includes configuration reading.
packagecom.example.instant;
importorg.bukkit.entity.Player;
importorg.bukkit.entity.LivingEntity;
importcom.nisovin.magicspells.util.Util;
importcom.nisovin.magicspells.util.MagicConfig;
importcom.nisovin.magicspells.spells.InstantSpell;
publicclassRollDiceSpellextendsInstantSpell {
privateintmin;
privateintmax;
privatefinalStringstrMessage;
publicRollDiceSpell(MagicConfigconfig, StringspellName) {
super(config, spellName);
min = getConfigInt("min", 0);
max = getConfigInt("max", 10);
strMessage = getConfigString("message", "Dice: ");
}
@OverridepublicPostCastActioncastSpell(LivingEntitylivingEntity, SpellCastStatestate, floatpower, String[] args) {
// Here we are dealing with a spell that only works for Player casters.if (state == SpellCastState.NORMAL && livingEntityinstanceofPlayercaster) {
// We're using Util methods not to create duplicate code.intrandom = min + Util.getRandomInt(max - min + 1);
caster.sendMessage(strMessage + random);
}
returnPostCastAction.HANDLE_NORMALLY;
}
}roll_dice:
spell-class: "com.example.instant.RollDiceSpell"min: 0max: 100message: "You rolled: "Here's how that code should look like since 4.0 Beta 13.
packagecom.example.instant;
importorg.bukkit.entity.Player;
importcom.nisovin.magicspells.util.CastResult;
importcom.nisovin.magicspells.util.SpellData;
importcom.nisovin.magicspells.util.MagicConfig;
importcom.nisovin.magicspells.spells.InstantSpell;
publicclassRollDiceSpellextendsInstantSpell {
privatefinalintmin;
privatefinalintmax;
privatefinalStringstrMessage;
publicRollDiceSpell(MagicConfigconfig, StringspellName) {
super(config, spellName);
min = getConfigInt("min", 0);
max = getConfigInt("max", 10);
strMessage = getConfigString("message", "Dice: ");
}
@OverridepublicCastResultcast(SpellDatadata) {
if (!(data.caster() instanceofPlayercaster)) returnnewCastResult(PostCastAction.ALREADY_HANDLED, data);
playSpellEffects(data);
caster.sendMessage(strMessage + random.nextInt(min, max));
returnnewCastResult(PostCastAction.HANDLE_NORMALLY, data);
}
}Warning
Since 4.0 Beta 13.
If you want an option to support variable replacement and other things expressions do, instead of grabbing the value using methods like:
// In classes extending "Spell"Stringkey = getConfigString("key", "");
// Other config-reading cases:ConfigurationSectionconfig = /* */;
Stringkey = config.getString("key", "");You can use the Spell#getConfigDataX-variant methods or the ConfigDataUtil util class to get a ConfigData<T> wrapping object:
// In classes extending "Spell"ConfigData<String> key = getConfigDataString("key", "");
// Other config-reading cases:ConfigurationSectionconfig = /* */;
ConfigData<String> key = ConfigDataUtil.getString(config, "key", "");With it, you can resolve its value during runtime:
@OverridepublicCastResultcast(SpellDatadata) {
playSpellEffects(data);
Stringkey = this.key.get(data);
. . .
returnnewCastResult(PostCastAction.HANDLE_NORMALLY, data);
}- This section includes the creation of custom modifier conditions, passive listeners, variables, and spell effects.
- If you would like to add these features, you can use the API from your custom plugin. However, if you don't want to add a separate plugin, you could utilise a custom spell class to achieve this.
- If you are using a custom plugin to load these modules, you could use this resource to load these classes more simply. It could also serve as an example of what follows. The plugin must "soft depend" on "MagicSpells".
- You have to create an event handler for the module you want to add, then add it through its manager there. The events are:
ConditionsLoadingEvent,PassiveListenersLoadingEvent,VariablesLoadingEvent, andSpellEffectsLoadingEvent. You can fetch the specific manager from static methods in the MagicSpells class or from the event getters. Each of these managers includes a method to add the module you want.
@EventHandlerpublicvoidonConditionLoad(ConditionsLoadingEventevent) {
MagicSpells.getConditionManager().addCondition("always", AlwaysCondition.class);
}- NOTE: Since 4.0 Beta 14:
- You can annotate your class with
@DependsOn, passing a plugin or an array of plugins required to be enabled before this addon is loaded. - You can alternatively annotate your custom class with
@Nameand add calling the add method without the name parameter.
- You can annotate your class with
@Name("always")
publicclassAlwaysConditionextendsCondition { . . . }
. . .
@EventHandlerpublicvoidonConditionLoad(ConditionsLoadingEventevent) {
MagicSpells.getConditionManager().addCondition(AlwaysCondition.class);
}A class extending NoMagicZone can be added to the no magic zone type list like this:
@EventHandlerpublicvoidonMSLoading(MagicSpellsLoadingEventevent) {
MagicSpells.getNoMagicZoneManager().addZoneType("cuboid", NoMagicZoneCuboid.class);
}Since 4.0 Beta 14 the class may be annotated with @DependsOn listing required plugins that need to be loaded before the zone is and with the @Name annotation holding its name instead of it being passed by the add method:
@Name("cuboid")
publicclassNoMagicZoneCuboidextendsNoMagicZone { . . . }
. . .
@EventHandlerpublicvoidonMSLoading(MagicSpellsLoadingEventevent) {
MagicSpells.getNoMagicZoneManager().addZoneType(NoMagicZoneCuboid.class);
}Warning
Since 4.0 Beta 14.
Registering custom cleansers for the Cleanse Spell to list under its remove option is possible. You can find some examples of how to implement a cleanser here.
Before 4.0 Beta 17:
importcom.nisovin.magicspells.spells.targeted.cleanse.util.Cleansers;
. . .
Cleansers.addCleanserClass(/* <Class which extends Cleanser>*/);Since 4.0 Beta 17:
@EventHandlerpublicvoidonMSLoading(MagicSpellsLoadingEventevent) {
MagicSpells.getCleanserManager().addCleanser(/* <Class which extends Cleanser>*/);
}Warning
Since 4.0 Beta 14.
You can find some examples of how to implement a mob goal here. The difference between PaperMC goals is that you extend CustomGoal instead, implement its CustomGoal#initialize(ConfigurationSection) method, and annotate the goal with @Name holding its goal key.
Before 4.0 Beta 17:
importcom.nisovin.magicspells.util.ai.CustomGoals;
. . .
CustomGoals.addGoal(/* <Class which extends CustomGoal>*/);Since 4.0 Beta 17:
@EventHandlerpublicvoidonMSLoading(MagicSpellsLoadingEventevent) {
MagicSpells.getCustomGoalsManager().addGoal(/* <Class which extends CustomGoal>*/);
}