Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6f139c47e | ||
|
|
1bae159f94 | ||
|
|
43d2835087 | ||
|
|
cb2d4d6a34 | ||
|
|
9bb73d8e90 | ||
|
|
86c4867206 | ||
|
|
43e13248d6 | ||
|
|
f6ff45228a | ||
|
|
af1c20f5c5 | ||
|
|
9ac564c345 | ||
|
|
857d51a7a1 | ||
|
|
ab5f8cee5a | ||
|
|
bdd518b280 | ||
|
|
494c1a87b2 | ||
|
|
f78f92b5f6 | ||
|
|
048b56c58c | ||
|
|
75dec2bf49 | ||
|
|
7119da13b7 | ||
|
|
6a8637922b | ||
|
|
abce7b898c | ||
|
|
562d7f382c | ||
|
|
c72b15c3a6 |
@@ -34,7 +34,7 @@ allprojects {
|
|||||||
// bStats, mcMMO, BentoBox
|
// bStats, mcMMO, BentoBox
|
||||||
maven { url 'https://repo.codemc.org/repository/maven-public/' }
|
maven { url 'https://repo.codemc.org/repository/maven-public/' }
|
||||||
|
|
||||||
// Spigot API
|
// Spigot API, Bungee API
|
||||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
||||||
|
|
||||||
// PlaceholderAPI
|
// PlaceholderAPI
|
||||||
@@ -54,6 +54,12 @@ allprojects {
|
|||||||
|
|
||||||
// CombatLogX
|
// CombatLogX
|
||||||
maven { url 'https://nexus.sirblobman.xyz/repository/public/' }
|
maven { url 'https://nexus.sirblobman.xyz/repository/public/' }
|
||||||
|
|
||||||
|
// IridiumSkyblock
|
||||||
|
maven { url 'https://nexus.iridiumdevelopment.net/repository/maven-releases/' }
|
||||||
|
|
||||||
|
// Velocity
|
||||||
|
maven { url 'https://repo.velocitypowered.com/snapshots/' }
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.willfp.eco.core;
|
package com.willfp.eco.core;
|
||||||
|
|
||||||
|
import com.willfp.eco.core.integrations.economy.EconomyManager;
|
||||||
import com.willfp.eco.core.proxy.ProxyConstants;
|
import com.willfp.eco.core.proxy.ProxyConstants;
|
||||||
import com.willfp.eco.util.ClassUtils;
|
import com.willfp.eco.util.ClassUtils;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -31,8 +32,11 @@ public class Prerequisite {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requires the server to be running an implementation of paper.
|
* Requires the server to have vault installed.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link EconomyManager#hasRegistrations()} instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static final Prerequisite HAS_VAULT = new Prerequisite(
|
public static final Prerequisite HAS_VAULT = new Prerequisite(
|
||||||
() -> ClassUtils.exists("net.milkbowl.vault.economy.Economy"),
|
() -> ClassUtils.exists("net.milkbowl.vault.economy.Economy"),
|
||||||
"Requires server to have vault"
|
"Requires server to have vault"
|
||||||
@@ -46,6 +50,22 @@ public class Prerequisite {
|
|||||||
"Requires server to be running 1.17+"
|
"Requires server to be running 1.17+"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires the server to be running an implementation of BungeeCord.
|
||||||
|
*/
|
||||||
|
public static final Prerequisite HAS_BUNGEECORD = new Prerequisite(
|
||||||
|
() -> ClassUtils.exists("net.md_5.bungee.api.event.ServerConnectedEvent"),
|
||||||
|
"Requires server to be running BungeeCord (or a fork)"
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires the server to be running an implementation of Velocity.
|
||||||
|
*/
|
||||||
|
public static final Prerequisite HAS_VELOCITY = new Prerequisite(
|
||||||
|
() -> ClassUtils.exists("com.velocitypowered.api.event.player.ServerConnectedEvent"),
|
||||||
|
"Requires server to be running Velocity (or a fork)"
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the necessary prerequisite condition has been met.
|
* If the necessary prerequisite condition has been met.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ public interface PlayerProfileHandler {
|
|||||||
* Save all player data.
|
* Save all player data.
|
||||||
*
|
*
|
||||||
* @param async If the saving should be done asynchronously.
|
* @param async If the saving should be done asynchronously.
|
||||||
|
* @deprecated async is now handled automatically depending on implementation.
|
||||||
*/
|
*/
|
||||||
void saveAll(boolean async);
|
@Deprecated
|
||||||
|
default void saveAll(boolean async) {
|
||||||
|
saveAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save all player data.
|
||||||
|
*/
|
||||||
|
void saveAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import lombok.Getter;
|
|||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A persistent data key is a key with a type that can be stored about an offline player.
|
* A persistent data key is a key with a type that can be stored about an offline player.
|
||||||
*
|
*
|
||||||
@@ -54,4 +56,13 @@ public class PersistentDataKey<T> {
|
|||||||
+ ", type=" + type
|
+ ", type=" + type
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all persistent data keys.
|
||||||
|
*
|
||||||
|
* @return The keys.
|
||||||
|
*/
|
||||||
|
public static Set<PersistentDataKey<?>> values() {
|
||||||
|
return Eco.getHandler().getKeyRegistry().getRegisteredKeys();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public interface AntigriefWrapper extends Integration {
|
|||||||
*
|
*
|
||||||
* @param player The player.
|
* @param player The player.
|
||||||
* @param block The block.
|
* @param block The block.
|
||||||
* @return If player cna break block.
|
* @return If player can break block.
|
||||||
*/
|
*/
|
||||||
boolean canBreakBlock(Player player, Block block);
|
boolean canBreakBlock(Player player, Block block);
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,11 @@ public abstract class AbstractItemStackBuilder<T extends ItemMeta, U extends Abs
|
|||||||
@Override
|
@Override
|
||||||
public U addEnchantment(@NotNull final Enchantment enchantment,
|
public U addEnchantment(@NotNull final Enchantment enchantment,
|
||||||
final int level) {
|
final int level) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
if (enchantment == null) {
|
||||||
|
return (U) this;
|
||||||
|
}
|
||||||
|
|
||||||
meta.addEnchant(enchantment, level, true);
|
meta.addEnchant(enchantment, level, true);
|
||||||
return (U) this;
|
return (U) this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ dependencies {
|
|||||||
compileOnly 'com.github.MilkBowl:VaultAPI:1.7'
|
compileOnly 'com.github.MilkBowl:VaultAPI:1.7'
|
||||||
compileOnly 'world.bentobox:bentobox:1.17.3-SNAPSHOT'
|
compileOnly 'world.bentobox:bentobox:1.17.3-SNAPSHOT'
|
||||||
compileOnly 'com.google.guava:guava:31.0.1-jre'
|
compileOnly 'com.google.guava:guava:31.0.1-jre'
|
||||||
|
compileOnly 'com.iridium:IridiumSkyblock:3.1.2'
|
||||||
|
compileOnly 'net.md-5:bungeecord-api:1.16-R0.5-SNAPSHOT'
|
||||||
|
compileOnly 'com.velocitypowered:velocity-api:3.0.0-SNAPSHOT'
|
||||||
|
|
||||||
// CombatLogX V10 + NewbieHelper Expansion
|
// CombatLogX V10 + NewbieHelper Expansion
|
||||||
compileOnly 'com.SirBlobman.combatlogx:CombatLogX-API:10.0.0.0-SNAPSHOT'
|
compileOnly 'com.SirBlobman.combatlogx:CombatLogX-API:10.0.0.0-SNAPSHOT'
|
||||||
|
|||||||
@@ -24,18 +24,11 @@ import com.willfp.eco.proxy.FastItemStackFactoryProxy
|
|||||||
import com.willfp.eco.proxy.SkullProxy
|
import com.willfp.eco.proxy.SkullProxy
|
||||||
import com.willfp.eco.proxy.TPSProxy
|
import com.willfp.eco.proxy.TPSProxy
|
||||||
import com.willfp.eco.spigot.arrows.ArrowDataListener
|
import com.willfp.eco.spigot.arrows.ArrowDataListener
|
||||||
import com.willfp.eco.spigot.data.DataListener
|
import com.willfp.eco.spigot.data.*
|
||||||
import com.willfp.eco.spigot.data.EcoPlayerProfileHandler
|
|
||||||
import com.willfp.eco.spigot.data.PlayerBlockListener
|
|
||||||
import com.willfp.eco.spigot.data.storage.DataHandler
|
import com.willfp.eco.spigot.data.storage.DataHandler
|
||||||
import com.willfp.eco.spigot.data.storage.MySQLDataHandler
|
import com.willfp.eco.spigot.data.storage.MySQLDataHandler
|
||||||
import com.willfp.eco.spigot.data.storage.YamlDataHandler
|
import com.willfp.eco.spigot.data.storage.YamlDataHandler
|
||||||
import com.willfp.eco.spigot.display.PacketAutoRecipe
|
import com.willfp.eco.spigot.display.*
|
||||||
import com.willfp.eco.spigot.display.PacketChat
|
|
||||||
import com.willfp.eco.spigot.display.PacketOpenWindowMerchant
|
|
||||||
import com.willfp.eco.spigot.display.PacketSetCreativeSlot
|
|
||||||
import com.willfp.eco.spigot.display.PacketSetSlot
|
|
||||||
import com.willfp.eco.spigot.display.PacketWindowItems
|
|
||||||
import com.willfp.eco.spigot.display.frame.clearFrames
|
import com.willfp.eco.spigot.display.frame.clearFrames
|
||||||
import com.willfp.eco.spigot.drops.CollatedRunnable
|
import com.willfp.eco.spigot.drops.CollatedRunnable
|
||||||
import com.willfp.eco.spigot.eventlisteners.EntityDeathByEntityListeners
|
import com.willfp.eco.spigot.eventlisteners.EntityDeathByEntityListeners
|
||||||
@@ -46,22 +39,8 @@ import com.willfp.eco.spigot.eventlisteners.armor.ArmorListener
|
|||||||
import com.willfp.eco.spigot.gui.GUIListener
|
import com.willfp.eco.spigot.gui.GUIListener
|
||||||
import com.willfp.eco.spigot.integrations.afk.AFKIntegrationCMI
|
import com.willfp.eco.spigot.integrations.afk.AFKIntegrationCMI
|
||||||
import com.willfp.eco.spigot.integrations.afk.AFKIntegrationEssentials
|
import com.willfp.eco.spigot.integrations.afk.AFKIntegrationEssentials
|
||||||
import com.willfp.eco.spigot.integrations.anticheat.AnticheatAAC
|
import com.willfp.eco.spigot.integrations.anticheat.*
|
||||||
import com.willfp.eco.spigot.integrations.anticheat.AnticheatAlice
|
import com.willfp.eco.spigot.integrations.antigrief.*
|
||||||
import com.willfp.eco.spigot.integrations.anticheat.AnticheatMatrix
|
|
||||||
import com.willfp.eco.spigot.integrations.anticheat.AnticheatNCP
|
|
||||||
import com.willfp.eco.spigot.integrations.anticheat.AnticheatSpartan
|
|
||||||
import com.willfp.eco.spigot.integrations.anticheat.AnticheatVulcan
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefBentoBox
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefCombatLogXV10
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefCombatLogXV11
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefFactionsUUID
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefGriefPrevention
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefKingdoms
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefLands
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefSuperiorSkyblock2
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefTowny
|
|
||||||
import com.willfp.eco.spigot.integrations.antigrief.AntigriefWorldGuard
|
|
||||||
import com.willfp.eco.spigot.integrations.customitems.CustomItemsHeadDatabase
|
import com.willfp.eco.spigot.integrations.customitems.CustomItemsHeadDatabase
|
||||||
import com.willfp.eco.spigot.integrations.customitems.CustomItemsItemsAdder
|
import com.willfp.eco.spigot.integrations.customitems.CustomItemsItemsAdder
|
||||||
import com.willfp.eco.spigot.integrations.customitems.CustomItemsOraxen
|
import com.willfp.eco.spigot.integrations.customitems.CustomItemsOraxen
|
||||||
@@ -144,6 +123,10 @@ abstract class EcoSpigotPlugin : EcoPlugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun handleDisable() {
|
override fun handleDisable() {
|
||||||
|
this.logger.info("Saving player data...")
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler).saveAllBlocking()
|
||||||
|
this.logger.info("Saved player data! Took ${System.currentTimeMillis() - start}ms")
|
||||||
Eco.getHandler().adventure?.close()
|
Eco.getHandler().adventure?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +141,7 @@ abstract class EcoSpigotPlugin : EcoPlugin(
|
|||||||
this.scheduler.runTimer(
|
this.scheduler.runTimer(
|
||||||
{
|
{
|
||||||
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler)
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler)
|
||||||
.autosave(this.configYml.getBool("autosave.async"))
|
.autosave()
|
||||||
},
|
},
|
||||||
this.configYml.getInt("autosave.ticks").toLong(),
|
this.configYml.getInt("autosave.ticks").toLong(),
|
||||||
this.configYml.getInt("autosave.ticks").toLong()
|
this.configYml.getInt("autosave.ticks").toLong()
|
||||||
@@ -173,6 +156,8 @@ abstract class EcoSpigotPlugin : EcoPlugin(
|
|||||||
override fun loadIntegrationLoaders(): List<IntegrationLoader> {
|
override fun loadIntegrationLoaders(): List<IntegrationLoader> {
|
||||||
return listOf(
|
return listOf(
|
||||||
// AntiGrief
|
// AntiGrief
|
||||||
|
IntegrationLoader("IridiumSkyblock") { AntigriefManager.register(AntigriefIridiumSkyblock()) },
|
||||||
|
IntegrationLoader("DeluxeCombat") { AntigriefManager.register(AntigriefDeluxeCombat()) },
|
||||||
IntegrationLoader("SuperiorSkyblock2") { AntigriefManager.register(AntigriefSuperiorSkyblock2()) },
|
IntegrationLoader("SuperiorSkyblock2") { AntigriefManager.register(AntigriefSuperiorSkyblock2()) },
|
||||||
IntegrationLoader("BentoBox") { AntigriefManager.register(AntigriefBentoBox()) },
|
IntegrationLoader("BentoBox") { AntigriefManager.register(AntigriefBentoBox()) },
|
||||||
IntegrationLoader("WorldGuard") { AntigriefManager.register(AntigriefWorldGuard()) },
|
IntegrationLoader("WorldGuard") { AntigriefManager.register(AntigriefWorldGuard()) },
|
||||||
@@ -248,7 +233,7 @@ abstract class EcoSpigotPlugin : EcoPlugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun loadListeners(): List<Listener> {
|
override fun loadListeners(): List<Listener> {
|
||||||
return listOf(
|
val listeners = mutableListOf(
|
||||||
NaturalExpGainListeners(),
|
NaturalExpGainListeners(),
|
||||||
ArmorListener(),
|
ArmorListener(),
|
||||||
EntityDeathByEntityListeners(this),
|
EntityDeathByEntityListeners(this),
|
||||||
@@ -260,5 +245,15 @@ abstract class EcoSpigotPlugin : EcoPlugin(
|
|||||||
DataListener(),
|
DataListener(),
|
||||||
PlayerBlockListener(this)
|
PlayerBlockListener(this)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (Prerequisite.HAS_BUNGEECORD.isMet) {
|
||||||
|
listeners.add(BungeeDataListener())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Prerequisite.HAS_VELOCITY.isMet) {
|
||||||
|
listeners.add(VelocityDataListener())
|
||||||
|
}
|
||||||
|
|
||||||
|
return listeners
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.willfp.eco.spigot.data
|
||||||
|
|
||||||
|
import com.willfp.eco.core.Eco
|
||||||
|
import net.md_5.bungee.api.event.ServerConnectedEvent
|
||||||
|
import net.md_5.bungee.api.event.ServerDisconnectEvent
|
||||||
|
import net.md_5.bungee.api.event.ServerSwitchEvent
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
|
||||||
|
class BungeeDataListener : Listener {
|
||||||
|
@EventHandler
|
||||||
|
fun onConnected(event: ServerConnectedEvent) {
|
||||||
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler).unloadPlayer(event.player.uniqueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onDisconnect(event: ServerDisconnectEvent) {
|
||||||
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler).unloadPlayer(event.player.uniqueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onSwitch(event: ServerSwitchEvent) {
|
||||||
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler).unloadPlayer(event.player.uniqueId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,11 +11,12 @@ class DataListener : Listener {
|
|||||||
@EventHandler
|
@EventHandler
|
||||||
fun onLeave(event: PlayerQuitEvent) {
|
fun onLeave(event: PlayerQuitEvent) {
|
||||||
PlayerUtils.updateSavedDisplayName(event.player)
|
PlayerUtils.updateSavedDisplayName(event.player)
|
||||||
Eco.getHandler().playerProfileHandler.savePlayer(event.player.uniqueId)
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler).unloadPlayer(event.player.uniqueId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
fun onJoin(event: PlayerJoinEvent) {
|
fun onJoin(event: PlayerJoinEvent) {
|
||||||
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler).unloadPlayer(event.player.uniqueId)
|
||||||
PlayerUtils.updateSavedDisplayName(event.player)
|
PlayerUtils.updateSavedDisplayName(event.player)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import com.willfp.eco.core.data.keys.PersistentDataKey
|
|||||||
import com.willfp.eco.internal.data.EcoPlayerProfile
|
import com.willfp.eco.internal.data.EcoPlayerProfile
|
||||||
import com.willfp.eco.spigot.EcoSpigotPlugin
|
import com.willfp.eco.spigot.EcoSpigotPlugin
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
class EcoPlayerProfileHandler(
|
class EcoPlayerProfileHandler(
|
||||||
private val plugin: EcoSpigotPlugin
|
private val plugin: EcoSpigotPlugin
|
||||||
@@ -32,40 +32,24 @@ class EcoPlayerProfileHandler(
|
|||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unloadPlayer(uuid: UUID) {
|
||||||
|
handler.savePlayer(uuid)
|
||||||
|
loaded.remove(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
override fun savePlayer(uuid: UUID) {
|
override fun savePlayer(uuid: UUID) {
|
||||||
writeToHandler(uuid)
|
handler.savePlayer(uuid)
|
||||||
saveToHandler()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeToHandler(uuid: UUID) {
|
override fun saveAll() {
|
||||||
val profile = load(uuid)
|
handler.saveAll(loaded.keys.toList())
|
||||||
|
|
||||||
for (key in Eco.getHandler().keyRegistry.registeredKeys) {
|
|
||||||
handler.write(uuid, key.key, profile.read(key) ?: key.defaultValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveToHandler() {
|
fun saveAllBlocking() {
|
||||||
handler.save()
|
handler.saveAllBlocking(loaded.keys.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveAll(async: Boolean) {
|
fun autosave() {
|
||||||
val saver = {
|
|
||||||
for ((uuid, _) in loaded) {
|
|
||||||
writeToHandler(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (async) {
|
|
||||||
plugin.scheduler.runAsync(saver)
|
|
||||||
} else {
|
|
||||||
saver.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun autosave(async: Boolean) {
|
|
||||||
if (Bukkit.getOnlinePlayers().isEmpty()) {
|
if (Bukkit.getOnlinePlayers().isEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -74,7 +58,7 @@ class EcoPlayerProfileHandler(
|
|||||||
plugin.logger.info("Auto-Saving player data!")
|
plugin.logger.info("Auto-Saving player data!")
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAll(async)
|
saveAll()
|
||||||
|
|
||||||
if (plugin.configYml.getBool("autosave.log")) {
|
if (plugin.configYml.getBool("autosave.log")) {
|
||||||
plugin.logger.info("Saved player data!")
|
plugin.logger.info("Saved player data!")
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.willfp.eco.spigot.data
|
||||||
|
|
||||||
|
import com.velocitypowered.api.event.connection.DisconnectEvent
|
||||||
|
import com.velocitypowered.api.event.player.ServerConnectedEvent
|
||||||
|
import com.willfp.eco.core.Eco
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
|
||||||
|
class VelocityDataListener : Listener {
|
||||||
|
@EventHandler
|
||||||
|
fun onConnected(event: ServerConnectedEvent) {
|
||||||
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler).unloadPlayer(event.player.uniqueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onDisconnect(event: DisconnectEvent) {
|
||||||
|
(Eco.getHandler().playerProfileHandler as EcoPlayerProfileHandler).unloadPlayer(event.player.uniqueId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,24 @@
|
|||||||
package com.willfp.eco.spigot.data.storage
|
package com.willfp.eco.spigot.data.storage
|
||||||
|
|
||||||
import org.bukkit.NamespacedKey
|
import org.bukkit.NamespacedKey
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
interface DataHandler {
|
interface DataHandler {
|
||||||
fun save()
|
fun save() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAll(uuids: Iterable<UUID>)
|
||||||
|
|
||||||
|
fun saveAllBlocking(uuids: Iterable<UUID>) {
|
||||||
|
saveAll(uuids)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateKeys() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> write(uuid: UUID, key: NamespacedKey, value: T)
|
fun <T> write(uuid: UUID, key: NamespacedKey, value: T)
|
||||||
|
fun savePlayer(uuid: UUID)
|
||||||
fun <T> read(uuid: UUID, key: NamespacedKey): T?
|
fun <T> read(uuid: UUID, key: NamespacedKey): T?
|
||||||
|
|
||||||
fun updateKeys()
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.willfp.eco.spigot.data.storage
|
package com.willfp.eco.spigot.data.storage
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||||
import com.willfp.eco.core.Eco
|
import com.willfp.eco.core.Eco
|
||||||
|
import com.willfp.eco.core.data.PlayerProfile
|
||||||
import com.willfp.eco.core.data.keys.PersistentDataKey
|
import com.willfp.eco.core.data.keys.PersistentDataKey
|
||||||
import com.willfp.eco.core.data.keys.PersistentDataKeyType
|
import com.willfp.eco.core.data.keys.PersistentDataKeyType
|
||||||
import com.willfp.eco.spigot.EcoSpigotPlugin
|
import com.willfp.eco.spigot.EcoSpigotPlugin
|
||||||
|
import org.apache.logging.log4j.Level
|
||||||
import org.bukkit.NamespacedKey
|
import org.bukkit.NamespacedKey
|
||||||
import org.jetbrains.exposed.dao.id.UUIDTable
|
import org.jetbrains.exposed.dao.id.UUIDTable
|
||||||
import org.jetbrains.exposed.sql.BooleanColumnType
|
import org.jetbrains.exposed.sql.BooleanColumnType
|
||||||
@@ -13,18 +16,25 @@ import org.jetbrains.exposed.sql.DoubleColumnType
|
|||||||
import org.jetbrains.exposed.sql.IntegerColumnType
|
import org.jetbrains.exposed.sql.IntegerColumnType
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
import org.jetbrains.exposed.sql.ResultRow
|
||||||
import org.jetbrains.exposed.sql.SchemaUtils
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
import org.jetbrains.exposed.sql.VarCharColumnType
|
import org.jetbrains.exposed.sql.VarCharColumnType
|
||||||
|
import org.jetbrains.exposed.sql.checkMappingConsistence
|
||||||
|
import org.jetbrains.exposed.sql.exposedLogger
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
|
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class MySQLDataHandler(
|
class MySQLDataHandler(
|
||||||
plugin: EcoSpigotPlugin
|
plugin: EcoSpigotPlugin
|
||||||
) : DataHandler {
|
) : DataHandler {
|
||||||
private val columns = mutableMapOf<String, Column<*>>()
|
private val columns = mutableMapOf<String, Column<*>>()
|
||||||
|
private val threadFactory = ThreadFactoryBuilder().setNameFormat("eco-mysql-thread-%d").build()
|
||||||
|
private val executor = Executors.newCachedThreadPool(threadFactory)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Database.connect(
|
Database.connect(
|
||||||
@@ -40,6 +50,15 @@ class MySQLDataHandler(
|
|||||||
transaction {
|
transaction {
|
||||||
SchemaUtils.create(Players)
|
SchemaUtils.create(Players)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get Exposed to shut the hell up
|
||||||
|
exposedLogger::class.java.getDeclaredField("logger").apply { isAccessible = true }
|
||||||
|
.apply {
|
||||||
|
get(exposedLogger).apply {
|
||||||
|
this.javaClass.getDeclaredMethod("setLevel", Level::class.java)
|
||||||
|
.invoke(this, Level.OFF)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateKeys() {
|
override fun updateKeys() {
|
||||||
@@ -52,21 +71,57 @@ class MySQLDataHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun save() {
|
override fun <T> write(uuid: UUID, key: NamespacedKey, value: T) {
|
||||||
// Do nothing
|
getPlayer(uuid)
|
||||||
|
writeAsserted(uuid, key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T> write(uuid: UUID, key: NamespacedKey, value: T) {
|
private fun <T> writeAsserted(uuid: UUID, key: NamespacedKey, value: T, async: Boolean = true) {
|
||||||
transaction {
|
|
||||||
getPlayer(uuid)
|
|
||||||
val column: Column<T> = getColumn(key.toString()) as Column<T>
|
val column: Column<T> = getColumn(key.toString()) as Column<T>
|
||||||
|
|
||||||
|
fun executeTransaction() {
|
||||||
|
transaction {
|
||||||
Players.update({ Players.id eq uuid }) {
|
Players.update({ Players.id eq uuid }) {
|
||||||
it[column] = value
|
it[column] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (async) {
|
||||||
|
executor.execute { executeTransaction() }
|
||||||
|
} else {
|
||||||
|
executeTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun savePlayer(uuid: UUID) {
|
||||||
|
savePlayer(uuid, async = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveAll(uuids: Iterable<UUID>) {
|
||||||
|
for (uuid in uuids) {
|
||||||
|
savePlayer(uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveAllBlocking(uuids: Iterable<UUID>) {
|
||||||
|
for (uuid in uuids) {
|
||||||
|
savePlayer(uuid, async = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun savePlayer(uuid: UUID, async: Boolean = true) {
|
||||||
|
val profile = PlayerProfile.load(uuid)
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
getPlayer(uuid)
|
||||||
|
|
||||||
|
for (key in Eco.getHandler().keyRegistry.registeredKeys) {
|
||||||
|
writeAsserted(uuid, key.key, profile.read(key), async = async)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun <T> read(uuid: UUID, key: NamespacedKey): T? {
|
override fun <T> read(uuid: UUID, key: NamespacedKey): T? {
|
||||||
var value: T? = null
|
var value: T? = null
|
||||||
transaction {
|
transaction {
|
||||||
@@ -76,9 +131,7 @@ class MySQLDataHandler(
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
object Players : UUIDTable("eco_players") {
|
object Players : UUIDTable("eco_players")
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> registerColumn(key: PersistentDataKey<T>, table: UUIDTable) {
|
private fun <T> registerColumn(key: PersistentDataKey<T>, table: UUIDTable) {
|
||||||
table.apply {
|
table.apply {
|
||||||
@@ -112,12 +165,40 @@ class MySQLDataHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlayer(uuid: UUID): ResultRow {
|
private fun getPlayer(uuid: UUID): ResultRow {
|
||||||
Players.select { Players.id eq uuid }.firstOrNull() ?: run {
|
val player = transaction {
|
||||||
Players.insert {
|
Players.select { Players.id eq uuid }.limit(1).singleOrNull()
|
||||||
it[id] = uuid
|
}
|
||||||
|
|
||||||
|
return if (player != null) {
|
||||||
|
player
|
||||||
|
} else {
|
||||||
|
transaction {
|
||||||
|
Players.insert { it[id] = uuid }
|
||||||
|
}
|
||||||
|
getPlayer(uuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Players.select { Players.id eq uuid }.first()
|
private fun createMissingTablesAndColumnsSilently(table: Table) {
|
||||||
|
with(TransactionManager.current()) {
|
||||||
|
fun execStatements(statements: List<String>) {
|
||||||
|
for (statement in statements) {
|
||||||
|
exec(statement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.dialect.resetCaches()
|
||||||
|
val createStatements = SchemaUtils.createStatements(table)
|
||||||
|
execStatements(createStatements)
|
||||||
|
commit()
|
||||||
|
val alterStatements = SchemaUtils.addMissingColumnsStatements(table)
|
||||||
|
execStatements(alterStatements)
|
||||||
|
commit()
|
||||||
|
val executedStatements = createStatements + alterStatements
|
||||||
|
val modifyTablesStatements = checkMappingConsistence(table).filter { it !in executedStatements }
|
||||||
|
execStatements(modifyTablesStatements)
|
||||||
|
commit()
|
||||||
|
db.dialect.resetCaches()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.willfp.eco.spigot.data.storage
|
package com.willfp.eco.spigot.data.storage
|
||||||
|
|
||||||
|
import com.willfp.eco.core.Eco
|
||||||
import com.willfp.eco.core.config.yaml.YamlBaseConfig
|
import com.willfp.eco.core.config.yaml.YamlBaseConfig
|
||||||
|
import com.willfp.eco.core.data.PlayerProfile
|
||||||
import com.willfp.eco.spigot.EcoSpigotPlugin
|
import com.willfp.eco.spigot.EcoSpigotPlugin
|
||||||
import org.bukkit.NamespacedKey
|
import org.bukkit.NamespacedKey
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class YamlDataHandler(
|
class YamlDataHandler(
|
||||||
@@ -15,8 +17,20 @@ class YamlDataHandler(
|
|||||||
dataYml.save()
|
dataYml.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateKeys() {
|
override fun saveAll(uuids: Iterable<UUID>) {
|
||||||
// Do nothing
|
for (uuid in uuids) {
|
||||||
|
savePlayer(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun savePlayer(uuid: UUID) {
|
||||||
|
val profile = PlayerProfile.load(uuid)
|
||||||
|
|
||||||
|
for (key in Eco.getHandler().keyRegistry.registeredKeys) {
|
||||||
|
write(uuid, key.key, profile.read(key))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T> write(uuid: UUID, key: NamespacedKey, value: T) {
|
override fun <T> write(uuid: UUID, key: NamespacedKey, value: T) {
|
||||||
|
|||||||
@@ -47,14 +47,8 @@ class PacketWindowItems(plugin: EcoPlugin) : AbstractPacketAdapter(plugin, Packe
|
|||||||
handleRateLimit(player)
|
handleRateLimit(player)
|
||||||
|
|
||||||
if (usingAsync(player)) {
|
if (usingAsync(player)) {
|
||||||
executor.execute {
|
fun modifyAndSend(itemStacks: MutableList<ItemStack>, windowId: Int, player: Player) {
|
||||||
try {
|
|
||||||
modifyWindowItems(itemStacks, windowId, player)
|
modifyWindowItems(itemStacks, windowId, player)
|
||||||
} catch (e: Exception) {
|
|
||||||
if (this.getPlugin().configYml.getBool("async-display.log-errors")) {
|
|
||||||
this.getPlugin().logger.warning("Error happened in async processing! Disable async display (/plugins/eco/config.yml) if this is a frequent issue")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val newPacket = packet.deepClone()
|
val newPacket = packet.deepClone()
|
||||||
newPacket.itemListModifier.write(0, itemStacks)
|
newPacket.itemListModifier.write(0, itemStacks)
|
||||||
@@ -63,6 +57,20 @@ class PacketWindowItems(plugin: EcoPlugin) : AbstractPacketAdapter(plugin, Packe
|
|||||||
|
|
||||||
ProtocolLibrary.getProtocolManager().sendServerPacket(player, newPacket)
|
ProtocolLibrary.getProtocolManager().sendServerPacket(player, newPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executor.execute {
|
||||||
|
try {
|
||||||
|
modifyAndSend(itemStacks, windowId, player)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (this.getPlugin().configYml.getBool("async-display.log-errors")) {
|
||||||
|
this.getPlugin().logger.warning("Error happened in async processing! Disable async display (/plugins/eco/config.yml) if this is a frequent issue")
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getPlugin().scheduler.run {
|
||||||
|
modifyAndSend(itemStacks, windowId, player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
packet.itemListModifier.write(0, modifyWindowItems(itemStacks, windowId, player))
|
packet.itemListModifier.write(0, modifyWindowItems(itemStacks, windowId, player))
|
||||||
}
|
}
|
||||||
@@ -91,7 +99,8 @@ class PacketWindowItems(plugin: EcoPlugin) : AbstractPacketAdapter(plugin, Packe
|
|||||||
|
|
||||||
private fun usingAsync(player: Player): Boolean {
|
private fun usingAsync(player: Player): Boolean {
|
||||||
if (this.getPlugin().configYml.getStrings("async-display.disable-on-types", false)
|
if (this.getPlugin().configYml.getStrings("async-display.disable-on-types", false)
|
||||||
.map { it.lowercase() }.contains(player.openInventory.type.name.lowercase())) {
|
.map { it.lowercase() }.contains(player.openInventory.type.name.lowercase())
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.willfp.eco.spigot.integrations.antigrief
|
||||||
|
|
||||||
|
import com.willfp.eco.core.integrations.antigrief.AntigriefWrapper
|
||||||
|
import nl.marido.deluxecombat.api.DeluxeCombatAPI
|
||||||
|
import org.bukkit.Location
|
||||||
|
import org.bukkit.block.Block
|
||||||
|
import org.bukkit.entity.LivingEntity
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
|
||||||
|
class AntigriefDeluxeCombat: AntigriefWrapper {
|
||||||
|
override fun getPluginName(): String {
|
||||||
|
return "DeluxeCombat";
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canBreakBlock(player: Player, block: Block): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canCreateExplosion(player: Player, location: Location): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canPlaceBlock(player: Player, block: Block): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canInjure(player: Player, victim: LivingEntity): Boolean {
|
||||||
|
val api = DeluxeCombatAPI()
|
||||||
|
return when(victim) {
|
||||||
|
is Player -> (api.hasProtection(victim) || !api.hasPvPEnabled(victim)) && !api.isInCombat(victim)
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.willfp.eco.spigot.integrations.antigrief
|
||||||
|
|
||||||
|
import com.iridium.iridiumskyblock.PermissionType
|
||||||
|
import com.iridium.iridiumskyblock.api.IridiumSkyblockAPI
|
||||||
|
import com.willfp.eco.core.integrations.antigrief.AntigriefWrapper
|
||||||
|
import org.bukkit.Location
|
||||||
|
import org.bukkit.block.Block
|
||||||
|
import org.bukkit.entity.LivingEntity
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
|
||||||
|
class AntigriefIridiumSkyblock : AntigriefWrapper {
|
||||||
|
private val api = IridiumSkyblockAPI.getInstance()
|
||||||
|
|
||||||
|
override fun canBreakBlock(
|
||||||
|
player: Player,
|
||||||
|
block: Block
|
||||||
|
): Boolean {
|
||||||
|
return api.getIslandPermission(api.getIslandViaLocation(block.location).orElse(null) ?: return true, api.getUser(player), PermissionType.BLOCK_BREAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canCreateExplosion(
|
||||||
|
player: Player,
|
||||||
|
location: Location
|
||||||
|
): Boolean {
|
||||||
|
return api.getIslandPermission(api.getIslandViaLocation(location).orElse(null) ?: return true, api.getUser(player), PermissionType.BLOCK_BREAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canPlaceBlock(
|
||||||
|
player: Player,
|
||||||
|
block: Block
|
||||||
|
): Boolean {
|
||||||
|
return api.getIslandPermission(api.getIslandViaLocation(block.location).orElse(null) ?: return true, api.getUser(player), PermissionType.BLOCK_PLACE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canInjure(
|
||||||
|
player: Player,
|
||||||
|
victim: LivingEntity
|
||||||
|
): Boolean {
|
||||||
|
return when (victim) {
|
||||||
|
is Player -> api.getIslandViaLocation(victim.location).orElse(null) != null
|
||||||
|
else -> api.getIslandPermission(api.getIslandViaLocation(victim.location).orElse(null) ?: return true, api.getUser(player), PermissionType.KILL_MOBS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPluginName(): String {
|
||||||
|
return "IridiumSkyblock"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is AntigriefWrapper) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return other.pluginName == this.pluginName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return this.pluginName.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ mysql:
|
|||||||
autosave:
|
autosave:
|
||||||
ticks: 20000 # The amount of ticks between autosaves
|
ticks: 20000 # The amount of ticks between autosaves
|
||||||
log: false # If auto-save messages should be sent to console
|
log: false # If auto-save messages should be sent to console
|
||||||
async: false # If saves should be performed asynchronously. May cause bugs without MySQL
|
|
||||||
|
|
||||||
|
|
||||||
# Options to fix villager bugs left behind from old (buggy) versions.
|
# Options to fix villager bugs left behind from old (buggy) versions.
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ softdepend:
|
|||||||
- Essentials
|
- Essentials
|
||||||
- Vault
|
- Vault
|
||||||
- BentoBox
|
- BentoBox
|
||||||
|
- DeluxeCombat
|
||||||
|
- IridiumSkyblock
|
||||||
|
- SuperiorSkyblock2
|
||||||
libraries:
|
libraries:
|
||||||
- 'org.reflections:reflections:0.9.12'
|
- 'org.reflections:reflections:0.9.12'
|
||||||
- 'org.apache.maven:maven-artifact:3.0.3'
|
- 'org.apache.maven:maven-artifact:3.0.3'
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
version = 6.13.1
|
version = 6.13.2
|
||||||
plugin-name = eco
|
plugin-name = eco
|
||||||
BIN
lib/DeluxeCombat API.jar
Normal file
BIN
lib/DeluxeCombat API.jar
Normal file
Binary file not shown.
Reference in New Issue
Block a user