Plugin Design

I have some questions that pertain towards plugin design.

I am developing an auto-announcer plugin. It’s a simple idea, a message is broadcasted every X seconds. However, I have some more in-depth features that I want to make available.

Plugin Basics
The vital settings of this plugin are simple: you have an interval of messages, broadcast prefix, broadcast suffix, and the message list (the messages to broadcast).

Frequency/Timing
For example of in-depth, if we have a fixed interval, and there’s 150 people on the server chatting, the message may need to be broadcasted more frequently. Or if chat is just not moving for a while, the messages hog up the chat (for players). I want to (eventually) incorporate Lua into the configurations, so people could make up their own little scripts for determining when a message is broadcasted, and which message is broadcasted. I might just scrap that idea, because it sounds a bit difficult.

External Support For External Apps
I know things (like Votifier, Buycraft/donations stuff) like to broadcast announcements that something happened. I would like to make available to make a simple HTTP request to the Minecraft server to broadcast the message.

User Broadcast Settings
I was also thinking about grouping announcements. Only people with a certain permission node will see certain messages. Maybe have an option to disable announcements if they don’t like advertisements?

Updates
Then, you have updates. The configuration might change, the plugin might change so I added a node for setting the configuration’s version(how do most plugins make update systems, by the way?).

Problem with Complex Features
If you’re looking into skimming through plugin pages and finding the first jar file you can find, upload to the server without thought, you probably want something that is simple, and works. However, having all these settings in the configuration file and commands would make you think “WTF? I’m going to get a simpler plugin that I can figure out.”

In my opinion, Group Manager was a great plugin to a lot of people because PEX was more complicated than Group Manager. What I’m trying to figure out is if I can design something that both someone who wants basic, sounds-like-it-is, out-of-the-box type of plugin, and satisfy those who want complex features to play with(developers, donation pages, web sites, etc,).

My Solution
So, I decided to implement a “simple” and “complex” mode. I am thinking that the simple version will just have the Plugin Basics and updates settings. It would also have the simple/complex setting.

Basically, I am thinking that simple/complex will just be a setting that tells the computer whether or not to show complicated settings/features to the user. When the mode is changed to “complex”, the complex config will be written/parsed.

In simple mode, the plugin will load all the commands that change simple features. To be a little verbose, if the user puts in a command that is considered complex, the plugin will just tell the use who execute the command “this command requires the ‘complex’ plugin mode.” (I’ll have an in-game command for changing simple/complex modes.) In this mode, comments will be targeted to dummies.

In complex mode, all features are loaded. I might even have some features parsed differently. A config for the complex features will be made, and more technical-savvy comments for specificness.

For the updates/version setting, I put it in an ‘advanced’ area of the configuration, as a “you don’t need to mess with these, but you can if you know what you’re doing” type thing.

I am not at all even close to the simple part of the plugin done. I’m just gathering ideas for future versions, and having my plugin ready for updates
Any critique on these ideas? Are they good? Bad? Features that have no use?

To me, it seems like extra effort to go through than necessary to provide simple/complex modes. It may be more effective to instead provide defaults for those sections that are useful in a general case, for instance do nothing with buycraft. That way, you don’t have to bother with writing and rewriting config files frequently, and can still provide a plug-and-play configuration. What groupmanager was able to accomplish was a good method of scaling - that is, the amount of configuration needed was proportional to the complexity of what you were trying to do. I don’t think going through the extra effort to hide parts of your plugin that you deem too complex from the uneducated user is going to help with much. Perhaps you could provide numerous defaults, and use a command to load them to config?

I do like the idea of scaling announcements with variables. Perhaps differential calculus could be used here? I think you could provide a default script (if scripting is what you want to lean towards) that is run every so often (for example, every 10 seconds - perhaps configure that) which determines whether it should be fired based on two factors: how long it has been since the last announcement, and the chat messages/unit of time, as an average since the last message and last update. That way, you can determine how frequently messages are appearing, both since the last message and in recent messages, to decide on whether to send the announcement.

I think using a permission node for each announcement, and providing a default one as well, would be effective to accomplish per-use broadcasts. For instance, you could have a default one labeled “ads” (with respect to your plugin of course) which all announcements can use, however you could have an announcement with “vote” permissions fire every time a vote is cast, and it would check against “vote” only. That way, someone without permission to see “ads” but with permission to see “vote” would only see votes, and someone with the opposite schema would only see ads.

As for updates, I think you should strive to compartmentalize your configuration as much as possible so as to prevent changes to sections through updates. Naturally, some parts may need to change based on the update, however you can try to minimize this by sectioning off parts of the config, and only changing parts where necessary. How you accomplish this is up to you, but it’s definitely something worth considering in order to provide longevity.

