So here's the problem. I'm trying to make a survival party plugin where the idea is that 3rd party plugins can be easily added. Currently I'm doing this using the following idea: https://prnt.sc/6qutzTzoxjNV
#Best Practices: Added functionality without inheritance
1 messages · Page 1 of 1 (latest)
Basically, the core plugin holds the real instance. Then, 3rd party plugins listen for the creation of new core plugin instances
is this something that you intend to sell on spigot?
It's going on spigot yh
I haven't really thought about selling it
Depends if I can actually make it decent or not
Why? Am I required to sell or not sell it to ask here or?
well there is a reason I asked that. So if this was going to be a premium plugin, the core plugin wouldn't be permitted to be sold because those addons rely on it
so you could make the core plugin free and the addons premium
since the core would always be available
Even if I made the API available but not the core plugin logic?
Premium core plugins are fine
you would still need the core to be available because its unreasonable to expect people to buy the core and then also need to buy addons to make it work
The only thing not allowed is premium plugins that depend on premium plugins
So essentially, you can't make an add-on to a premium plugin?
I was under the impression the core at minimum had to be available
P sure I've seen this be done
The only thing in the guidelines is
- (premium) Resources must not depend on other premium resources.
oh well if the rules don't forbid it, then nvm
anyways, your ideo of providing functionality that isn't required but someone wanted to extend it is fine. You would just make use of a custom class loader is all
Does that include premium resources designed to be add-ons and therefore useless without the core plugin?
EITHER WAY, my main question's not really about selling and that
Yes back to the main question and not them side ones
It's about, what's the best way to go about adding 3rd party functionality to an object
Because, like, I want to use inheritance, but then I'd have to make loads of instances of the same data, since I want my plugin to work regardless of which combination of 3rd party plugins are being used
Like I don't want to force a specific inheritance ladder
oh its like I said, you would essentially do what spigot does
So currently I've been using wrapper (I think that's the term? Just a class that holds an instance of the core instance but also holds the extra functionality)
use a custom class loader and essentially you treat the addons as plugins but plugins within your plugin
Oh, so make them not actual plugins?
But just jars that the core plugin reads?
But I don't get how that's a better way of achieving my goal? Like I'd still be using the exact same wrapper tactic
that is what a plugin is
spigot just reads the jars of plugins and loads the classes lol. Those jars don't do anything on their own
but you can make your plugin load additional plugins specific to it though just like spigot does it
however, we typically call these addons
just to distinguish that it is specific to that one plugin
so its not confusing
Okay fair
But I still don't get how that makes adding complexity to object instances any easier
because you don't worry about instances in your core, instead you let the addons worry about. Your core just makes available information that it believes is relevant
and only handles core logic not really specific to anything
Its not easy making an API
but with some practice you can do it though 🙂
Are you saying not to make an instance in the core code?
Or just don't write it with add ons in mind?
correct, you shouldn't be doing anything specific that would require addons being present
so the only code that should be there is anything it can do in the absence of such
if you need additional functionality, that is when you make a addon to provide it
addons can even be utility as well
Ngl, I've been writing the plugin having some of the default code act like an add-on. This is because I want these features to be replaceable with more complex systems by add ons
For example
The core permissions system is very basic. But I want it to be replaceable with a more customiseable one
So I've been writing it as an addon. Like, I haven't included code for this permission system in the core code
Because if a server owner doesn't want to use the default system, what's the point of it being recorded and using it space when it's unused?
Core Plugin -- core api --addon
\
addon
this is how it needs to be structured
your core plugin is to implement your api otherwise it doesn't really provide much on its own lol
and then the addons depend on the core api, and then your core plugin should have a directory where addons get placed and it looks for jars to load
core plugin should provide things like events management. Registering and notifying etc
and then anything else that is specific to the core that the addons may need
then, the addons extend off that to provide further functionality
another way this can be done without an addon, is boolean options. If your code detects its disabled it instead just skips that code in question and doesn't bother with it. Disabled code doesn't take up memory unless you do it wrong where it does so even if its supposed to be disabled lol
but the downside of this though, is that an addon can't make use of disabled code either
Okay, this is how I've been doing it up until now:
API class:
String uuid;
String getUuid()```
Add-on class:
```class MemberWrapper
Member instance;
String getUuid() { return instance.getUuid(); }
## extra functionality specific to add-on```
Is that the right kinda thing?
your core plugin should create instances of the addons to hold in memory since the addons don't run themselves
this is why spigot requires specifying a main class so it knows which class it needs to create an instance of and retain
and thus for plugins everything starts from there and you can do the same for your addons as well
you can require that plugins create/provide some kind of uuid
since that is the purpose of API's
Right, but I only want one member instance per real player. Surely if the add-ons create their own instances, that's not ideal?
probably not, in which case you have something like a plugin manager that would handle that
this is why spigot requires you to extend JavaPlugin in your main class 🙂
Yeah, but surely the alternative is to have the core plugin create an instance that the add ons can access
so that some things can be provided to the plugin via the server and api
also going to need to learn how to make singletons as well
singletons are classes where its impossible or nearly impossible to have more then one instance of
Yh, I use mostly static classes for that
You don't want to make static classes for that, because statics guarantee an object exists regardless if values are present or not. Singletons on the other hand can exist or not exist and if it doesn't exist can be created at that time
private static final Set<Party> parties = new HashSet<>();
/**
* Register new party
* @param party input party
* @return TRUE if successful, FALSE if input invalid
*/
public static boolean registerParty(@NotNull final Party party) {
// Validation
if (getPartyByName(party.getName()) != null || getPartyById(party.getId()) != null) return false;
party.add(party);
return true;
}```
That's an example of part of the API I've got so far
this is an example of a class being a singleton
so, how you would stop there being more then one instance is you would make the constructor for the class private
and then that line I linked would invoke that private method but also checking to see if an instance doesn't already exist
if it does it provides that instance instead
if it doesn't exist, it automatically creates one
but yeah, basically your biggest thing with your project
is designing an API
which as I said from the beginning is not easy and takes time to learn to do properly
Yep, well that's part of the reason I'm doing it
I mean its easy in that you can create one, just not easy to have it well structured and all that fun stuff lol
even I don't create very good api's all the time 😛
So with your example, I don't get why you made an instance if there's no constructor
Like, I'd understand if it triggered some procedures upon creation (like, checking a config or something)
its an old plugin and its for internal purposes and not meant to be used directly
I have an api provided that doesn't involve the caching etc
and its main reason is to handle reloads
on a reload, objects get cleared so instead of ensuring all objects are all created at a given time, that method will create it automatically if its missing for whatever reason
Right
How do you mean, a plugin manager would handle it?
Like, it'll hold the add ons?
well, as I said there is a reason spigot requires the extending of JavaPlugin
JavaPlugin if you ever looked extends Plugin
and then in the implementation, the server provides Plugin with some things, like Config
which plugins don't have to worry about initializing that object or needing to do much to access it
getConfig specifically refers to individual plugins config.yml
yet the server is providing it 🙂
So are you saying that, for add on specific functions for a core object, I should just handle it in a manager or something? Rather than a wrapper I mean
Like getting the "Member" class instance isn't the issue
It's how best to add attributes and methods onto it
Yeah kind of one of the purposes of a Manager. It manages stuff in various ways 😛
so adding some information to a plugins instance in the core for example would be a prime example a manager handling that
because you don't want the plugin/addon providing that since it can't be reliable lol
Yh but using a manager class for a thing like perms would mean mapping attributes to the main instance. Surely using OOP would be better than one fat hash map
ah, but I can show you something better
Please do :)
a perms class that doesn't use a hashmap at all
no hashmaps at all 🙂
even handles dynamic perms too
that is, perms it doesn't know about ahead of time
not overly difficult
Yeah, I know about enums. But your example is reading stuff that's already stored
When I said "permissions", I mean like party-specific perms. Should've clarified
as I said, that enum class even handles permissions it doesn't know about
So like, when you join a party, it starts you off with default perms
And then the owner can allocate them etc
yeah, you can easily have an interfaces for this, and then just extend the base one to further add onto it
this allows you providing some default perms in your plugin while also allowing others to extend further
but hashmap isn't necessary per-say, you can even create custom player objects
that have these perms already attached
Yes, this is what I've been doing
But I'm asking about how best to achieve it. Like, using a wrapper class or whether there's some method of using inheritance for it
you could do both
you would need a wrapper anyways since you can't just make a player object that the server doesn't know about
so you would need a wrapper to take that player object and stuff it in another one
but at the same time you can provide inheritance too from your base wrapper implementation
Would I need to implement a wrapper in the main plugin though?
well yes, if you want a custom player object
the inheritance if you want to provide a structure to the addons to access said thing
or you can just make it globally available to do whatever with lol
Does it need to wrap the player object though?
Like, I've just been having the plugin hold a player object that just holds a uuid
It may be wise as that is the easiest way to provide the same information from the server into your custom player object without duplicating it yourself
So it can be mapped to player objects, but doesn't require it. That way, it can easily deal with offline players
I suppose
IDK, guess I don't like the idea of getting player objects just from their uuid
well UUID's are unique o.O
Yeah but like, offline players and online players are treated differently
not really
Online Player extends offlineplayer
which is why you are supposed to use offline player whenever possible
I see
and only use online player if you really need it
and its not hard to obtain a online player object from offline player
it has a method to check if the player is online
and you can cast
OfflinePlayer player = (OfflinePlayer) Bukkit.getPlayer(UUID);
this is perfectly valid and you can go the other way too
now obviously there is some differences between the two as an online player has more information attached to it
but they are not handled differnently though, even if you are using UUID's lol
yes, it can't exist otherwise unless you fake the data in it, which the server doesn't and the API doesn't allow you to just arbitrarily create a Player object either
not without using NMS anyways
I see
but even with NMS it isn't a Player object, rather a PlayerNPC instead
well NPC disguised as a player lol
Okay, so is this an alright structure?
Wait hang on
I still don't get where the inheritance comes in
Sorry if I'm being dumb
I thought you meant like, the add on extends the API's object, but then you'd have to create a new instance of it for each add on
well you don't need a new instance for each addon
you provide the instance in such a way that they can't do whatever they want with it, but the inheritance would allow them to extend it
just like how spigot provides interfaces for various entities, for you to use
so spigot provides a player object, that only the server can create, but it provides inheritance to allow you to change that player object to other things though
or just provide more information
What, for like NPCs?
I don't get how inheritance can help you when the API has already created the object instance
Like, I get that you can use the interfaces to run its methods
But like, I don't get how you can add functionality it, only make use of the API's pre-written functionality
Wait, do you mean I should implement the API's interfaces to the wrapper?
API's don't work unless something implements it lol
and inheritance in the API doesn't work unless something implements at minimum the base interface
Well yeah, but the add on's wrapper doesn't need to go into the API
the addon shouldn't need its own wrapper
this is why API inheritance is a thing
just because you didn't provide additional interfaces
doesn't mean an addon can't just extend it
but at minimum, a base interface needs to be provided to allow that
I'm really sorry, I still don't get what can be extended
Do you mean the Member object?
I mean, this is what my code looks like currently:
public class Member {
private OfflinePlayer player;
public Member(String uuid) {
player = Bukkit.getOfflinePlayer(UUID.fromString(uuid));
}
public OfflinePlayer getPlayer() { return player; }
}
public final class MemberManager() {
private static Set<Member> members = new HashSet<>();
public static void registerMember(Member member) { members.add(member); }
}
// ADD-ON CODE
public final class MemberWrapper {
private Member member;
private Role role = Role.DEFAULT;
public MemberWrapper(Member member) { this.member = member; }
public Member getMember() { return member; }
public Role getRole() { return role; }
}```
Well, roughly. This is just a demo
public interface CorePlayer {
OfflinePlayer getOfflinePlayer();
//whatever is specific to a core player
}
public CorePlayerImpl implements CorePlayer {
private UUID playerUUID;
public CorePlayerImpl(UUID playerUUID) {
this.playerUUID = playerUUID;
}
@Override
public OfflinePlayer getOfflinePlayer() {
return Bukkit.getOfflinePlayer(playerUUID);
}
now, you have an interface to extend from
but so do the addons
anyways that is the premise
Okay, does the add on object implement the interface too?
I'd guess so, but then what's the point? Since the API never touches the add on version of the object
it wouldn't be necessary for the addon to implement the interface unless they wanted to provide their own custom object
they would merely need to just extend the interface
Okay but in that case why use interfaces? Why not just have the add-on deal with the implementation class directly?
because you need something ensure structure
you don't want all your addons to be dealing with their own player objects
instead you would provide that from the core
if they do need to add more info to the object
they can use an interface while still being compatible with the core
for example LivingEntity in Spigot being able to be casted to Player
or vice versa
LivingEntity isn't necessarily implemented specifically, rather it derives from the base Entity
Entity -> LivingEntity -> Player
Yeah, so for an add on that does need to add more info
It should implement CorePlayer interface
?
The add on shouldn't be creating any instance that the core sees
There's not really any point in it IMO
So I don't get why it needs to be compatible
In your spigot example, the Player class is used because it's specifically a player that's being created
But in mine, the core creates a Member object. There is no other type of Member
Instead, the add ons just want to add attributes and methods to all Member instances that the core creates