-
Notifications
You must be signed in to change notification settings - Fork 58
API
Kingdoms API is one of the things that was reconsidered a lot when it was being coded. It's quite stable and easy to learn.
Anyone with basic Java knowledge can use the API.
The API will definitely change if needed without hesitation, so some plugins might break after API updates.
Don't forget to add Kingdoms
to your plugin.yml
-> softdepend
The version specified in this page is considered to be the most up to date version with the latest API version that's guaranteed to be on Maven. Other newer versions are 90% not going to be available.
version: 1.16.8.1.1
Maven
<dependency>
<groupId>com.github.cryptomorin</groupId>
<artifactId>kingdoms</artifactId>
<version>version</version>
<scope>provided</scope>
</dependency>
Gradle
repositories {
mavenCentral()
}
dependencies {
compileOnly 'com.github.cryptomorin:kingdoms:version'
}
Manual You can download the plugin for free on the Discord server.
If you're seeing that a beta version is not specified in the version field above, that's because you generally shouldn't add support for any software that's in beta version, including Kingdoms since the API is subject to a lot of changes during that time. Of course this means that you might have to update the support once the version is stable, but that has its own disadvantages. If you wish to do so, then you'll have to temporarily add Kingdoms as a local dependency, which is quite easy with Gradle (it's also possible with Maven, but a little harder). For more information about beta versions, please refer to stability section.
You might also want to take a look at multi-plugin APIs such as FactionsBridge which supports a variety of group and land management plugins.
All the objects have optimized equals
and hashCode
methods that can be used, but it's always recommended to choose the correct data from the object you want to compare. For example, never use equals
for Lands, they have optimized methods, but not when you're trying to compare them normally. Refer to Optimization section for more info.
You can figure out most of the things you're looking for just by following your IDE's auto completion. Take a look at some examples at the end of the page.
90% of the API is thread-safe unless the method is dealing with Bukkit related operations. Most of the API is also marked with Nullability Annotations and JetBrains annotations to help programmers.
Certain methods of some classes begin with the word unsafe
which should not be used and are usually for allowing to modify collections or maps that are designed to not be directly modified or changing values while bypassing checks. These methods are for internal usage only and you should absolutely not even call them at all unless you know what you're doing.
The plugin also has some extension functions that eases the process of obtaining kingdom objects and they can all be found in KingdomsBukkitExtensions.kt
file. However note that this file also contains other extensions that are just for ease of working with Bukkit objects themselves as well, mostly for UUID
s.
You can get a kingdom player by either using an OfflinePlayer
or UUID
object.
They're both the same, none is accurate or performs better than the other.
Player player = ...;
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(player);
You can get a kingdom/nation either by its UUID or name. It's always recommended to use the UUID.
Kingdom kingdom = Kingdom.getKingdom(name/id);
Nation nation = Nation.getNation(name/id);
It's also useful to know that Kingdom
and Nation
objects both inherit from a class called Group
which shares the most common methods for these two objects. These two objects also have some methods that are exactly the same as Group
, but with a different name. E.g. Nation#getCapitalId()
and Kingdom#getKingId()
are equivalent to Group#getOwner()
90% of the time you don't even need to get a kingdom this way, most of the kingdom objects have a way to get to the kingdom object. They're all named getKingdom()
Notice that some objects have getKingdomId()
method, this method is highly recommended instead of getKingdom()
for performance reasons.
For example instead of doing KingdomPlayer#getKingdom().equals(Kingdom)
do Kingdom#isMember(Player)
or KingdomPlayer#getKingdomId().equals(kingdomId)
Kingdom
objects are cached for KingdomPlayer
and Land
.
KingdomPlayer kp = ...;
if (kp.hasKingdom()) { // A better way is to use kp.getKingdom() and check if it's null. Same for nations.
Kingdom kingdom = kp.getKingdom();
if (kingdom.hasNation()) {
Nation nation = kingdom.getNation();
// ...
}
}
Kingdoms save locations (other than kingdom and nation home locations) in SimpleLocation
and SimpleChunkLocation
objects for memory performance. You can use the Object#of()
method of these objects to get the location for your right object. Using the correct one can improve performance.
-
SimpleLocation is the simplified version of
Location
-
SimpleChunkLocation is the simplified version of
Chunk
These two objects have most of the useful methods (even more) of Location
and Chunk
A land that's null
is a land that has absolutely no (kingdom related) data attached to it.
A land that's not null
, but is also not claimed (!isClaimed()
) doesn't have a kingdom, but it might have protected chests, turrets, structures or other data in it.
Player player = ...;
SimpleChunkLocation chunk = SimpleChunkLocation.of(player.getLocation());
Land land = chunk.getLand();
// or simply
Land land = Land.getLand(player.getLocation());
if (land == null) return;
Structure structure = land.getStructure();
SimpleLocation = simpLocation = SimpleLocation.of(player.getLocation());
Turret turret = land.getTurrets().get(simpLocation);
if (land.isClaimed()) {
Kingdom kingdom = land.getKingdom();
// ...
}
Top kingdoms and nations depend on their might.
Make sure to use these methods properly and use them asynchronous when possible.
// Kingdoms
Collection<Kingdom> kingdoms = DataHandler.get().getKingdomManager().getKingdoms();
// Nations
Collection<Nation> nations = DataHandler.get().getNationManager().getNations();
// Lands
Collection<Land> lands = DataHandler.get().getLandManager().getLands();
In this example we get the top 10 kingdoms/nations.
// Kingdoms
List<Kingdom> kingdoms = DataHandler.get().getKingdomManager().getTopKingdoms(0, 10);
// Nations
List<Nation> kingdoms = DataHandler.get().getNationManager().getTopNations(0, 10);
Block block = ...;
Optional<ProtectionSign> protectionSign = ProtectionSign.getProtection(block);
if (!protectionSign.isPresent()) return;
ProtectionSign protection = protectionSign.get();
List<UUID> group = protection.getGroup();
Since kingdoms is extremely customizable, it'll become extremely hard to maintain two methods that do the same thing, one that performs actions and interacts with the user (such as playing sounds and sending messages) and one that just returns a boolean for API purposes for actions such as PvPing, building or interacting. A solution is to introduce classes that can do all of these called processors:
Processor | Description |
---|---|
ClaimProcessor |
Used for claiming lands. |
UnclaimProcessor |
Used for unclaiming lands. |
BuildingProcessor |
Used for placing/breaking blocks. |
InteractProcessor |
For use/interact. |
These processors implement KingdomsProcessor
:
- process(): Processes the issue and sees if there's any issue.
- isSuccessful(): Whether there are no issues with performing this action.
-
getIssue(): If there is an issue. This is a
Messenger
, but should not be used directly. You should usesendIssue()
instead as Messengers themselves rely on variables for inside the messages. sendIssue() will take care of those variables which was saved duringprocess()
. - finalizeProcess(): Should be called only if isSucessful() is true. This is when there is no issue with performing an action, but certain things have to be changed based on the plugin configuration. Such as build charges or claiming costs.
E.g. Modifying the ClaimProcessor
. There are variety of methods that you can override, in this example we used initialChecks
. In kingdoms processor, when a messenger is returned, it indicates an issue and the message is sent to the player that performed the action if any.
public final class CustomClaimProcessor extends ClaimProcessor {
public static void register() { // Call this on your onEnable()
ClaimProcessor.setBuilder(CustomClaimProcessor::new);
}
protected CustomClaimProcessor(SimpleChunkLocation chunk, KingdomPlayer kp, Kingdom kingdom) {
super(chunk, kp, kingdom);
}
@Override
protected Messenger initialChecks() {
if (kingdom.getMembers().size() > 100) {
return new StaticMessenger("&cKingdoms with more than 100 members cannot claim lands.");
}
return null; // There are no issues.
}
}
There are some unstandard/undocumented processors that differ in how they work:
Processor | Description |
---|---|
KingdomsChatProcessor |
Sending messages to specific channels from certain players. |
KingdomItemHologramProcessor |
Showing holograms to players for KingdomItems. |
LanguagePackDownloader |
Installs/Updates language packs with an advanced fast retrying system. |
CheckupProcessor |
For [/k admin fsck] processors. Check the CheckupProcessor API section. |
ConditionProcessor |
Simple class for processing conditional options with variable handling. |
PvPManager.canFight() |
Returns a boolean if two players can fight or not. |
Here I'll give you some examples for when and which methods to use for your specific case.
This first example applies to getLand(), getKingdom(), getNation() and etc...
KingdomPlayer kp = ...;
// DONT
if (kp.hasKingdom()) {
if (kp.getKingdom().hasAttribute(...) && kp.getKingdom().equals(...))
}
// DO
Kingdom kingdom = kp.getKingdom();
if (kingdom != null) {
if (kingdom.hasAttribute(...) && kingdom.equals(...))
}
Checking if a land belongs to a kingdom.
Scenario 1: You have a location (Location, SimpleChunkLocation, SimpleLocation, Block and etc...) and a Kingdom instance.
// Or convert it to SimpleChunkLocation of other types.
SimpleChunkLocation chunk = ...;
Kingdom kingdom = ...;
// DONT
Land land = chunk.getLand();
if (land != null && land.isClaimed && land.getKingdom().equals(kingdom))
// DO
if (kingdom.isClaimed(chunk))
Scenario 2: You have a Land instance and the Kingdom ID.
Land land = ...;
UUID kingdomId = ...;
// DONT
Kingdom kingdom = Kingdom.getKingdom(kingdomId);
if (kingdom.isClaimed(land.getLocation()))
// OR
if (kingdom.getId().equals(land.getKingdomId()))
// DO
if (land.getKingdomId().equals(kingdomID))
Comparing Lands
Land one = ...;
Land two = ...;
// DONT
if (one.equals(two))
// DO
if (one.simpleEquals(two))
Saving data
// DONT
Map<Land, ...> map = new HashMap<>();
Set<Land> set = new HashSet<>();
// DO
Map<SimpleChunkLocation, ...> map = new HashMap<>();
Set<SimpleChunkLocation> set = new HashSet<>();
// DONT
Map<Turret, ...> map = new HashMap<>();
Set<Turret> set = new HashSet<>();
// DO
Map<SimpleLocation, ...> map = new HashMap<>();
Set<SimpleLocation> set = new HashSet<>();
// This applies for structures which need to be saved with their SimpleLocation as well and
// Kingdom and KingdomPlayer which need to saved with their UUID.
Making custom turrets and structures are more complicated because of the abstraction level.
Turrets and Structures have 2 different main objects, 3 for turrets.
When it's mentioned that a class is a another x class
it means that class extends x class
.
- KingdomItem: Unlike the name, each turret and structure that's placed is called a kingdom item. They handle data for each turret and structure such as their location, ammo, holograms and etc...
- KingdomItemType: The main mechanics for the turrets and structures that are the same type. They handle turrets and structures GUIs, how their data is saved and how turrets activate.
- KingdomItemStyle: The modified version of (extends) KingdomItemType for turrets. They modify how turrets GUI works and how they activate.
KingdomItem class for turrets is called Turret and for structures it's called Structure.
KingdomItemType class for turret types is called TurretType and for structures it's called StructureType
So, each Structure & Turret is a KingdomItem and each KingdomItem has a KingdomItemType.
For turrets, each Turret has a KingdomItemStyle which is technically a KingdomItemType but with modified options which you can find the in turret config.
Structures cannot have a KingdomItemStyle because their mechanics are so unique that there is nothing special about them to be modified.
Let's understand KingdomItemStyle and KingdomItemType a little more.
Go to Plugins/Kingdoms/Turrets
folder in your server. You'll see all the turret files. Each of these files are a KingdomItemStyle and their KingdomItemType that they're modifying is the type
property inside the file.
Now this means that servers can make their own KingdomItemStyle without needing to code anything.
That makes it more easier to understand.
For example, by default there is a turret style named arrow
.yml that uses the type arrow
in the Turrets folder.
In this case, our KingdomItem is a RangedTurret which is a Turret.
And our KingdomItemStyle is a RangedTurretType which is a TurretType which is a KingdomItemType.
Yes, in this case both of our KingdomItemType and KingdomItemStyle are called arrow
which is allowed, but they're not the same.
As an example for structures, take a look at powercell
structure in structures.yml
:
- KingdomItem is a Structure.
- KingdomItemType is a StructurePowercell which is a StructureType
But now for an Extractor
:
- KingdomItem is an Extractor which is a Structure
- KingdomItemType is a StructureExtractor which is a StructureType
Now you might ask why extractors have their own kingdom item object but not powercells? Because extractors save data such as the last collected time milliseconds, but powercells don't need to store any extra information.
IMPORTANT:
If the turret or structure you're making has additional data (that is not temporary or transient) other than the ones provided in the superclass, you MUST override its KingdomItem hashCode() method in addition to calling its super.hashCode()
while building the hash. If you don't, the server might not save your data to the data files.
Example of overriding hashcode methods correctly for a kingdom item like extractors that has extra data:
@Override
public int hashCode() {
int prime = 31;
int result = 15;
result = prime * result + super.hashCode();
result = prime * result + lastCollector.hashCode();
result = prime * result + Long.hashCode(lastCollected);
return result;
}
Now let's make a complicated turret.
public class TurretGhasterBlaster extends RangedTurret { // RangedTurret extend Turret and Turret extends KingdomItem<Turret>
private int blasts = 0;
private int durability = 1000;
public GhasterBlasterTurret(KingdomItemStyle type, SimpleLocation location) {
super(type, location);
}
@Override
public void activate(LivingEntity target, Kingdom kingdom) {
super.activate(target, kingdom);
target.setVelocity(new Vector(0, blasts, 0));
blasts++;
durability--;
if (durability == 0) remove(getLand());
}
@Override
public int hashCode() {
int prime = 31;
int result = 15;
result = prime * result + super.hashCode();
result = prime * result + blasts;
result = prime * result + durability;
return result;
}
public int getBlasts() { return blasts; }
public void setBlasts(int blasts) { this.blasts = blasts; }
}
public class TurretGhasterBlaster extends TurretTypeRanged { // TurretTypeRanged extend TurretType and TurretType extends KingdomItemType<Turret>
public TurretGhasterBlaster () {
super("ghasterblaster", false, true);
}
@Override
public boolean activate(TurretActivateEvent event) { ... }
@Override
public void serialize(JsonObject json, Turret turret, Type type, JsonSerializationContext context) {
GhasterBlaster rangedTurret = (GhasterBlaster) turret;
super.serialize(json, turret, type, context);
json.addProperty("ammo", rangedTurret.getAmmo());
json.addProperty("blasts", rangedTurret.getBlasts());
}
@Override
public Turret deserialize(KingdomItemStyle style, JsonObject json, Type type, JsonDeserializationContext context) throws JsonParseException {
GhasterBlaster turret = new GhasterBlaster(style, null);
turret.setLevel(json.get("level").getAsInt());
turret.setAmmo(json.get("ammo").getAsInt());
turret.setBlasts(json.get("blasts").getAsInt());
turret.setHolograms(context.deserialize(json.get("holograms"), new TypeToken<List<UUID>>() {
}.getType()));
return turret;
}
@Override
public Turret build(KingdomItemStyle style, SimpleLocation location, KingdomPlayer kp, NBTWrappers.NBTTagCompound tag) {
Turret turret = new GhasterBlaster(style, location);
if (tag != null) turret.setData(tag);
return turret;
}
Now let's register our turret type.
register(new TurretGhasterBlaster());
Our style is automatically registered.
Now in our Turrets folder let's add blaster.yml
file
name: "&0Ghaster&cBlaster"
type: ghasterblaster
cost: 300
max-level: 3
hologram:
1:
lines:
- "&8-=[ &0Ghaster&cBlaster &8]=-"
height: 1
placing:
whitelist: true
blocks:
- CONTAINS:FENCE
- CONTAINS:WALL
range: 7 + lvl
cooldown: 8 - lvl
max-targets: min(2, 1 + (lvl + 1))
max-ammo: lvl * 2000
upgrade-cost: lvl * 80
fire: 1
particle: ~
effects:
1: []
projectile:
1: FIREBALL
speed: 1 + (lvl * 0.8)
damage: lvl * 4
block:
1: PLAYER_HEAD
skull:
1: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmNlZGVjMDRkMjM4MGNkNzcwMjdmOWQ0NDQ1NWM5OGI3ZWRjNWY2NjRjYTBkZDMwYTYxMDY5MDM5MTUzOTFkYiJ9fX0="
2: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmQzNzI4NTc5MzEzMWVkNzU1ZjFiMDA5OGYyOWRkNDEzZDY3NjU2YjYyMDg3Mjg5MzU0OTJiNDliMWQwZDRiYSJ9fX0="
item:
1:
name: "&0Ghaster&cBlaster Turret"
material: FIRE_CHARGE
lore:
- "&6A similar turret as flame"
- "turret but really fast with multiple targets."
- ""
- "&9Attributes&8:"
- "&7⚫ &2Level&8: &6%level_roman%"
- "&7⚫ &2Ammo&8: &6%ammo%"
- "&7⚫ &2Range&8: &6%range%"
- "&7⚫ &2Max Targets&8: &6%max_targets%"
- "&7⚫ &2Max Ammo&8: &6%max_ammo%"
- "&7⚫ &2Cooldown&8: &6%cooldown%"
- "&7⚫ &2Speed&8: &6%speed%"
- "&7⚫ &2Damage&8: &6%damage%"
It's similar to Bukkit's metadata except it's specifically made for kingdoms and stays after restarts.
All the main kingdom objects support metadata (which inherit the KingdomsObject
class):
- KingdomPlayer
- Land
- Kingdom
- Nation
- Mails
- KingdomItem
Metadata is simply a map with the key as a Namespace and the value as an object.
To modify this map, use getMetadata()
method. Some custom data objects might not be stored correctly. Please check your data once to see if the data is stored the way you expected or it might lead to some data corruptions.
So as a simple example, to save an int to a kingdom, you can use the following:
// You should preferably only use a single namespace for all your data.
private static final META_NS = new Namespace("MyPlugin", "LEVEL");
Kingdom kingdom = ...;
KingdomMetadataHandler metaInt = new StandardKingdomMetadataHandler(META_NS);
kingdom.getMetadata().put(metaInt, new StandardKingdomMetadata(10));
// Retrieve the value:
StandardKingdomMetadata data = (StandardKingdomMetadata) kingdom.getMetadata().get(META_NS);
int lvl = data.asInt(); // If the data types don't match, an error will be thrown.
A full example of registering a functioning metadata and using it can be found here which uses advanced metadata handlers instead of the simple standard handler.
Don't forget to call signalDisable()
after registering a custom metadata handler. This is not needed for the standard metadata handler.
Event | Description |
---|---|
KingdomCreateEvent |
Called when a kingdom is created via test /k create (Kingdom(UUID id, UUID king) constructor) |
KingdomDisbandEvent |
Called when a kingdom is disbanded either from /k disband, taxes or inactivities. (Kingdom#disband() ) |
KingdomKingChangeEvent |
When the king of the kingdom changes. |
GroupRenameEvent |
When a kingdom/nation is renamed with /k rename or /k nation rename or by other means. |
PlayerRankChangeEvent |
When a player is demoted/promoted. |
KingdomInviteEvent |
When a player invites another player to the kingdom. |
KingdomRelationshipChangeEvent |
Called when a kingdom relationship changes from commands. (Kingdom#acceptRelationShipRequest(...)
|
KingdomSetHomeEvent |
Called when a player sets/changes their kingdom home (Kingdom#setHome() ) |
OpenProtectedBlockEvent |
Called when a player opens a protected block using protection signs. |
ChampionAbilityEvent |
Called when a champion uses an ability. |
KingdomInvadeAttackEvent |
Called when either the champion or the player attack each other. |
KingdomInvadeEndEvent |
Called when an invasion ends with the invasion results and reason. |
KingdomPreInvadeEvent |
Called before the cooldown of /k invade starts. |
KingdomInvadeEvent |
Called after the cooldown of /k invade command. |
SiegeCannonHitEvent |
When a Siege Cannon's projectile hits a land. You can modify its shield damage. |
KingdomJoinEvent |
Called when someone join a kingdom. This is also called right before KingdomCreateEvent when someone creates a kingdom. |
KingdomLeaveEvent |
Called when someone leaves a kingdom. |
GroupResourcePointConvertEvent |
Called when a player donates resource points to a nation or a kingdom. Specifically called when KingdomPlayer#donate is used. |
LandChangeEvent |
Called when someone goes from a land to another land. This is basically a chunk change event but with kingdoms data. (for performance reason. |
LandLoadEvent |
When a kingdom land data loads. (When chunks load) Note that this will not do anything if the land is null (with no data attached to it) |
LandUnloadEvent |
When a kingdom land data unloads. (When chunks unload) Note that this will not do anything if the land is null (with no data attached |
ClaimLandEvent |
When someone claims one or more lands for a kingdom in one or different worlds. |
UnclaimLandEvent |
When someone unclaims one or more lands for a kingdom in one or different worlds. Called even for /k unclaim all |
NexusMoveEvent |
After someone used /k nexus to move their nexus. |
KingdomItemBreakEvent<? extends KingdomItem<?>> |
When someone breaks a structure or a turret block either from the GUI or manually. |
KingdomItemPlaceEvent<? extends KingdomItem<?>> |
When someone places a structure or a turret. |
KingdomItemInteractEvent<? extends KingdomItem<?>> |
A player right-clicks on a structure or a turret to open its GUI. |
TurretActivateEvent |
When a turret wants to target an entity. |
GroupRelationshipChangeEvent |
When a nation/kingdom changes their relationship with another nation/kingdom. |
GroupHomeTeleportEvent |
When a player teleports to nation/kingdom home. |
KingdomColorChangeEvent |
When Group#setColor is called. Which is usually when a player changes it from their nexus settings. |
KingdomFlagChangeEvent |
When Group#setFlag is called. Which is usually when a player changes it from their nexus settings. |
KingdomGUIOpenEvent |
When the plugin is about to open a GUI for the player (before it's opened). You can modify the GUI. |
MassWarStartEvent |
When Mass War event starts. |
MassWarEndEvent |
When Mass War event ends. |
KingdomMiscUpgradeUpgradeEvent |
When a misc-upgrade is upgraded. |
KingdomMiscUpgradeToggleEvent |
When a misc-upgrade is enabled/disabled. |
KingdomChampionUpgradeUpgradeEvent |
When a champion upgrade is upgraded. |
KingdomPowerupUpgradeEvent |
When a powerup is upgraded. |
KingdomItemUpgradeEvent |
When a structure/turret is upgraded. |
RankPermissionChangeEvent |
When a player changes the permissions of a rank through Nexus GUI. |
ExtractorCollectEvent |
When a player collects resource points of an extractor by opening its GUI or using /k extractor command. |
OutpostPurchaseItemEvent |
When a player purchases an item from an Outpost structure. |
WarpPadTeleportEvent |
When a player teleports to another structure using Warp Pad structure. |
KingdomPacifismStateChangeEvent |
When a player changes the pacifism state of the kingdom through nexus. |
GroupShieldPurchaseEvent |
When a player purchases a shield for their kingdom through nexus settings. |
MailSendEvent |
When a player sends a mail to another kingdom or replies to another player. |
PlayerChangeChatChannelEvent |
When a player switches channels using /k c command. |
GroupServerTaxPayEvent |
When a group pays server taxes. Not kingdom or nation taxes. |
RankCreateEvent , RankDeleteEvent , RankNameChangeEvent , RankColorChangeEvent , RankSymbolChangeEvent , RankPriorityChangeEvent , RankMaxClaimsChangeEvent , RankMaterialChangeEvent
|
These are called when a player changes the ranks through nexus Ranks & Permissions section. |
All kingdom events support metadata which uses Namespace. This can be helpful for including arbitrary information inside events for later uses by other plugins.
Kingdoms allows you to define your own kingdom permissions, relationship attributes and audit logs. All these are done through registries which work with Namespace
class.
Available Registries
Registry | Object | Description |
---|---|---|
Kingdoms#getPermissionRegistery() |
KingdomPermission |
Permissions are for kingdom/nation ranks which the admins of the kingdom can change in their Nexus GUI. |
Kingdoms#getRelationAttributeRegistry() |
RelationAttribute |
Attributes are agreements/permissions between two kingdoms/nations |
Kingdoms#getAuditLogRegistry() |
AuditLog & AuditLogProvider
|
A record of changes and actions in a kingdom/nation. |
To register your own namespaced value, you have to register them in your plugin's onLoad()
method. Doing so in onEnable
will not work. You also cannot unregister them since it'll cause data inconsistencies due to performance reasons.
In order to register your value, you have to make your own unique namespaced value to avoid conflicts. A namespace can be created like:
Namespace ns = new Namespace("PluginName", "TEST");
The first parameter PluginName
can only contain alphabets (no spaces, numbers, underscores, dashes and etc). And the second parameter must be an all-caps alphanumeric (including underlines) value.
public static final KingdomPermission PERMISSION_INV_TAKE = new KingdomPermission(new Namespace("PluginName", "INV_TAKE"));
@Override
public void onLoad() {
Kingdoms.get().getPermissionRegistery().register(PERMISSION_INV_TAKE);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onInventoryTake(InventoryClickEvent event) {
Player player = (Player) event.getWhoClicked();
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(player);
SimpleChunkLocation chunk = SimpleChunkLocation.of(player.getLocation);
Kingdom kingdom = kp.getKingdom();
if (kingdom == null || land == null || !kingdom.isClaimed(chunk)) return;
if (!kp.hasPermission(PERMISSION_INV_TAKE)) event.setCancelled(true);
}
public static final RelationAttribute ATTR_SHOOT_IN_LAND = new RelationAttribute(new Namespace("PluginName", "SHOOT"));
@Override
public void onLoad() {
Kingdoms.get().getRelationAttributeRegistry().register(ATTR_SHOOT_IN_LAND);
}
@EventHandler(ignoreCancelled = true)
public void onPlayerShoot(ProjectileLaunchEvent event) {
Projectile projectile = event.getEntity();
if (!(projectile.getShooter() instanceof Player)) return;
Player shooter = (Player) projectile.getShooter();
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(shooter);
Kingdom kpKingdom = kp.getKingdom();
Land land = Land.getLand(shooter.getLocation());
Kingdom landKingdom = land == null || !land.isClaimed() ? null : land.getKingdom();
if (!ATTR_SHOOT_IN_LAND.hasAttribute(kpKingdom, landKingdom)) event.setCancelled(true);
}
The following example show very simple cases on how to use these logs, however in some cases where it's possible to gather bulk information, it's recommended to make your own delayed log system to gather information for a certain type of action that a player does. For example in below, you could make a log collector class and wait for 10 seconds every time the player enters a new chunk that belongs to its kingdom and if there were no new logs for 10 seconds, add all the chunks that the player has visited in a single list (or set if you want to remove duplicates) this way you're improving the user experience, performance, storage and RAM.
public final class LogLandChange extends AuditLog {
private SimpleChunkLocation from, to;
protected LogLandChange() {}
public LogLandChange(SimpleChunkLocation from, SimpleChunkLocation to) {
this.from = from;
this.to = to;
}
private static final Namespace NS = new Namespace("PluginName", "LAND_CHANGE");
public static final AuditLogProvider PROVIDER = new AuditLogProvider() {
@Override
public AuditLog construct() {
return new LogLandChange();
}
@Override
public Namespace getNamespace() {
return NS;
}
};
@Override
public AuditLogProvider getProvider() {
return PROVIDER;
}
@Override
public void deserialize(DeserializationContext context) {
super.deserialize(context);
JsonObject json = context.getJson();
this.from = SimpleChunkLocation.fromString(json.get("from").getAsString());
this.to = SimpleChunkLocation.fromString(json.get("to").getAsString());
}
@Override
public void serialize(SerializationContext context) {
super.serialize(context);
JsonObject json = context.getJson();
json.addProperty("from", from.toString());
json.addProperty("to", to.toString());
}
@Override
protected void addEdits(MessageBuilder builder) {
super.addEdits(builder);
builder.parse("from", "&2" + + from.getWorld() + ", " + from.getX() + ", " + from.getZ());
// Or using kingdoms built-in language entries:
builder.parse("to", KingdomsLang.LOCATIONS_CHUNK.parse(LocationUtils.getChunkEdits(to)));
}
}
@Override
public void onLoad() {
Kingdoms.get().getAuditLogRegistry().register(LogLandChange.PROVIDER);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onLandChange(LandChangeEvent event) {
KingdomPlayer kp = event.getKingdomPlayer();
Kingdom kingdom = kp.getKingdom();
if (kingdom == null) return;
if (kingdom.isClaimed(event.getFromChunk()) || kingdom.isClaimed(event.getToChunk())) {
kingdom.log(new LogLandChange(event.getFromChunk(), event.getToChunk()));
}
}
Now there's a high chance server owners might want to use your registered values in their configs. The way these values are used in configs are a little different. They're used in namespace:value
format where namespace
is untouched, however value
is converted into an all lowercase word with underscores replaced with dashes. Also since colons have special meanings in yaml, they have to use one of the string literals.
The config entry corresponds to the Namespace#getConfigOptionName()
method.
From previous examples:
Registry | Namespace | Config Name | Relevant Configs |
---|---|---|---|
KingdomPermission | new Namespace("PluginName", "INV_TAKE") |
PluginName:inv-take |
ranks.yml |
RelationAttribute | new Namespace("PluginName", "SHOOT") |
PluginName:shoot |
relations.yml |
AuditLog | new Namespace("PluginName", "LAND_CHANGE") |
PluginName:land-change |
logs.yml GUI |
Note: If you registered a custom namespace'd value to the plugin, make sure that you call Kingdoms.get().signalFullSave()
on your onDisable()
or if you're using an addon, you can simply call signalDisable()
from the class that implemented the Addon interface.
We're going to take a look at some examples that are only a little harder to find for developers.
Getting all the kingdom allies.
List<Kingdom> allies = damagerKingdom.getKingdomsWithRelation(KingdomRelation.ALLY);
Checking if player has permission in kingdom or nation.
Player player = ...;
KingdomPlayer kp = new KingdomPlayer(player);
if (kp.hasKingdom()) {
kp.hasPermission(DefaultKingdomPermission.HOME);
if (kp.getKingdom().hasNation()) kp.hasNationPermission(DefaultKingdomPermission.HOME); // Spawn
}
Joining a kingdom.
Player player = ...;
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(player);
Kingdom kingdom = Kingdom.getKingdom(...);
kp.joinKingdom(kingdom);
Opening /k map for players.
Player player = ...;
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(player);
kp.buildMap().display(); // This considers the custom map size set by the player.
// or
new KingdomsMap().forPlayer(player).display();
If you're using EntityDamageByEntityEvent
event, simply check if the event is cancelled. Kingdoms uses LOW
event priority to make it easier for other plugins.
To check if two players can PvP each other in any circumstances you can use the following code. This will consider all the kingdom features and configured options.
Note: It's important that which player is the damager and which is the victim (method arguments order).
Player damager = ...;
Player victim = ...;
boolean canFight = PvPManager.canFight(damager, victim);
An oversimplified manual way to do this:
Player damager = ...;
Player victim = ...;
KingdomPlayer damagerKp = KingdomPlayer.getKingdomPlayer(damager);
if (damagerKp.isAdmin() || !damagerKp.hasKingdom()) return true;
KingdomPlayer victimKp = KingdomPlayer.getKingdomPlayer(victim);
if (!victimKp.hasKingdom()) return true;
if (damagerKp.isPvp() && victimKp.isPvp()) return true;
Kingdom damagerKingdom = damagerKp.getKingdom();
Kingdom victimKingdom = victimKp.getKingdom();
return !victimKingdom.hasAttribute(damagerKingdom, KingdomRelation.Attribute.CEASEFIRE);
Checking if an entity is a kingdom entity. A kingdom entity is any entity that is spawned by Kingdoms plugin.
This includes champions, turret soldiers, guards and etc.
LivingEntity entity = ...;
boolean isKingdomMob = KingdomEntityRegistry.isKingdomEntity(entity);
Player player = ...;
Location location = player.getLocation();
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(player);
if (!kp.hasKingdom()) return;
// WorldGuard Support
if (ServiceHandler.isLocationInRegion(loc)) return;
SimpleChunkLocation simpLocation = new SimpleChunkLocation(loc);
Land land = Land.getLand(location);
if (land != null && !land.isClaimed()) kp.getKingdom().claim(kp, simpLocation);
You don't need to use everything from here. Take what you need.
Collection<ItemStack> items = ...;
List<ItemStack> stacked = XItemStack.stack(items);
Player player = ...;
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(player);
// You can leave the second argument as null if you don't want to modify anything.
Pair<Long, List<ItemStack>> result = ResourcePointManager.convertToResourcePoints(stacked, (item, leftOvers) -> {
// Return null if you want the item to be handled normally according to the config.
// You can also add a part of the item to the leftOvers list. You don't need to clone the item.
// If you return a nonnull value, the item will not be added to the leftOvers list and the amount will be added to the result.
});
long added = result.getKey();
List<ItemStack> leftOvers = result.getValue();
KingdomResourcePointDonateEvent event = kp.donate(added, stacked, leftOvers);
if (!event.isCancelled()) {
added = event.getAmount();
leftOvers = event.getLeftOvers();
kingdom.addResourcePoints(added);
if (leftOvers != null) XItemStack.giveOrDrop(player, true, leftOvers.toArray(new ItemStack[0]));
}
OfflinePlayer player = ...;
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(player);
Kingdom kingdom = kp.getKingdom();
Pair<Boolean, Double> result = kingdom.payTaxes(player);
result.getKey(); // Could pay?
result.getValue(); // Tax amount the player paid.
if (!kingdom.hasNation()) return;
Nation nation = kingdom.getNation();
nation.payTaxes(kingdom);
Block block = ...;
SimpleLocation location = new SimpleLocation(block);
TurretStyle turretStyle = TurretRegistry.getStyle("arrow");
StructureStyle structureStyle = StructureRegistry.getStyle("powercell");
// You can leave the kp and nbt data as null if you don't have it.
Land land = location.toSimpleChunkLocation().getLand();
Turret turret = turretStyle.getType().build(type, location, kp, nbt);
Structure structure = structureStyle.getType().build(type, location, kp, nbt);
land.getTurrets().put(location, turret);
land.getStructures().put(location, structure);
turret.spawnHolograms(land.getKingdom());
structure.spawnHolograms(land.getKingdom());
# This can be aquired using Land#getStructures() or Land#getTurrets()
KingdomItem<?> kingdomItem = ...;
KingdomItemRemoveContext context = new KingdomItemRemoveContext(); # All the properties are optional.
context.setCause(event); # If this was caused by another event
context.setPlayer(kp); # If a player caused this item to be removed.
context.dropsItem(false); # If the item should be dropped after being removed.
context.modifier(Consumer<KingdomItemBreakEvent<?>>) # Usually used for modifying the metadata of the event being firing it.
kingdomItem.remove(context);
ItemStack item = ...;
NBTWrappers.NBTTagCompound nbt = ItemNBT.getTag(item);
nbt = nbt.getCompound(Kingdoms.NBT);
// It's not a kingdom item!
if (nbt == null) return;
String tag = nbt.get(StructureType.METADATA, NBTType.STRING);
// It's a structure.
if (tag != null) return;
tag = nbt.get(TurretType.METADATA, NBTType.STRING);
if (tag != null) return;// It's a turret.
// If it ever passed the above check then I don't know what the hell you did to the item.
Checking if a player is invading or an entity is a champion.
Entity entity;
if (entity.hasMetadata(Invasion.METADATA)) {
if (entity instanceof Player) // invader
else // champion
}
// Or more easily for players
KingdomPlayer kp;
kp.isInvading()
Getting invasions
// From players:
Player player = ...;
KingdomPlayer kp = KingdomPlayer.getKingdomPlayer(player);
Invasion invasion = kp.getInvasion();
// From entity (champions):
Entity entity = ...;
Invasion invasion = Invasion.getInvasion(entity);
Land mainLand = invasion.getOriginLand();
Getting invasion stuff from land.
Land land = ...;
Invasion invasion = land.getInvasions().get(invaderKingdomId);
if (invasion.getChampion().isValid()) invasion.end(KingdomInvadeEndEvent.InvasionResult.TIMES_UP);
As you can see an invasion session is described by an Invasion
class. This class holds all possible information about an invasion. A standard type of invasions are plunder invasions which is handled by Plunder
class which extends Invasion
.
In order to start an invasion manually, you can use the following code:
KingdomPreInvadeEvent event = InvasionFactory.standardInvasion(land, acceptedLands, player, ransackMode);
if (event.isCancelled()) return;
Invasion invasion = event.getInvasion();
// The KingdomInvadeEvent gets called when the cooldown of standardInvasion is ended.
You can use InvasionFactory
to access a variety of different helpful tools regarding invasions since the standardInvasion()
method and its more raw version prepareInvasion()
method almost perform no checks such as shield, pacifism, invasion costs and etc that the /k invade command does.
Replacing an ability for champions (you cannot create a new one)
public class ChampionAbilityBoom extends ChampionAbility implements Listener {
public ChampionAbilityResistance() {
super(ChampionUpgrade.THOR);
}
@EventHandler
public void onDamage(KingdomInvadeAttackEvent event) {
if (cantUse(event.getInvasion().getDefender().getKingdom())) return;
if (event.isAttackerChampion()) return;
if (!MathUtils.hasChance(10) return;
if (callEvent(event.getInvasion())) return;
Entity champion = event.getEntity();
Player player = event.getInvasion().getInvader().getPlayer();
// A much better way of getting the player is:
player = (Player) event.getDamager();
player.getWorld().createExplosion(player.getLocation(), 3f);
}
}
new ChampionAbilityBoom();
Checking relation attributes between two kingdoms needs to follow an order.
Kingdom defender = ...;
Kingdom attacker = ...;
boolean hasAttribute = defender.hasAttribute(attacker, ...);
This code will check the relation attributes for attacker
, so for example if defender
disabled CEASEFIRE
attribute for allies and attacker
is allies with defender
, hasAttribute
will return false. Since the attributes are checked from attacker
and only defender
changed that attribute. Now it'll return true the other way around. If defender is the attacker, and the attacker is the defender (it doesn't mean that defender and attacker instances switch, we just switch the order that we check their attribute):
boolean hasAttribute = attacker.hasAttribute(defender, ...);
Since the order matters, sometimes you'll have to do a few extra checks before using the kingdom objects. To make it easier and safer, you can use the following method:
KingdomRelation.Attribute attribute = ...;
boolean hasAttribute = attribute.hasAttribute(kingdom, otherKingdom);
Listening to KingdomItemEvent
s is a little special since they use generic types.
In this example, we're going to use KingdomItemPlaceEvent
// If you want to listen to either structures or turrets.
@EventHandler
public void onPlace(KingdomItemPlaceEvent<?> event) {
if (event.getKingdomItem() instanceof Structure) {
Structure structure = (Structure) event.getKingdomItem();
}
if (event.getKingdomItem() instanceof Turret) {
Turret structure = (Turret) event.getKingdomItem();
}
}
// Listening to only one type of kingdom item. In this example, structures.
@EventHandler
public void onPlace(KingdomItemPlaceEvent<Structure> event) {
if (!(event.getKingdomItem() instanceof Structure)) return; // Yes, this line is still necessary.
Structure structure = event.getKingdomItem();
}
In this example, I modified arrow
turret style to only be purchasable by opped players.
@EventHandler(ignoreCancelled = true)
public void onNexusOpen(KingdomGUIOpenEvent event) {
InteractiveGUI gui = event.getGUI();
// Make sure this is the turret's GUI that's being opened.
if (!gui.is(KingdomsGUI.STRUCTURES_NEXUS_TURRETS)) return;
// getOption() returns an option that has been already pushed to the GUI.
Optional<GUIOption> optionOpt = gui.getOption("arrow");
if (!optionOpt.isPresent()) return;
GUIOption option = optionOpt.get();
Runnable fn = option.getRunnables().values().iterator().next();
Runnable newFn = () -> {
if (!event.getPlayer().isOp()) {
event.getPlayer().sendMessage("You need to be op to buy this turret.");
return;
}
// This preserves the original functionality of the option so you won't need to copy the entire functionality yourself.
fn.run();
};
// toArray() is necessary to prevent concurrent modification.
for (ClickType clickType : option.getRunnables().keySet().toArray(new ClickType[0])) {
// The plugin usually has a single functionality for all clicking types.
option.getRunnables().put(clickType, newFn);
}
}
The location API in Kingdoms is a bit complicated. There are many classes to do the same thing with different mutability and datasets. So instead of listing them all here, there's a general formula that can be followed to get the desired class that you want, or just use Location
class (not from Bukkit, but Kingdoms) that is accepted anywhere that other location types are accepted.
This is going to be like writing chemical equations, where we have a carbon element that's usually in the middle and others as prefix and suffix. Our main element here is going to Vector
or Location
. You have to go thru this list in order:
-
Immutable (
prefix
): All location types that are not intended to be changed (final
modifier for xyz) have this prefix. -
Block: All locations that have an integer
int
xyz. If this is not specified, the xyz will be of typedouble
-
Main Element:
- Vector: This location will only contain xyz data without any world data.
- Location: This location will contain both xyz and world data.
-
Dimension:
- 3: This location has xyz coordinates.
- 2: This location only has xz coordinates. (Not all location types support this)
E.g.
After finding your desired type, they all should support a static of()
method that you can get an instance of these locations.
Terminology - Spigot - Discord