An alternative for Lua is JavaScript with Nashhorn(google it). I don’t know which fits better and which one is easier to implement.
I planned some broadcaster plugin thing a long time ago. It should react to events fired by plugins. The event contains some important values to display. Example: I have a lottery plugin. It will fire LotteryWonEvent. In the config of your plugin the user can define how the values are displayed, like

@de.randombyte.lottery.LotteryWonEvent
"The pot of <pot> was won by <winner>"

Formattings like color would also be possible.

I don’t have experience with config updates. But I will change the config of one of my plugin soon. The config is mostly generated by the plugin as a data storage. I’ll force the user to delete and regenerate the config.

Updating old configs to a new version isn’t too bad. I did it with ClearMob in this fashion:

 if(rootnode.getNode("ListType").isVirtual()==false)
	{	
		
		rootnode.getNode("Clearing","ListType").setValue(rootnode.getNode("ListType").getValue());
		rootnode.removeChild("ListType");
	}
	if(rootnode.getNode("Interval").isVirtual()==false)
	{	
		
		rootnode.getNode("Clearing","Interval").setValue(rootnode.getNode("Interval").getValue());
		rootnode.removeChild("Interval");
	}

Basically, it would check if a node existsed, if it did, it would set a new node to the new position, then remove the old node.

More examples can be found in the config class of ClearMob: Here

One thing that you need to realize in my opinion is that if you do a complex plugin, it will leave a mark on the plugin as a whole. You can try to provide reasonable defaults and an easy config to hide this fact, but you can’t remove it.

Do note here that there is a difference between a complex plugin, and an under friendly plugin. If a plugin is easy for users to navigate through, and they don’t really feel that they have to look up any documentation, then the plugin will feel less complex. It might still allow for all sorts of crazy things, but if you make it easy for users to use, they will forget that fact.

If you want to go in this direction, then you always need to keep in mind what the users sees when they look at your plugin. If they see a section if the config labelled “advanced”, even if they never go in there and do something, they might become a bit more intimidated to change features. One way to solve that is to put the advanced config in a physical different file. Opening a file is a much more conscious decision than to scroll through one and notice an advanced section. In general if you want to appear user friendly, try to minimize information overload.

For the config stuff, I normally prefer to model different versions of configs and config objects as physical different classes. (This only works if you model the config as a class in the first place. Note that I’m not talking about the class that loads the config, but instead more how to pass that information on to the rest of the plugin.)

A solution to that is to have two separate configs be generated based on the option. For instance, At the top you have a node like “Config Type: Simple” (By default) and it will gen certain nodes. But if it gets switched to Advanced, it could easily add in the additional nodes and restructure the entire thing during a reload.

you don’t need ==false just add !

if(!rootnode.getNode("ListType").isVirtual()) {

}

Thank you all for your suggestions.

@axle2005
One comment though: the part where you said ‘on next reload,’ I’m not sure what exactly you mean. Do you mean: server restart, a plugin reload command, config relaod? I would probably do the last two.

Also, how would I subconsciously discourage plug-n-play users from going into the ‘advanced’ config file? Have a “main.cfg” and “advanced.cfg”? But then that would intimidate them…

I think the position of nodes might also be important. The user reads the top of a file to the bottom of the file. If they find something they want, they will probably won’t feel the need to change anything after that.

However, HoconConfiguration stuff (insert official name here) does some weird thing with ordering nodes. It doesn’t order nodes based on when the method is called, it orders them alphabetically upon configuration save.

The configuration sectioning sounds like a great idea.

Also, I’m not really liking the idea of delete and force a reconfiguration. Personally, this would piss me off as a server owner. It would make me feel like the plugin will be more of a hassle than help because it can’t remember settings.

Also, how do I respectfully have a plugin update itself? I want to be as helpful with auto-updates, but not intrusive(like Windows updates these days).

There was contradictory statements between hiding configuration nodes based on a ‘mode’ or ‘config type.’ Could you explain your sides a little more? Should I still have modes?

In just going to stater the cold hard truth here too. If people want something simple, they will most likely use a plugin that is inherently simple. Many use plugins like Nucleus and get most of what they need there. It’s only if they need more that they get a more complex plugin.

Well, based on my suggestion of changing the config based on Simple/Advanced, you wouldn’t NEED to delete nodes to go from simple to advanced. You could honestly just re position them,

Unfortunately yes, Hocon is forced Alphabetically: Topic Related
But you can get creative with child node names to get around this.

The reload I mentioned is a GameReloadEvent event. It is called when /sponge plugins reload is used. Or you can make a / reload command if you see fit.

As for the final part.

Initially when the plugin is loaded, you could have it automatically set the plugin ‘mode’ to Simple, and add in the few nodes you wanted to provide with it. So…
ConfigMode: Simple
ConfigOption1: False
ConfigOption2: True
ConfigOption3: Killallplayers

And IF the ConfigMode gets changed to Advanced, have it add in the additional nodes and shuffle things around when you run a reload.

