mirror of
https://github.com/EssentialsX/Essentials.git
synced 2024-11-13 06:15:08 +01:00
Merge branch '2.x' into rework/providers
# Conflicts: # Essentials/src/main/java/com/earth2me/essentials/signs/SignEnchant.java
This commit is contained in:
commit
b5b3de98a1
1
.github/workflows/build-master.yml
vendored
1
.github/workflows/build-master.yml
vendored
@ -59,6 +59,7 @@ jobs:
|
||||
cp -r EssentialsAntiBuild/build/docs/javadoc/ javadocs/EssentialsAntiBuild/
|
||||
cp -r EssentialsChat/build/docs/javadoc/ javadocs/EssentialsChat/
|
||||
cp -r EssentialsDiscord/build/docs/javadoc/ javadocs/EssentialsDiscord/
|
||||
cp -r EssentialsDiscordLink/build/docs/javadoc/ javadocs/EssentialsDiscordLink/
|
||||
cp -r EssentialsGeoIP/build/docs/javadoc/ javadocs/EssentialsGeoIP/
|
||||
cp -r EssentialsProtect/build/docs/javadoc/ javadocs/EssentialsProtect/
|
||||
cp -r EssentialsSpawn/build/docs/javadoc/ javadocs/EssentialsSpawn/
|
||||
|
@ -24,4 +24,4 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
@ -1,5 +1,6 @@
|
||||
package com.earth2me.essentials;
|
||||
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import net.ess3.api.IEssentials;
|
||||
import net.ess3.provider.PersistentDataProvider;
|
||||
@ -47,7 +48,7 @@ public class EssentialsBlockListener implements Listener {
|
||||
if (is != null && is.getType() != null && !MaterialUtil.isAir(is.getType())) {
|
||||
final ItemStack cloneIs = is.clone();
|
||||
cloneIs.setAmount(1);
|
||||
user.getBase().getInventory().addItem(cloneIs);
|
||||
Inventories.addItem(user.getBase(), cloneIs);
|
||||
user.getBase().updateInventory();
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.earth2me.essentials;
|
||||
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
import net.ess3.api.IEssentials;
|
||||
import org.bukkit.Location;
|
||||
@ -52,12 +52,12 @@ public class EssentialsEntityListener implements Listener {
|
||||
if (eDefend instanceof Player) {
|
||||
onPlayerVsPlayerDamage(event, (Player) eDefend, attacker);
|
||||
} else if (eDefend instanceof Ageable) {
|
||||
final ItemStack hand = attacker.getBase().getItemInHand();
|
||||
final ItemStack hand = Inventories.getItemInMainHand(attacker.getBase());
|
||||
if (ess.getSettings().isMilkBucketEasterEggEnabled()
|
||||
&& hand != null && hand.getType() == Material.MILK_BUCKET) {
|
||||
((Ageable) eDefend).setBaby();
|
||||
hand.setType(Material.BUCKET);
|
||||
attacker.getBase().setItemInHand(hand);
|
||||
Inventories.setItemInMainHand(attacker.getBase(), hand);
|
||||
attacker.getBase().updateInventory();
|
||||
event.setCancelled(true);
|
||||
}
|
||||
@ -98,7 +98,7 @@ public class EssentialsEntityListener implements Listener {
|
||||
}
|
||||
|
||||
private void onPlayerVsPlayerPowertool(final EntityDamageByEntityEvent event, final Player defender, final User attacker) {
|
||||
final List<String> commandList = attacker.getPowertool(attacker.getBase().getItemInHand());
|
||||
final List<String> commandList = attacker.getPowertool(Inventories.getItemInHand(attacker.getBase()));
|
||||
if (commandList != null && !commandList.isEmpty()) {
|
||||
for (final String tempCommand : commandList) {
|
||||
final String command = powertoolPlayer.matcher(tempCommand).replaceAll(defender.getName());
|
||||
@ -196,73 +196,23 @@ public class EssentialsEntityListener implements Listener {
|
||||
final ISettings.KeepInvPolicy vanish = ess.getSettings().getVanishingItemsPolicy();
|
||||
final ISettings.KeepInvPolicy bind = ess.getSettings().getBindingItemsPolicy();
|
||||
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_11_2_R01) && (vanish != ISettings.KeepInvPolicy.KEEP || bind != ISettings.KeepInvPolicy.KEEP)) {
|
||||
for (final ItemStack stack : event.getEntity().getInventory()) {
|
||||
if (stack != null && !MaterialUtil.isAir(stack.getType())) {
|
||||
if (stack.getEnchantments().containsKey(Enchantment.VANISHING_CURSE)) {
|
||||
if (vanish == ISettings.KeepInvPolicy.DELETE) {
|
||||
event.getEntity().getInventory().remove(stack);
|
||||
} else if (vanish == ISettings.KeepInvPolicy.DROP) {
|
||||
event.getDrops().add(stack);
|
||||
event.getEntity().getInventory().remove(stack);
|
||||
}
|
||||
}
|
||||
if (stack.getEnchantments().containsKey(Enchantment.BINDING_CURSE)) {
|
||||
if (bind == ISettings.KeepInvPolicy.DELETE) {
|
||||
event.getEntity().getInventory().remove(stack);
|
||||
} else if (bind == ISettings.KeepInvPolicy.DROP) {
|
||||
event.getEntity().getInventory().remove(stack);
|
||||
event.getDrops().add(stack);
|
||||
}
|
||||
Inventories.removeItems(user.getBase(), stack -> {
|
||||
if (vanish != ISettings.KeepInvPolicy.KEEP && stack.getEnchantments().containsKey(Enchantment.VANISHING_CURSE)) {
|
||||
if (vanish == ISettings.KeepInvPolicy.DROP) {
|
||||
event.getDrops().add(stack.clone());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Now check armor
|
||||
final ItemStack[] armor = event.getEntity().getInventory().getArmorContents();
|
||||
for (int i = 0; i < armor.length; i++) {
|
||||
final ItemStack stack = armor[i];
|
||||
if (stack != null && !MaterialUtil.isAir(stack.getType())) {
|
||||
if (stack.getEnchantments().containsKey(Enchantment.VANISHING_CURSE)) {
|
||||
if (vanish == ISettings.KeepInvPolicy.DELETE) {
|
||||
armor[i] = null;
|
||||
} else if (vanish == ISettings.KeepInvPolicy.DROP) {
|
||||
if (!event.getDrops().contains(stack)) {
|
||||
event.getDrops().add(stack);
|
||||
}
|
||||
armor[i] = null;
|
||||
}
|
||||
}
|
||||
if (stack.getEnchantments().containsKey(Enchantment.BINDING_CURSE)) {
|
||||
if (bind == ISettings.KeepInvPolicy.DELETE) {
|
||||
armor[i] = null;
|
||||
} else if (bind == ISettings.KeepInvPolicy.DROP) {
|
||||
if (!event.getDrops().contains(stack)) {
|
||||
event.getDrops().add(stack);
|
||||
}
|
||||
armor[i] = null;
|
||||
}
|
||||
if (bind != ISettings.KeepInvPolicy.KEEP && stack.getEnchantments().containsKey(Enchantment.BINDING_CURSE)) {
|
||||
if (bind == ISettings.KeepInvPolicy.DROP) {
|
||||
event.getDrops().add(stack.clone());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
event.getEntity().getInventory().setArmorContents(armor);
|
||||
|
||||
// Now check offhand
|
||||
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_9_R01)) {
|
||||
final ItemStack stack = event.getEntity().getInventory().getItemInOffHand();
|
||||
//noinspection ConstantConditions
|
||||
if (stack != null && !MaterialUtil.isAir(stack.getType())) {
|
||||
final boolean isVanish = stack.getEnchantments().containsKey(Enchantment.VANISHING_CURSE);
|
||||
final boolean isBind = stack.getEnchantments().containsKey(Enchantment.BINDING_CURSE);
|
||||
if (isVanish || isBind) {
|
||||
event.getEntity().getInventory().setItemInOffHand(null);
|
||||
if ((isVanish && vanish == ISettings.KeepInvPolicy.DROP) || (isBind && bind == ISettings.KeepInvPolicy.DROP)) {
|
||||
if (!event.getDrops().contains(stack)) {
|
||||
event.getDrops().add(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.earth2me.essentials;
|
||||
|
||||
import com.earth2me.essentials.commands.Commandfireball;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.textreader.IText;
|
||||
import com.earth2me.essentials.textreader.KeywordReplacer;
|
||||
import com.earth2me.essentials.textreader.TextInput;
|
||||
@ -194,17 +195,26 @@ public class EssentialsPlayerListener implements Listener, FakeAccessor {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ess.getSettings().cancelAfkOnMove() && !ess.getSettings().getFreezeAfkPlayers()) {
|
||||
event.getHandlers().unregister(this);
|
||||
final User user = ess.getUser(event.getPlayer());
|
||||
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "Unregistering move listener");
|
||||
if (user.isFreeze()) {
|
||||
final Location from = event.getFrom();
|
||||
final Location to = event.getTo().clone();
|
||||
to.setX(from.getX());
|
||||
to.setY(from.getY());
|
||||
to.setZ(from.getZ());
|
||||
try {
|
||||
event.setTo(LocationUtil.getSafeDestination(ess, to));
|
||||
} catch (final Exception ex) {
|
||||
event.setTo(to);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
final User user = ess.getUser(event.getPlayer());
|
||||
if (!ess.getSettings().cancelAfkOnMove() && !ess.getSettings().getFreezeAfkPlayers()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.isAfk() && ess.getSettings().getFreezeAfkPlayers()) {
|
||||
final Location from = event.getFrom();
|
||||
final Location origTo = event.getTo();
|
||||
@ -561,7 +571,7 @@ public class EssentialsPlayerListener implements Listener, FakeAccessor {
|
||||
final User user = ess.getUser(event.getPlayer());
|
||||
final ItemStack stack = new ItemStack(Material.EGG, 1);
|
||||
if (user.hasUnlimited(stack)) {
|
||||
user.getBase().getInventory().addItem(stack);
|
||||
Inventories.addItem(user.getBase(), stack);
|
||||
user.getBase().updateInventory();
|
||||
}
|
||||
}
|
||||
|
@ -336,4 +336,8 @@ public interface IUser {
|
||||
List<String> getPastUsernames();
|
||||
|
||||
void addPastUsername(String username);
|
||||
|
||||
boolean isFreeze();
|
||||
|
||||
void setFreeze(boolean freeze);
|
||||
}
|
||||
|
@ -2,12 +2,11 @@ package com.earth2me.essentials;
|
||||
|
||||
import com.earth2me.essentials.Trade.OverflowType;
|
||||
import com.earth2me.essentials.commands.NoChargeException;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.textreader.IText;
|
||||
import com.earth2me.essentials.textreader.KeywordReplacer;
|
||||
import com.earth2me.essentials.textreader.SimpleTextInput;
|
||||
import com.earth2me.essentials.utils.DateUtil;
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.earth2me.essentials.utils.NumberUtil;
|
||||
import net.ess3.api.IEssentials;
|
||||
import net.ess3.api.events.KitClaimEvent;
|
||||
@ -15,11 +14,11 @@ import net.ess3.provider.SerializationProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
@ -215,57 +214,34 @@ public class Kit {
|
||||
stack = metaStack.getItemStack();
|
||||
}
|
||||
|
||||
if (autoEquip) {
|
||||
final Material material = stack.getType();
|
||||
final PlayerInventory inventory = user.getBase().getInventory();
|
||||
if (MaterialUtil.isHelmet(material) && isEmptyStack(inventory.getHelmet())) {
|
||||
inventory.setHelmet(stack);
|
||||
continue;
|
||||
} else if (MaterialUtil.isChestplate(material) && isEmptyStack(inventory.getChestplate())) {
|
||||
inventory.setChestplate(stack);
|
||||
continue;
|
||||
} else if (MaterialUtil.isLeggings(material) && isEmptyStack(inventory.getLeggings())) {
|
||||
inventory.setLeggings(stack);
|
||||
continue;
|
||||
} else if (MaterialUtil.isBoots(material) && isEmptyStack(inventory.getBoots())) {
|
||||
inventory.setBoots(stack);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
itemList.add(stack);
|
||||
}
|
||||
|
||||
final Map<Integer, ItemStack> overfilled;
|
||||
final boolean allowOversizedStacks = user.isAuthorized("essentials.oversizedstacks");
|
||||
final int maxStackSize = user.isAuthorized("essentials.oversizedstacks") ? ess.getSettings().getOversizedStackSize() : 0;
|
||||
final boolean isDropItemsIfFull = ess.getSettings().isDropItemsIfFull();
|
||||
if (isDropItemsIfFull) {
|
||||
if (allowOversizedStacks) {
|
||||
overfilled = InventoryWorkaround.addOversizedItems(user.getBase().getInventory(), ess.getSettings().getOversizedStackSize(), itemList.toArray(new ItemStack[0]));
|
||||
} else {
|
||||
overfilled = InventoryWorkaround.addItems(user.getBase().getInventory(), itemList.toArray(new ItemStack[0]));
|
||||
|
||||
final ItemStack[] itemArray = itemList.toArray(new ItemStack[0]);
|
||||
if (!isDropItemsIfFull && !Inventories.hasSpace(user.getBase(), maxStackSize, autoEquip, itemArray)) {
|
||||
user.sendMessage(tl("kitInvFullNoDrop"));
|
||||
return false;
|
||||
}
|
||||
|
||||
final Map<Integer, ItemStack> leftover = Inventories.addItem(user.getBase(), maxStackSize, autoEquip, itemArray);
|
||||
if (!isDropItemsIfFull && !leftover.isEmpty()) {
|
||||
// Inventories#hasSpace should prevent this state from EVER being reached; If it does, something has gone terribly wrong, and we should just give up and hope people report it :(
|
||||
throw new IllegalStateException("Something has gone terribly wrong while adding items to the user's inventory. Please report this to the EssentialsX developers. Items left over: " + leftover + ". Original items: " + Arrays.toString(itemArray));
|
||||
}
|
||||
|
||||
for (final ItemStack itemStack : leftover.values()) {
|
||||
int spillAmount = itemStack.getAmount();
|
||||
if (maxStackSize != 0) {
|
||||
itemStack.setAmount(Math.min(spillAmount, itemStack.getMaxStackSize()));
|
||||
}
|
||||
for (final ItemStack itemStack : overfilled.values()) {
|
||||
int spillAmount = itemStack.getAmount();
|
||||
if (!allowOversizedStacks) {
|
||||
itemStack.setAmount(Math.min(spillAmount, itemStack.getMaxStackSize()));
|
||||
}
|
||||
while (spillAmount > 0) {
|
||||
user.getWorld().dropItemNaturally(user.getLocation(), itemStack);
|
||||
spillAmount -= itemStack.getAmount();
|
||||
}
|
||||
spew = true;
|
||||
}
|
||||
} else {
|
||||
if (allowOversizedStacks) {
|
||||
overfilled = InventoryWorkaround.addAllOversizedItems(user.getBase().getInventory(), ess.getSettings().getOversizedStackSize(), itemList.toArray(new ItemStack[0]));
|
||||
} else {
|
||||
overfilled = InventoryWorkaround.addAllItems(user.getBase().getInventory(), itemList.toArray(new ItemStack[0]));
|
||||
}
|
||||
if (overfilled != null) {
|
||||
user.sendMessage(tl("kitInvFullNoDrop"));
|
||||
return false;
|
||||
while (spillAmount > 0) {
|
||||
user.getWorld().dropItemNaturally(user.getLocation(), itemStack);
|
||||
spillAmount -= itemStack.getAmount();
|
||||
}
|
||||
spew = true;
|
||||
}
|
||||
user.getBase().updateInventory();
|
||||
|
||||
@ -292,8 +268,4 @@ public class Kit {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isEmptyStack(ItemStack stack) {
|
||||
return stack == null || MaterialUtil.isAir(stack.getType());
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package com.earth2me.essentials;
|
||||
|
||||
import net.ess3.api.IUser;
|
||||
import net.essentialsx.api.v2.services.mail.MailService;
|
||||
import net.essentialsx.api.v2.events.UserMailEvent;
|
||||
import net.essentialsx.api.v2.services.mail.MailMessage;
|
||||
import net.essentialsx.api.v2.services.mail.MailSender;
|
||||
import net.essentialsx.api.v2.services.mail.MailService;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
@ -35,6 +37,12 @@ public class MailServiceImpl implements MailService {
|
||||
}
|
||||
|
||||
private void sendMail(IUser recipient, MailMessage message) {
|
||||
final UserMailEvent event = new UserMailEvent(recipient, message);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ArrayList<MailMessage> messages = recipient.getMailMessages();
|
||||
messages.add(0, message);
|
||||
recipient.setMailList(messages);
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.earth2me.essentials;
|
||||
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.EnumUtil;
|
||||
import com.earth2me.essentials.utils.StringUtil;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
@ -362,8 +362,8 @@ public enum MobData {
|
||||
((Horse) spawned).getInventory().setArmor(new ItemStack((Material) this.value, 1));
|
||||
} else if (this.type.equals(EntityType.ZOMBIE.getEntityClass()) || this.type.equals(EntityType.SKELETON)) {
|
||||
final EntityEquipment invent = ((LivingEntity) spawned).getEquipment();
|
||||
InventoryWorkaround.setItemInMainHand(invent, new ItemStack((Material) this.value, 1));
|
||||
InventoryWorkaround.setItemInMainHandDropChance(invent, 0.1f);
|
||||
Inventories.setItemInMainHand(invent, new ItemStack((Material) this.value, 1));
|
||||
Inventories.setItemInMainHandDropChance(invent, 0.1f);
|
||||
}
|
||||
} else if (this.value.equals(Data.RAID_LEADER)) {
|
||||
((Raider) spawned).setPatrolLeader(true);
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.earth2me.essentials;
|
||||
|
||||
import com.earth2me.essentials.Mob.MobException;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.EnumUtil;
|
||||
import com.earth2me.essentials.utils.LocationUtil;
|
||||
import com.earth2me.essentials.utils.StringUtil;
|
||||
@ -251,8 +251,8 @@ public final class SpawnMob {
|
||||
private static void defaultMobData(final EntityType type, final Entity spawned) {
|
||||
if (type == EntityType.SKELETON) {
|
||||
final EntityEquipment invent = ((LivingEntity) spawned).getEquipment();
|
||||
InventoryWorkaround.setItemInMainHand(invent, new ItemStack(Material.BOW, 1));
|
||||
InventoryWorkaround.setItemInMainHandDropChance(invent, 0.1f);
|
||||
Inventories.setItemInMainHand(invent, new ItemStack(Material.BOW, 1));
|
||||
Inventories.setItemInMainHandDropChance(invent, 0.1f);
|
||||
}
|
||||
|
||||
if (type == MobCompat.ZOMBIFIED_PIGLIN) {
|
||||
@ -260,8 +260,8 @@ public final class SpawnMob {
|
||||
setVillager(zombie, false);
|
||||
|
||||
final EntityEquipment invent = zombie.getEquipment();
|
||||
InventoryWorkaround.setItemInMainHand(invent, new ItemStack(GOLDEN_SWORD, 1));
|
||||
InventoryWorkaround.setItemInMainHandDropChance(invent, 0.1f);
|
||||
Inventories.setItemInMainHand(invent, new ItemStack(GOLDEN_SWORD, 1));
|
||||
Inventories.setItemInMainHandDropChance(invent, 0.1f);
|
||||
}
|
||||
|
||||
if (type == EntityType.ZOMBIE) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.earth2me.essentials;
|
||||
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.craftbukkit.SetExpFix;
|
||||
import com.earth2me.essentials.utils.NumberUtil;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
@ -8,7 +8,6 @@ import net.ess3.api.IEssentials;
|
||||
import net.ess3.api.IUser;
|
||||
import net.ess3.api.MaxMoneyException;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.io.File;
|
||||
@ -16,6 +15,7 @@ import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -197,7 +197,7 @@ public class Trade {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getItemStack() != null && !user.getBase().getInventory().containsAtLeast(itemStack, itemStack.getAmount())) {
|
||||
if (getItemStack() != null && !Inventories.containsAtLeast(user.getBase(), itemStack, itemStack.getAmount())) {
|
||||
future.completeExceptionally(new ChargeException(tl("missingItems", getItemStack().getAmount(), ess.getItemDb().name(getItemStack()))));
|
||||
return;
|
||||
}
|
||||
@ -225,52 +225,33 @@ public class Trade {
|
||||
user.giveMoney(getMoney());
|
||||
}
|
||||
if (getItemStack() != null) {
|
||||
// This stores the would be overflow
|
||||
final Map<Integer, ItemStack> overFlow = InventoryWorkaround.addAllItems(user.getBase().getInventory(), getItemStack());
|
||||
if (type == OverflowType.ABORT && !Inventories.hasSpace(user.getBase(), 0, false, getItemStack())) {
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "abort paying " + user.getName() + " itemstack " + getItemStack().toString() + " due to lack of inventory space ");
|
||||
}
|
||||
return Collections.singletonMap(0, getItemStack());
|
||||
}
|
||||
|
||||
if (overFlow != null) {
|
||||
switch (type) {
|
||||
case ABORT:
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "abort paying " + user.getName() + " itemstack " + getItemStack().toString() + " due to lack of inventory space ");
|
||||
final Map<Integer, ItemStack> leftover = Inventories.addItem(user.getBase(), getItemStack());
|
||||
user.getBase().updateInventory();
|
||||
if (!leftover.isEmpty()) {
|
||||
if (type == OverflowType.RETURN) {
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " partial itemstack " + getItemStack().toString() + " with overflow " + leftover.get(0).toString());
|
||||
}
|
||||
return leftover;
|
||||
} else {
|
||||
for (final ItemStack itemStack : leftover.values()) {
|
||||
int spillAmount = itemStack.getAmount();
|
||||
itemStack.setAmount(Math.min(spillAmount, itemStack.getMaxStackSize()));
|
||||
while (spillAmount > 0) {
|
||||
user.getBase().getWorld().dropItemNaturally(user.getBase().getLocation(), itemStack);
|
||||
spillAmount -= itemStack.getAmount();
|
||||
}
|
||||
|
||||
return overFlow;
|
||||
|
||||
case RETURN:
|
||||
// Pay the user the items, and return overflow
|
||||
final Map<Integer, ItemStack> returnStack = InventoryWorkaround.addItems(user.getBase().getInventory(), getItemStack());
|
||||
user.getBase().updateInventory();
|
||||
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " partial itemstack " + getItemStack().toString() + " with overflow " + returnStack.get(0).toString());
|
||||
}
|
||||
|
||||
return returnStack;
|
||||
case DROP:
|
||||
// Pay the users the items directly, and drop the rest, will always return no overflow.
|
||||
final Map<Integer, ItemStack> leftOver = InventoryWorkaround.addItems(user.getBase().getInventory(), getItemStack());
|
||||
final Location loc = user.getBase().getLocation();
|
||||
for (final ItemStack loStack : leftOver.values()) {
|
||||
final int maxStackSize = loStack.getType().getMaxStackSize();
|
||||
final int stacks = loStack.getAmount() / maxStackSize;
|
||||
final int leftover = loStack.getAmount() % maxStackSize;
|
||||
final Item[] itemStacks = new Item[stacks + (leftover > 0 ? 1 : 0)];
|
||||
for (int i = 0; i < stacks; i++) {
|
||||
final ItemStack stack = loStack.clone();
|
||||
stack.setAmount(maxStackSize);
|
||||
itemStacks[i] = loc.getWorld().dropItem(loc, stack);
|
||||
}
|
||||
if (leftover > 0) {
|
||||
final ItemStack stack = loStack.clone();
|
||||
stack.setAmount(leftover);
|
||||
itemStacks[stacks] = loc.getWorld().dropItem(loc, stack);
|
||||
}
|
||||
}
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " partial itemstack " + getItemStack().toString() + " and dropping overflow " + leftOver.get(0).toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " partial itemstack " + getItemStack().toString() + " and dropping overflow " + leftover.get(0).toString());
|
||||
}
|
||||
}
|
||||
} else if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "paying " + user.getName() + " itemstack " + getItemStack().toString());
|
||||
@ -315,11 +296,11 @@ public class Trade {
|
||||
if (ess.getSettings().isDebug()) {
|
||||
ess.getLogger().log(Level.INFO, "charging user " + user.getName() + " itemstack " + getItemStack().toString());
|
||||
}
|
||||
if (!user.getBase().getInventory().containsAtLeast(getItemStack(), getItemStack().getAmount())) {
|
||||
if (!Inventories.containsAtLeast(user.getBase(), getItemStack(), getItemStack().getAmount())) {
|
||||
future.completeExceptionally(new ChargeException(tl("missingItems", getItemStack().getAmount(), getItemStack().getType().toString().toLowerCase(Locale.ENGLISH).replace("_", " "))));
|
||||
return;
|
||||
}
|
||||
user.getBase().getInventory().removeItem(getItemStack());
|
||||
Inventories.removeItemAmount(user.getBase(), getItemStack(), getItemStack().getAmount());
|
||||
user.getBase().updateInventory();
|
||||
}
|
||||
if (command != null) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.earth2me.essentials;
|
||||
|
||||
import com.earth2me.essentials.commands.IEssentialsCommand;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.economy.EconomyLayer;
|
||||
import com.earth2me.essentials.economy.EconomyLayers;
|
||||
import com.earth2me.essentials.messaging.IMessageRecipient;
|
||||
@ -27,7 +28,6 @@ import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.metadata.FixedMetadataValue;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
@ -90,6 +90,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
|
||||
private String lastHomeConfirmation;
|
||||
private long lastHomeConfirmationTimestamp;
|
||||
private Boolean toggleShout;
|
||||
private boolean freeze = false;
|
||||
private transient final List<String> signCopy = Lists.newArrayList("", "", "", "");
|
||||
private transient long lastVanishTime = System.currentTimeMillis();
|
||||
|
||||
@ -1141,12 +1142,7 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
|
||||
* Returns the {@link ItemStack} in the main hand or off-hand. If the main hand is empty then the offhand item is returned - also nullable.
|
||||
*/
|
||||
public ItemStack getItemInHand() {
|
||||
if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_9_R01)) {
|
||||
return getBase().getInventory().getItemInHand();
|
||||
} else {
|
||||
final PlayerInventory inventory = getBase().getInventory();
|
||||
return inventory.getItemInMainHand() != null ? inventory.getItemInMainHand() : inventory.getItemInOffHand();
|
||||
}
|
||||
return Inventories.getItemInHand(getBase());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1196,6 +1192,16 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
|
||||
return signCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFreeze() {
|
||||
return freeze;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFreeze(boolean freeze) {
|
||||
this.freeze = freeze;
|
||||
}
|
||||
|
||||
public boolean isBaltopExempt() {
|
||||
if (getBase().isOnline()) {
|
||||
final boolean exempt = isAuthorized("essentials.balancetop.exclude");
|
||||
|
@ -3,6 +3,7 @@ package com.earth2me.essentials;
|
||||
import com.earth2me.essentials.commands.NotEnoughArgumentsException;
|
||||
import com.earth2me.essentials.config.ConfigurateUtil;
|
||||
import com.earth2me.essentials.config.EssentialsConfiguration;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@ -101,7 +102,7 @@ public class Worth implements IConf {
|
||||
}
|
||||
|
||||
int max = 0;
|
||||
for (final ItemStack s : user.getBase().getInventory().getContents()) {
|
||||
for (final ItemStack s : Inventories.getInventory(user.getBase(), false)) {
|
||||
if (s == null || !s.isSimilar(is)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.utils.FormatUtil;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.EnumUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.bukkit.Material;
|
||||
@ -52,7 +52,7 @@ public class Commandbook extends EssentialsCommand {
|
||||
if (isAuthor(bmeta, player) || user.isAuthorized("essentials.book.others")) {
|
||||
final ItemStack newItem = new ItemStack(WRITABLE_BOOK, item.getAmount());
|
||||
newItem.setItemMeta(bmeta);
|
||||
InventoryWorkaround.setItemInMainHand(user.getBase(), newItem);
|
||||
Inventories.setItemInMainHand(user.getBase(), newItem);
|
||||
user.sendMessage(tl("editBookContents"));
|
||||
} else {
|
||||
throw new Exception(tl("denyBookEdit"));
|
||||
@ -65,7 +65,7 @@ public class Commandbook extends EssentialsCommand {
|
||||
}
|
||||
final ItemStack newItem = new ItemStack(Material.WRITTEN_BOOK, item.getAmount());
|
||||
newItem.setItemMeta(bmeta);
|
||||
InventoryWorkaround.setItemInMainHand(user.getBase(), newItem);
|
||||
Inventories.setItemInMainHand(user.getBase(), newItem);
|
||||
user.sendMessage(tl("bookLocked"));
|
||||
} else {
|
||||
throw new Exception(tl("holdBook"));
|
||||
|
@ -2,7 +2,7 @@ package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.CommandSource;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.NumberUtil;
|
||||
import com.earth2me.essentials.utils.StringUtil;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
@ -22,8 +22,6 @@ import java.util.Set;
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class Commandclearinventory extends EssentialsCommand {
|
||||
|
||||
private static final int BASE_AMOUNT = 100000;
|
||||
private static final int EXTENDED_CAP = 8;
|
||||
|
||||
public Commandclearinventory() {
|
||||
@ -114,38 +112,30 @@ public class Commandclearinventory extends EssentialsCommand {
|
||||
}
|
||||
}
|
||||
|
||||
if (type == ClearHandlerType.ALL_EXCEPT_ARMOR) {
|
||||
if (type != ClearHandlerType.SPECIFIC_ITEM) {
|
||||
final boolean armor = type == ClearHandlerType.ALL_INCLUDING_ARMOR;
|
||||
if (showExtended) {
|
||||
sender.sendMessage(tl("inventoryClearingAllItems", player.getDisplayName()));
|
||||
sender.sendMessage(tl(armor ? "inventoryClearingAllArmor" : "inventoryClearingAllItems", player.getDisplayName()));
|
||||
}
|
||||
InventoryWorkaround.clearInventoryNoArmor(player.getInventory());
|
||||
InventoryWorkaround.setItemInOffHand(player, null);
|
||||
} else if (type == ClearHandlerType.ALL_INCLUDING_ARMOR) {
|
||||
if (showExtended) {
|
||||
sender.sendMessage(tl("inventoryClearingAllArmor", player.getDisplayName()));
|
||||
}
|
||||
InventoryWorkaround.clearInventoryNoArmor(player.getInventory());
|
||||
InventoryWorkaround.setItemInOffHand(player, null);
|
||||
player.getInventory().setArmorContents(null);
|
||||
Inventories.removeItems(player, item -> true, armor);
|
||||
} else {
|
||||
for (final Item item : items) {
|
||||
final ItemStack stack = new ItemStack(item.getMaterial());
|
||||
if (VersionUtil.PRE_FLATTENING) {
|
||||
//noinspection deprecation
|
||||
stack.setDurability(item.getData());
|
||||
}
|
||||
|
||||
// amount -1 means all items will be cleared
|
||||
if (amount == -1) {
|
||||
stack.setAmount(BASE_AMOUNT);
|
||||
final ItemStack removedStack = player.getInventory().removeItem(stack).get(0);
|
||||
final int removedAmount = BASE_AMOUNT - removedStack.getAmount() + InventoryWorkaround.clearItemInOffHand(player, stack);
|
||||
final int removedAmount = Inventories.removeItemSimilar(player, stack, true);
|
||||
if (removedAmount > 0 || showExtended) {
|
||||
sender.sendMessage(tl("inventoryClearingStack", removedAmount, stack.getType().toString().toLowerCase(Locale.ENGLISH), player.getDisplayName()));
|
||||
}
|
||||
} else {
|
||||
stack.setAmount(amount < 0 ? 1 : amount);
|
||||
if (player.getInventory().containsAtLeast(stack, amount)) {
|
||||
if (Inventories.removeItemAmount(player, stack, amount)) {
|
||||
sender.sendMessage(tl("inventoryClearingStack", amount, stack.getType().toString().toLowerCase(Locale.ENGLISH), player.getDisplayName()));
|
||||
player.getInventory().removeItem(stack);
|
||||
} else {
|
||||
if (showExtended) {
|
||||
sender.sendMessage(tl("inventoryClearFail", player.getDisplayName(), amount, stack.getType().toString().toLowerCase(Locale.ENGLISH)));
|
||||
@ -203,7 +193,7 @@ public class Commandclearinventory extends EssentialsCommand {
|
||||
}
|
||||
|
||||
private String formatCommand(final String commandLabel, final String[] args) {
|
||||
return "/" + commandLabel + " " + StringUtil.joinList(" ", args);
|
||||
return "/" + commandLabel + " " + StringUtil.joinList(" ", (Object[]) args);
|
||||
}
|
||||
|
||||
private enum ClearHandlerType {
|
||||
|
@ -4,6 +4,7 @@ import com.earth2me.essentials.ChargeException;
|
||||
import com.earth2me.essentials.Trade;
|
||||
import com.earth2me.essentials.Trade.OverflowType;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
import net.ess3.api.MaxMoneyException;
|
||||
import org.bukkit.Material;
|
||||
@ -39,7 +40,7 @@ public class Commandcondense extends EssentialsCommand {
|
||||
if (args.length > 0) {
|
||||
is = ess.getItemDb().getMatching(user, args);
|
||||
} else {
|
||||
for (final ItemStack stack : user.getBase().getInventory().getContents()) {
|
||||
for (final ItemStack stack : Inventories.getInventory(user.getBase(), false)) {
|
||||
if (stack == null || stack.getType() == Material.AIR) {
|
||||
continue;
|
||||
}
|
||||
@ -85,7 +86,7 @@ public class Commandcondense extends EssentialsCommand {
|
||||
|
||||
int amount = 0;
|
||||
|
||||
for (final ItemStack contents : user.getBase().getInventory().getContents()) {
|
||||
for (final ItemStack contents : Inventories.getInventory(user.getBase(), false)) {
|
||||
if (contents != null && contents.isSimilar(stack)) {
|
||||
amount += contents.getAmount();
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.CommandSource;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.DateUtil;
|
||||
import com.earth2me.essentials.utils.PasteUtil;
|
||||
import net.ess3.provider.SerializationProvider;
|
||||
@ -38,7 +39,7 @@ public class Commandcreatekit extends EssentialsCommand {
|
||||
// Command handler will auto fail if this fails.
|
||||
final long delay = Long.parseLong(args[1]);
|
||||
final String kitname = args[0];
|
||||
final ItemStack[] items = user.getBase().getInventory().getContents();
|
||||
final ItemStack[] items = Inventories.getInventory(user.getBase(), true);
|
||||
final List<String> list = new ArrayList<>();
|
||||
|
||||
boolean useSerializationProvider = ess.getSettings().isUseBetterKits();
|
||||
|
@ -3,7 +3,7 @@ package com.earth2me.essentials.commands;
|
||||
import com.earth2me.essentials.Enchantments;
|
||||
import com.earth2me.essentials.MetaItemStack;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.StringUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.bukkit.Material;
|
||||
@ -57,7 +57,7 @@ public class Commandenchant extends EssentialsCommand {
|
||||
final MetaItemStack metaStack = new MetaItemStack(stack);
|
||||
final Enchantment enchantment = metaStack.getEnchantment(user, args[0]);
|
||||
metaStack.addEnchantment(user.getSource(), ess.getSettings().allowUnsafeEnchantments() && user.isAuthorized("essentials.enchantments.allowunsafe"), enchantment, level);
|
||||
InventoryWorkaround.setItemInMainHand(user.getBase(), metaStack.getItemStack());
|
||||
Inventories.setItemInMainHand(user.getBase(), metaStack.getItemStack());
|
||||
user.getBase().updateInventory();
|
||||
final String enchantName = enchantment.getName().toLowerCase(Locale.ENGLISH).replace('_', ' ');
|
||||
if (level == 0) {
|
||||
|
@ -3,6 +3,7 @@ package com.earth2me.essentials.commands;
|
||||
import com.earth2me.essentials.CommandSource;
|
||||
import com.earth2me.essentials.EssentialsUpgrade;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.economy.EconomyLayer;
|
||||
import com.earth2me.essentials.economy.EconomyLayers;
|
||||
import com.earth2me.essentials.userstorage.ModernUserMap;
|
||||
@ -24,11 +25,13 @@ import net.ess3.provider.OnlineModeProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
@ -95,6 +98,7 @@ public class Commandessentials extends EssentialsCommand {
|
||||
"EssentialsAntiBuild",
|
||||
"EssentialsChat",
|
||||
"EssentialsDiscord",
|
||||
"EssentialsDiscordLink",
|
||||
"EssentialsGeoIP",
|
||||
"EssentialsProtect",
|
||||
"EssentialsSpawn",
|
||||
@ -156,6 +160,10 @@ public class Commandessentials extends EssentialsCommand {
|
||||
runUserMap(sender, args);
|
||||
break;
|
||||
|
||||
case "itemtest":
|
||||
runItemTest(server, sender, commandLabel, args);
|
||||
break;
|
||||
|
||||
// "#EasterEgg"
|
||||
case "nya":
|
||||
case "nyan":
|
||||
@ -170,6 +178,56 @@ public class Commandessentials extends EssentialsCommand {
|
||||
}
|
||||
}
|
||||
|
||||
public void runItemTest(Server server, CommandSource sender, String commandLabel, String[] args) {
|
||||
if (!sender.isAuthorized("essentials.itemtest", ess) || args.length < 2 || !sender.isPlayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = sender.getPlayer();
|
||||
assert player != null;
|
||||
|
||||
switch (args[1]) {
|
||||
case "slot": {
|
||||
if (args.length < 3) {
|
||||
return;
|
||||
}
|
||||
player.getInventory().setItem(Integer.parseInt(args[2]), new ItemStack(Material.DIRT));
|
||||
break;
|
||||
}
|
||||
case "overfill": {
|
||||
sender.sendMessage(Inventories.addItem(player, 42, false, new ItemStack(Material.DIAMOND_SWORD, 1), new ItemStack(Material.DIRT, 32), new ItemStack(Material.DIRT, 32)).toString());
|
||||
break;
|
||||
}
|
||||
case "overfill2": {
|
||||
if (args.length < 4) {
|
||||
return;
|
||||
}
|
||||
final boolean armor = Boolean.parseBoolean(args[2]);
|
||||
final boolean add = Boolean.parseBoolean(args[3]);
|
||||
final ItemStack[] items = new ItemStack[]{new ItemStack(Material.DIAMOND_SWORD, 1), new ItemStack(Material.DIRT, 32), new ItemStack(Material.DIRT, 32), new ItemStack(Material.DIAMOND_HELMET, 4), new ItemStack(Material.CHAINMAIL_LEGGINGS, 1)};
|
||||
if (Inventories.hasSpace(player, 0, armor, items)) {
|
||||
if (add) {
|
||||
sender.sendMessage(Inventories.addItem(player, 0, armor, items).toString());
|
||||
}
|
||||
sender.sendMessage("SO MUCH SPACE!");
|
||||
} else {
|
||||
sender.sendMessage("No space!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "remove": {
|
||||
if (args.length < 3) {
|
||||
return;
|
||||
}
|
||||
Inventories.removeItemExact(player, new ItemStack(Material.PUMPKIN, 1), Boolean.parseBoolean(args[2]));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Displays the command's usage.
|
||||
private void showUsage(final CommandSource sender) throws Exception {
|
||||
throw new NotEnoughArgumentsException();
|
||||
|
@ -3,7 +3,7 @@ package com.earth2me.essentials.commands;
|
||||
import com.earth2me.essentials.CommandSource;
|
||||
import com.earth2me.essentials.MetaItemStack;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.NumberUtil;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
@ -81,13 +81,8 @@ public class Commandgive extends EssentialsLoopCommand {
|
||||
final ItemStack finalStack = stack;
|
||||
loopOnlinePlayersConsumer(server, sender, false, true, args[0], player -> {
|
||||
sender.sendMessage(tl("giveSpawn", finalStack.getAmount(), itemName, player.getDisplayName()));
|
||||
final Map<Integer, ItemStack> leftovers;
|
||||
|
||||
if (player.isAuthorized("essentials.oversizedstacks")) {
|
||||
leftovers = InventoryWorkaround.addOversizedItems(player.getBase().getInventory(), ess.getSettings().getOversizedStackSize(), finalStack);
|
||||
} else {
|
||||
leftovers = InventoryWorkaround.addItems(player.getBase().getInventory(), finalStack);
|
||||
}
|
||||
final Map<Integer, ItemStack> leftovers = Inventories.addItem(player.getBase(), player.isAuthorized("essentials.oversizedstacks") ? ess.getSettings().getOversizedStackSize() : 0, finalStack);
|
||||
|
||||
for (final ItemStack item : leftovers.values()) {
|
||||
if (isDropItemsIfFull) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.TriState;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
@ -67,7 +67,7 @@ public class Commandhat extends EssentialsCommand {
|
||||
} else {
|
||||
final ItemStack air = new ItemStack(Material.AIR);
|
||||
inv.setHelmet(air);
|
||||
InventoryWorkaround.addItems(user.getBase().getInventory(), head);
|
||||
Inventories.addItem(user.getBase(), head);
|
||||
user.sendMessage(tl("hatRemoved"));
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.MetaItemStack;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
@ -63,11 +63,7 @@ public class Commanditem extends EssentialsCommand {
|
||||
|
||||
final String displayName = stack.getType().toString().toLowerCase(Locale.ENGLISH).replace('_', ' ');
|
||||
user.sendMessage(tl("itemSpawn", stack.getAmount(), displayName));
|
||||
if (user.isAuthorized("essentials.oversizedstacks")) {
|
||||
InventoryWorkaround.addOversizedItems(user.getBase().getInventory(), ess.getSettings().getOversizedStackSize(), stack);
|
||||
} else {
|
||||
InventoryWorkaround.addItems(user.getBase().getInventory(), stack);
|
||||
}
|
||||
Inventories.addItem(user.getBase(), user.isAuthorized("essentials.oversizedstacks") ? ess.getSettings().getOversizedStackSize() : 0, stack);
|
||||
user.getBase().updateInventory();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.FormatUtil;
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.earth2me.essentials.utils.NumberUtil;
|
||||
@ -24,8 +25,8 @@ public class Commanditemlore extends EssentialsCommand {
|
||||
|
||||
@Override
|
||||
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
|
||||
final ItemStack item = user.getBase().getItemInHand();
|
||||
if (MaterialUtil.isAir(item.getType())) {
|
||||
final ItemStack item = Inventories.getItemInHand(user.getBase());
|
||||
if (item == null || MaterialUtil.isAir(item.getType())) {
|
||||
throw new Exception(tl("itemloreInvalidItem"));
|
||||
}
|
||||
|
||||
@ -73,8 +74,8 @@ public class Commanditemlore extends EssentialsCommand {
|
||||
} else if (args.length == 2) {
|
||||
switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||
case "set": {
|
||||
final ItemStack item = user.getBase().getItemInHand();
|
||||
if (!MaterialUtil.isAir(item.getType()) && item.hasItemMeta() && item.getItemMeta().hasLore()) {
|
||||
final ItemStack item = Inventories.getItemInHand(user.getBase());
|
||||
if (item != null && !MaterialUtil.isAir(item.getType()) && item.hasItemMeta() && item.getItemMeta().hasLore()) {
|
||||
final List<String> lineNumbers = new ArrayList<>();
|
||||
for (int i = 1; i <= item.getItemMeta().getLore().size(); i++) {
|
||||
lineNumbers.add(String.valueOf(i));
|
||||
@ -92,8 +93,8 @@ public class Commanditemlore extends EssentialsCommand {
|
||||
} else if (args.length == 3) {
|
||||
if (args[0].equalsIgnoreCase("set") && NumberUtil.isInt(args[1])) {
|
||||
final int i = Integer.parseInt(args[1]);
|
||||
final ItemStack item = user.getBase().getItemInHand();
|
||||
if (!MaterialUtil.isAir(item.getType()) && item.hasItemMeta() && item.getItemMeta().hasLore() && item.getItemMeta().getLore().size() >= i) {
|
||||
final ItemStack item = Inventories.getItemInHand(user.getBase());
|
||||
if (item != null && !MaterialUtil.isAir(item.getType()) && item.hasItemMeta() && item.getItemMeta().hasLore() && item.getItemMeta().getLore().size() >= i) {
|
||||
return Lists.newArrayList(FormatUtil.unformatString(user, "essentials.itemlore", item.getItemMeta().getLore().get(i - 1)));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.FormatUtil;
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.earth2me.essentials.utils.TriState;
|
||||
@ -23,8 +24,8 @@ public class Commanditemname extends EssentialsCommand {
|
||||
|
||||
@Override
|
||||
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
|
||||
final ItemStack item = user.getBase().getItemInHand();
|
||||
if (MaterialUtil.isAir(item.getType())) {
|
||||
final ItemStack item = Inventories.getItemInHand(user.getBase());
|
||||
if (item == null || MaterialUtil.isAir(item.getType())) {
|
||||
user.sendMessage(tl("itemnameInvalidItem"));
|
||||
return;
|
||||
}
|
||||
@ -50,8 +51,8 @@ public class Commanditemname extends EssentialsCommand {
|
||||
@Override
|
||||
protected List<String> getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) {
|
||||
if (args.length == 1) {
|
||||
final ItemStack item = user.getBase().getItemInHand();
|
||||
if (!MaterialUtil.isAir(item.getType()) && item.hasItemMeta() && item.getItemMeta().hasDisplayName()) {
|
||||
final ItemStack item = Inventories.getItemInHand(user.getBase());
|
||||
if (item != null && !MaterialUtil.isAir(item.getType()) && item.hasItemMeta() && item.getItemMeta().hasDisplayName()) {
|
||||
return Lists.newArrayList(FormatUtil.unformatString(user, "essentials.itemname", item.getItemMeta().getDisplayName()));
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.CommandSource;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.StringUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.bukkit.Material;
|
||||
@ -22,7 +23,7 @@ public class Commandpowertool extends EssentialsCommand {
|
||||
@Override
|
||||
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
|
||||
final String command = getFinalArg(args, 0);
|
||||
final ItemStack itemStack = user.getBase().getItemInHand();
|
||||
final ItemStack itemStack = Inventories.getItemInHand(user.getBase());
|
||||
powertool(user.getSource(), user, itemStack, command);
|
||||
}
|
||||
|
||||
@ -111,7 +112,7 @@ public class Commandpowertool extends EssentialsCommand {
|
||||
}
|
||||
|
||||
try {
|
||||
final ItemStack itemStack = user.getBase().getItemInHand();
|
||||
final ItemStack itemStack = Inventories.getItemInHand(user.getBase());
|
||||
final List<String> powertools = user.getPowertool(itemStack);
|
||||
for (final String tool : powertools) {
|
||||
options.add("r:" + tool);
|
||||
|
@ -3,6 +3,7 @@ package com.earth2me.essentials.commands;
|
||||
import com.earth2me.essentials.ChargeException;
|
||||
import com.earth2me.essentials.Trade;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.earth2me.essentials.utils.StringUtil;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
@ -62,7 +63,7 @@ public class Commandrepair extends EssentialsCommand {
|
||||
|
||||
public void repairAll(final User user) throws Exception {
|
||||
final List<String> repaired = new ArrayList<>();
|
||||
repairItems(user.getBase().getInventory().getContents(), user, repaired);
|
||||
repairItems(Inventories.getInventory(user.getBase(), false), user, repaired);
|
||||
|
||||
if (user.isAuthorized("essentials.repair.armor")) {
|
||||
repairItems(user.getBase().getInventory().getArmorContents(), user, repaired);
|
||||
|
@ -2,6 +2,7 @@ package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.Trade;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.NumberUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import net.ess3.api.events.UserBalanceUpdateEvent;
|
||||
@ -108,11 +109,11 @@ public class Commandsell extends EssentialsCommand {
|
||||
//TODO: Prices for Enchantments
|
||||
final ItemStack ris = is.clone();
|
||||
ris.setAmount(amount);
|
||||
if (!user.getBase().getInventory().containsAtLeast(ris, amount)) {
|
||||
if (!Inventories.containsAtLeast(user.getBase(), ris, amount)) {
|
||||
// This should never happen.
|
||||
throw new IllegalStateException("Trying to remove more items than are available.");
|
||||
}
|
||||
user.getBase().getInventory().removeItem(ris);
|
||||
Inventories.removeItemAmount(user.getBase(), ris, ris.getAmount());
|
||||
user.getBase().updateInventory();
|
||||
Trade.log("Command", "Sell", "Item", user.getName(), new Trade(ris, ess), user.getName(), new Trade(result, ess), user.getLocation(), user.getMoney(), ess);
|
||||
user.giveMoney(result, null, UserBalanceUpdateEvent.Cause.COMMAND_SELL);
|
||||
|
@ -22,7 +22,7 @@ public class Commandsetworth extends EssentialsCommand {
|
||||
final ItemStack stack;
|
||||
final String price;
|
||||
if (args.length == 1) {
|
||||
stack = user.getBase().getInventory().getItemInHand();
|
||||
stack = user.getItemInHand();
|
||||
price = args[0];
|
||||
} else {
|
||||
stack = ess.getItemDb().get(args[0]);
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.InventoryWorkaround;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.EnumUtil;
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
@ -9,7 +9,6 @@ import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -60,26 +59,21 @@ public class Commandskull extends EssentialsCommand {
|
||||
}
|
||||
|
||||
private void editSkull(final User user, final ItemStack stack, final SkullMeta skullMeta, final String owner, final boolean spawn) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//Run this stuff async because SkullMeta#setOwner causes a http request.
|
||||
skullMeta.setDisplayName("§fSkull of " + owner);
|
||||
skullMeta.setOwner(owner);
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stack.setItemMeta(skullMeta);
|
||||
if (spawn) {
|
||||
InventoryWorkaround.addItems(user.getBase().getInventory(), stack);
|
||||
user.sendMessage(tl("givenSkull", owner));
|
||||
return;
|
||||
}
|
||||
user.sendMessage(tl("skullChanged", owner));
|
||||
}
|
||||
}.runTask(ess);
|
||||
}
|
||||
}.runTaskAsynchronously(ess);
|
||||
ess.runTaskAsynchronously(() -> {
|
||||
//Run this stuff async because SkullMeta#setOwner causes a http request.
|
||||
skullMeta.setDisplayName("§fSkull of " + owner);
|
||||
//noinspection deprecation
|
||||
skullMeta.setOwner(owner);
|
||||
ess.scheduleSyncDelayedTask(() -> {
|
||||
stack.setItemMeta(skullMeta);
|
||||
if (spawn) {
|
||||
Inventories.addItem(user.getBase(), stack);
|
||||
user.sendMessage(tl("givenSkull", owner));
|
||||
return;
|
||||
}
|
||||
user.sendMessage(tl("skullChanged", owner));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.earth2me.essentials.commands;
|
||||
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@ -75,8 +76,8 @@ public class Commandunlimited extends EssentialsCommand {
|
||||
if (!target.hasUnlimited(stack)) {
|
||||
message = "enableUnlimited";
|
||||
enableUnlimited = true;
|
||||
if (!target.getBase().getInventory().containsAtLeast(stack, stack.getAmount())) {
|
||||
target.getBase().getInventory().addItem(stack);
|
||||
if (!Inventories.containsAtLeast(target.getBase(), stack, stack.getAmount())) {
|
||||
Inventories.addItem(target.getBase(), stack);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ public class Commandwhois extends EssentialsCommand {
|
||||
sender.sendMessage(tl("whoisHunger", user.getBase().getFoodLevel(), user.getBase().getSaturation()));
|
||||
sender.sendMessage(tl("whoisExp", SetExpFix.getTotalExperience(user.getBase()), user.getBase().getLevel()));
|
||||
sender.sendMessage(tl("whoisLocation", user.getLocation().getWorld().getName(), user.getLocation().getBlockX(), user.getLocation().getBlockY(), user.getLocation().getBlockZ()));
|
||||
final long playtimeMs = System.currentTimeMillis() - (user.getBase().getStatistic(PLAY_ONE_TICK) * 50);
|
||||
final long playtimeMs = System.currentTimeMillis() - (user.getBase().getStatistic(PLAY_ONE_TICK) * 50L);
|
||||
sender.sendMessage(tl("whoisPlaytime", DateUtil.formatDateDiff(playtimeMs)));
|
||||
if (!ess.getSettings().isEcoDisabled()) {
|
||||
sender.sendMessage(tl("whoisMoney", NumberUtil.displayCurrency(user.getMoney(), ess)));
|
||||
|
@ -33,7 +33,9 @@ import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -303,6 +305,19 @@ public class EssentialsConfiguration {
|
||||
return ConfigurateUtil.getMap(configurationNode);
|
||||
}
|
||||
|
||||
public Map<String, String> getStringMap(String path) {
|
||||
final CommentedConfigurationNode node = getInternal(path);
|
||||
if (node == null || !node.isMap()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
final Map<String, String> map = new LinkedHashMap<>();
|
||||
for (Map.Entry<Object, CommentedConfigurationNode> entry : node.childrenMap().entrySet()) {
|
||||
map.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue().rawScalar()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public void removeProperty(String path) {
|
||||
final CommentedConfigurationNode node = getInternal(path);
|
||||
if (node != null) {
|
||||
|
@ -0,0 +1,404 @@
|
||||
package com.earth2me.essentials.craftbukkit;
|
||||
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EntityEquipment;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class Inventories {
|
||||
private static final int HELM_SLOT = 39;
|
||||
private static final int CHEST_SLOT = 38;
|
||||
private static final int LEG_SLOT = 37;
|
||||
private static final int BOOT_SLOT = 36;
|
||||
private static final boolean HAS_OFFHAND = VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_9_R01);
|
||||
|
||||
private Inventories() {
|
||||
}
|
||||
|
||||
public static ItemStack getItemInHand(final Player player) {
|
||||
if (!HAS_OFFHAND) {
|
||||
//noinspection deprecation
|
||||
return player.getInventory().getItemInHand();
|
||||
}
|
||||
final PlayerInventory inventory = player.getInventory();
|
||||
final ItemStack main = inventory.getItemInMainHand();
|
||||
return !isEmpty(main) ? main : inventory.getItemInOffHand();
|
||||
}
|
||||
|
||||
public static ItemStack getItemInMainHand(final Player player) {
|
||||
if (!HAS_OFFHAND) {
|
||||
//noinspection deprecation
|
||||
return player.getInventory().getItemInHand();
|
||||
}
|
||||
return player.getInventory().getItemInMainHand();
|
||||
}
|
||||
|
||||
public static void setItemInMainHand(final Player player, final ItemStack stack) {
|
||||
if (HAS_OFFHAND) {
|
||||
player.getInventory().setItemInMainHand(stack);
|
||||
} else {
|
||||
//noinspection deprecation
|
||||
player.setItemInHand(stack);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setItemInMainHand(final EntityEquipment entityEquipment, final ItemStack stack) {
|
||||
if (HAS_OFFHAND) {
|
||||
entityEquipment.setItemInMainHand(stack);
|
||||
} else {
|
||||
//noinspection deprecation
|
||||
entityEquipment.setItemInHand(stack);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setItemInMainHandDropChance(final EntityEquipment entityEquipment, final float chance) {
|
||||
if (HAS_OFFHAND) {
|
||||
entityEquipment.setItemInMainHandDropChance(chance);
|
||||
} else {
|
||||
//noinspection deprecation
|
||||
entityEquipment.setItemInHandDropChance(chance);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean containsAtLeast(final Player player, final ItemStack item, int amount) {
|
||||
for (final ItemStack invItem : player.getInventory().getContents()) {
|
||||
if (isEmpty(invItem)) {
|
||||
continue;
|
||||
}
|
||||
if (invItem.isSimilar(item)) {
|
||||
amount -= invItem.getAmount();
|
||||
if (amount <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean hasSpace(final Player player, final int maxStack, final boolean includeArmor, ItemStack... items) {
|
||||
items = normalizeItems(cloneItems(items));
|
||||
final InventoryData inventoryData = parseInventoryData(player.getInventory(), items, maxStack, includeArmor);
|
||||
|
||||
final List<Integer> emptySlots = inventoryData.getEmptySlots();
|
||||
for (final ItemStack item : items) {
|
||||
if (isEmpty(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int itemMax = Math.max(maxStack, item.getMaxStackSize());
|
||||
final List<Integer> partialSlots = inventoryData.getPartialSlots().get(item);
|
||||
while (true) {
|
||||
if (partialSlots == null || partialSlots.isEmpty()) {
|
||||
if (emptySlots.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
emptySlots.remove(0);
|
||||
if (item.getAmount() > itemMax) {
|
||||
item.setAmount(item.getAmount() - itemMax);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
final int slot = partialSlots.remove(0);
|
||||
ItemStack existing = player.getInventory().getItem(slot);
|
||||
if (isEmpty(existing)) {
|
||||
existing = item.clone();
|
||||
existing.setAmount(0);
|
||||
}
|
||||
|
||||
final int amount = item.getAmount();
|
||||
final int existingAmount = existing.getAmount();
|
||||
|
||||
if (amount + existingAmount <= itemMax) {
|
||||
break;
|
||||
} else {
|
||||
item.setAmount(amount + existingAmount - itemMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Map<Integer, ItemStack> addItem(final Player player, final ItemStack... items) {
|
||||
return addItem(player, 0, false, items);
|
||||
}
|
||||
|
||||
public static Map<Integer, ItemStack> addItem(final Player player, final int maxStack, final ItemStack... items) {
|
||||
return addItem(player, maxStack, false, items);
|
||||
}
|
||||
|
||||
public static Map<Integer, ItemStack> addItem(final Player player, final int maxStack, final boolean allowArmor, ItemStack... items) {
|
||||
items = normalizeItems(cloneItems(items));
|
||||
final Map<Integer, ItemStack> leftover = new HashMap<>();
|
||||
final InventoryData inventoryData = parseInventoryData(player.getInventory(), items, maxStack, allowArmor);
|
||||
|
||||
final List<Integer> emptySlots = inventoryData.getEmptySlots();
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
final ItemStack item = items[i];
|
||||
if (isEmpty(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int itemMax = Math.max(maxStack, item.getMaxStackSize());
|
||||
final List<Integer> partialSlots = inventoryData.getPartialSlots().get(item);
|
||||
while (true) {
|
||||
if (partialSlots == null || partialSlots.isEmpty()) {
|
||||
if (emptySlots.isEmpty()) {
|
||||
leftover.put(i, item);
|
||||
break;
|
||||
}
|
||||
|
||||
final int slot = emptySlots.remove(0);
|
||||
if (item.getAmount() > itemMax) {
|
||||
final ItemStack split = item.clone();
|
||||
split.setAmount(itemMax);
|
||||
player.getInventory().setItem(slot, split);
|
||||
item.setAmount(item.getAmount() - itemMax);
|
||||
} else {
|
||||
player.getInventory().setItem(slot, item);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
final int slot = partialSlots.remove(0);
|
||||
ItemStack existing = player.getInventory().getItem(slot);
|
||||
if (isEmpty(existing)) {
|
||||
existing = item.clone();
|
||||
existing.setAmount(0);
|
||||
}
|
||||
|
||||
final int amount = item.getAmount();
|
||||
final int existingAmount = existing.getAmount();
|
||||
|
||||
if (amount + existingAmount <= itemMax) {
|
||||
existing.setAmount(amount + existingAmount);
|
||||
player.getInventory().setItem(slot, existing);
|
||||
break;
|
||||
} else {
|
||||
existing.setAmount(itemMax);
|
||||
player.getInventory().setItem(slot, existing);
|
||||
item.setAmount(amount + existingAmount - itemMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return leftover;
|
||||
}
|
||||
|
||||
public static ItemStack[] getInventory(final Player player, final boolean includeArmor) {
|
||||
final ItemStack[] items = new ItemStack[41];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
if (!includeArmor && isArmorSlot(i)) {
|
||||
items[i] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
items[i] = player.getInventory().getItem(i);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public static void removeItemExact(final Player player, final ItemStack toRemove, final boolean includeArmor) {
|
||||
removeItems(player, itemStack -> itemStack.equals(toRemove), includeArmor);
|
||||
}
|
||||
|
||||
public static int removeItemSimilar(final Player player, final ItemStack toRemove, final boolean includeArmor) {
|
||||
return removeItems(player, itemStack -> itemStack.isSimilar(toRemove), includeArmor);
|
||||
}
|
||||
|
||||
public static int removeItems(final Player player, final Predicate<ItemStack> removePredicate, final boolean includeArmor) {
|
||||
int removedAmount = 0;
|
||||
final ItemStack[] items = player.getInventory().getContents();
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
if (!includeArmor && isArmorSlot(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final ItemStack item = items[i];
|
||||
if (isEmpty(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (removePredicate.test(item)) {
|
||||
removedAmount += item.getAmount();
|
||||
item.setAmount(0);
|
||||
player.getInventory().setItem(i, item);
|
||||
}
|
||||
}
|
||||
return removedAmount;
|
||||
}
|
||||
|
||||
public static boolean removeItemAmount(final Player player, final ItemStack toRemove, int amount) {
|
||||
final List<Integer> clearSlots = new ArrayList<>();
|
||||
final ItemStack[] items = player.getInventory().getContents();
|
||||
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
final ItemStack item = items[i];
|
||||
if (isEmpty(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.isSimilar(toRemove)) {
|
||||
if (item.getAmount() >= amount) {
|
||||
item.setAmount(item.getAmount() - amount);
|
||||
player.getInventory().setItem(i, item);
|
||||
for (final int slot : clearSlots) {
|
||||
clearSlot(player, slot);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
amount -= item.getAmount();
|
||||
clearSlots.add(i);
|
||||
}
|
||||
|
||||
if (amount == 0) {
|
||||
for (final int slot : clearSlots) {
|
||||
clearSlot(player, slot);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void clearSlot(final Player player, final int slot) {
|
||||
final ItemStack item = player.getInventory().getItem(slot);
|
||||
if (!isEmpty(item)) {
|
||||
item.setAmount(0);
|
||||
player.getInventory().setItem(slot, item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSlot(final Player inventory, final int slot, final ItemStack item) {
|
||||
inventory.getInventory().setItem(slot, item);
|
||||
}
|
||||
|
||||
private static ItemStack[] normalizeItems(final ItemStack[] items) {
|
||||
if (items.length <= 1) {
|
||||
return items;
|
||||
}
|
||||
|
||||
final ItemStack[] normalizedItems = new ItemStack[items.length];
|
||||
int nextNormalizedIndex = 0;
|
||||
inputLoop:
|
||||
for (final ItemStack item : items) {
|
||||
if (isEmpty(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < nextNormalizedIndex; j++) {
|
||||
final ItemStack normalizedItem = normalizedItems[j];
|
||||
if (isEmpty(normalizedItem)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.isSimilar(normalizedItem)) {
|
||||
normalizedItem.setAmount(normalizedItem.getAmount() + item.getAmount());
|
||||
continue inputLoop;
|
||||
}
|
||||
}
|
||||
normalizedItems[nextNormalizedIndex++] = item;
|
||||
}
|
||||
|
||||
return normalizedItems;
|
||||
}
|
||||
|
||||
private static ItemStack[] cloneItems(final ItemStack[] items) {
|
||||
final ItemStack[] clonedItems = new ItemStack[items.length];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
final ItemStack item = items[i];
|
||||
if (isEmpty(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
clonedItems[i] = item.clone();
|
||||
}
|
||||
|
||||
return clonedItems;
|
||||
}
|
||||
|
||||
private static InventoryData parseInventoryData(final Inventory inventory, final ItemStack[] items, final int maxStack, final boolean includeArmor) {
|
||||
final ItemStack[] inventoryContents = inventory.getContents();
|
||||
final List<Integer> emptySlots = new ArrayList<>();
|
||||
final HashMap<ItemStack, List<Integer>> partialSlots = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < inventoryContents.length; i++) {
|
||||
if (!includeArmor && isArmorSlot(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final ItemStack invItem = inventoryContents[i];
|
||||
if (isEmpty(invItem)) {
|
||||
emptySlots.add(i);
|
||||
} else {
|
||||
for (final ItemStack newItem : items) {
|
||||
if (invItem.getAmount() < Math.max(maxStack, invItem.getMaxStackSize()) && invItem.isSimilar(newItem)) {
|
||||
partialSlots.computeIfAbsent(newItem, k -> new ArrayList<>()).add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert empty armor slots to partial slots if we have armor items in the inventory, otherwise remove them from the empty slots.
|
||||
if (includeArmor) {
|
||||
ItemStack helm = null;
|
||||
ItemStack chest = null;
|
||||
ItemStack legs = null;
|
||||
ItemStack boots = null;
|
||||
for (final ItemStack item : items) {
|
||||
if (isEmpty(item)) {
|
||||
continue;
|
||||
}
|
||||
if (helm == null && MaterialUtil.isHelmet(item.getType())) {
|
||||
helm = item;
|
||||
if (emptySlots.contains(HELM_SLOT)) {
|
||||
partialSlots.computeIfAbsent(helm, k -> new ArrayList<>()).add(HELM_SLOT);
|
||||
}
|
||||
} else if (chest == null && MaterialUtil.isChestplate(item.getType())) {
|
||||
chest = item;
|
||||
if (emptySlots.contains(CHEST_SLOT)) {
|
||||
partialSlots.computeIfAbsent(chest, k -> new ArrayList<>()).add(CHEST_SLOT);
|
||||
}
|
||||
} else if (legs == null && MaterialUtil.isLeggings(item.getType())) {
|
||||
legs = item;
|
||||
if (emptySlots.contains(LEG_SLOT)) {
|
||||
partialSlots.computeIfAbsent(legs, k -> new ArrayList<>()).add(LEG_SLOT);
|
||||
}
|
||||
} else if (boots == null && MaterialUtil.isBoots(item.getType())) {
|
||||
boots = item;
|
||||
if (emptySlots.contains(BOOT_SLOT)) {
|
||||
partialSlots.computeIfAbsent(boots, k -> new ArrayList<>()).add(BOOT_SLOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
emptySlots.remove((Object) HELM_SLOT);
|
||||
emptySlots.remove((Object) CHEST_SLOT);
|
||||
emptySlots.remove((Object) LEG_SLOT);
|
||||
emptySlots.remove((Object) BOOT_SLOT);
|
||||
}
|
||||
|
||||
return new InventoryData(emptySlots, partialSlots);
|
||||
}
|
||||
|
||||
private static boolean isEmpty(final ItemStack stack) {
|
||||
return stack == null || MaterialUtil.isAir(stack.getType());
|
||||
}
|
||||
|
||||
private static boolean isArmorSlot(final int slot) {
|
||||
return slot == HELM_SLOT || slot == CHEST_SLOT || slot == LEG_SLOT || slot == BOOT_SLOT;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.earth2me.essentials.craftbukkit;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class InventoryData {
|
||||
private final List<Integer> emptySlots;
|
||||
private final HashMap<ItemStack, List<Integer>> partialSlots;
|
||||
|
||||
public InventoryData(List<Integer> emptySlots, HashMap<ItemStack, List<Integer>> partialSlots) {
|
||||
this.emptySlots = emptySlots;
|
||||
this.partialSlots = partialSlots;
|
||||
}
|
||||
|
||||
public List<Integer> getEmptySlots() {
|
||||
return emptySlots;
|
||||
}
|
||||
|
||||
public HashMap<ItemStack, List<Integer>> getPartialSlots() {
|
||||
return partialSlots;
|
||||
}
|
||||
}
|
@ -1,245 +0,0 @@
|
||||
package com.earth2me.essentials.craftbukkit;
|
||||
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EntityEquipment;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* This class can be removed when https://github.com/Bukkit/CraftBukkit/pull/193 is accepted to CraftBukkit
|
||||
*/
|
||||
public final class InventoryWorkaround {
|
||||
/*
|
||||
Spigot 1.9, for whatever reason, decided to merge the armor and main player inventories without providing a way
|
||||
to access the main inventory. There's lots of ugly code in here to work around that.
|
||||
*/
|
||||
private static final int USABLE_PLAYER_INV_SIZE = 36;
|
||||
private static final boolean IS_OFFHAND = VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_9_R01);
|
||||
|
||||
private InventoryWorkaround() {
|
||||
}
|
||||
|
||||
private static int firstPartial(final Inventory inventory, final ItemStack item, final int maxAmount) {
|
||||
if (item == null) {
|
||||
return -1;
|
||||
}
|
||||
final ItemStack[] stacks = inventory.getContents();
|
||||
for (int i = 0; i < stacks.length; i++) {
|
||||
final ItemStack cItem = stacks[i];
|
||||
if (cItem != null && cItem.getAmount() < maxAmount && cItem.isSimilar(item)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean isCombinedInventory(final Inventory inventory) {
|
||||
return inventory instanceof PlayerInventory && inventory.getContents().length > USABLE_PLAYER_INV_SIZE;
|
||||
}
|
||||
|
||||
// Clears inventory without clearing armor
|
||||
public static void clearInventoryNoArmor(final PlayerInventory inventory) {
|
||||
if (isCombinedInventory(inventory)) {
|
||||
for (int i = 0; i < USABLE_PLAYER_INV_SIZE; i++) {
|
||||
inventory.setItem(i, null);
|
||||
}
|
||||
} else {
|
||||
inventory.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static Inventory makeTruncatedPlayerInventory(final PlayerInventory playerInventory) {
|
||||
final Inventory fakeInventory = Bukkit.getServer().createInventory(null, USABLE_PLAYER_INV_SIZE);
|
||||
fakeInventory.setContents(Arrays.copyOf(playerInventory.getContents(), fakeInventory.getSize()));
|
||||
return fakeInventory;
|
||||
}
|
||||
|
||||
// Returns what it couldn't store
|
||||
// This will will abort if it couldn't store all items
|
||||
public static Map<Integer, ItemStack> addAllItems(final Inventory inventory, final ItemStack... items) {
|
||||
final ItemStack[] contents = inventory.getContents();
|
||||
|
||||
final Inventory fakeInventory;
|
||||
if (isCombinedInventory(inventory)) {
|
||||
fakeInventory = makeTruncatedPlayerInventory((PlayerInventory) inventory);
|
||||
} else {
|
||||
fakeInventory = Bukkit.getServer().createInventory(null, inventory.getType());
|
||||
fakeInventory.setContents(contents);
|
||||
}
|
||||
final Map<Integer, ItemStack> overflow = addItems(fakeInventory, items);
|
||||
if (overflow.isEmpty()) {
|
||||
addItems(inventory, items);
|
||||
return null;
|
||||
}
|
||||
return addItems(fakeInventory, items);
|
||||
}
|
||||
|
||||
public static Map<Integer, ItemStack> addAllOversizedItems(final Inventory inventory, final int oversizedStacks, final ItemStack... items) {
|
||||
final ItemStack[] contents = inventory.getContents();
|
||||
|
||||
final Inventory fakeInventory;
|
||||
if (isCombinedInventory(inventory)) {
|
||||
fakeInventory = makeTruncatedPlayerInventory((PlayerInventory) inventory);
|
||||
} else {
|
||||
fakeInventory = Bukkit.getServer().createInventory(null, inventory.getType());
|
||||
fakeInventory.setContents(contents);
|
||||
}
|
||||
final Map<Integer, ItemStack> overflow = addOversizedItems(fakeInventory, oversizedStacks, items);
|
||||
if (overflow.isEmpty()) {
|
||||
addOversizedItems(inventory, oversizedStacks, items);
|
||||
return null;
|
||||
}
|
||||
return overflow;
|
||||
}
|
||||
|
||||
// Returns what it couldn't store
|
||||
public static Map<Integer, ItemStack> addItems(final Inventory inventory, final ItemStack... items) {
|
||||
return addOversizedItems(inventory, 0, items);
|
||||
}
|
||||
|
||||
// Returns what it couldn't store
|
||||
// Set oversizedStack to below normal stack size to disable oversized stacks
|
||||
public static Map<Integer, ItemStack> addOversizedItems(final Inventory inventory, final int oversizedStacks, final ItemStack... items) {
|
||||
if (isCombinedInventory(inventory)) {
|
||||
final Inventory fakeInventory = makeTruncatedPlayerInventory((PlayerInventory) inventory);
|
||||
final Map<Integer, ItemStack> overflow = addOversizedItems(fakeInventory, oversizedStacks, items);
|
||||
for (int i = 0; i < fakeInventory.getContents().length; i++) {
|
||||
inventory.setItem(i, fakeInventory.getContents()[i]);
|
||||
}
|
||||
return overflow;
|
||||
}
|
||||
|
||||
final Map<Integer, ItemStack> leftover = new HashMap<>();
|
||||
|
||||
/*
|
||||
* TODO: some optimization - Create a 'firstPartial' with a 'fromIndex' - Record the lastPartial per Material -
|
||||
* Cache firstEmpty result
|
||||
*/
|
||||
|
||||
// combine items
|
||||
|
||||
final ItemStack[] combined = new ItemStack[items.length];
|
||||
for (final ItemStack item : items) {
|
||||
if (item == null || item.getAmount() < 1) {
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; j < combined.length; j++) {
|
||||
if (combined[j] == null) {
|
||||
combined[j] = item.clone();
|
||||
break;
|
||||
}
|
||||
if (combined[j].isSimilar(item)) {
|
||||
combined[j].setAmount(combined[j].getAmount() + item.getAmount());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < combined.length; i++) {
|
||||
final ItemStack item = combined[i];
|
||||
if (item == null || item.getType() == Material.AIR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// Do we already have a stack of it?
|
||||
final int maxAmount = Math.max(oversizedStacks, item.getType().getMaxStackSize());
|
||||
final int firstPartial = firstPartial(inventory, item, maxAmount);
|
||||
|
||||
// Drat! no partial stack
|
||||
if (firstPartial == -1) {
|
||||
// Find a free spot!
|
||||
final int firstFree = inventory.firstEmpty();
|
||||
|
||||
if (firstFree == -1) {
|
||||
// No space at all!
|
||||
leftover.put(i, item);
|
||||
break;
|
||||
} else {
|
||||
// More than a single stack!
|
||||
if (item.getAmount() > maxAmount) {
|
||||
final ItemStack stack = item.clone();
|
||||
stack.setAmount(maxAmount);
|
||||
inventory.setItem(firstFree, stack);
|
||||
item.setAmount(item.getAmount() - maxAmount);
|
||||
} else {
|
||||
// Just store it
|
||||
inventory.setItem(firstFree, item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// So, apparently it might only partially fit, well lets do just that
|
||||
final ItemStack partialItem = inventory.getItem(firstPartial);
|
||||
|
||||
final int amount = item.getAmount();
|
||||
final int partialAmount = partialItem.getAmount();
|
||||
|
||||
// Check if it fully fits
|
||||
if (amount + partialAmount <= maxAmount) {
|
||||
partialItem.setAmount(amount + partialAmount);
|
||||
break;
|
||||
}
|
||||
|
||||
// It fits partially
|
||||
partialItem.setAmount(maxAmount);
|
||||
item.setAmount(amount + partialAmount - maxAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
return leftover;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setItemInMainHand(final Player p, final ItemStack item) {
|
||||
if (IS_OFFHAND) {
|
||||
p.getInventory().setItemInMainHand(item);
|
||||
} else {
|
||||
p.setItemInHand(item);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setItemInMainHand(final EntityEquipment invent, final ItemStack item) {
|
||||
if (IS_OFFHAND) {
|
||||
invent.setItemInMainHand(item);
|
||||
} else {
|
||||
invent.setItemInHand(item);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setItemInMainHandDropChance(final EntityEquipment invent, final float chance) {
|
||||
if (IS_OFFHAND) {
|
||||
invent.setItemInMainHandDropChance(chance);
|
||||
} else {
|
||||
invent.setItemInHandDropChance(chance);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setItemInOffHand(final Player p, final ItemStack item) {
|
||||
if (IS_OFFHAND) {
|
||||
p.getInventory().setItemInOffHand(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static int clearItemInOffHand(final Player p, final ItemStack item) {
|
||||
if (IS_OFFHAND) {
|
||||
int removedAmount = 0;
|
||||
if (p.getInventory().getItemInOffHand().getType().equals(item.getType())) {
|
||||
removedAmount = p.getInventory().getItemInOffHand().getAmount();
|
||||
p.getInventory().setItemInOffHand(null);
|
||||
}
|
||||
return removedAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package com.earth2me.essentials.items;
|
||||
|
||||
import com.earth2me.essentials.IConf;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.FormatUtil;
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
@ -156,14 +157,14 @@ public abstract class AbstractItemDb implements IConf, net.ess3.api.IItemDb {
|
||||
} else if (args[0].equalsIgnoreCase("hand")) {
|
||||
is.add(user.getItemInHand().clone());
|
||||
} else if (args[0].equalsIgnoreCase("inventory") || args[0].equalsIgnoreCase("invent") || args[0].equalsIgnoreCase("all")) {
|
||||
for (final ItemStack stack : user.getBase().getInventory().getContents()) {
|
||||
for (final ItemStack stack : Inventories.getInventory(user.getBase(), true)) {
|
||||
if (stack == null || stack.getType() == Material.AIR) {
|
||||
continue;
|
||||
}
|
||||
is.add(stack.clone());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("blocks")) {
|
||||
for (final ItemStack stack : user.getBase().getInventory().getContents()) {
|
||||
for (final ItemStack stack : Inventories.getInventory(user.getBase(), true)) {
|
||||
if (stack == null || stack.getType() == Material.AIR || !stack.getType().isBlock()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package com.earth2me.essentials.perm;
|
||||
import com.earth2me.essentials.Essentials;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.utils.TriState;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
@ -10,9 +11,15 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface IPermissionsHandler {
|
||||
String getGroup(Player base);
|
||||
boolean addToGroup(OfflinePlayer base, String group);
|
||||
|
||||
List<String> getGroups(Player base);
|
||||
boolean removeFromGroup(OfflinePlayer base, String group);
|
||||
|
||||
String getGroup(OfflinePlayer base);
|
||||
|
||||
List<String> getGroups(OfflinePlayer base);
|
||||
|
||||
List<String> getGroups();
|
||||
|
||||
boolean canBuild(Player base, String group);
|
||||
|
||||
|
@ -10,8 +10,10 @@ import com.earth2me.essentials.perm.impl.ModernVaultHandler;
|
||||
import com.earth2me.essentials.perm.impl.SuperpermsHandler;
|
||||
import com.earth2me.essentials.utils.TriState;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -34,7 +36,7 @@ public class PermissionsHandler implements IPermissionsHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup(final Player base) {
|
||||
public String getGroup(final OfflinePlayer base) {
|
||||
final long start = System.nanoTime();
|
||||
String group = handler.getGroup(base);
|
||||
if (group == null) {
|
||||
@ -45,16 +47,42 @@ public class PermissionsHandler implements IPermissionsHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getGroups(final Player base) {
|
||||
public List<String> getGroups(final OfflinePlayer base) {
|
||||
final long start = System.nanoTime();
|
||||
List<String> groups = handler.getGroups(base);
|
||||
final List<String> groups = new ArrayList<>();
|
||||
groups.add(defaultGroup);
|
||||
groups.addAll(handler.getGroups(base));
|
||||
checkPermLag(start, String.format("Getting groups for %s", base.getName()));
|
||||
return Collections.unmodifiableList(groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getGroups() {
|
||||
final long start = System.nanoTime();
|
||||
List<String> groups = handler.getGroups();
|
||||
if (groups == null || groups.isEmpty()) {
|
||||
groups = Collections.singletonList(defaultGroup);
|
||||
}
|
||||
checkPermLag(start, String.format("Getting groups for %s", base.getName()));
|
||||
checkPermLag(start, "Getting all groups");
|
||||
return Collections.unmodifiableList(groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addToGroup(OfflinePlayer base, String group) {
|
||||
final long start = System.nanoTime();
|
||||
final boolean result = handler.addToGroup(base, group);
|
||||
checkPermLag(start, String.format("Adding group to %s", base.getName()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeFromGroup(OfflinePlayer base, String group) {
|
||||
final long start = System.nanoTime();
|
||||
final boolean result = handler.removeFromGroup(base, group);
|
||||
checkPermLag(start, String.format("Removing group from %s", base.getName()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBuild(final Player base, final String group) {
|
||||
return handler.canBuild(base, group);
|
||||
|
@ -3,6 +3,7 @@ package com.earth2me.essentials.perm.impl;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
@ -32,13 +33,34 @@ public abstract class AbstractVaultHandler extends SuperpermsHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup(final Player base) {
|
||||
return perms.getPrimaryGroup(base);
|
||||
public String getGroup(final OfflinePlayer base) {
|
||||
if (base.isOnline()) {
|
||||
return perms.getPrimaryGroup(base.getPlayer());
|
||||
}
|
||||
return perms.getPrimaryGroup(null, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getGroups(final Player base) {
|
||||
return Arrays.asList(perms.getPlayerGroups(base));
|
||||
public List<String> getGroups(final OfflinePlayer base) {
|
||||
if (base.isOnline()) {
|
||||
return Arrays.asList(perms.getPlayerGroups(base.getPlayer()));
|
||||
}
|
||||
return Arrays.asList(perms.getPlayerGroups(null, base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getGroups() {
|
||||
return Arrays.asList(perms.getGroups());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addToGroup(OfflinePlayer base, String group) {
|
||||
return perms.playerAddGroup(null, base, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeFromGroup(OfflinePlayer base, String group) {
|
||||
return perms.playerRemoveGroup(null, base, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -5,6 +5,7 @@ import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.perm.IPermissionsHandler;
|
||||
import com.earth2me.essentials.utils.TriState;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionAttachmentInfo;
|
||||
@ -21,12 +22,27 @@ public class SuperpermsHandler implements IPermissionsHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup(final Player base) {
|
||||
public boolean addToGroup(OfflinePlayer base, String group) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeFromGroup(OfflinePlayer base, String group) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup(final OfflinePlayer base) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getGroups(final Player base) {
|
||||
public List<String> getGroups(final OfflinePlayer base) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getGroups() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import com.earth2me.essentials.ChargeException;
|
||||
import com.earth2me.essentials.Enchantments;
|
||||
import com.earth2me.essentials.Trade;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import net.ess3.api.IEssentials;
|
||||
import net.ess3.provider.MaterialTagProvider;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
@ -66,7 +67,7 @@ public class SignEnchant extends EssentialsSign {
|
||||
|
||||
@Override
|
||||
protected boolean onSignInteract(final ISign sign, final User player, final String username, final IEssentials ess) throws SignException, ChargeException {
|
||||
final ItemStack playerHand = player.getBase().getItemInHand();
|
||||
final ItemStack playerHand = Inventories.getItemInHand(player.getBase());
|
||||
final MaterialTagProvider tagProvider = ess.getProviders().get(MaterialTagProvider.class);
|
||||
final String itemName = sign.getLine(1);
|
||||
final ItemStack search = itemName.equals("*") || itemName.equalsIgnoreCase("any") || (tagProvider != null && tagProvider.tagExists(itemName) && tagProvider.isTagged(itemName, playerHand.getType())) ? null : getItemStack(itemName, 1, ess);
|
||||
|
@ -5,6 +5,7 @@ import com.earth2me.essentials.Trade;
|
||||
import com.earth2me.essentials.Trade.OverflowType;
|
||||
import com.earth2me.essentials.Trade.TradeType;
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.craftbukkit.Inventories;
|
||||
import com.earth2me.essentials.utils.MaterialUtil;
|
||||
import com.earth2me.essentials.utils.NumberUtil;
|
||||
import net.ess3.api.IEssentials;
|
||||
@ -84,10 +85,11 @@ public class SignTrade extends EssentialsSign {
|
||||
|
||||
private Trade rechargeSign(final ISign sign, final IEssentials ess, final User player) throws SignException, ChargeException {
|
||||
final Trade trade = getTrade(sign, 2, AmountType.COST, false, true, ess);
|
||||
if (trade.getItemStack() != null && player.getBase().getItemInHand() != null && trade.getItemStack().getType() == player.getBase().getItemInHand().getType() && MaterialUtil.getDamage(trade.getItemStack()) == MaterialUtil.getDamage(player.getBase().getItemInHand()) && trade.getItemStack().getEnchantments().equals(player.getBase().getItemInHand().getEnchantments())) {
|
||||
ItemStack stack = Inventories.getItemInHand(player.getBase());
|
||||
if (trade.getItemStack() != null && stack != null && !MaterialUtil.isAir(stack.getType()) && trade.getItemStack().getType() == stack.getType() && MaterialUtil.getDamage(trade.getItemStack()) == MaterialUtil.getDamage(stack) && trade.getItemStack().getEnchantments().equals(stack.getEnchantments())) {
|
||||
final int amount = trade.getItemStack().getAmount();
|
||||
if (player.getBase().getInventory().containsAtLeast(trade.getItemStack(), amount)) {
|
||||
final ItemStack stack = player.getBase().getItemInHand().clone();
|
||||
if (Inventories.containsAtLeast(player.getBase(), trade.getItemStack(), amount)) {
|
||||
stack = stack.clone();
|
||||
stack.setAmount(amount);
|
||||
final Trade store = new Trade(stack, ess);
|
||||
addAmount(sign, 2, store, ess);
|
||||
|
@ -37,8 +37,9 @@ public final class VersionUtil {
|
||||
public static final BukkitVersion v1_18_2_R01 = BukkitVersion.fromString("1.18.2-R0.1-SNAPSHOT");
|
||||
public static final BukkitVersion v1_19_R01 = BukkitVersion.fromString("1.19-R0.1-SNAPSHOT");
|
||||
public static final BukkitVersion v1_19_2_R01 = BukkitVersion.fromString("1.19.2-R0.1-SNAPSHOT");
|
||||
public static final BukkitVersion v1_19_3_R01 = BukkitVersion.fromString("1.19.3-R0.1-SNAPSHOT");
|
||||
|
||||
private static final Set<BukkitVersion> supportedVersions = ImmutableSet.of(v1_8_8_R01, v1_9_4_R01, v1_10_2_R01, v1_11_2_R01, v1_12_2_R01, v1_13_2_R01, v1_14_4_R01, v1_15_2_R01, v1_16_5_R01, v1_17_1_R01, v1_18_2_R01, v1_19_2_R01);
|
||||
private static final Set<BukkitVersion> supportedVersions = ImmutableSet.of(v1_8_8_R01, v1_9_4_R01, v1_10_2_R01, v1_11_2_R01, v1_12_2_R01, v1_13_2_R01, v1_14_4_R01, v1_15_2_R01, v1_16_5_R01, v1_17_1_R01, v1_18_2_R01, v1_19_3_R01);
|
||||
|
||||
public static final boolean PRE_FLATTENING = VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_13_0_R01);
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
package net.essentialsx.api.v2.events;
|
||||
|
||||
import net.ess3.api.IUser;
|
||||
import net.essentialsx.api.v2.services.mail.MailMessage;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called when mail is sent to a {@link net.ess3.api.IUser IUser} by another player or the console.
|
||||
*/
|
||||
public class UserMailEvent extends Event implements Cancellable {
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private final IUser recipient;
|
||||
private final MailMessage message;
|
||||
private boolean canceled;
|
||||
|
||||
public UserMailEvent(IUser recipient, MailMessage message) {
|
||||
this.recipient = recipient;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the recipient of this mail.
|
||||
* @return the recipient.
|
||||
*/
|
||||
public IUser getRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying {@link MailMessage} for this mail.
|
||||
* @return the message.
|
||||
*/
|
||||
public MailMessage getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.canceled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return canceled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
@ -240,6 +240,12 @@ discordbroadcastCommandUsage1Description=Sends the given message to the specifie
|
||||
discordbroadcastInvalidChannel=\u00a74Discord channel \u00a7c{0}\u00a74 does not exist.
|
||||
discordbroadcastPermission=\u00a74You do not have permission to send messages to the \u00a7c{0}\u00a74 channel.
|
||||
discordbroadcastSent=\u00a76Message sent to \u00a7c{0}\u00a76!
|
||||
discordCommandAccountArgumentUser=The Discord account to look up
|
||||
discordCommandAccountDescription=Looks up the linked Minecraft account for either yourself or another Discord user
|
||||
discordCommandAccountResponseLinked=Your account is linked to the Minecraft account: **{0}**
|
||||
discordCommandAccountResponseLinkedOther={0}'s account is linked to the Minecraft account: **{1}**
|
||||
discordCommandAccountResponseNotLinked=You do not have a linked Minecraft account.
|
||||
discordCommandAccountResponseNotLinkedOther={0} does not have a linked Minecraft account.
|
||||
discordCommandDescription=Sends the discord invite link to the player.
|
||||
discordCommandLink=\u00a76Join our Discord server at \u00a7c{0}\u00a76!
|
||||
discordCommandUsage=/<command>
|
||||
@ -248,6 +254,14 @@ discordCommandUsage1Description=Sends the discord invite link to the player
|
||||
discordCommandExecuteDescription=Executes a console command on the Minecraft server.
|
||||
discordCommandExecuteArgumentCommand=The command to be executed
|
||||
discordCommandExecuteReply=Executing command: "/{0}"
|
||||
discordCommandUnlinkDescription=Unlinks the Minecraft account currently linked to your Discord account
|
||||
discordCommandUnlinkInvalidCode=You do not currently have a Minecraft account linked to Discord!
|
||||
discordCommandUnlinkUnlinked=Your Discord account has been unlinked from all associated Minecraft accounts.
|
||||
discordCommandLinkArgumentCode=The code provided in-game to link your Minecraft account
|
||||
discordCommandLinkDescription=Links your Discord account with your Minecraft account using a code from the in-game /link command
|
||||
discordCommandLinkHasAccount=You already have an account linked! To unlink your current account, type /unlink.
|
||||
discordCommandLinkInvalidCode=Invalid linking code! Make sure you've run /link in-game and copied the code correctly.
|
||||
discordCommandLinkLinked=Successfully linked your account!
|
||||
discordCommandListDescription=Gets a list of online players.
|
||||
discordCommandListArgumentGroup=A specific group to limit your search by
|
||||
discordCommandMessageDescription=Messages a player on the Minecraft server.
|
||||
@ -265,8 +279,20 @@ discordErrorNoPrimary=You did not define a primary channel or your defined prima
|
||||
discordErrorNoPrimaryPerms=Your bot cannot speak in your primary channel, #{0}. Please make sure your bot has read and write permissions in all channels you wish to use.
|
||||
discordErrorNoToken=No token provided! Please follow the tutorial in the config in order to setup the plugin.
|
||||
discordErrorWebhook=An error occurred while sending messages to your console channel\! This was likely caused by accidentally deleting your console webhook. This can usually by fixed by ensuring your bot has the "Manage Webhooks" permission and running "/ess reload".
|
||||
discordLinkInvalidGroup=Invalid group {0} was provided for role {1}. The following groups are available: {2}
|
||||
discordLinkInvalidRole=An invalid role ID, {0}, was provided for group: {1}. You can see the ID of roles with the /roleinfo command in Discord.
|
||||
discordLinkInvalidRoleInteract=The role, {0} ({1}), cannot be used for group->role synchronization because it above your bot''s upper most role. Either move your bot''s role above "{0}" or move "{0}" below your bot''s role.
|
||||
discordLinkInvalidRoleManaged=The role, {0} ({1}), cannot be used for group->role synchronization because it is managed by another bot or integration.
|
||||
discordLinkLinked=\u00a76To link your Minecraft account to Discord, type \u00a7c{0} \u00a76in the Discord server.
|
||||
discordLinkLinkedAlready=\u00a76You have already linked your Discord account! If you wish to unlink your discord account use \u00a7c/unlink\u00a76.
|
||||
discordLinkLoginKick=\u00a76You must link your Discord account before you can join this server.\n\u00a76To link your Minecraft account to Discord, type\:\n\u00a7c{0}\n\u00a76in this server''s Discord server\:\n\u00a7c{1}
|
||||
discordLinkLoginPrompt=\u00a76You must link your Discord account before you can move, chat on or interact with this server. To link your Minecraft account to Discord, type \u00a7c{0} \u00a76in this server''s Discord server\: \u00a7c{1}
|
||||
discordLinkNoAccount=\u00a76You do not currently have a Discord account linked to your Minecraft account.
|
||||
discordLinkPending=\u00a76You already have a link code. To complete linking your Minecraft account to Discord, type \u00a7c{0} \u00a76in the Discord server.
|
||||
discordLinkUnlinked=\u00a76Unlinked your Minecraft account from all associated discord accounts.
|
||||
discordLoggingIn=Attempting to login to Discord...
|
||||
discordLoggingInDone=Successfully logged in as {0}
|
||||
discordMailLine=**New mail from {0}:** {1}
|
||||
discordNoSendPermission=Cannot send message in channel: #{0} Please ensure the bot has "Send Messages" permission in that channel\!
|
||||
discordReloadInvalid=Tried to reload EssentialsX Discord config while the plugin is in an invalid state! If you've modified your config, restart your server.
|
||||
disposal=Disposal
|
||||
@ -661,6 +687,10 @@ lightningCommandUsage2=/<command> <player> <power>
|
||||
lightningCommandUsage2Description=Strikes lighting at the target player with the given power
|
||||
lightningSmited=\u00a76Thou hast been smitten\!
|
||||
lightningUse=\u00a76Smiting\u00a7c {0}
|
||||
linkCommandDescription=Generates a code to link your Minecraft account to Discord.
|
||||
linkCommandUsage=/<command>
|
||||
linkCommandUsage1=/<command>
|
||||
linkCommandUsage1Description=Generates a code for the /link command on Discord
|
||||
listAfkTag=\u00a77[AFK]\u00a7r
|
||||
listAmount=\u00a76There are \u00a7c{0}\u00a76 out of maximum \u00a7c{1}\u00a76 players online.
|
||||
listAmountHidden=\u00a76There are \u00a7c{0}\u00a76/\u00a7c{1}\u00a76 out of maximum \u00a7c{2}\u00a76 players online.
|
||||
@ -1028,7 +1058,7 @@ repairCommandUsage2Description=Repairs all items in your inventory
|
||||
repairEnchanted=\u00a74You are not allowed to repair enchanted items.
|
||||
repairInvalidType=\u00a74This item cannot be repaired.
|
||||
repairNone=\u00a74There were no items that needed repairing.
|
||||
replyFromDiscord=**Reply from {0}\:** `{1}`
|
||||
replyFromDiscord=**Reply from {0}\:** {1}
|
||||
replyLastRecipientDisabled=\u00a76Replying to last message recipient \u00a7cdisabled\u00a76.
|
||||
replyLastRecipientDisabledFor=\u00a76Replying to last message recipient \u00a7cdisabled \u00a76for \u00a7c{0}\u00a76.
|
||||
replyLastRecipientEnabled=\u00a76Replying to last message recipient \u00a7cenabled\u00a76.
|
||||
@ -1402,6 +1432,10 @@ unlimitedCommandUsage3=/<command> clear [player]
|
||||
unlimitedCommandUsage3Description=Clears all unlimited items for yourself or another player if specified
|
||||
unlimitedItemPermission=\u00a74No permission for unlimited item \u00a7c{0}\u00a74.
|
||||
unlimitedItems=\u00a76Unlimited items\:\u00a7r
|
||||
unlinkCommandDescription=Unlinks your Minecraft account from the currently linked Discord account.
|
||||
unlinkCommandUsage=/<command>
|
||||
unlinkCommandUsage1=/<command>
|
||||
unlinkCommandUsage1Description=Unlinks your Minecraft account from the currently linked Discord account.
|
||||
unmutedPlayer=\u00a76Player\u00a7c {0} \u00a76unmuted.
|
||||
unsafeTeleportDestination=\u00a74The teleport destination is unsafe and teleport-safety is disabled.
|
||||
unsupportedBrand=\u00a74The server platform you are currently running does not provide the capabilities for this feature.
|
||||
|
@ -4,6 +4,9 @@ import net.essentialsx.api.v2.events.discord.DiscordChatMessageEvent;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A class which provides numerous methods to interact with EssentialsX Discord.
|
||||
*/
|
||||
@ -47,6 +50,34 @@ public interface DiscordService {
|
||||
*/
|
||||
InteractionController getInteractionController();
|
||||
|
||||
/**
|
||||
* Gets an {@link InteractionMember} by their Discord ID.
|
||||
* @param id The ID of the member to look up.
|
||||
* @return A future which will complete with the member or null if none is reachable.
|
||||
*/
|
||||
CompletableFuture<InteractionMember> getMemberById(final String id);
|
||||
|
||||
/**
|
||||
* Gets an {@link InteractionRole} by its Discord ID.
|
||||
* @param id The ID of the role to look up.
|
||||
* @return the role or null if none by that ID exists.
|
||||
*/
|
||||
InteractionRole getRole(final String id);
|
||||
|
||||
/**
|
||||
* Adds or removes {@link InteractionRole roles} to the given {@link InteractionMember}.
|
||||
* @param member The member to add/remove roles to/from.
|
||||
* @param addRoles The roles to add to the {@link InteractionMember member}, or null to add none.
|
||||
* @param removeRoles The roles to remove from the {@link InteractionMember member}, or null to remove none.
|
||||
* @return A future which will complete when all requests operations have been completed.
|
||||
*/
|
||||
CompletableFuture<Void> modifyMemberRoles(final InteractionMember member, final Collection<InteractionRole> addRoles, final Collection<InteractionRole> removeRoles);
|
||||
|
||||
/**
|
||||
* Gets the Discord invite URL given in the EssentialsX Discord configuration.
|
||||
*/
|
||||
String getInviteUrl();
|
||||
|
||||
/**
|
||||
* Gets unstable API that is subject to change at any time.
|
||||
* @return {@link Unsafe the unsafe} instance.
|
||||
|
@ -4,22 +4,10 @@ package net.essentialsx.api.v2.services.discord;
|
||||
* Represents an argument type to be shown on the Discord client.
|
||||
*/
|
||||
public enum InteractionCommandArgumentType {
|
||||
STRING(3),
|
||||
INTEGER(4),
|
||||
BOOLEAN(5),
|
||||
USER(6),
|
||||
CHANNEL(7);
|
||||
|
||||
private final int id;
|
||||
InteractionCommandArgumentType(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the internal Discord ID for this argument type.
|
||||
* @return the internal Discord ID.
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
STRING,
|
||||
INTEGER,
|
||||
BOOLEAN,
|
||||
USER,
|
||||
CHANNEL,
|
||||
ROLE,
|
||||
}
|
||||
|
@ -51,6 +51,13 @@ public interface InteractionEvent {
|
||||
*/
|
||||
InteractionChannel getChannelArgument(String key);
|
||||
|
||||
/**
|
||||
* Helper method to get the role representation of the argument by the given key or null if none by that key is present.
|
||||
* @param key The key of the argument to lookup.
|
||||
* @return the role value or null
|
||||
*/
|
||||
InteractionRole getRoleArgument(String key);
|
||||
|
||||
/**
|
||||
* Gets the channel ID where this interaction occurred.
|
||||
* @return the channel ID.
|
||||
|
@ -27,6 +27,12 @@ public interface InteractionMember {
|
||||
return getName() + "#" + getDiscriminator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the discord mention of this member.
|
||||
* @return this member's mention.
|
||||
*/
|
||||
String getAsMention();
|
||||
|
||||
/**
|
||||
* Gets the nickname of this member or their username if they don't have one.
|
||||
* @return this member's nickname or username if none is present.
|
||||
@ -58,6 +64,20 @@ public interface InteractionMember {
|
||||
*/
|
||||
boolean hasRoles(List<String> roleDefinitions);
|
||||
|
||||
/**
|
||||
* Returns true if the user has the specified {@link InteractionRole role}.
|
||||
* @param role The role to check for.
|
||||
* @return true if the member has the specified role.
|
||||
*/
|
||||
boolean hasRole(InteractionRole role);
|
||||
|
||||
/**
|
||||
* Returns true if the user has a role by the specified ID.
|
||||
* @param roleId The role id to check for.
|
||||
* @return true if the member has a role by the specified ID.
|
||||
*/
|
||||
boolean hasRole(String roleId);
|
||||
|
||||
/**
|
||||
* Sends a private message to this member with the given content.
|
||||
* @param content The message to send.
|
||||
|
@ -0,0 +1,54 @@
|
||||
package net.essentialsx.api.v2.services.discord;
|
||||
|
||||
/**
|
||||
* Represents a role of an interaction member.
|
||||
*/
|
||||
public interface InteractionRole {
|
||||
/**
|
||||
* Gets the name of this role.
|
||||
* @return this role's name.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Gets the mention of this role.
|
||||
* @return this role's mention.
|
||||
*/
|
||||
String getAsMention();
|
||||
|
||||
/**
|
||||
* Whether this role is managed by an external integration.
|
||||
* @return true if the role is managed.
|
||||
*/
|
||||
boolean isManaged();
|
||||
|
||||
/**
|
||||
* Whether this role is the default role given to all users (@everyone).
|
||||
* @return true if this is the default role.
|
||||
*/
|
||||
boolean isPublicRole();
|
||||
|
||||
/**
|
||||
* Gets the raw RGB color value of this role.
|
||||
* @return this role's color value.
|
||||
*/
|
||||
int getColorRaw();
|
||||
|
||||
/**
|
||||
* Whether this role's color is the default one (has no color).
|
||||
* @return true if the role has no color.
|
||||
*/
|
||||
boolean isDefaultColor();
|
||||
|
||||
/**
|
||||
* Whether this role can be given to other members by the current logged in bot.
|
||||
* @return true if this role can be interacted with by the current bot user.
|
||||
*/
|
||||
boolean canInteract();
|
||||
|
||||
/**
|
||||
* Gets the ID of this role.
|
||||
* @return this role's ID.
|
||||
*/
|
||||
String getId();
|
||||
}
|
@ -54,6 +54,7 @@ public final class MessageType {
|
||||
*/
|
||||
public static final class DefaultTypes {
|
||||
public final static MessageType JOIN = new MessageType("join", true);
|
||||
public final static MessageType FIRST_JOIN = new MessageType("first-join", true);
|
||||
public final static MessageType LEAVE = new MessageType("leave", true);
|
||||
public final static MessageType CHAT = new MessageType("chat", true);
|
||||
public final static MessageType DEATH = new MessageType("death", true);
|
||||
@ -64,7 +65,7 @@ public final class MessageType {
|
||||
public final static MessageType SERVER_STOP = new MessageType("server-stop", false);
|
||||
public final static MessageType KICK = new MessageType("kick", false);
|
||||
public final static MessageType MUTE = new MessageType("mute", false);
|
||||
private final static MessageType[] VALUES = new MessageType[]{JOIN, LEAVE, CHAT, DEATH, AFK, ADVANCEMENT, ACTION, SERVER_START, SERVER_STOP, KICK, MUTE};
|
||||
private final static MessageType[] VALUES = new MessageType[]{JOIN, FIRST_JOIN, LEAVE, CHAT, DEATH, AFK, ADVANCEMENT, ACTION, SERVER_START, SERVER_STOP, KICK, MUTE};
|
||||
|
||||
/**
|
||||
* Gets an array of all the default {@link MessageType MessageTypes}.
|
||||
|
@ -255,6 +255,18 @@ public class DiscordSettings implements IConf {
|
||||
"username", "displayname", "joinmessage", "online", "unique");
|
||||
}
|
||||
|
||||
public MessageFormat getFirstJoinFormat(Player player) {
|
||||
final String format = getFormatString("first-join");
|
||||
final String filled;
|
||||
if (plugin.isPAPI() && format != null) {
|
||||
filled = me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, format);
|
||||
} else {
|
||||
filled = format;
|
||||
}
|
||||
return generateMessageFormat(filled, ":arrow_right: :first_place: {displayname} has joined the server for the first time!", false,
|
||||
"username", "displayname", "joinmessage", "online", "unique");
|
||||
}
|
||||
|
||||
public MessageFormat getQuitFormat(Player player) {
|
||||
final String format = getFormatString("quit");
|
||||
final String filled;
|
||||
|
@ -13,6 +13,7 @@ import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.entities.Emote;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.Webhook;
|
||||
import net.dv8tion.jda.api.events.ShutdownEvent;
|
||||
@ -26,9 +27,13 @@ import net.essentialsx.api.v2.events.discord.DiscordMessageEvent;
|
||||
import net.essentialsx.api.v2.services.discord.DiscordService;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionController;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionException;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionMember;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionRole;
|
||||
import net.essentialsx.api.v2.services.discord.MessageType;
|
||||
import net.essentialsx.api.v2.services.discord.Unsafe;
|
||||
import net.essentialsx.discord.interactions.InteractionControllerImpl;
|
||||
import net.essentialsx.discord.interactions.InteractionMemberImpl;
|
||||
import net.essentialsx.discord.interactions.InteractionRoleImpl;
|
||||
import net.essentialsx.discord.interactions.commands.ExecuteCommand;
|
||||
import net.essentialsx.discord.interactions.commands.ListCommand;
|
||||
import net.essentialsx.discord.interactions.commands.MessageCommand;
|
||||
@ -46,7 +51,10 @@ import org.bukkit.plugin.ServicePriority;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.security.auth.login.LoginException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -333,7 +341,7 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
|
||||
if (current != null) {
|
||||
current.close();
|
||||
}
|
||||
channelIdToWebhook.remove(channel.getId());
|
||||
channelIdToWebhook.remove(channel.getId()).close();
|
||||
continue;
|
||||
}
|
||||
typeToChannelId.put(type, channel.getId());
|
||||
@ -392,7 +400,7 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
|
||||
|
||||
shutdownConsoleRelay(false);
|
||||
consoleWebhook = DiscordUtil.getWebhookClient(webhookId, webhookToken, jda.getHttpClient());
|
||||
if (injector == null) {
|
||||
if (injector == null || injector.isRemoved()) {
|
||||
injector = new ConsoleInjector(this);
|
||||
injector.start();
|
||||
}
|
||||
@ -430,6 +438,10 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
|
||||
|
||||
shutdownConsoleRelay(true);
|
||||
|
||||
for (WebhookClient webhook : channelIdToWebhook.values()) {
|
||||
webhook.close();
|
||||
}
|
||||
|
||||
// Unregister leftover jda listeners
|
||||
for (Object obj : jda.getRegisteredListeners()) {
|
||||
if (!(obj instanceof EventListener)) { // Yeah bro I wish I knew too :/
|
||||
@ -462,6 +474,54 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<InteractionMember> getMemberById(final String id) {
|
||||
final CompletableFuture<InteractionMember> future = new CompletableFuture<>();
|
||||
getGuild().retrieveMemberById(id).queue(member -> {
|
||||
if (member != null) {
|
||||
future.complete(new InteractionMemberImpl(member));
|
||||
return;
|
||||
}
|
||||
future.complete(null);
|
||||
}, fail -> future.complete(null));
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionRole getRole(String id) {
|
||||
final Role role = getGuild().getRoleById(id);
|
||||
return role == null ? null : new InteractionRoleImpl(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> modifyMemberRoles(InteractionMember member, Collection<InteractionRole> addRoles, Collection<InteractionRole> removeRoles) {
|
||||
if ((addRoles == null || addRoles.isEmpty()) && (removeRoles == null || removeRoles.isEmpty())) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
final List<Role> add = new ArrayList<>();
|
||||
final List<Role> remove = new ArrayList<>();
|
||||
if (addRoles != null) {
|
||||
for (final InteractionRole role : addRoles) {
|
||||
add.add(((InteractionRoleImpl) role).getJdaObject());
|
||||
}
|
||||
}
|
||||
if (removeRoles != null) {
|
||||
for (final InteractionRole role : removeRoles) {
|
||||
remove.add(((InteractionRoleImpl) role).getJdaObject());
|
||||
}
|
||||
}
|
||||
|
||||
final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
guild.modifyMemberRoles(((InteractionMemberImpl) member).getJdaObject(), add, remove).queue(future::complete);
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInviteUrl() {
|
||||
return getSettings().getDiscordUrl();
|
||||
}
|
||||
|
||||
public JDA getJda() {
|
||||
return jda;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionChannel;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionEvent;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionMember;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionRole;
|
||||
import net.essentialsx.discord.EssentialsDiscord;
|
||||
import net.essentialsx.discord.util.DiscordUtil;
|
||||
|
||||
@ -79,6 +80,12 @@ public class InteractionEventImpl implements InteractionEvent {
|
||||
return mapping == null ? null : new InteractionChannelImpl(mapping.getAsGuildChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionRole getRoleArgument(String key) {
|
||||
final OptionMapping mapping = event.getOption(key);
|
||||
return mapping == null ? null : new InteractionRoleImpl(mapping.getAsRole());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelId() {
|
||||
return event.getChannel().getId();
|
||||
|
@ -3,7 +3,9 @@ package net.essentialsx.discord.interactions;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.PrivateChannel;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionMember;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionRole;
|
||||
import net.essentialsx.discord.util.DiscordUtil;
|
||||
|
||||
import java.util.List;
|
||||
@ -26,6 +28,11 @@ public class InteractionMemberImpl implements InteractionMember {
|
||||
return member.getUser().getDiscriminator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAsMention() {
|
||||
return member.getAsMention();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEffectiveName() {
|
||||
return member.getEffectiveName();
|
||||
@ -51,6 +58,21 @@ public class InteractionMemberImpl implements InteractionMember {
|
||||
return DiscordUtil.hasRoles(member, roleDefinitions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(InteractionRole role) {
|
||||
return hasRole(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(String roleId) {
|
||||
for (final Role role : member.getRoles()) {
|
||||
if (role.getId().equals(roleId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Member getJdaObject() {
|
||||
return member;
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
package net.essentialsx.discord.interactions;
|
||||
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionRole;
|
||||
|
||||
public class InteractionRoleImpl implements InteractionRole {
|
||||
private final Role role;
|
||||
|
||||
public InteractionRoleImpl(Role role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return role.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAsMention() {
|
||||
return role.getAsMention();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isManaged() {
|
||||
return role.isManaged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublicRole() {
|
||||
return role.isPublicRole();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColorRaw() {
|
||||
return role.getColorRaw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefaultColor() {
|
||||
return role.getColorRaw() == Role.DEFAULT_COLOR_RAW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInteract() {
|
||||
return role.getGuild().getSelfMember().canInteract(role);
|
||||
}
|
||||
|
||||
public Role getJdaObject() {
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return role.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InteractionRoleImpl{name=" + getName() + ",id=" + getId() + "}";
|
||||
}
|
||||
}
|
@ -99,14 +99,19 @@ public class BukkitListener implements Listener {
|
||||
public void onJoin(AsyncUserDataLoadEvent event) {
|
||||
// Delay join to let nickname load
|
||||
if (!isSilentJoinQuit(event.getUser(), "join") && !isVanishHide(event.getUser())) {
|
||||
sendJoinQuitMessage(event.getUser().getBase(), event.getJoinMessage(), true);
|
||||
// Check if this is the first time the player has joined
|
||||
if (!event.getUser().getBase().hasPlayedBefore()) {
|
||||
sendJoinQuitMessage(event.getUser().getBase(), event.getJoinMessage(), MessageType.DefaultTypes.FIRST_JOIN);
|
||||
} else {
|
||||
sendJoinQuitMessage(event.getUser().getBase(), event.getJoinMessage(), MessageType.DefaultTypes.JOIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
if (!isSilentJoinQuit(event.getPlayer(), "quit") && !isVanishHide(event.getPlayer())) {
|
||||
sendJoinQuitMessage(event.getPlayer(), event.getQuitMessage(), false);
|
||||
sendJoinQuitMessage(event.getPlayer(), event.getQuitMessage(), MessageType.DefaultTypes.LEAVE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,20 +129,35 @@ public class BukkitListener implements Listener {
|
||||
return;
|
||||
}
|
||||
if (event.getValue()) {
|
||||
sendJoinQuitMessage(event.getAffected().getBase(), ChatColor.YELLOW + event.getAffected().getName() + " left the game", false);
|
||||
sendJoinQuitMessage(event.getAffected().getBase(), ChatColor.YELLOW + event.getAffected().getName() + " left the game", MessageType.DefaultTypes.LEAVE);
|
||||
return;
|
||||
}
|
||||
sendJoinQuitMessage(event.getAffected().getBase(), ChatColor.YELLOW + event.getAffected().getName() + " joined the game", true);
|
||||
sendJoinQuitMessage(event.getAffected().getBase(), ChatColor.YELLOW + event.getAffected().getName() + " joined the game", MessageType.DefaultTypes.JOIN);
|
||||
}
|
||||
|
||||
public void sendJoinQuitMessage(final Player player, final String message, boolean join) {
|
||||
sendDiscordMessage(join ? MessageType.DefaultTypes.JOIN : MessageType.DefaultTypes.LEAVE,
|
||||
MessageUtil.formatMessage(join ? jda.getSettings().getJoinFormat(player) : jda.getSettings().getQuitFormat(player),
|
||||
public void sendJoinQuitMessage(final Player player, final String message, MessageType type) {
|
||||
int userCount = jda.getPlugin().getEss().getUsers().getUserCount();
|
||||
final MessageFormat format;
|
||||
switch (type.getKey()) {
|
||||
case "join":
|
||||
format = jda.getSettings().getJoinFormat(player);
|
||||
break;
|
||||
case "first-join":
|
||||
format = jda.getSettings().getFirstJoinFormat(player);
|
||||
break;
|
||||
default: // So that it will always be initialised. Other options shouldn't be possible.
|
||||
format = jda.getSettings().getQuitFormat(player);
|
||||
userCount = userCount - 1;
|
||||
break;
|
||||
|
||||
}
|
||||
sendDiscordMessage(type,
|
||||
MessageUtil.formatMessage(format,
|
||||
MessageUtil.sanitizeDiscordMarkdown(player.getName()),
|
||||
MessageUtil.sanitizeDiscordMarkdown(player.getDisplayName()),
|
||||
MessageUtil.sanitizeDiscordMarkdown(message),
|
||||
jda.getPlugin().getEss().getOnlinePlayers().size() - (join ? 0 : 1),
|
||||
jda.getPlugin().getEss().getUsers().getUserCount()),
|
||||
jda.getPlugin().getEss().getOnlinePlayers().size(),
|
||||
userCount),
|
||||
player);
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ public class ConsoleInjector extends AbstractAppender {
|
||||
private final JDADiscordService jda;
|
||||
private final BlockingQueue<String> messageQueue = new LinkedBlockingQueue<>();
|
||||
private final int taskId;
|
||||
private boolean removed = false;
|
||||
|
||||
public ConsoleInjector(JDADiscordService jda) {
|
||||
super("EssentialsX-ConsoleInjector", null, null, false);
|
||||
@ -93,5 +94,10 @@ public class ConsoleInjector extends AbstractAppender {
|
||||
((Logger) LogManager.getRootLogger()).removeAppender(this);
|
||||
Bukkit.getScheduler().cancelTask(taskId);
|
||||
messageQueue.clear();
|
||||
removed = true;
|
||||
}
|
||||
|
||||
public boolean isRemoved() {
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,8 @@ console:
|
||||
message-types:
|
||||
# Join messages sent when a player joins the Minecraft server.
|
||||
join: primary
|
||||
# Join messages sent when a player joins the Minecraft server for the first time. This type is sent instead of the join type.
|
||||
first-join: primary
|
||||
# Leave messages sent when a player leaves the Minecraft server.
|
||||
leave: primary
|
||||
# Chat messages sent when a player chats on the Minecraft server.
|
||||
@ -307,6 +309,15 @@ messages:
|
||||
# - {unique}: The amount of unique players to ever join the server
|
||||
# ... PlaceholderAPI placeholders are also supported here too!
|
||||
join: ":arrow_right: {displayname} has joined!"
|
||||
# This is the message sent to Discord when a player joins the minecraft server for the first time.
|
||||
# The following placeholders can be used here:
|
||||
# - {username}: The name of the user joining
|
||||
# - {displayname}: The display name of the user joining
|
||||
# - {joinmessage}: The full default join message used in game
|
||||
# - {online}: The amount of players online
|
||||
# - {unique}: The amount of unique players to ever join the server
|
||||
# ... PlaceholderAPI placeholders are also supported here too!
|
||||
first-join: ":arrow_right: :first_place: {displayname} has joined the server for the first time!"
|
||||
# This is the message sent to Discord when a player leaves the minecraft server.
|
||||
# The following placeholders can be used here:
|
||||
# - {username}: The name of the user leaving
|
||||
|
102
EssentialsDiscordLink/README.md
Normal file
102
EssentialsDiscordLink/README.md
Normal file
@ -0,0 +1,102 @@
|
||||
# EssentialsX Discord Link
|
||||
|
||||
EssentialsX Discord Link is an addon for EssentialsX Discord which provides numerous features related to
|
||||
group/role synchronization.
|
||||
|
||||
EssentialsX Discord Link offers features such as:
|
||||
* Vault Group -> Discord Role Synchronization
|
||||
* Discord Role -> Vault Group Synchronization
|
||||
* Prevent unlinked players from joining
|
||||
* Prevent unlinked players from moving/chatting
|
||||
* & more...
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
> * [Setting Up Role Sync](#setting-up-role-sync)
|
||||
> * [Linking an Account](#linking-an-account)
|
||||
> * [Developer API](#developer-api)
|
||||
|
||||
---
|
||||
|
||||
## Setting Up Role Sync
|
||||
|
||||
In EssentialsX Discord Link, you can define a synchronizations for both Vault groups -> Discord roles and for
|
||||
Discord roles -> Vault groups.
|
||||
|
||||
The following tutorial (as an example) will show how to give players with the Discord role `Patreon` the `donator`
|
||||
Vault group and how to give players with the `vip` Vault group the `VIP` Discord role.
|
||||
|
||||
0. First, head to your server's role page in order to get their IDs.
|
||||
|
||||
1. For both the `Patreon` and `VIP` role, right click them and click on "Copy ID".
|
||||
> ![Copy Role ID](https://i.imgur.com/YS9P2ej.gif)
|
||||
> Right Click on Role(s) -> `Copy ID` -> Paste into Notepad for later step
|
||||
|
||||
2. Now that you have the IDs you need from Discord, you can begin configuring the plugin. First place the
|
||||
EssentialsX Discord Link jar (you can download it [here](https://essentialsx.net/downloads.html) if you do not
|
||||
already have it) in your plugins folder and then start your server.
|
||||
> ![Start Server](https://i.imgur.com/64IwqoO.gif)
|
||||
> Drag EssentialsXDiscordLink jar into plugins folder -> Start Server
|
||||
|
||||
3. Once the server started, open the config for EssentialsX Discord Link at
|
||||
`plugins/EssentialsDiscordLink/config.yml`. Once opened, put `group-name: role-id` in the `groups` section
|
||||
to create a Vault group -> Discord role synchronization (`vip: 882835722640433242` for this example); Then put
|
||||
`role-id: group-name` in the `roles` section to create a Discord role -> Vault group synchronization
|
||||
(`882835662280224818: donator` for this example). When done, save the file.
|
||||
> ![Paste Synchronizations](https://i.imgur.com/JYZHzW0.gif)
|
||||
> Paste Vault->Discord syncs in the group section & Discord->Vault syncs in the roles section
|
||||
|
||||
5. Finally, once the file is saved, run `ess reload` from your console and then linked accounts should now have
|
||||
their groups/roles linked between Minecraft/Discord! Now that you completed the basics of group/role syncing,
|
||||
go back up to the [Table of Contents](#table-of-contents) to see what else you can do!
|
||||
|
||||
---
|
||||
|
||||
## Linking an Account
|
||||
|
||||
0. This assumes the server has started and you have joined the server.
|
||||
|
||||
1. Once on the server, run `/link` in Minecraft and take note of the code if gives you.
|
||||
> ![Run /link](https://i.imgur.com/1EdqdOa.gif)
|
||||
> Run `/link` in Minecraft
|
||||
|
||||
2. Next, all you have to do is run the `/link` command in discord with the code provided.
|
||||
> ![Run /link in Discord](https://i.imgur.com/yXkvMDX.gif)
|
||||
> Run `/link` with the code in Discord
|
||||
|
||||
3. That's it! Now that you've learned how to link an account, go back up to the
|
||||
[Table of Contents](#table-of-contents) to see what else you can do!
|
||||
|
||||
---
|
||||
|
||||
## Developer API
|
||||
|
||||
EssentialsX Discord Link has a simple API to provide very simple methods to check if players are linked,
|
||||
link players, unlink players, and to get linked player data.
|
||||
|
||||
Outside the specific examples below, you can also view javadocs for EssentialsX Discord Link
|
||||
[here](https://jd-v2.essentialsx.net/EssentialsDiscordLink).
|
||||
|
||||
### Get a linked player's Discord tag
|
||||
|
||||
The following example shows how to get a linked player's Discord tag (in `Name#0000` format) or null if the player
|
||||
isn't linked.
|
||||
|
||||
```java
|
||||
public String getDiscordTag(final Player player) {
|
||||
// Gets the API service for EssentialsX Discord Link
|
||||
final DiscordLinkService linkApi = Bukkit.getServicesManager().load(DiscordLinkService.class);
|
||||
|
||||
final String discordId = linkApi.getDiscordId(player.getUniqueId());
|
||||
if (discordId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Gets the API service for EssentialsX Discord which we will use to get the actual user
|
||||
final DiscordService discordApi = Bukkit.getServicesManager().load(DiscordService.class);
|
||||
|
||||
final InteractionMember member = discordApi.getMemberById(discordId).join();
|
||||
return member == null ? null : member.getTag();
|
||||
}
|
||||
```
|
8
EssentialsDiscordLink/build.gradle
Normal file
8
EssentialsDiscordLink/build.gradle
Normal file
@ -0,0 +1,8 @@
|
||||
plugins {
|
||||
id("essentials.module-conventions")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly project(':EssentialsX')
|
||||
compileOnly project(':EssentialsXDiscord')
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package net.essentialsx.api.v2.events.discordlink;
|
||||
|
||||
import net.ess3.api.IUser;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionMember;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Fired when a User's link status has changed.
|
||||
*/
|
||||
public class DiscordLinkStatusChangeEvent extends Event {
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private final IUser user;
|
||||
private final InteractionMember member;
|
||||
private final String memberId;
|
||||
private final boolean state;
|
||||
private final Cause cause;
|
||||
|
||||
public DiscordLinkStatusChangeEvent(IUser user, InteractionMember member, String memberId, boolean state, Cause cause) {
|
||||
this.user = user;
|
||||
this.member = member;
|
||||
this.memberId = memberId;
|
||||
this.state = state;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Essentials {@link IUser user} whose link status has been changed in this event.
|
||||
* @return the user.
|
||||
*/
|
||||
public IUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Discord {@link InteractionMember member} whose link status has been changed in this event.
|
||||
* <p>
|
||||
* This will return {@code null} if {@link #getCause()} returns {@link Cause#UNSYNC_LEAVE}.
|
||||
* @see #getCause()
|
||||
* @see #getMemberId()
|
||||
* @return the member or null.
|
||||
*/
|
||||
public InteractionMember getMember() {
|
||||
return member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the Discord member whose link status has been changed in this event.
|
||||
* <p>
|
||||
* Unlink {@link #getMember()}, this method will never return null.
|
||||
* @return the member's id.
|
||||
*/
|
||||
public String getMemberId() {
|
||||
return memberId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new link status of this {@link #getUser() user} after this event.
|
||||
* @return true if the user is linked to a discord account.
|
||||
*/
|
||||
public boolean isLinked() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cause which triggered this event.
|
||||
* @see Cause
|
||||
* @return the cause.
|
||||
*/
|
||||
public Cause getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cause of the link status change.
|
||||
*/
|
||||
public enum Cause {
|
||||
/**
|
||||
* Used when a player successfully completes an account link with the /link account in Minecraft.
|
||||
*/
|
||||
SYNC_PLAYER,
|
||||
/**
|
||||
* Used when a player is linked via an external plugin using API.
|
||||
*/
|
||||
SYNC_API,
|
||||
/**
|
||||
* Used when a player unlinks their account via the /unlink Discord or Minecraft command.
|
||||
*/
|
||||
UNSYNC_PLAYER,
|
||||
/**
|
||||
* Used when a player is unlinked via an external plugin using API.
|
||||
*/
|
||||
UNSYNC_API,
|
||||
/**
|
||||
* Used when a player is unlinked due to them leaving the Discord server.
|
||||
*/
|
||||
UNSYNC_LEAVE,
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package net.essentialsx.api.v2.services.discordlink;
|
||||
|
||||
import net.essentialsx.api.v2.services.discord.InteractionMember;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A class which provides numerous methods to interact with the link module for EssentialsX Discord.
|
||||
*/
|
||||
public interface DiscordLinkService {
|
||||
/**
|
||||
* Gets the Discord ID linked to the given {@link UUID} or {@code null} if none is present.
|
||||
* @param uuid the {@link UUID} of the player to lookup.
|
||||
* @return the Discord ID or {@code null}.
|
||||
*/
|
||||
String getDiscordId(final UUID uuid);
|
||||
|
||||
/**
|
||||
* Checks if there is a Discord account linked to the given {@link UUID}.
|
||||
* @param uuid the {@link UUID} to check.
|
||||
* @return true if there is a Discord account linked to the given {@link UUID}.
|
||||
*/
|
||||
default boolean isLinked(final UUID uuid) {
|
||||
return getDiscordId(uuid) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link UUID} linked to the given Discord ID or {@code null} if none is present.
|
||||
* @param discordId The Discord ID to lookup.
|
||||
* @return the {@link UUID} or {@code null}.
|
||||
*/
|
||||
UUID getUUID(final String discordId);
|
||||
|
||||
/**
|
||||
* Checks if there is a Minecraft account linked to the given Discord ID.
|
||||
* @param discordId the Discord ID to check.
|
||||
* @return true if there is a Minecraft account linked to the given Discord ID.
|
||||
*/
|
||||
default boolean isLinked(final String discordId) {
|
||||
return getUUID(discordId) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links the given {@link UUID} to the given {@link InteractionMember}.
|
||||
* <p>
|
||||
* This will automatically trigger role sync (if configured) for the given
|
||||
* player if this method returns {@code true}.
|
||||
* <p>
|
||||
* This method will return true if the accounts are successfully linked, or
|
||||
* false if either the provided {@link UUID} or {@link InteractionMember} are
|
||||
* already linked to another account.
|
||||
* @param uuid The {@link UUID} of the target player.
|
||||
* @param member The {@link InteractionMember} to link to the target player.
|
||||
* @see net.essentialsx.api.v2.services.discord.DiscordService#getMemberById(String) to get an
|
||||
* {@link InteractionMember} by their ID.
|
||||
* @see #isLinked(UUID) to ensure the given {@link UUID} isn't already linked to an account.
|
||||
* @see #isLinked(String) to ensure the given {@link InteractionMember} isn't already linked to an account.
|
||||
* @throws IllegalArgumentException if either of the {@link UUID} or {@link InteractionMember} are null.
|
||||
* @return true if the accounts were linked successfully, otherwise false.
|
||||
*/
|
||||
boolean linkAccount(final UUID uuid, final InteractionMember member);
|
||||
|
||||
/**
|
||||
* Unlinks the given {@link UUID} with its associated Discord account (if present).
|
||||
* <p>
|
||||
* This will automatically trigger role unsync (if configured) for the given player if this method
|
||||
* returns {@code true}.
|
||||
* @param uuid The {@link UUID} of the player to unlink.
|
||||
* @throws IllegalArgumentException if the provided {@link UUID} is null.
|
||||
* @return true if there was an account associated with the given {@link UUID}, otherwise false.
|
||||
*/
|
||||
boolean unlinkAccount(final UUID uuid);
|
||||
|
||||
/**
|
||||
* Unlinks the given {@link InteractionMember} with its associated Minecraft account (if present).
|
||||
* <p>
|
||||
* This will automatically trigger role unsync (if configured) for the given {@link InteractionMember}
|
||||
* if this method returns {@code true}.
|
||||
* @param member The {@link InteractionMember} to unlink.
|
||||
* @throws IllegalArgumentException if the provided {@link InteractionMember} is null.
|
||||
* @return true if there was a linked Minecraft account associated with the given
|
||||
* {@link InteractionMember}, otherwise false.
|
||||
*/
|
||||
boolean unlinkAccount(final InteractionMember member);
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
package net.essentialsx.discordlink;
|
||||
|
||||
import com.earth2me.essentials.IEssentialsModule;
|
||||
import com.google.common.base.Preconditions;
|
||||
import net.ess3.api.IUser;
|
||||
import net.essentialsx.api.v2.events.discordlink.DiscordLinkStatusChangeEvent;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionMember;
|
||||
import net.essentialsx.api.v2.services.discordlink.DiscordLinkService;
|
||||
import net.essentialsx.discordlink.rolesync.RoleSyncManager;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class AccountLinkManager implements IEssentialsModule, DiscordLinkService {
|
||||
private static final char[] CODE_CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
|
||||
|
||||
private final EssentialsDiscordLink ess;
|
||||
private final AccountStorage storage;
|
||||
private final RoleSyncManager roleSyncManager;
|
||||
|
||||
private final Map<String, UUID> codeToUuidMap = new ConcurrentHashMap<>();
|
||||
|
||||
public AccountLinkManager(EssentialsDiscordLink ess, AccountStorage storage, RoleSyncManager roleSyncManager) {
|
||||
this.ess = ess;
|
||||
this.storage = storage;
|
||||
this.roleSyncManager = roleSyncManager;
|
||||
}
|
||||
|
||||
public String createCode(final UUID uuid) throws IllegalArgumentException {
|
||||
final Optional<Map.Entry<String, UUID>> prevCode = codeToUuidMap.entrySet().stream().filter(stringUUIDEntry -> stringUUIDEntry.getValue().equals(uuid)).findFirst();
|
||||
if (prevCode.isPresent()) {
|
||||
throw new IllegalArgumentException(prevCode.get().getKey());
|
||||
}
|
||||
|
||||
final String code = generateCode();
|
||||
|
||||
codeToUuidMap.put(code, uuid);
|
||||
return code;
|
||||
}
|
||||
|
||||
public UUID getPendingUUID(final String code) {
|
||||
return codeToUuidMap.remove(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDiscordId(final UUID uuid) {
|
||||
return storage.getDiscordId(uuid);
|
||||
}
|
||||
|
||||
public IUser getUser(final String discordId) {
|
||||
final UUID uuid = getUUID(discordId);
|
||||
if (uuid == null) {
|
||||
return null;
|
||||
}
|
||||
return ess.getEss().getUser(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID(final String discordId) {
|
||||
return storage.getUUID(discordId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlinkAccount(InteractionMember member) {
|
||||
Preconditions.checkNotNull(member, "member cannot be null");
|
||||
|
||||
if (!isLinked(member.getId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
removeAccount(member, DiscordLinkStatusChangeEvent.Cause.UNSYNC_API);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeAccount(final InteractionMember member, final DiscordLinkStatusChangeEvent.Cause cause) {
|
||||
final UUID uuid = getUUID(member.getId());
|
||||
if (storage.remove(member.getId())) {
|
||||
ensureAsync(() -> {
|
||||
final IUser user = ess.getEss().getUser(uuid);
|
||||
ensureSync(() -> ess.getServer().getPluginManager().callEvent(new DiscordLinkStatusChangeEvent(user, member, member.getId(), false, cause)));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
ensureAsync(() -> roleSyncManager.unSync(uuid, member.getId()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlinkAccount(UUID uuid) {
|
||||
Preconditions.checkNotNull(uuid, "uuid cannot be null");
|
||||
|
||||
if (!isLinked(uuid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ensureAsync(() -> removeAccount(ess.getEss().getUser(uuid), DiscordLinkStatusChangeEvent.Cause.UNSYNC_API));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeAccount(final IUser user, final DiscordLinkStatusChangeEvent.Cause cause) {
|
||||
final String id = getDiscordId(user.getBase().getUniqueId());
|
||||
if (storage.remove(user.getBase().getUniqueId())) {
|
||||
ess.getApi().getMemberById(id).thenAccept(member -> ensureSync(() ->
|
||||
ess.getServer().getPluginManager().callEvent(new DiscordLinkStatusChangeEvent(user, member, id, false, cause))));
|
||||
return true;
|
||||
}
|
||||
ensureAsync(() -> roleSyncManager.unSync(user.getBase().getUniqueId(), id));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean linkAccount(UUID uuid, InteractionMember member) {
|
||||
Preconditions.checkNotNull(uuid, "uuid cannot be null");
|
||||
Preconditions.checkNotNull(member, "member cannot be null");
|
||||
|
||||
if (isLinked(uuid) || isLinked(member.getId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
registerAccount(uuid, member, DiscordLinkStatusChangeEvent.Cause.SYNC_API);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void registerAccount(final UUID uuid, final InteractionMember member, final DiscordLinkStatusChangeEvent.Cause cause) {
|
||||
storage.add(uuid, member.getId());
|
||||
ensureAsync(() -> roleSyncManager.sync(uuid, member.getId()));
|
||||
ensureAsync(() -> {
|
||||
final IUser user = ess.getEss().getUser(uuid);
|
||||
ensureSync(() -> ess.getServer().getPluginManager().callEvent(new DiscordLinkStatusChangeEvent(user, member, member.getId(), true, cause)));
|
||||
});
|
||||
}
|
||||
|
||||
private void ensureSync(final Runnable runnable) {
|
||||
if (ess.getServer().isPrimaryThread()) {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
ess.getEss().scheduleSyncDelayedTask(runnable);
|
||||
}
|
||||
|
||||
private void ensureAsync(final Runnable runnable) {
|
||||
if (!ess.getServer().isPrimaryThread()) {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
ess.getEss().runTaskAsynchronously(runnable);
|
||||
}
|
||||
|
||||
private String generateCode() {
|
||||
final char[] code = new char[8];
|
||||
final Random random = ThreadLocalRandom.current();
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
code[i] = CODE_CHARACTERS[random.nextInt(CODE_CHARACTERS.length)];
|
||||
}
|
||||
final String result = new String(code);
|
||||
|
||||
if (codeToUuidMap.containsKey(result)) {
|
||||
// If this happens, buy a lottery ticket.
|
||||
return generateCode();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package net.essentialsx.discordlink;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class AccountStorage {
|
||||
private final Gson gson = new Gson();
|
||||
private final EssentialsDiscordLink plugin;
|
||||
private final File accountFile;
|
||||
private final BiMap<String, String> uuidToDiscordIdMap;
|
||||
private final AtomicBoolean mapDirty = new AtomicBoolean(false);
|
||||
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public AccountStorage(final EssentialsDiscordLink plugin) throws IOException {
|
||||
this.plugin = plugin;
|
||||
this.accountFile = new File(plugin.getDataFolder(), "accounts.json");
|
||||
if (!plugin.getDataFolder().exists() && !plugin.getDataFolder().mkdirs()) {
|
||||
throw new IOException("Unable to create account file!");
|
||||
}
|
||||
if (!accountFile.exists() && !accountFile.createNewFile()) {
|
||||
throw new IOException("Unable to create account file!");
|
||||
}
|
||||
try (final Reader reader = new FileReader(accountFile)) {
|
||||
//noinspection UnstableApiUsage
|
||||
final Map<String, String> map = gson.fromJson(reader, new TypeToken<Map<String, String>>() {}.getType());
|
||||
uuidToDiscordIdMap = map == null ? Maps.synchronizedBiMap(HashBiMap.create()) : Maps.synchronizedBiMap(HashBiMap.create(map));
|
||||
}
|
||||
|
||||
executorService.scheduleWithFixedDelay(() -> {
|
||||
if (!mapDirty.compareAndSet(true, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (plugin.getEss().getSettings().isDebug()) {
|
||||
plugin.getLogger().log(Level.INFO, "Saving linked discord accounts to disk...");
|
||||
}
|
||||
|
||||
final Map<String, String> clone;
|
||||
clone = new HashMap<>(uuidToDiscordIdMap);
|
||||
try (final Writer writer = new FileWriter(accountFile)) {
|
||||
gson.toJson(clone, writer);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to save link accounts!", e);
|
||||
mapDirty.set(true); // mark the map as dirty and pray it fixes itself :D
|
||||
}
|
||||
}, 10, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public BiMap<String, String> getRawStorageMap() {
|
||||
return HashBiMap.create(uuidToDiscordIdMap);
|
||||
}
|
||||
|
||||
public void add(final UUID uuid, final String discordId) {
|
||||
uuidToDiscordIdMap.forcePut(uuid.toString(), discordId);
|
||||
queueSave();
|
||||
}
|
||||
|
||||
public boolean remove(final UUID uuid) {
|
||||
final boolean success = uuidToDiscordIdMap.remove(uuid.toString()) != null;
|
||||
queueSave();
|
||||
return success;
|
||||
}
|
||||
|
||||
public boolean remove(final String discordId) {
|
||||
final boolean success = uuidToDiscordIdMap.values().removeIf(discordId::equals);
|
||||
queueSave();
|
||||
return success;
|
||||
}
|
||||
|
||||
public UUID getUUID(final String discordId) {
|
||||
final String uuid = uuidToDiscordIdMap.inverse().get(discordId);
|
||||
return uuid == null ? null : UUID.fromString(uuid);
|
||||
}
|
||||
|
||||
public String getDiscordId(final UUID uuid) {
|
||||
return uuidToDiscordIdMap.get(uuid.toString());
|
||||
}
|
||||
|
||||
public void queueSave() {
|
||||
mapDirty.set(true);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
try {
|
||||
executorService.shutdown();
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Timed out while saving!");
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
if (mapDirty.get()) {
|
||||
try (final Writer writer = new FileWriter(accountFile)) {
|
||||
gson.toJson(uuidToDiscordIdMap, writer);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to shutdown link accounts save!", e);
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package net.essentialsx.discordlink;
|
||||
|
||||
import com.earth2me.essentials.IConf;
|
||||
import com.earth2me.essentials.config.EssentialsConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
public class DiscordLinkSettings implements IConf {
|
||||
private final EssentialsDiscordLink plugin;
|
||||
private final EssentialsConfiguration config;
|
||||
|
||||
private LinkPolicy linkPolicy;
|
||||
private Map<String, String> roleSyncGroups;
|
||||
private Map<String, String> roleSyncRoles;
|
||||
|
||||
public DiscordLinkSettings(EssentialsDiscordLink plugin) {
|
||||
this.plugin = plugin;
|
||||
this.config = new EssentialsConfiguration(new File(plugin.getDataFolder(), "config.yml"), "/config.yml", EssentialsDiscordLink.class);
|
||||
reloadConfig();
|
||||
}
|
||||
|
||||
public LinkPolicy getLinkPolicy() {
|
||||
return linkPolicy;
|
||||
}
|
||||
|
||||
public boolean isBlockUnlinkedChat() {
|
||||
return config.getBoolean("block-unlinked-chat", false);
|
||||
}
|
||||
|
||||
public boolean isUnlinkOnLeave() {
|
||||
return config.getBoolean("unlink-on-leave", true);
|
||||
}
|
||||
|
||||
public boolean isRelayMail() {
|
||||
return config.getBoolean("relay-mail", true);
|
||||
}
|
||||
|
||||
public boolean isRoleSyncRemoveRoles() {
|
||||
return config.getBoolean("role-sync.remove-roles", true);
|
||||
}
|
||||
|
||||
public boolean isRoleSyncRemoveGroups() {
|
||||
return config.getBoolean("role-sync.remove-groups", true);
|
||||
}
|
||||
|
||||
public int getRoleSyncResyncDelay() {
|
||||
return config.getInt("role-sync.resync-delay", 5);
|
||||
}
|
||||
|
||||
public boolean isRoleSyncPrimaryGroupOnly() {
|
||||
return config.getBoolean("role-sync.primary-group-only", false);
|
||||
}
|
||||
|
||||
public Map<String, String> getRoleSyncGroups() {
|
||||
return roleSyncGroups;
|
||||
}
|
||||
|
||||
private Map<String, String> _getRoleSyncGroups() {
|
||||
return config.getStringMap("role-sync.groups");
|
||||
}
|
||||
|
||||
public Map<String, String> getRoleSyncRoles() {
|
||||
return roleSyncRoles;
|
||||
}
|
||||
|
||||
private Map<String, String> _getRoleSyncRoles() {
|
||||
return config.getStringMap("role-sync.roles");
|
||||
}
|
||||
|
||||
public enum LinkPolicy {
|
||||
KICK,
|
||||
FREEZE,
|
||||
NONE;
|
||||
|
||||
static LinkPolicy fromName(final String name) {
|
||||
for (LinkPolicy policy : values()) {
|
||||
if (policy.name().equalsIgnoreCase(name)) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
return LinkPolicy.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
config.load();
|
||||
|
||||
linkPolicy = LinkPolicy.fromName(config.getString("link-policy", "none"));
|
||||
roleSyncGroups = _getRoleSyncGroups();
|
||||
roleSyncRoles = _getRoleSyncRoles();
|
||||
|
||||
plugin.onReload();
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package net.essentialsx.discordlink;
|
||||
|
||||
import com.earth2me.essentials.EssentialsLogger;
|
||||
import com.earth2me.essentials.IEssentials;
|
||||
import com.earth2me.essentials.metrics.MetricsWrapper;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.essentialsx.api.v2.services.discord.DiscordService;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionException;
|
||||
import net.essentialsx.api.v2.services.discordlink.DiscordLinkService;
|
||||
import net.essentialsx.discord.EssentialsDiscord;
|
||||
import net.essentialsx.discordlink.commands.discord.AccountInteractionCommand;
|
||||
import net.essentialsx.discordlink.commands.discord.LinkInteractionCommand;
|
||||
import net.essentialsx.discordlink.commands.discord.UnlinkInteractionCommand;
|
||||
import net.essentialsx.discordlink.listeners.LinkBukkitListener;
|
||||
import net.essentialsx.discordlink.rolesync.RoleSyncManager;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class EssentialsDiscordLink extends JavaPlugin {
|
||||
private transient IEssentials ess;
|
||||
private transient MetricsWrapper metrics = null;
|
||||
|
||||
private DiscordService api;
|
||||
private DiscordLinkSettings settings;
|
||||
private AccountStorage accounts;
|
||||
private AccountLinkManager linkManager;
|
||||
private RoleSyncManager roleSyncManager;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
ess = (IEssentials) getServer().getPluginManager().getPlugin("Essentials");
|
||||
final EssentialsDiscord essDiscord = (EssentialsDiscord) getServer().getPluginManager().getPlugin("EssentialsDiscord");
|
||||
if (ess == null || !ess.isEnabled() || essDiscord == null || !essDiscord.isEnabled()) {
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
if (!getDescription().getVersion().equals(ess.getDescription().getVersion())) {
|
||||
getLogger().log(Level.WARNING, tl("versionMismatchAll"));
|
||||
}
|
||||
|
||||
api = getServer().getServicesManager().load(DiscordService.class);
|
||||
|
||||
settings = new DiscordLinkSettings(this);
|
||||
ess.addReloadListener(settings);
|
||||
try {
|
||||
accounts = new AccountStorage(this);
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Unable to create link accounts file", e);
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
roleSyncManager = new RoleSyncManager(this);
|
||||
linkManager = new AccountLinkManager(this, accounts, roleSyncManager);
|
||||
|
||||
getServer().getPluginManager().registerEvents(new LinkBukkitListener(this), this);
|
||||
getServer().getServicesManager().register(DiscordLinkService.class, linkManager, this, ServicePriority.Normal);
|
||||
|
||||
if (!(api.getInteractionController().getCommand("link") instanceof LinkInteractionCommand)) {
|
||||
try {
|
||||
api.getInteractionController().registerCommand(new AccountInteractionCommand(linkManager));
|
||||
api.getInteractionController().registerCommand(new LinkInteractionCommand(linkManager));
|
||||
api.getInteractionController().registerCommand(new UnlinkInteractionCommand(linkManager));
|
||||
} catch (InteractionException e) {
|
||||
e.printStackTrace();
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ess.getPermissionsHandler().registerContext("essentials:linked", user ->
|
||||
Collections.singleton(String.valueOf(linkManager.isLinked(user.getUUID()))), () -> ImmutableSet.of("true", "false"));
|
||||
|
||||
if (metrics == null) {
|
||||
metrics = new MetricsWrapper(this, 11462, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (accounts != null) {
|
||||
accounts.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void onReload() {
|
||||
if (roleSyncManager != null) {
|
||||
roleSyncManager.onReload();
|
||||
}
|
||||
}
|
||||
|
||||
public IEssentials getEss() {
|
||||
return ess;
|
||||
}
|
||||
|
||||
public DiscordService getApi() {
|
||||
return api;
|
||||
}
|
||||
|
||||
public DiscordLinkSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public AccountStorage getAccountStorage() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public AccountLinkManager getLinkManager() {
|
||||
return linkManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
return ess.onCommandEssentials(sender, command, label, args, EssentialsDiscordLink.class.getClassLoader(), "net.essentialsx.discordlink.commands.bukkit.Command", "essentials.", linkManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
try {
|
||||
return EssentialsLogger.getLoggerProvider(this);
|
||||
} catch (Throwable ignored) {
|
||||
// In case Essentials isn't installed/loaded
|
||||
return super.getLogger();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.essentialsx.discordlink.commands.bukkit;
|
||||
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.commands.EssentialsCommand;
|
||||
import net.essentialsx.discordlink.AccountLinkManager;
|
||||
import org.bukkit.Server;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class Commandlink extends EssentialsCommand {
|
||||
public Commandlink() {
|
||||
super("link");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Server server, User user, String commandLabel, String[] args) {
|
||||
final AccountLinkManager manager = (AccountLinkManager) module;
|
||||
if (manager.isLinked(user.getUUID())) {
|
||||
user.sendMessage(tl("discordLinkLinkedAlready"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final String code = manager.createCode(user.getBase().getUniqueId());
|
||||
user.sendMessage(tl("discordLinkLinked", "/link " + code));
|
||||
} catch (final IllegalArgumentException e) {
|
||||
user.sendMessage(tl("discordLinkPending", "/link " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.essentialsx.discordlink.commands.bukkit;
|
||||
|
||||
import com.earth2me.essentials.User;
|
||||
import com.earth2me.essentials.commands.EssentialsCommand;
|
||||
import net.essentialsx.api.v2.events.discordlink.DiscordLinkStatusChangeEvent;
|
||||
import net.essentialsx.discordlink.AccountLinkManager;
|
||||
import org.bukkit.Server;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class Commandunlink extends EssentialsCommand {
|
||||
public Commandunlink() {
|
||||
super("unlink");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Server server, User user, String commandLabel, String[] args) {
|
||||
final AccountLinkManager manager = (AccountLinkManager) module;
|
||||
if (!manager.removeAccount(user, DiscordLinkStatusChangeEvent.Cause.UNSYNC_PLAYER)) {
|
||||
user.sendMessage(tl("discordLinkNoAccount"));
|
||||
return;
|
||||
}
|
||||
|
||||
user.sendMessage(tl("discordLinkUnlinked"));
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package net.essentialsx.discordlink.commands.discord;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.ess3.api.IUser;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionCommand;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionCommandArgument;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionCommandArgumentType;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionEvent;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionMember;
|
||||
import net.essentialsx.discordlink.AccountLinkManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class AccountInteractionCommand implements InteractionCommand {
|
||||
private final List<InteractionCommandArgument> arguments;
|
||||
private final AccountLinkManager accounts;
|
||||
|
||||
public AccountInteractionCommand(AccountLinkManager accounts) {
|
||||
this.arguments = ImmutableList.of(new InteractionCommandArgument("user", tl("discordCommandAccountArgumentUser"), InteractionCommandArgumentType.USER, false));
|
||||
this.accounts = accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEphemeral() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "account";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return tl("discordCommandAccountDescription");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InteractionCommandArgument> getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(InteractionEvent event) {
|
||||
final InteractionMember userArg = event.getUserArgument("user");
|
||||
final InteractionMember effectiveUser = userArg == null ? event.getMember() : userArg;
|
||||
final IUser user = accounts.getUser(effectiveUser.getId());
|
||||
if (user == null) {
|
||||
event.reply(tl(event.getMember().getId().equals(effectiveUser.getId()) ? "discordCommandAccountResponseNotLinked" : "discordCommandAccountResponseNotLinkedOther", effectiveUser.getAsMention()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getMember().getId().equals(effectiveUser.getId())) {
|
||||
event.reply(tl("discordCommandAccountResponseLinked", user.getName()));
|
||||
return;
|
||||
}
|
||||
event.reply(tl("discordCommandAccountResponseLinkedOther", effectiveUser.getAsMention(), user.getName()));
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package net.essentialsx.discordlink.commands.discord;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.essentialsx.api.v2.events.discordlink.DiscordLinkStatusChangeEvent;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionCommand;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionCommandArgument;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionCommandArgumentType;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionEvent;
|
||||
import net.essentialsx.discordlink.AccountLinkManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class LinkInteractionCommand implements InteractionCommand {
|
||||
private final List<InteractionCommandArgument> arguments;
|
||||
private final AccountLinkManager accounts;
|
||||
|
||||
public LinkInteractionCommand(final AccountLinkManager accounts) {
|
||||
this.arguments = ImmutableList.of(new InteractionCommandArgument("code", tl("discordCommandLinkArgumentCode"), InteractionCommandArgumentType.STRING, true));
|
||||
this.accounts = accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(InteractionEvent event) {
|
||||
if (accounts.isLinked(event.getMember().getId())) {
|
||||
event.reply(tl("discordCommandLinkHasAccount"));
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID uuid = accounts.getPendingUUID(event.getStringArgument("code"));
|
||||
if (uuid == null) {
|
||||
event.reply(tl("discordCommandLinkInvalidCode"));
|
||||
return;
|
||||
}
|
||||
|
||||
accounts.registerAccount(uuid, event.getMember(), DiscordLinkStatusChangeEvent.Cause.SYNC_PLAYER);
|
||||
event.reply(tl("discordCommandLinkLinked"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEphemeral() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "link";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return tl("discordCommandLinkDescription");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InteractionCommandArgument> getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
package net.essentialsx.discordlink.commands.discord;
|
||||
|
||||
import net.essentialsx.api.v2.events.discordlink.DiscordLinkStatusChangeEvent;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionCommand;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionCommandArgument;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionEvent;
|
||||
import net.essentialsx.discordlink.AccountLinkManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class UnlinkInteractionCommand implements InteractionCommand {
|
||||
private final AccountLinkManager accounts;
|
||||
|
||||
public UnlinkInteractionCommand(final AccountLinkManager accounts) {
|
||||
this.accounts = accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(InteractionEvent event) {
|
||||
if (!accounts.removeAccount(event.getMember(), DiscordLinkStatusChangeEvent.Cause.UNSYNC_PLAYER)) {
|
||||
event.reply(tl("discordCommandUnlinkInvalidCode"));
|
||||
return;
|
||||
}
|
||||
event.reply(tl("discordCommandUnlinkUnlinked"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEphemeral() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "unlink";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return tl("discordCommandUnlinkDescription");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InteractionCommandArgument> getArguments() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
package net.essentialsx.discordlink.listeners;
|
||||
|
||||
import com.earth2me.essentials.utils.FormatUtil;
|
||||
import net.essentialsx.api.v2.events.AsyncUserDataLoadEvent;
|
||||
import net.essentialsx.api.v2.events.UserMailEvent;
|
||||
import net.essentialsx.api.v2.events.discord.DiscordMessageEvent;
|
||||
import net.essentialsx.api.v2.events.discordlink.DiscordLinkStatusChangeEvent;
|
||||
import net.essentialsx.api.v2.services.discord.MessageType;
|
||||
import net.essentialsx.discord.util.MessageUtil;
|
||||
import net.essentialsx.discordlink.DiscordLinkSettings;
|
||||
import net.essentialsx.discordlink.EssentialsDiscordLink;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class LinkBukkitListener implements Listener {
|
||||
private final EssentialsDiscordLink ess;
|
||||
|
||||
public LinkBukkitListener(EssentialsDiscordLink ess) {
|
||||
this.ess = ess;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onMail(final UserMailEvent event) {
|
||||
if (!ess.getSettings().isRelayMail()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String discordId = ess.getLinkManager().getDiscordId(event.getRecipient().getBase().getUniqueId());
|
||||
if (discordId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String sanitizedName = MessageUtil.sanitizeDiscordMarkdown(event.getMessage().getSenderUsername());
|
||||
final String sanitizedMessage = MessageUtil.sanitizeDiscordMarkdown(FormatUtil.stripFormat(event.getMessage().getMessage()));
|
||||
|
||||
ess.getApi().getMemberById(discordId).thenAccept(member -> {
|
||||
member.sendPrivateMessage(tl("discordMailLine", sanitizedName, sanitizedMessage));
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onConnect(final AsyncPlayerPreLoginEvent event) {
|
||||
if (ess.getSettings().getLinkPolicy() != DiscordLinkSettings.LinkPolicy.KICK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ess.getLinkManager().isLinked(event.getUniqueId())) {
|
||||
String code;
|
||||
try {
|
||||
code = ess.getLinkManager().createCode(event.getUniqueId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
code = e.getMessage();
|
||||
}
|
||||
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, tl("discordLinkLoginKick", "/link " + code, ess.getApi().getInviteUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onInteract(final PlayerInteractEvent event) {
|
||||
if (ess.getSettings().getLinkPolicy() != DiscordLinkSettings.LinkPolicy.FREEZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ess.getLinkManager().isLinked(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onCommand(final PlayerCommandPreprocessEvent event) {
|
||||
if (ess.getSettings().getLinkPolicy() != DiscordLinkSettings.LinkPolicy.FREEZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
//todo maybe allowed commands
|
||||
if (!ess.getLinkManager().isLinked(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true);
|
||||
String code;
|
||||
try {
|
||||
code = ess.getLinkManager().createCode(event.getPlayer().getUniqueId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
code = e.getMessage();
|
||||
}
|
||||
event.getPlayer().sendMessage(tl("discordLinkLoginPrompt", "/link " + code, ess.getApi().getInviteUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onChat(final AsyncPlayerChatEvent event) {
|
||||
if (ess.getSettings().getLinkPolicy() != DiscordLinkSettings.LinkPolicy.FREEZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ess.getLinkManager().isLinked(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true);
|
||||
String code;
|
||||
try {
|
||||
code = ess.getLinkManager().createCode(event.getPlayer().getUniqueId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
code = e.getMessage();
|
||||
}
|
||||
event.getPlayer().sendMessage(tl("discordLinkLoginPrompt", "/link " + code, ess.getApi().getInviteUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onUserDataLoad(final AsyncUserDataLoadEvent event) {
|
||||
if (ess.getSettings().getLinkPolicy() != DiscordLinkSettings.LinkPolicy.FREEZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ess.getLinkManager().isLinked(event.getUser().getBase().getUniqueId())) {
|
||||
event.getUser().setFreeze(true);
|
||||
String code;
|
||||
try {
|
||||
code = ess.getLinkManager().createCode(event.getUser().getBase().getUniqueId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
code = e.getMessage();
|
||||
}
|
||||
event.getUser().sendMessage(tl("discordLinkLoginPrompt", "/link " + code, ess.getApi().getInviteUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onDiscordMessage(final DiscordMessageEvent event) {
|
||||
if (ess.getSettings().isBlockUnlinkedChat() && event.getType() == MessageType.DefaultTypes.CHAT && !ess.getLinkManager().isLinked(event.getUUID())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onUserLinkStatusChange(final DiscordLinkStatusChangeEvent event) {
|
||||
if (event.isLinked()) {
|
||||
event.getUser().setFreeze(false);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ess.getSettings().getLinkPolicy()) {
|
||||
case KICK: {
|
||||
String code;
|
||||
try {
|
||||
code = ess.getLinkManager().createCode(event.getUser().getBase().getUniqueId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
code = e.getMessage();
|
||||
}
|
||||
final String finalCode = code;
|
||||
final Runnable kickTask = () -> event.getUser().getBase().kickPlayer(tl("discordLinkLoginKick", "/link " + finalCode, ess.getApi().getInviteUrl()));
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
kickTask.run();
|
||||
} else {
|
||||
ess.getEss().scheduleSyncDelayedTask(kickTask);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FREEZE: {
|
||||
String code;
|
||||
try {
|
||||
code = ess.getLinkManager().createCode(event.getUser().getBase().getUniqueId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
code = e.getMessage();
|
||||
}
|
||||
event.getUser().sendMessage(tl("discordLinkLoginPrompt", "/link " + code, ess.getApi().getInviteUrl()));
|
||||
event.getUser().setFreeze(true);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
package net.essentialsx.discordlink.rolesync;
|
||||
|
||||
import com.earth2me.essentials.UUIDPlayer;
|
||||
import com.google.common.collect.BiMap;
|
||||
import net.essentialsx.api.v2.events.discordlink.DiscordLinkStatusChangeEvent;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionMember;
|
||||
import net.essentialsx.api.v2.services.discord.InteractionRole;
|
||||
import net.essentialsx.discordlink.EssentialsDiscordLink;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public class RoleSyncManager implements Listener {
|
||||
private final EssentialsDiscordLink ess;
|
||||
private final Map<String, InteractionRole> groupToRoleMap = new HashMap<>();
|
||||
private final Map<String, String> roleIdToGroupMap = new HashMap<>();
|
||||
|
||||
public RoleSyncManager(final EssentialsDiscordLink ess) {
|
||||
this.ess = ess;
|
||||
Bukkit.getPluginManager().registerEvents(this, ess);
|
||||
onReload();
|
||||
this.ess.getEss().runTaskTimerAsynchronously(() -> {
|
||||
if (groupToRoleMap.isEmpty() && roleIdToGroupMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BiMap<String, String> uuidToDiscordCopy = ess.getAccountStorage().getRawStorageMap();
|
||||
final Map<String, InteractionRole> groupToRoleMapCopy = new HashMap<>(groupToRoleMap);
|
||||
final Map<String, String> roleIdToGroupMapCopy = new HashMap<>(roleIdToGroupMap);
|
||||
final boolean primaryOnly = ess.getSettings().isRoleSyncPrimaryGroupOnly();
|
||||
final boolean removeGroups = ess.getSettings().isRoleSyncRemoveGroups();
|
||||
final boolean removeRoles = ess.getSettings().isRoleSyncRemoveRoles();
|
||||
for (final Map.Entry<String, String> entry : uuidToDiscordCopy.entrySet()) {
|
||||
sync(new UUIDPlayer(UUID.fromString(entry.getKey())), entry.getValue(), groupToRoleMapCopy, roleIdToGroupMapCopy, primaryOnly, removeGroups, removeRoles);
|
||||
}
|
||||
}, 0, ess.getSettings().getRoleSyncResyncDelay() * 1200L);
|
||||
}
|
||||
|
||||
public void sync(final UUID uuid, final String discordId) {
|
||||
final Map<String, InteractionRole> groupToRoleMapCopy = new HashMap<>(groupToRoleMap);
|
||||
final Map<String, String> roleIdToGroupMapCopy = new HashMap<>(roleIdToGroupMap);
|
||||
final boolean primaryOnly = ess.getSettings().isRoleSyncPrimaryGroupOnly();
|
||||
final boolean removeGroups = ess.getSettings().isRoleSyncRemoveGroups();
|
||||
final boolean removeRoles = ess.getSettings().isRoleSyncRemoveRoles();
|
||||
sync(new UUIDPlayer(uuid), discordId, groupToRoleMapCopy, roleIdToGroupMapCopy, primaryOnly, removeGroups, removeRoles);
|
||||
}
|
||||
|
||||
public void sync(final Player player, final String discordId, final Map<String, InteractionRole> groupToRoleMap, final Map<String, String> roleIdToGroupMap,
|
||||
final boolean primaryOnly, final boolean removeGroups, final boolean removeRoles) {
|
||||
final List<String> groups = primaryOnly ?
|
||||
Collections.singletonList(ess.getEss().getPermissionsHandler().getGroup(player)) : ess.getEss().getPermissionsHandler().getGroups(player);
|
||||
final InteractionMember member = ess.getApi().getMemberById(discordId).join();
|
||||
|
||||
if (member == null) {
|
||||
if (ess.getSettings().isUnlinkOnLeave()) {
|
||||
ess.getLinkManager().removeAccount(ess.getEss().getUser(player.getUniqueId()), DiscordLinkStatusChangeEvent.Cause.UNSYNC_LEAVE);
|
||||
} else {
|
||||
unSync(player.getUniqueId(), discordId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final List<InteractionRole> toAdd = new ArrayList<>();
|
||||
final List<InteractionRole> toRemove = new ArrayList<>();
|
||||
|
||||
for (final Map.Entry<String, InteractionRole> entry : groupToRoleMap.entrySet()) {
|
||||
if (groups.contains(entry.getKey()) && !member.hasRole(entry.getValue())) {
|
||||
toAdd.add(entry.getValue());
|
||||
} else if (removeRoles && !groups.contains(entry.getKey()) && member.hasRole(entry.getValue())) {
|
||||
toRemove.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
for (final Map.Entry<String, String> entry : roleIdToGroupMap.entrySet()) {
|
||||
if (member.hasRole(entry.getKey()) && !groups.contains(entry.getValue())) {
|
||||
ess.getEss().getPermissionsHandler().addToGroup(player, entry.getValue());
|
||||
} else if (removeGroups && !member.hasRole(entry.getKey()) && groups.contains(entry.getValue())) {
|
||||
ess.getEss().getPermissionsHandler().removeFromGroup(player, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (toAdd.isEmpty() && toRemove.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ess.getApi().modifyMemberRoles(member, toAdd, toRemove);
|
||||
}
|
||||
|
||||
public void unSync(final UUID uuid, final String discordId) {
|
||||
final boolean removeGroups = ess.getSettings().isRoleSyncRemoveGroups();
|
||||
final boolean removeRoles = ess.getSettings().isRoleSyncRemoveRoles();
|
||||
if (!removeGroups && !removeRoles) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, InteractionRole> groupToRoleMapCopy = new HashMap<>(groupToRoleMap);
|
||||
final Map<String, String> roleIdToGroupMapCopy = new HashMap<>(roleIdToGroupMap);
|
||||
|
||||
final Player player = new UUIDPlayer(uuid);
|
||||
final InteractionMember member = ess.getApi().getMemberById(discordId).join();
|
||||
|
||||
if (removeGroups) {
|
||||
for (final String group : roleIdToGroupMapCopy.values()) {
|
||||
ess.getEss().getPermissionsHandler().removeFromGroup(player, group);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the member is no longer in the guild (null), they don't have any roles anyway.
|
||||
if (removeRoles && member != null) {
|
||||
ess.getApi().modifyMemberRoles(member, null, groupToRoleMapCopy.values());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
ess.getEss().runTaskAsynchronously(() -> {
|
||||
if (ess.getLinkManager().isLinked(event.getPlayer().getUniqueId())) {
|
||||
sync(event.getPlayer().getUniqueId(), ess.getLinkManager().getDiscordId(event.getPlayer().getUniqueId()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onReload() {
|
||||
groupToRoleMap.clear();
|
||||
roleIdToGroupMap.clear();
|
||||
|
||||
final List<String> groups = ess.getEss().getPermissionsHandler().getGroups();
|
||||
|
||||
for (final Map.Entry<String, String> entry : ess.getSettings().getRoleSyncGroups().entrySet()) {
|
||||
if (isExampleRole(entry.getValue())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String group = entry.getKey();
|
||||
final InteractionRole role = ess.getApi().getRole(entry.getValue());
|
||||
if (!groups.contains(group)) {
|
||||
ess.getLogger().warning(tl("discordLinkInvalidGroup", group, entry.getValue(), groups));
|
||||
continue;
|
||||
}
|
||||
if (role == null) {
|
||||
ess.getLogger().warning(tl("discordLinkInvalidRole", entry.getValue(), group));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (role.isManaged() || role.isPublicRole()) {
|
||||
ess.getLogger().warning(tl("discordLinkInvalidRoleManaged", role.getName(), role.getId()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!role.canInteract()) {
|
||||
ess.getLogger().warning(tl("discordLinkInvalidRoleInteract", role.getName(), role.getId()));
|
||||
continue;
|
||||
}
|
||||
|
||||
groupToRoleMap.put(group, role);
|
||||
}
|
||||
|
||||
for (final Map.Entry<String, String> entry : ess.getSettings().getRoleSyncRoles().entrySet()) {
|
||||
if (isExampleRole(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final InteractionRole role = ess.getApi().getRole(entry.getKey());
|
||||
final String group = entry.getValue();
|
||||
if (role == null) {
|
||||
ess.getLogger().warning(tl("discordLinkInvalidRole", entry.getKey(), group));
|
||||
continue;
|
||||
}
|
||||
if (!groups.contains(group)) {
|
||||
ess.getLogger().warning(tl("discordLinkInvalidGroup", group, entry.getKey(), groups));
|
||||
continue;
|
||||
}
|
||||
|
||||
roleIdToGroupMap.put(role.getId(), group);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExampleRole(final String role) {
|
||||
return role.equals("0") || role.equals("11111111111111111") || role.equals("22222222222222222") || role.equals("33333333333333333");
|
||||
}
|
||||
}
|
52
EssentialsDiscordLink/src/main/resources/config.yml
Normal file
52
EssentialsDiscordLink/src/main/resources/config.yml
Normal file
@ -0,0 +1,52 @@
|
||||
#############################################################
|
||||
# +-------------------------------------------------------+ #
|
||||
# | EssentialsX DiscordLink | #
|
||||
# +-------------------------------------------------------+ #
|
||||
#############################################################
|
||||
|
||||
# This is the config file for EssentialsXDiscordLink.
|
||||
# This config was generated for version ${full.version}.
|
||||
|
||||
# The desired behavior when a player hasn't linked their Minecraft account to Discord.
|
||||
# Accepts the following values:
|
||||
# - kick: Kicks the player with a link code and requires they link their discord account before they can join.
|
||||
# - freeze: Prevents player from moving/interacting/doing commands when they join until they link their discord account.
|
||||
# - none: Places no restrictions on players for unlinked accounts.
|
||||
link-policy: none
|
||||
|
||||
# Whether to ignore Discord messages from unlinked members and hide them from Minecraft chat.
|
||||
block-unlinked-chat: false
|
||||
|
||||
# Whether someone's Minecraft account should be unlinked when they leave the Discord server.
|
||||
unlink-on-leave: true
|
||||
|
||||
# Whether linked player's incoming mail should be DM'd to them on Discord.
|
||||
relay-mail: true
|
||||
|
||||
# MC group to Discord role sync settings
|
||||
# Allows for the ability to give players discord roles based on their Minecraft groups and/or give players Minecraft
|
||||
# groups based on their Discord roles.
|
||||
role-sync:
|
||||
# Whether EssentialsX DiscordLink should remove synced Discord roles from players who unlink their Minecraft account,
|
||||
# leave the Discord server, or who no longer have the groups that awarded them the role in the first place.
|
||||
remove-roles: true
|
||||
# Whether EssentialsX DiscordLink should remove synced Minecraft groups from players who unlink their Discord account,
|
||||
# or who no longer have the Discord roles that awarded them the group in the first place.
|
||||
remove-groups: true
|
||||
# The amount of time (in minutes) between which EssentialsX DiscordLink should audit player groups/roles.
|
||||
# Requires a restart after changing.
|
||||
resync-delay: 5
|
||||
# Whether EssentialsX DiscordLink should only consider the primary group of Minecraft users
|
||||
primary-group-only: false
|
||||
# Minecraft group to Discord role ID synchronization.
|
||||
# Players in the following groups listed here will receive the corresponding role ID on discord when they link
|
||||
# their Minecraft account to their Discord account.
|
||||
groups:
|
||||
default: 000000000000000000
|
||||
admin: 11111111111111111
|
||||
# Discord role ID to Minecraft group synchronization.
|
||||
# Users with the following roles listed here will receive the corresponding group in Minecraft when they link
|
||||
# their Discord account to their Minecraft account.
|
||||
roles:
|
||||
22222222222222222: vip
|
||||
33333333333333333: booster
|
18
EssentialsDiscordLink/src/main/resources/plugin.yml
Normal file
18
EssentialsDiscordLink/src/main/resources/plugin.yml
Normal file
@ -0,0 +1,18 @@
|
||||
name: EssentialsDiscordLink
|
||||
main: net.essentialsx.discordlink.EssentialsDiscordLink
|
||||
# Note to developers: This next line cannot change, or the automatic versioning system will break.
|
||||
version: ${full.version}
|
||||
website: https://essentialsx.net/
|
||||
description: EssentialsX Discord addon which allows you link your Minecraft and Discord accounts together.
|
||||
authors: [JRoy]
|
||||
depend: [EssentialsDiscord]
|
||||
api-version: 1.13
|
||||
commands:
|
||||
link:
|
||||
description: Generates a code to link your Minecraft account to Discord.
|
||||
usage: /<command>
|
||||
aliases: [elink, discordlink, ediscordlink]
|
||||
unlink:
|
||||
description: Unlinks your Minecraft account from any associated Discord account.
|
||||
usage: /<command>
|
||||
aliases: [eunlink, discordunlink, ediscordunlink]
|
@ -26,7 +26,7 @@ however, have some new requirements:
|
||||
* **EssentialsX requires CraftBukkit, Spigot or Paper to run.** Other server software may work, but these are not tested
|
||||
by the team and we may not be able to help with any issues that occur.
|
||||
* **EssentialsX currently supports Minecraft versions 1.8.8, 1.9.4, 1.10.2, 1.11.2, 1.12.2, 1.13.2, 1.14.4, 1.15.2,
|
||||
1.16.5, 1.17.1, 1.18.2, and 1.19.2.**
|
||||
1.16.5, 1.17.1, 1.18.2, and 1.19.3.**
|
||||
* **EssentialsX currently requires Java 8 or higher.** We recommend using the latest Java version supported by your
|
||||
server software.
|
||||
* **EssentialsX requires [Vault](http://dev.bukkit.org/bukkit-plugins/vault/) to enable using chat prefix/suffixes and
|
||||
|
@ -10,7 +10,7 @@ plugins {
|
||||
val baseExtension = extensions.create<EssentialsBaseExtension>("essentials", project)
|
||||
|
||||
val checkstyleVersion = "8.36.2"
|
||||
val spigotVersion = "1.19.2-R0.1-SNAPSHOT"
|
||||
val spigotVersion = "1.19.3-R0.1-SNAPSHOT"
|
||||
val junit5Version = "5.7.0"
|
||||
val mockitoVersion = "3.2.0"
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@ public final class ReflUtil {
|
||||
public static final NMSVersion V1_17_R1 = NMSVersion.fromString("v1_17_R1");
|
||||
public static final NMSVersion V1_18_R1 = NMSVersion.fromString("v1_18_R1");
|
||||
public static final NMSVersion V1_19_R1 = NMSVersion.fromString("v1_19_R1");
|
||||
public static final NMSVersion V1_19_R2 = NMSVersion.fromString("v1_19_R2");
|
||||
private static final Map<String, Class<?>> classCache = new HashMap<>();
|
||||
private static final Table<Class<?>, String, Method> methodCache = HashBasedTable.create();
|
||||
private static final Table<Class<?>, MethodParams, Method> methodParamCache = HashBasedTable.create();
|
||||
|
@ -18,7 +18,9 @@ public class ReflServerStateProvider implements ServerStateProvider {
|
||||
MethodHandle isRunning = null;
|
||||
|
||||
final String MDFIVEMAGICLETTER;
|
||||
if (ReflUtil.getNmsVersionObject().isHigherThanOrEqualTo(ReflUtil.V1_19_R1)) {
|
||||
if (ReflUtil.getNmsVersionObject().isHigherThanOrEqualTo(ReflUtil.V1_19_R2)) {
|
||||
MDFIVEMAGICLETTER = "v";
|
||||
} else if (ReflUtil.getNmsVersionObject().isHigherThanOrEqualTo(ReflUtil.V1_19_R1)) {
|
||||
MDFIVEMAGICLETTER = "u";
|
||||
} else if (ReflUtil.getNmsVersionObject().isHigherThanOrEqualTo(ReflUtil.V1_18_R1)) {
|
||||
MDFIVEMAGICLETTER = "v";
|
||||
|
@ -37,6 +37,7 @@ sequenceOf(
|
||||
"AntiBuild",
|
||||
"Chat",
|
||||
"Discord",
|
||||
"DiscordLink",
|
||||
"GeoIP",
|
||||
"Protect",
|
||||
"Spawn",
|
||||
|
Loading…
Reference in New Issue
Block a user