Spells plugin - API for developers

Discussion in 'Resources' started by NathanWolf, Jan 23, 2011.

Thread Status:
Not open for further replies.
  1. Offline

    NathanWolf

    Some of you may be familiar with my Spells plugin, which is great fun.

    What you might not know, is that it could be a great way to jump start your plugin development.

    Now, with javadocs and sample code!

    Do you want to write a plugin that:
    • Interacts with the player or world in some way?
    • Can take advantage of an easy-to-use targeting system?
    • Needs to perform a frequent one-off action?
    • Does anything that could be considered a "magic spell"?
    • Wants to safely modify the world (with undo)?
    If any of the above fits the bill, or you just want to play around with some fun abilities on your server, then maybe writing a Spells-dependent plugin is for you!

    What Spells offers:

    • A targeting framework, for getting blocks at the player's cursor. The system is configurable to pass through an arbitrary list of materials (air, water, glass, etc). This was originally ported (by me) from hMod's HitBlox code- I'll be happy to switch to whatever Bukkit offers when it's time.
    • A movement framework, for getting a place to stand, moving up or down through the world, etc.
    • A permissions framework, eventually to make use of the Permissions plugin
    • Easy integration into a "casting" system, eventually to provide reagants and MP support
    • Easy integration into the Wand plugin for console-free command use
    • A deep an easy-to-use undo system for world modification
    If this is all sounds great, then here's the disclaimer:

    I didn't originally develop Spells with an API in mind, as such. However, due to the way I write my code, and the way Java works, there's no real reason you can't make use of it.

    My first user, dida55, was kind enough to provide a sample project, which you can download:

    SampleSpell.zip download
    SampleSpell on github

    This is currently just a copy of the "Absorb" builtin spell wrapped in its own plugin- eventually I want to add examples of how to do various things in onCast, but for now it's a great way to get up and running- just use it as a template.

    What you would need to do is create a basic, empty plugin (or start with the SampleSpell template).

    Then, download and reference Spells.jar, or pull the Spells source from github. You need at least Spells v0.52.

    There is one last tricky bit you need to set up - create a MANIFEST.mf file (see SpellSample for an example) that specifies Spells.jar on your plugin's classpath.

    This will make sure that Spells loads before your plugin, so the base classes are available.

    When you export your jar to test your plugin, you need to use this manifest file. This is the "-m" parameter for the jar program, or if you use Eclipse, there is an option on the last pane of the JAR export wizard to use your manifest file.

    Now, on to the code!

    Your plugin needs to maintain a reference to the spells API:
    Code:
    import com.elmakers.mine.bukkit.plugins.spells.Spells;
    import com.elmakers.mine.bukkit.plugins.spells.SpellsPlugin;
    
    class YourPlugin extends Java Plugin
    {
    private Spells spells = null;
    ...
    
    Then, in your onEnable() callback, you would reference Spells like so:

    Code:
    @Override
    public void onEnable()
    {
        Plugin checkForSpells = this.getServer().getPluginManager().getPlugin("Spells");
    
        if (checkForSpells != null)
        {
            SpellsPlugin spellsPlugin = (SpellsPlugin)checkForSpells;
            this.spells = spellsPlugin.getSpells();
        }
        else
        {
            log.warning("The XXXXXXX plugin depends on the Spells plugin");
            this.getServer().getPluginManager().disablePlugin(this);
            return;
        }
    
        spells.addSpell(new YourCoolSpell());
    }
    
    Next, create a class "YourCoolSpell" based on com.elmakers.mine.bukkit.plugins.spells.Spell.

    If you use a tool like Eclipse, it will automatically create the functions you need to override. Otherwise, the abstract interface looks like this:

    Code:
    
    import org.bukkit.Material;
    
    public class MyCoolSpell extends Spell
    {
    
    @Override
    public boolean onCast(String[] parameters)
    {
    	// TODO Perform your actions!
    	return false;
    }
    
    @Override
    protected String getName()
    {
    	return "mycoolplugin";
    }
    
    @Override
    public String getCategory()
    {
    	return "tools";
    }
    
    @Override
    public String getDescription()
    {
    	return "Does something awesome!";
    }
    
    @Override
    public Material getMaterial()
    {
    	// TODO Choose a material to represent this spell's
    	 // icon in the Wand.
    	return Material.AIR;
    }
    
    }
    
    And, that's pretty much it. Check out the javadocs, focusing on the Spell and Spells classes to get a feel for the API. I'm thinking of integrating all of the Spells functionality directly into Spell to make the API easier to use in the future.

    In a general sense, you access basic utility functions through Spells methods- such as getTargetBlock, or isUnderwater. You can access the player casting the spell with the "player" member variable, and the Spells API with the "spells" member variable.

    The Spells class has a few system-level functions that are useful for plugins to call, such as addToUndoQueue or scheduleCleanup.

    It also provides a basic listener interface, to pass on listen events (and material selection events). This system is very basic at present, and I don't want it to get overused. The idea is not to have a ton of spells all listening for events all the time. It's better to activate your command, listen for a while, and then de-activate. But, use as you see fit!

    Some highlights:

    • Create a BlockList and register it with spells.addToUndoQueue to enable undoing world modifications.
    • Call getTargetBlock() to retrieve the block the player is currently targeting.
    • Call targetThrough(material) to modify targeting behavior
    • Call getLastBlock to get the BlockFace of the target block
    • Call handy (and configurable) functions such as isUnderwater(), isOkToStandIn(block), isOkToStandOn(block), etc.
    • Use spells.scheduleCleanup() to have a BlockList get automatically undone at a later time.
    • Use startMaterialUse() and finishMaterialUse() to make use of the material-choosing system.
    • Use spells.registerEvent and unregisterEvent to let your spell listen for (a small set of) events.

    Feedback is always welcome and appreciated!
     
  2. Offline

    dida55

    Hey, Im really interested in this, but i'm not sure how to implement the Spells.jar ? Just add it to resources, thesame wy as i added CraftBukkit.jar? thanks
     
  3. Offline

    NathanWolf

    Right, exactly. Just add it to your project the same an any other external jar.

    I don't have javadocs yet, so you basically have to follow the examples above to get started once you've got the linking set up.

    It'll be easier if you can bind to the source- a lot easier, really. If you have git, you can pull a read-only copy and then just add it as a reference (this is easy in Eclipse, not sure otherwise).

    This would give you access to all of the source, which would make it much easier to see how to do things.

    I hope to get to javadocs eventually!

    Also, it's worth noting that this code is going to change quite a bit soon- I'll be integrating it with my Persistence and Classes plugins.

    The basic structure for creating and registering a Spell class won't change much, if at all, though- so any work you would do would carry over fine. Just thought I'd mention it :)
     
  4. Offline

    dida55

    Yay, thanks! I'll start working with this in... about one and a half hour :p I hope it'll make coding easier!
     
  5. Offline

    NathanWolf

    It really should! At least, if you want to make something that fits well within the mold provided :)

    Once the integration is complete, I'll probably call it 1.0. Persistence and Classes integration will open up a world of power underneath the hood that you'll instantly have available if you're using Spells. This will include:
    • Dynamic, data-driven user, group and permission management
    • Easy, customized persistence- so your data can survive a server reboot
    • Data-driven messages, so admins can customize any in-game text your plugins use
    • A common data-driven material list mechanism
    • (eventually) Permissions support
    and more....

    Let me know when you've got something going- you're going to be my first "customer" :) I'm happy to help.
     
  6. Offline

    dida55

    I will surely post it :)

    However, i'm new to plugin developing, though i already made two working ones, but If you have some spare time , Id greatly appreciate if you could make an examle usig the api.

    If not, and you have no time for it, no problem, I'll still try to do something :)
     
  7. Offline

    NathanWolf

    Really, any of the builtin spells (in the "builtin" package in Spells), are great examples of how to use the API. If you've already made working plugins, you've got the hard part done! Basically just start by copy+pasting the sample code in the OP above, and then checkout some example code in the builtin package.

    I'd recommend starting with absorb (github link), it's a really simple spell- about 60 lines of code, all told- and I use a LOT of whitespace and blocking :)

    The rest of the spells in there are all great examples, too, in varying degrees of complexity. They can show you how to use the undo system, the targeting system, material selection, spell variants, etc. Let me know if there's a particular system you're interested in using, and I can point you to a good example builtin.

    Otherwise, feel free to just browse the repo! And, of course, install the Spells plugin and play with the spells in-game to get a feel for how the systems work in practice.
     
  8. Offline

    dida55

    Finally I'm on PC :) Thanks for those links, I'll start working ;) Hope i can get some examples work (at least) today.
    --- merged: Feb 1, 2011 7:18 PM ---
    Alrght, here's my Absorb.java file :p
    Code:
    package com.bukkit.dida55.Absorb;
    import com.elmakers.mine.bukkit.plugins.spells.Spells;
    import com.elmakers.mine.bukkit.plugins.spells.SpellsPlugin;
    import com.bukkit.dida55.Absorb.AbsorbSpell;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.HashMap;
    import org.bukkit.Server;
    import org.bukkit.block.Block;
    import org.bukkit.entity.Player;
    import org.bukkit.event.Event;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.plugin.PluginDescriptionFile;
    import org.bukkit.plugin.PluginLoader;
    import org.bukkit.plugin.java.JavaPlugin;
    import org.bukkit.plugin.PluginManager;
    
    public class Absorb extends JavaPlugin
    {
        public Absorb(PluginLoader pluginLoader, Server instance,
                PluginDescriptionFile desc, File folder, File plugin,
                ClassLoader cLoader) {
            super(pluginLoader, instance, desc, folder, plugin, cLoader);
            // TODO Auto-generated constructor stub
        }
        private Spells spells = null;
        @Override
        public void onEnable()
        {
            Plugin checkForSpells = this.getServer().getPluginManager().getPlugin("Spells");
    
            if (checkForSpells != null)
            {
                SpellsPlugin spellsPlugin = (SpellsPlugin)checkForSpells;
                this.spells = spellsPlugin.getSpells();
            }
            else
            {
                log.warning("The XXXXXXX plugin depends on the Spells plugin");
                this.getServer().getPluginManager().disablePlugin(this);
                return;
            }
    
            spells.addSpell(new AbsorbSpell());
        }
        @Override
        public void onDisable() {
            // TODO Auto-generated method stub
    
        }
    
    }
    
    And here is my AbsorbSpell.java file:
    Code:
    package com.bukkit.dida55.Absorb;
    import java.io.File;
    
    import org.bukkit.Material;
    import org.bukkit.Server;
    import org.bukkit.block.Block;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.material.MaterialData;
    import org.bukkit.plugin.PluginDescriptionFile;
    import org.bukkit.plugin.PluginLoader;
    
    import com.bukkit.dida55.Absorb.Absorb;
    import com.elmakers.mine.bukkit.plugins.spells.Spell;
    import com.elmakers.mine.bukkit.plugins.spells.utilities.PluginProperties;
    public class AbsorbSpell extends Absorb {
    
        public AbsorbSpell(PluginLoader pluginLoader, Server instance,
                PluginDescriptionFile desc, File folder, File plugin,
                ClassLoader cLoader) {
            super(pluginLoader, instance, desc, folder, plugin, cLoader);
            // TODO Auto-generated constructor stub
        }
        private int defaultAmount = 1;
    
        @Override
        public boolean onCast(String[] parameters)
        {
        if (!isUnderwater())
        {
        noTargetThrough(Material.STATIONARY_WATER);
        noTargetThrough(Material.WATER);
        }
        Block target = getTargetBlock();
    
        if (target == null)
        {
        castMessage(player, "No target");
        return false;
        }
        int amount = defaultAmount;
        castMessage(player, "Absorbing some " + target.getType().name().toLowerCase());
        ItemStack itemStack = new ItemStack(target.getType(), amount);
        itemStack.setData(new MaterialData(target.getType(), target.getData()));
        player.getWorld().dropItem(player.getLocation(), itemStack);
        return true;
        }
    
        @Override
        public String getName()
        {
        return "absorb";
        }
    
        @Override
        public String getDescription()
        {
        return "Give yourself some of your target";
        }
    
        @Override
        public String getCategory()
        {
        return "construction";
        }
    
        @Override
        public void onLoad(PluginProperties properties)
        {
        defaultAmount = properties.getInteger("spells-absorb-amount", defaultAmount);
        }
    
        @Override
        public Material getMaterial()
        {
        return Material.BUCKET;
        }
    }
    
    Probably because of my noobness, but i cant figure out whats wrong
     
  9. Offline

    NathanWolf

    No worries :)

    I think your only issue is that you're trying to extend from an existing spell - well, sort of... try using "class MyAbsorbSpell extends Spell" instead. And make sure to change your plugin to use "addSpell(new MyAbsorbSpell()"

    Always use "Spell" as your base class- and just to avoid conflicts with the builtins, I wouldn't use the same class name as one of those spells- but I imagine that won't really come up once you start making your own spell.

    Hm- besides that, change the name of your spell- what the getName() function returns. Spell names have to be unique- this may change to some sort of scoped id in the future to prevent collisions. Right now, your spell would conflict with the built-in absorb spell, so change it to "myabsorb" or something for now :)

    Once done, if everything compiles and runs, you should be able to "/cast myabsorb" for a spell that is functionally identical to absorb.

    Keep me posted!
     
  10. Offline

    dida55

    Things are fine now, only log. gives an error, it is undefined
     
  11. Offline

    NathanWolf

    Ah- yeah, sorry- you've got to assign that to Logger.getLogger("Minecraft") if you want to log to the minecraft server log.

    Glad it's getting there! :)
     
  12. Offline

    dida55

    Hehe, i think it's done but how do i cast it? o.o

    Edit: im stupid, trying to fix
    --- merged: Feb 1, 2011 9:12 PM ---
    I fixed the name but i can't cast it o.o Spell doesnt exist
     
  13. Offline

    Archelaus

    This actually looks pretty good. Well done on the API work :p

    and I'm sorry for going off topic, but dida55, does your name come for the qualification "DiDA"?
     
  14. Offline

    NathanWolf

    Thank you! I hesitate to call it an "API" at this point, but I hope it gets there.

    Really, I just looked at the code and though "Hey, other people could easily add to this..."

    I'm hoping we can get it working now that I've got a test subject :D
    --- merged: Feb 2, 2011 4:04 AM ---
    I just released version 0.77 of Spells, which is mainly an API release. I made several key methods of Spells public instead of protected, which may have been keeping your test Spell from working.

    I've also started doing some real documentation, and there are now javadocs you can view! I've documented most of the Spell API. I'm hoping to move much of the functionality currently found in Spells directly into Spell, at which point that should be the only place you need to look in the docs.

    For now, some of the more advanced functionality such as the undo system is still accessed through Spells.

    Ok, now I might be willing to call it an API :)
     
  15. Offline

    dida55

    Nope :p
    --- merged: Feb 2, 2011 2:41 PM ---
    Yay, AWESOME! ;) I've PMed you my source though, as you've asked, hope you can catch the error
     
  16. Offline

    NathanWolf

    Thanks for the source! I did some quick renaming, and turned it into a SampleSpell project that is a ready-to-go spell plugin.

    As it turns out, there is some Jar-Fu you need to do when exporting your plugin, involving a manifest file to get everything to link properly at runtime. Otherwise, you may get an error when your plugin loads about the missing "Spell" base class.
     
  17. Offline

    dida55

    SampleSpell works, officially confirmed :) Tested on #210. I'll jump into developing my own spells now! Thanks NathanWolf!
     
  18. Offline

    NathanWolf

    Awesome!
     
  19. Offline

    dida55

    I copy pasted both the blink spell and the blast spell, but none of them worked :(
     
  20. Offline

    NathanWolf

    You can't really copy+paste a whole spell, since it will probably then conflict with the builtin one of the same class/name.

    You should be able to compile and use the SampleSpell project as-is- if you use Eclipse, it's super easy, just import the project. Otherwise- it's a bit trickier, and I can't help you as much since Eclipse is kind of a crutch of mine now :)
     
  21. Offline

    dida55

    The samplespell worked, and i've modified all info in Blast and Blink that were needed to be modified =/
     
  22. Offline

    NathanWolf

    If the sample spell works, start there- if you want to take functionality from other spells, you'll have to copy+paste out of its onCast function, and then also copy over any member variables, helper functions, etc.

    That way, your spell is still its own spell- you're just taking functionality.

    However, what I would urge you to do instead is try to do something unique, using the builtin spells as reference. Maybe start by making a spell that changes the Material of the target block to gold? This would be a much better learning experience, and you'd end up with something that is your own creation.

    Write all the onCast code yourself, but take a look at FillSpell (for instance) to see how it's done. Keep it simple at first, skip over the BlockList stuff (the undo system), for instance, and just do the minimal code it takes. It would probably be about 4 lines of code, all told!

    Let me know how you get along.... :)
     
  23. Offline

    dida55

    Yay, thanks ;) I wanted to copy some plugins in order to test and then modify, but of course my plans are to make own spells! By tomorrow i will surely have something to show!
     
  24. Offline

    unrivaledneo

    Thanks for this maybe ill quit being lazy and try to learn so i can have my Force Powers!!
     
  25. Offline

    NathanWolf

    Sounds fun- I wanted to make a "push" spell that would shove other entities away... could be fun!

    On my list is modifying the targeting system to (optionally) target entities as well as blocks. That would make a push spell like this pretty easy.
     
  26. Offline

    unrivaledneo

    I just seeing if i can get into this, i have no idea what im doing at all lol.
     
  27. Offline

    NathanWolf

    It's pretty easy to get going! If you have Eclipse installed, just download the SampleSpell zip, and put it in your workspace. If you can get up and running with that, you should be able to make your own spells.
     
  28. Offline

    Raphfrk

    Have you considered implementing entity (or at least player) targeting? I was looking into it for ManaAlias, but never really finished it.

    You already have the getAimVector() method. Something like

    Code:
     Vector targetPosition = new Vector(currentLoc.getX(), currentLoc.getY(), currentLoc.getZ());
    
    targetPosition.subtract(playerPosition);
    
    double lengthSquared = targetPosition.lengthSquared();
    
    if( lengthSquared > rangeSquared ) continue;
    
    double length = Math.sqrt(lengthSquared);
    
    double cosAngle = targetPosition.dot(playerDirection)/length;
    
    With vectors A (dot) B / length gives the cos of the angle, but you don't actually have to do the inverse cos. The cos of the angle is fine.

    Also, split the players into groups, so that you only need to check nearby ones.
     
  29. Offline

    NathanWolf

    Indeed! I actually have an issue open for it and everything :) You can see in the list, after it are lots of items involving giving some existing spells a PvP upgrade (disintegrate, frost, etc), adding new PvP spells (fire), etc :D

    I also have lots of really cool plans for how targeting could work with a (somewhat malicious) player-targeted variant of rewind :)

    Speaking of rewind, in case anyone's interested (don't think anyone's really paying that much attention yet), I plan on moving the undo system (more or less in its entirety) into a core Persistence feature.

    I already planned on using Persistence to store my undo data once Spells integrates it, but the more I thought about it, the more generally useful I decided that a bounding-box-enabled, persistable list of blocks and values would be - especially when coupled with a generic system to undo such a block list in its entirety, detect a block being in a list, scheduling a list for automatic later cleanup, etc.

    It's generally a very useful system, and I'd like for it to be available outside of the context of Spells.

    This means very little in terms of Spells use, really- by the time I move the functionality over, Spells will already be Persistence-enabled, so you'll still have access to the undo system from within Spells.
    --- merged: Feb 6, 2011 2:40 AM ---
    You're probably right, for performance- that becomes difficult, though, since I have to keep everything in sync (detect entities moving around / spawning / dieing / etc)

    I'll probably start with a simple entity list check each time- I can do a large bounding-box distance check to start with, so it's really only as expensive as iterating over all of the entities.

    Then from there do real distance / angle checks- add any entities that fit within the aim cone to a list, sort by distance, and choose the closest one.

    That's more or less how I wrote the auto-targeting in Stranglehold :) (Well, in a nutshell- the weighing algorithm was more complex than just distance, it took into account target threat level, and also looked for environmental triggers you could shoot)

    Of course, there the Unreal Engine handled keeping track of nearby entities... so if something like that needs to happen for performance, I'll see what can be done. I imagine I could maintain some sort of cache that I update every X seconds (at most, on demand) to at least keep repeated wand swings from bogging things down.
     
  30. Offline

    rcjrrjcr

    Well, for my plugin, I wrote a set of interfaces to handle any kind of economy or permission plugin. Currently, it only supports GroupManager, Permissions, iConomy and EssentialsEco, but more can be added.
    You can find it here - https://github.com/rcjrrjcr/RcjrUtils
    It also include some helper classes that I use in BuyAbilities. I think that these will be of interest:
    • SearchHelper.java - This class is basically a simple spellchecker, but it is able to work with any words you provide it.
    • ChatHelper.java - This class contains utility functions to wrap messages so that they will fit in the player's screen, and a function that allows the player to page through a list of strings.
    Maybe these can be of some help.
     
Thread Status:
Not open for further replies.

Share This Page