ConfigMode: Advanced
ConfigOption1: False
ConfigOption3: Killallplayers
ConfigOption3(child): Lightning
ConfigOption3(child): 20 Hearts of Damage
ConfigOption3(child): Delete Items
ConfigOption2_newPostion: True

Now of course this all requires some extra coding, as you need to make sure when its in simple mode, your pluign can not try and call a Advanced node else it will through a Null Exception.

I think you should try to avoid not having all of your config available immediately, rather than depending on “modes” to load different setups. Of course, you can section off your config however you please, but as others have said, ease of use is key. Using “modes” makes the plugin more confusing. If anything, you should try to have dynamically structured configs. For instance, say you had a node called “announcements”. Each child node would be it’s own announcement, order not considered. Each announcement could be a simple announcement, with a “text” child node only. You would then stick a “delay” node in the “announcements” node, and that could be a simple config done. That way, if you want additional features, you can just check against the child announcement node. You could add a “commands” node, a “delay” node to override the global delay, a “permission” node, etc. to any of the announcement nodes. Doing this allows users to have a simple configuration of the plugin with just messages, or an advanced configuration with extra customization in each announcement.

As far as auto-updating, I think that Ore will be offering some services like that once it is setup, however I would advise against setting up auto-updates until that is done. For now, it is recommended to stick to version checking (perhaps you could have a node to check how detailed version updates should be? For example, 1 checks major version in a major.minor.dev versioning number, 2 checks minor and 3 checks dev. Each level would have updates more frequently as you develop your plugin).

I think you should also separate types of messages you have in your plugin. Specifically, your “announcements” should be separate from your “event triggers”. So whenever BuyCraft things happen, that should be in a different section from whenever your auto-broadcaster fires. You should also separate each trigger into it’s own node; that way, when you add a new trigger plugin, like BuyCraft, or update an existing one, it affects the least amount of configuration.

EDIT: Just a note about the announcements having complex nodes in them: they should all be loaded in checking against every node you have. So every announcement node, when used to create an announcement, should check “text”, “delay”, “permission”, “commands”, etc. You should then provide default values in the getValue calls. For instance, you could provide an empty string for permissions, an empty list for commands, the global delay value for delay, etc. That way, you won’t trigger NullPointerExceptions and you will still have all functionality retained.

The key thing here is that you are overthinking it, and in the process, you’re potentally hurting the user experience. I think there are some valuable points made on this post, but there is also a bit of an overload of information here. It’s OK, everyone who wants to write a large plugin like this goes though it (I did too, and still do!), but there are a few things to bear in mind.

Start simple. This is the most important thing to realise. You’re trying to run before you can walk. This is all pointless if you can’t send a message on a timer. Build a configuration file that the plugin loads up on first start (and reload) - then start your mecahnisms to print messages on a timer. Then, make it so you can have multiple sets of messages that can be broadcasted. Then add a permission requirement. Build it up, one thing at a time.

Going more technical, a good plugin design/architecture is essential here. Don’t hard code things, make it all pluggable. Don’t rush things, and realise you’ll likely get things wrong at first. While I didn’t have a perfect design for Nucleus at first, when I added more and more things in, it was so much easier to refine it because I already had something.

Plug ‘n’ Play vs. Advanced Configs

Simple - generate the advanced configs but provide sensible defaults upon generation. Your simple configs should always be a subset of the advanced configs. By having a toggle, you make more work for everyone, including yourself - if someone switches from simple to advanced, you have to convert data. If the reverse is done, what do you do with advanced customistaions? You’ll get the blame for anything that is lost. The simplest solution for everyone is having the advanced configuration with sensible defaults. If you use HOCON, you can add comments to every config key too, to make it that bit easier.

Have you seen how many files Nucleus has in the configuration alone? :wink: In all seriousness, it’s only intimidating if you let it be intimidating. With careful design and a good set of comments in the files, it’s not intimidating to have more than one config file, it’s flexible. You have to put in the effort to make it worthwhile for users though - meaningful comments go a long way.

Configurate (Sponge’s library for writing config files) actually comes with a great way to update configuration files, but it’s not very well documented and not very many people know about them - GitHub - zml2008/Configurate: A simple configuration library for Java applications providing a node structure, a variety of formats, and tools for transformation. I use them in Nucleus to transform my configuration files as things become obsolete. The idea is that you create a series of TransformActions, and you then run your configuration through them.

The best way to get a firmer idea in your head is to start writing the plugin, with a firm design in mind, and show us something that you want feedback on. Ideas and feedback doesn’t just come from the start of the process.

This drew my eye, and I need to impart on you that this is going to be harder to do right. Be careful to make sure that you create a secure system for allowing HTTP requests to your server, because if you don’t, you might find that this is a severe vulnerability in your plugin. I would not want to use a plugin that allows others to broadcast random messages to my server…

1 Like