mirror of
https://github.com/Artillex-Studios/AxMinions.git
synced 2025-01-08 19:07:41 +01:00
Close to finish
This commit is contained in:
parent
d5a48830ba
commit
ad0c56a6fd
@ -20,8 +20,6 @@ class Config(file: File, stream: InputStream) {
|
||||
@JvmStatic
|
||||
fun DEFAULT_MINION_LIMIT() = AxMinionsAPI.INSTANCE.getConfig().get("default-minion-limit", 5)
|
||||
@JvmStatic
|
||||
fun ALLOW_FLOATING_MINIONS() = AxMinionsAPI.INSTANCE.getConfig().get("allow-floating-minions", false)
|
||||
@JvmStatic
|
||||
fun ONLY_OWNER_BREAK() = AxMinionsAPI.INSTANCE.getConfig().get("only-owner-break", true)
|
||||
@JvmStatic
|
||||
fun DISPLAY_WARNINGS() = AxMinionsAPI.INSTANCE.getConfig().get("display-warnings", true)
|
||||
|
@ -46,7 +46,7 @@ interface Minion : InventoryHolder {
|
||||
|
||||
fun getOwnerUUID(): UUID
|
||||
|
||||
fun setTool(tool: ItemStack)
|
||||
fun setTool(tool: ItemStack, save: Boolean = true)
|
||||
|
||||
fun getTool(): ItemStack?
|
||||
|
||||
@ -74,7 +74,7 @@ interface Minion : InventoryHolder {
|
||||
|
||||
fun getLinkedChest(): Location?
|
||||
|
||||
fun setDirection(direction: Direction)
|
||||
fun setDirection(direction: Direction, save: Boolean = true)
|
||||
|
||||
fun getDirection(): Direction
|
||||
|
||||
|
@ -49,6 +49,7 @@ abstract class MinionType(private val name: String, private val defaults: InputS
|
||||
val builder = ItemBuilder(config.getSection("item"), Placeholder.unparsed("level", level.toString()), Placeholder.unparsed("actions", actions.toString()))
|
||||
builder.storePersistentData(Keys.MINION_TYPE, PersistentDataType.STRING, name)
|
||||
builder.storePersistentData(Keys.LEVEL, PersistentDataType.INTEGER, level)
|
||||
builder.storePersistentData(Keys.STATISTICS, PersistentDataType.LONG, actions)
|
||||
|
||||
return builder.clonedGet()
|
||||
}
|
||||
@ -76,12 +77,12 @@ abstract class MinionType(private val name: String, private val defaults: InputS
|
||||
private fun <T> get(key: String, level: Int, defaultValue: T?, clazz: Class<T>): T? {
|
||||
var n = defaultValue
|
||||
|
||||
config.getSection("upgrades").getRoutesAsStrings(false).fastFor {
|
||||
config.getSection("upgrades").getRoutesAsStrings(false).forEach {
|
||||
if (it.toInt() > level) {
|
||||
return n
|
||||
}
|
||||
|
||||
if (config.backingDocument.getAsOptional("upgrades.$it.$key", clazz).isEmpty) return@fastFor
|
||||
if (config.backingDocument.getAsOptional("upgrades.$it.$key", clazz).isEmpty) return@forEach
|
||||
|
||||
n = config.get("upgrades.$it.$key")
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
package com.artillexstudios.axminions.api.utils
|
||||
|
||||
import java.util.Collections
|
||||
import java.util.Queue
|
||||
|
||||
inline fun <T> Array<T>.fastFor(action: (T) -> Unit) {
|
||||
val indices = indices
|
||||
for (i in indices) {
|
||||
@ -18,18 +15,9 @@ inline fun <T> List<T>.fastFor(action: (T) -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> Collection<T>.fastFor(action: (T) -> Unit) {
|
||||
if (isEmpty()) return
|
||||
val indices = indices
|
||||
|
||||
for (i in indices) {
|
||||
action(elementAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <K, V> Map<K, V>.fastFor(action: (K, V) -> Unit) {
|
||||
if (isEmpty()) return
|
||||
entries.fastFor {
|
||||
entries.forEach {
|
||||
action(it.key, it.value)
|
||||
}
|
||||
}
|
@ -78,15 +78,7 @@ object MinionUtils {
|
||||
|
||||
FACES.fastFor {
|
||||
val relative = block.getRelative(it)
|
||||
if (visited.contains(relative)) {
|
||||
queue.add(relative)
|
||||
visited.add(relative)
|
||||
}
|
||||
}
|
||||
} else if (type.endsWith("_LEAVES")) {
|
||||
FACES.fastFor {
|
||||
val relative = block.getRelative(it)
|
||||
if (visited.contains(relative)) {
|
||||
if (!visited.contains(relative)) {
|
||||
queue.add(relative)
|
||||
visited.add(relative)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.artillexstudios.axminions.api.warnings
|
||||
|
||||
import com.artillexstudios.axapi.hologram.HologramFactory
|
||||
import com.artillexstudios.axminions.api.config.Config
|
||||
import net.kyori.adventure.text.Component
|
||||
import com.artillexstudios.axminions.api.minions.Minion
|
||||
|
||||
@ -13,6 +14,8 @@ abstract class Warning(private val name: String) {
|
||||
abstract fun getContent(): Component
|
||||
|
||||
fun display(minion: Minion) {
|
||||
if (!Config.DISPLAY_WARNINGS()) return
|
||||
|
||||
if (minion.getWarning() == null) {
|
||||
val hologram = HologramFactory.get()
|
||||
.spawnHologram(minion.getLocation().clone().add(0.0, 1.35, 0.0), minion.getLocation().toString())
|
||||
|
@ -4,6 +4,7 @@ import com.artillexstudios.axapi.AxPlugin
|
||||
import com.artillexstudios.axapi.data.ThreadedQueue
|
||||
import com.artillexstudios.axapi.libs.libby.libby.BukkitLibraryManager
|
||||
import com.artillexstudios.axapi.libs.libby.libby.Library
|
||||
import com.artillexstudios.axapi.nms.v1_20_R2.entity.EntityTracker
|
||||
import com.artillexstudios.axminions.api.AxMinionsAPI
|
||||
import com.artillexstudios.axminions.api.AxMinionsAPIImpl
|
||||
import com.artillexstudios.axminions.api.config.Config
|
||||
@ -22,6 +23,7 @@ import com.artillexstudios.axminions.listeners.MinionInventoryListener
|
||||
import com.artillexstudios.axminions.listeners.MinionPlaceListener
|
||||
import com.artillexstudios.axminions.listeners.WorldListener
|
||||
import com.artillexstudios.axminions.minions.MinionTicker
|
||||
import com.artillexstudios.axminions.minions.Minions
|
||||
import com.artillexstudios.axminions.minions.miniontype.CollectorMinionType
|
||||
import com.artillexstudios.axminions.minions.miniontype.FarmerMinionType
|
||||
import com.artillexstudios.axminions.minions.miniontype.FisherMinionType
|
||||
@ -30,6 +32,8 @@ import com.artillexstudios.axminions.minions.miniontype.MinerMinionType
|
||||
import com.artillexstudios.axminions.minions.miniontype.SellerMinionType
|
||||
import com.artillexstudios.axminions.minions.miniontype.SlayerMinionType
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.bukkit.Bukkit
|
||||
import revxrsal.commands.bukkit.BukkitCommandHandler
|
||||
|
||||
@ -100,9 +104,11 @@ class AxMinionsPlugin : AxPlugin() {
|
||||
}
|
||||
|
||||
// Retroactively load minions for the already loaded worlds
|
||||
Bukkit.getWorlds().fastFor { world ->
|
||||
MinionTypes.getMinionTypes().fastFor { _, v ->
|
||||
dataHandler.loadMinionsForWorld(v, world)
|
||||
dataQueue.submit {
|
||||
Bukkit.getWorlds().fastFor { world ->
|
||||
MinionTypes.getMinionTypes().fastFor { _, v ->
|
||||
dataHandler.loadMinionsForWorld(v, world)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +122,16 @@ class AxMinionsPlugin : AxPlugin() {
|
||||
}
|
||||
|
||||
MinionTicker.startTicking()
|
||||
|
||||
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate({
|
||||
dataQueue.submit {
|
||||
Minions.get().fastFor { pos ->
|
||||
pos.minions.fastFor { minion ->
|
||||
dataHandler.saveMinion(minion)
|
||||
}
|
||||
}
|
||||
}
|
||||
},0, Config.AUTO_SAVE_MINUTES(), TimeUnit.MINUTES)
|
||||
}
|
||||
|
||||
override fun disable() {
|
||||
|
@ -7,7 +7,6 @@ import com.artillexstudios.axminions.api.config.Messages
|
||||
import com.artillexstudios.axminions.api.data.DataHandler
|
||||
import com.artillexstudios.axminions.api.integrations.Integrations
|
||||
import com.artillexstudios.axminions.api.minions.Minion
|
||||
import com.artillexstudios.axminions.api.utils.fastFor
|
||||
import com.artillexstudios.axminions.minions.Minions
|
||||
import org.bukkit.entity.Player
|
||||
import java.io.File
|
||||
@ -41,9 +40,13 @@ class AxMinionsAPIImpl(private val plugin: AxMinionsPlugin) : AxMinionsAPI {
|
||||
override fun getMinionLimit(player: Player): Int {
|
||||
var limit = Config.DEFAULT_MINION_LIMIT()
|
||||
|
||||
player.effectivePermissions.fastFor {
|
||||
player.effectivePermissions.forEach {
|
||||
val permission = it.permission
|
||||
if (!permission.startsWith("axminions.limit.")) return@fastFor
|
||||
if (permission.equals("*", true)) {
|
||||
return Int.MAX_VALUE
|
||||
}
|
||||
|
||||
if (!permission.startsWith("axminions.limit.")) return@forEach
|
||||
if (permission.contains("*")) {
|
||||
return Int.MAX_VALUE
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.artillexstudios.axminions.commands
|
||||
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
|
||||
import com.artillexstudios.axapi.utils.StringUtils
|
||||
import com.artillexstudios.axminions.AxMinionsPlugin
|
||||
import com.artillexstudios.axminions.api.AxMinionsAPI
|
||||
@ -9,6 +8,7 @@ import com.artillexstudios.axminions.api.minions.miniontype.MinionType
|
||||
import com.artillexstudios.axminions.api.minions.miniontype.MinionTypes
|
||||
import com.artillexstudios.axminions.api.utils.fastFor
|
||||
import com.artillexstudios.axminions.converter.LitMinionsConverter
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.entity.Player
|
||||
import revxrsal.commands.annotation.AutoComplete
|
||||
|
@ -118,30 +118,44 @@ class Integrations : Integrations {
|
||||
|
||||
if (Bukkit.getPluginManager().getPlugin("SuperiorSkyblock2") != null) {
|
||||
register(SuperiorSkyBlock2Integration())
|
||||
Bukkit.getConsoleSender()
|
||||
.sendMessage(StringUtils.formatToString("<#33FF33>[AxMinions] Hooked into SuperiorSkyblock2!"))
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().getPlugin("WorldGuard") != null) {
|
||||
register(WorldGuardIntegration())
|
||||
Bukkit.getConsoleSender()
|
||||
.sendMessage(StringUtils.formatToString("<#33FF33>[AxMinions] Hooked into WorldGuard!"))
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().getPlugin("BentoBox") != null) {
|
||||
register(BentoBoxIntegration())
|
||||
Bukkit.getConsoleSender()
|
||||
.sendMessage(StringUtils.formatToString("<#33FF33>[AxMinions] Hooked into BentoBox!"))
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().getPlugin("GriefPrevention") != null) {
|
||||
register(GriefPreventionIntegration())
|
||||
Bukkit.getConsoleSender()
|
||||
.sendMessage(StringUtils.formatToString("<#33FF33>[AxMinions] Hooked into GriefPrevention!"))
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().getPlugin("Lands") != null) {
|
||||
register(LandsIntegration())
|
||||
Bukkit.getConsoleSender()
|
||||
.sendMessage(StringUtils.formatToString("<#33FF33>[AxMinions] Hooked into Lands!"))
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().getPlugin("IridiumSkyBlock") != null) {
|
||||
register(IridiumSkyBlockIntegration())
|
||||
Bukkit.getConsoleSender()
|
||||
.sendMessage(StringUtils.formatToString("<#33FF33>[AxMinions] Hooked into IridiumSkyBlock!"))
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager().getPlugin("KingdomsX") != null) {
|
||||
register(KingdomsXIntegration())
|
||||
Bukkit.getConsoleSender()
|
||||
.sendMessage(StringUtils.formatToString("<#33FF33>[AxMinions] Hooked into KingdomsX!"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.artillexstudios.axminions.listeners
|
||||
|
||||
import com.artillexstudios.axapi.scheduler.Scheduler
|
||||
import com.artillexstudios.axminions.AxMinionsPlugin
|
||||
import com.artillexstudios.axminions.api.events.MinionKillEntityEvent
|
||||
import com.artillexstudios.axminions.api.utils.fastFor
|
||||
@ -18,18 +19,20 @@ class MinionDamageListener : Listener {
|
||||
event.minion.setActions(event.minion.getActionAmount() + entitySize)
|
||||
event.minion.setStorage(event.minion.getStorage() + ThreadLocalRandom.current().nextInt(1, 4) * entitySize)
|
||||
|
||||
event.target.location.world!!.getNearbyEntities(event.target.location, 4.0, 4.0, 4.0).filterIsInstance<Item>().fastFor { item ->
|
||||
if (event.minion.getLinkedInventory()?.firstEmpty() == -1) {
|
||||
Warnings.CONTAINER_FULL.display(event.minion)
|
||||
return
|
||||
Scheduler.get().runLaterAt(event.target.location, {
|
||||
event.target.location.world!!.getNearbyEntities(event.target.location, 4.0, 4.0, 4.0).filterIsInstance<Item>().fastFor { item ->
|
||||
if (event.minion.getLinkedInventory()?.firstEmpty() == -1) {
|
||||
Warnings.CONTAINER_FULL.display(event.minion)
|
||||
return@runLaterAt
|
||||
}
|
||||
|
||||
val amount = AxMinionsPlugin.integrations.getStackerIntegration().getStackSize(item)
|
||||
val stack = item.itemStack
|
||||
stack.amount = amount.toInt()
|
||||
|
||||
event.minion.addToContainerOrDrop(stack)
|
||||
item.remove()
|
||||
}
|
||||
|
||||
val amount = AxMinionsPlugin.integrations.getStackerIntegration().getStackSize(item)
|
||||
val stack = item.itemStack
|
||||
stack.amount = amount.toInt()
|
||||
|
||||
event.minion.addToContainerOrDrop(stack)
|
||||
item.remove()
|
||||
}
|
||||
}, 2)
|
||||
}
|
||||
}
|
@ -160,6 +160,7 @@ class MinionInventoryListener : Listener {
|
||||
}
|
||||
} else {
|
||||
player.giveExp(stored.toInt())
|
||||
minion.setStorage(0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.artillexstudios.axminions.listeners
|
||||
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
|
||||
import com.artillexstudios.axapi.scheduler.Scheduler
|
||||
import com.artillexstudios.axapi.utils.StringUtils
|
||||
import com.artillexstudios.axminions.AxMinionsPlugin
|
||||
import com.artillexstudios.axminions.api.AxMinionsAPI
|
||||
@ -54,9 +53,7 @@ class MinionPlaceListener : Listener {
|
||||
}
|
||||
|
||||
val locationId = AxMinionsPlugin.dataHandler.getLocationID(location)
|
||||
val minion = Minion(location, event.player.uniqueId, event.player, minionType, 1, ItemStack(Material.AIR), null, Direction.NORTH, 0, 0.0, locationId, 0)
|
||||
minion.setLevel(level)
|
||||
minion.setActions(stats)
|
||||
val minion = Minion(location, event.player.uniqueId, event.player, minionType, level, ItemStack(Material.AIR), null, Direction.NORTH, stats, 0.0, locationId, 0)
|
||||
minion.setTicking(true)
|
||||
event.item?.amount = event.item?.amount?.minus(1) ?: 0
|
||||
if (Config.DEBUG()) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.artillexstudios.axminions.listeners
|
||||
|
||||
import com.artillexstudios.axminions.AxMinionsPlugin
|
||||
import com.artillexstudios.axminions.api.minions.miniontype.MinionTypes
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.Listener
|
||||
@ -9,6 +10,8 @@ class WorldListener : Listener {
|
||||
|
||||
@EventHandler
|
||||
fun onWorldLoadEvent(event: WorldLoadEvent) {
|
||||
MinionTypes.loadForWorld(event.world)
|
||||
AxMinionsPlugin.dataQueue.submit {
|
||||
MinionTypes.loadForWorld(event.world)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import com.artillexstudios.axapi.entity.impl.PacketEntity
|
||||
import com.artillexstudios.axapi.events.PacketEntityInteractEvent
|
||||
import com.artillexstudios.axapi.hologram.Hologram
|
||||
import com.artillexstudios.axapi.hologram.HologramFactory
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
|
||||
import com.artillexstudios.axapi.scheduler.Scheduler
|
||||
import com.artillexstudios.axapi.serializers.Serializers
|
||||
import com.artillexstudios.axapi.utils.EquipmentSlot
|
||||
@ -20,12 +19,14 @@ import com.artillexstudios.axminions.api.config.Messages
|
||||
import com.artillexstudios.axminions.api.minions.Direction
|
||||
import com.artillexstudios.axminions.api.minions.Minion
|
||||
import com.artillexstudios.axminions.api.minions.miniontype.MinionType
|
||||
import com.artillexstudios.axminions.api.minions.miniontype.MinionTypes
|
||||
import com.artillexstudios.axminions.api.utils.Keys
|
||||
import com.artillexstudios.axminions.api.utils.fastFor
|
||||
import com.artillexstudios.axminions.api.warnings.Warning
|
||||
import com.artillexstudios.axminions.api.warnings.Warnings
|
||||
import com.artillexstudios.axminions.listeners.LinkingListener
|
||||
import java.util.UUID
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.Material
|
||||
@ -57,6 +58,7 @@ class Minion(
|
||||
private lateinit var entity: PacketArmorStand
|
||||
private var nextAction = 0
|
||||
private var range = 0.0
|
||||
|
||||
@Volatile
|
||||
private var dirty = true
|
||||
private var armTick = 2.0
|
||||
@ -73,7 +75,7 @@ class Minion(
|
||||
Minions.load(this)
|
||||
|
||||
if (linkedChest != null) {
|
||||
Scheduler.get().executeAt(linkedChest) {
|
||||
Scheduler.get().runAt(linkedChest) {
|
||||
linkedInventory = (linkedChest?.block?.state as? Container)?.inventory
|
||||
}
|
||||
}
|
||||
@ -95,11 +97,15 @@ class Minion(
|
||||
if (event.isAttack) {
|
||||
if (ownerUUID == event.player.uniqueId && Config.ONLY_OWNER_BREAK()) {
|
||||
breakMinion(event)
|
||||
} else if (AxMinionsPlugin.integrations.getProtectionIntegration().canBuildAt(event.player, event.packetEntity.location)) {
|
||||
} else if (AxMinionsPlugin.integrations.getProtectionIntegration()
|
||||
.canBuildAt(event.player, event.packetEntity.location)
|
||||
) {
|
||||
breakMinion(event)
|
||||
}
|
||||
} else {
|
||||
if (ownerUUID == event.player.uniqueId || AxMinionsPlugin.integrations.getProtectionIntegration().canBuildAt(event.player, event.packetEntity.location)) {
|
||||
if (ownerUUID == event.player.uniqueId || AxMinionsPlugin.integrations.getProtectionIntegration()
|
||||
.canBuildAt(event.player, event.packetEntity.location)
|
||||
) {
|
||||
Scheduler.get().run {
|
||||
openInventory(event.player)
|
||||
}
|
||||
@ -119,7 +125,7 @@ class Minion(
|
||||
debugHologram?.addLine(StringUtils.format("ticking: $ticking"))
|
||||
}
|
||||
|
||||
setDirection(direction)
|
||||
setDirection(direction, false)
|
||||
updateArmour()
|
||||
}
|
||||
|
||||
@ -128,6 +134,20 @@ class Minion(
|
||||
val tool = getTool()
|
||||
val asItem = getAsItem()
|
||||
val remaining = event.player.inventory.addItem(tool, asItem)
|
||||
if (getType() == MinionTypes.getMinionTypes()["seller"]) {
|
||||
AxMinionsPlugin.integrations.getEconomyIntegration()?.let {
|
||||
getOwner().let { player ->
|
||||
it.giveBalance(player, storage)
|
||||
setStorage(0.0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
owner.player?.let {
|
||||
it.giveExp(storage.toInt())
|
||||
setStorage(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
remove()
|
||||
|
||||
remaining.fastFor { _, i ->
|
||||
@ -162,30 +182,66 @@ class Minion(
|
||||
}
|
||||
|
||||
private fun updateInventory(inventory: Inventory) {
|
||||
AxMinionsAPI.INSTANCE.getConfig().getConfig().getSection("gui.items").getRoutesAsStrings(false).fastFor {
|
||||
if (it.equals("filler")) return@fastFor
|
||||
AxMinionsAPI.INSTANCE.getConfig().getConfig().getSection("gui.items").getRoutesAsStrings(false).forEach {
|
||||
if (it.equals("filler")) return@forEach
|
||||
val item: ItemStack
|
||||
if (it.equals("upgrade", true) || it.equals("statistics", true)) {
|
||||
val level = Placeholder.unparsed("level", level.toString())
|
||||
val nextLevel = Placeholder.unparsed(
|
||||
val level = Placeholder.parsed("level", level.toString())
|
||||
val nextLevel = Placeholder.parsed(
|
||||
"next_level", when (type.hasReachedMaxLevel(this)) {
|
||||
true -> Messages.UPGRADES_MAX_LEVEL_REACHED()
|
||||
else -> (this.level + 1).toString()
|
||||
}
|
||||
)
|
||||
val range = Placeholder.unparsed("range", type.getDouble("range", this.level).toString())
|
||||
val nextRange = Placeholder.unparsed("next_range", type.getDouble("range", this.level + 1).toString())
|
||||
val extra = Placeholder.unparsed("extra", type.getDouble("extra", this.level).toString())
|
||||
val nextExtra = Placeholder.unparsed("next_extra", type.getDouble("extra", this.level + 1).toString())
|
||||
val speed = Placeholder.unparsed("speed", type.getDouble("speed", this.level).toString())
|
||||
val nextSpeed = Placeholder.unparsed("next_speed", type.getDouble("speed", this.level + 1).toString())
|
||||
val price = Placeholder.unparsed("price", type.getDouble("requirements.money", this.level + 1).toString())
|
||||
val range = Placeholder.parsed("range", type.getDouble("range", this.level).toString())
|
||||
val nextRange = Placeholder.parsed(
|
||||
"next_range",
|
||||
if (type.hasReachedMaxLevel(this)) Messages.UPGRADES_MAX_LEVEL_REACHED() else type.getDouble(
|
||||
"range",
|
||||
this.level + 1
|
||||
).toString()
|
||||
)
|
||||
val extra = Placeholder.parsed("extra", type.getDouble("extra", this.level).toString())
|
||||
val nextExtra = Placeholder.parsed(
|
||||
"next_extra",
|
||||
if (type.hasReachedMaxLevel(this)) Messages.UPGRADES_MAX_LEVEL_REACHED() else type.getDouble(
|
||||
"extra",
|
||||
this.level + 1
|
||||
).toString()
|
||||
)
|
||||
val speed = Placeholder.parsed("speed", type.getDouble("speed", this.level).toString())
|
||||
val nextSpeed = Placeholder.parsed(
|
||||
"next_speed",
|
||||
if (type.hasReachedMaxLevel(this)) Messages.UPGRADES_MAX_LEVEL_REACHED() else type.getDouble(
|
||||
"speed",
|
||||
this.level + 1
|
||||
).toString()
|
||||
)
|
||||
val price = Placeholder.parsed(
|
||||
"price",
|
||||
if (type.hasReachedMaxLevel(this)) Messages.UPGRADES_MAX_LEVEL_REACHED() else type.getDouble(
|
||||
"requirements.money",
|
||||
this.level + 1
|
||||
).toString()
|
||||
)
|
||||
val requiredActions =
|
||||
Placeholder.unparsed("required_actions", type.getDouble("requirements.actions", this.level + 1).toString())
|
||||
val stored = Placeholder.unparsed("storage", storage.toString())
|
||||
val actions = Placeholder.unparsed("actions", actions.toString())
|
||||
val multiplier = Placeholder.unparsed("multiplier", type.getDouble("multiplier", this.level).toString())
|
||||
val nextMultiplier = Placeholder.unparsed("next_multiplier", type.getDouble("multiplier", this.level + 1).toString())
|
||||
Placeholder.parsed(
|
||||
"required_actions",
|
||||
if (type.hasReachedMaxLevel(this)) Messages.UPGRADES_MAX_LEVEL_REACHED() else type.getDouble(
|
||||
"requirements.actions",
|
||||
this.level + 1
|
||||
).toString()
|
||||
)
|
||||
val stored = Placeholder.parsed("storage", storage.toString())
|
||||
val actions = Placeholder.parsed("actions", actions.toString())
|
||||
val multiplier = Placeholder.parsed("multiplier", type.getDouble("multiplier", this.level).toString())
|
||||
val nextMultiplier = Placeholder.parsed(
|
||||
"next_multiplier",
|
||||
if (type.hasReachedMaxLevel(this)) Messages.UPGRADES_MAX_LEVEL_REACHED() else type.getDouble(
|
||||
"multiplier",
|
||||
this.level + 1
|
||||
).toString()
|
||||
)
|
||||
|
||||
item = ItemBuilder(
|
||||
type.getConfig().getSection("gui.$it"),
|
||||
@ -292,7 +348,7 @@ class Minion(
|
||||
return this.ownerUUID
|
||||
}
|
||||
|
||||
override fun setTool(tool: ItemStack) {
|
||||
override fun setTool(tool: ItemStack, save: Boolean) {
|
||||
this.tool = tool.clone()
|
||||
dirty = true
|
||||
|
||||
@ -302,8 +358,10 @@ class Minion(
|
||||
entity.setItem(EquipmentSlot.MAIN_HAND, tool.clone())
|
||||
}
|
||||
|
||||
AxMinionsPlugin.dataQueue.submit {
|
||||
AxMinionsPlugin.dataHandler.saveMinion(this)
|
||||
if (save) {
|
||||
AxMinionsPlugin.dataQueue.submit {
|
||||
AxMinionsPlugin.dataHandler.saveMinion(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,13 +444,15 @@ class Minion(
|
||||
return this.linkedChest
|
||||
}
|
||||
|
||||
override fun setDirection(direction: Direction) {
|
||||
override fun setDirection(direction: Direction, save: Boolean) {
|
||||
this.direction = direction
|
||||
location.yaw = direction.yaw
|
||||
entity.teleport(location)
|
||||
|
||||
AxMinionsPlugin.dataQueue.submit {
|
||||
AxMinionsPlugin.dataHandler.saveMinion(this)
|
||||
if (save) {
|
||||
AxMinionsPlugin.dataQueue.submit {
|
||||
AxMinionsPlugin.dataHandler.saveMinion(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,7 +498,7 @@ class Minion(
|
||||
entity.setItem(entry, null)
|
||||
}
|
||||
|
||||
setTool(this.tool ?: ItemStack(Material.AIR))
|
||||
setTool(this.tool ?: ItemStack(Material.AIR), false)
|
||||
|
||||
type.getSection("items.helmet", level)?.let {
|
||||
entity.setItem(EquipmentSlot.HELMET, ItemBuilder(it).get())
|
||||
|
@ -2,23 +2,24 @@ package com.artillexstudios.axminions.minions
|
||||
|
||||
import com.artillexstudios.axminions.api.minions.Minion
|
||||
import com.artillexstudios.axminions.api.minions.utils.ChunkPos
|
||||
import com.artillexstudios.axminions.api.utils.fastFor
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import org.bukkit.Chunk
|
||||
|
||||
object Minions {
|
||||
private val minions = ConcurrentLinkedQueue<ChunkPos>()
|
||||
private val mutex = Object()
|
||||
private val minions = arrayListOf<ChunkPos>()
|
||||
|
||||
fun addTicking(chunk: Chunk) {
|
||||
val chunkX = chunk.x
|
||||
val chunkZ = chunk.z
|
||||
|
||||
run breaking@ {
|
||||
minions.fastFor {
|
||||
if (it.x == chunkX && it.z == chunkZ) {
|
||||
it.setTicking(true)
|
||||
return@breaking
|
||||
run breaking@{
|
||||
synchronized(mutex) {
|
||||
minions.forEach {
|
||||
if (it.x == chunkX && it.z == chunkZ) {
|
||||
it.setTicking(true)
|
||||
return@breaking
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,9 +29,11 @@ object Minions {
|
||||
val chunkX = chunk.x
|
||||
val chunkZ = chunk.z
|
||||
|
||||
minions.fastFor {
|
||||
if (it.x == chunkX && it.z == chunkZ) {
|
||||
return true
|
||||
synchronized(mutex) {
|
||||
minions.forEach {
|
||||
if (it.x == chunkX && it.z == chunkZ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,11 +44,13 @@ object Minions {
|
||||
val chunkX = chunk.x
|
||||
val chunkZ = chunk.z
|
||||
|
||||
run breaking@ {
|
||||
minions.fastFor {
|
||||
if (it.x == chunkX && it.z == chunkZ) {
|
||||
it.setTicking(false)
|
||||
return@breaking
|
||||
run breaking@{
|
||||
synchronized(mutex) {
|
||||
minions.forEach {
|
||||
if (it.x == chunkX && it.z == chunkZ) {
|
||||
it.setTicking(false)
|
||||
return@breaking
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,52 +60,62 @@ object Minions {
|
||||
val chunkX = round(minion.getLocation().x) shr 4
|
||||
val chunkZ = round(minion.getLocation().z) shr 4
|
||||
|
||||
var pos: ChunkPos? = null
|
||||
run breaking@ {
|
||||
minions.fastFor {
|
||||
if (it.x == chunkX && it.z == chunkZ) {
|
||||
pos = it
|
||||
return@breaking
|
||||
synchronized(mutex) {
|
||||
var pos: ChunkPos? = null
|
||||
run breaking@{
|
||||
|
||||
minions.forEach {
|
||||
if (it.x == chunkX && it.z == chunkZ) {
|
||||
pos = it
|
||||
return@breaking
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pos === null) {
|
||||
pos = ChunkPos(chunkX, chunkZ)
|
||||
minions.add(pos!!)
|
||||
}
|
||||
if (pos === null) {
|
||||
pos = ChunkPos(chunkX, chunkZ)
|
||||
minions.add(pos!!)
|
||||
}
|
||||
|
||||
pos!!.addMinion(minion)
|
||||
|
||||
pos!!.addMinion(minion)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(minion: Minion) {
|
||||
val chunkX = round(minion.getLocation().x) shr 4
|
||||
val chunkZ = round(minion.getLocation().z) shr 4
|
||||
|
||||
val iterator = minions.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val next = iterator.next()
|
||||
synchronized(mutex) {
|
||||
val iterator = minions.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val next = iterator.next()
|
||||
|
||||
if (next.x == chunkX && next.z == chunkZ) {
|
||||
if (next.removeMinion(minion)) {
|
||||
iterator.remove()
|
||||
if (next.x == chunkX && next.z == chunkZ) {
|
||||
if (next.removeMinion(minion)) {
|
||||
iterator.remove()
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMinions(): List<Minion> {
|
||||
val list = mutableListOf<Minion>()
|
||||
minions.fastFor {
|
||||
list.addAll(it.minions)
|
||||
}
|
||||
synchronized(mutex) {
|
||||
minions.forEach {
|
||||
list.addAll(it.minions)
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(list)
|
||||
return Collections.unmodifiableList(list)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun get(): ConcurrentLinkedQueue<ChunkPos> {
|
||||
return minions
|
||||
internal fun get(): ArrayList<ChunkPos> {
|
||||
synchronized(mutex) {
|
||||
return minions
|
||||
}
|
||||
}
|
||||
|
||||
private infix fun round(double: Double): Int {
|
||||
|
@ -20,7 +20,8 @@ class CollectorMinionType : MinionType("collector", AxMinionsPlugin.INSTANCE.get
|
||||
override fun onToolDirty(minion: Minion) {
|
||||
val minionImpl = minion as com.artillexstudios.axminions.minions.Minion
|
||||
minionImpl.setRange(getDouble("range", minion.getLevel()))
|
||||
val efficiency = 1.0 - (minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1)
|
||||
val tool = minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1
|
||||
val efficiency = 1.0 - if (tool > 0.9) 0.9 else tool
|
||||
minionImpl.setNextAction((getLong("speed", minion.getLevel()) * efficiency).roundToInt())
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,8 @@ class FarmerMinionType : MinionType("farmer", AxMinionsPlugin.INSTANCE.getResour
|
||||
override fun onToolDirty(minion: Minion) {
|
||||
val minionImpl = minion as com.artillexstudios.axminions.minions.Minion
|
||||
minionImpl.setRange(getDouble("range", minion.getLevel()))
|
||||
val efficiency = 1.0 - (minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1)
|
||||
val tool = minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1
|
||||
val efficiency = 1.0 - if (tool > 0.9) 0.9 else tool
|
||||
minionImpl.setNextAction((getLong("speed", minion.getLevel()) * efficiency).roundToInt())
|
||||
}
|
||||
|
||||
@ -55,7 +56,7 @@ class FarmerMinionType : MinionType("farmer", AxMinionsPlugin.INSTANCE.getResour
|
||||
Material.CACTUS, Material.SUGAR_CANE, Material.BAMBOO -> {
|
||||
MinionUtils.getPlant(block).fastFor {
|
||||
val blockDrops = it.getDrops(minion.getTool())
|
||||
blockDrops.fastFor { itemStack ->
|
||||
blockDrops.forEach { itemStack ->
|
||||
size += itemStack.amount
|
||||
}
|
||||
drops.addAll(blockDrops)
|
||||
@ -65,7 +66,7 @@ class FarmerMinionType : MinionType("farmer", AxMinionsPlugin.INSTANCE.getResour
|
||||
|
||||
Material.MELON, Material.PUMPKIN -> {
|
||||
val blockDrops = block.getDrops(minion.getTool())
|
||||
blockDrops.fastFor { itemStack ->
|
||||
blockDrops.forEach { itemStack ->
|
||||
size += itemStack.amount
|
||||
}
|
||||
drops.addAll(blockDrops)
|
||||
@ -76,7 +77,7 @@ class FarmerMinionType : MinionType("farmer", AxMinionsPlugin.INSTANCE.getResour
|
||||
val ageable = block.blockData as Ageable
|
||||
if (ageable.age != ageable.maximumAge) return@fastFor
|
||||
val blockDrops = block.getDrops(minion.getTool())
|
||||
blockDrops.fastFor { itemStack ->
|
||||
blockDrops.forEach { itemStack ->
|
||||
size += itemStack.amount
|
||||
}
|
||||
drops.addAll(blockDrops)
|
||||
@ -88,7 +89,7 @@ class FarmerMinionType : MinionType("farmer", AxMinionsPlugin.INSTANCE.getResour
|
||||
val ageable = block.blockData as Ageable
|
||||
if (ageable.age != ageable.maximumAge) return@fastFor
|
||||
val blockDrops = block.getDrops(minion.getTool())
|
||||
blockDrops.fastFor { itemStack ->
|
||||
blockDrops.forEach { itemStack ->
|
||||
size += itemStack.amount
|
||||
}
|
||||
drops.addAll(blockDrops)
|
||||
|
@ -23,7 +23,8 @@ class FisherMinionType : MinionType("fisher", AxMinionsPlugin.INSTANCE.getResour
|
||||
override fun onToolDirty(minion: Minion) {
|
||||
val minionImpl = minion as com.artillexstudios.axminions.minions.Minion
|
||||
minionImpl.setRange(getDouble("range", minion.getLevel()))
|
||||
val efficiency = 1.0 - (minion.getTool()?.getEnchantmentLevel(Enchantment.LURE)?.div(10.0) ?: 0.1)
|
||||
val tool = minion.getTool()?.getEnchantmentLevel(Enchantment.LURE)?.div(10.0) ?: 0.1
|
||||
val efficiency = 1.0 - if (tool > 0.9) 0.9 else tool
|
||||
minionImpl.setNextAction((getLong("speed", minion.getLevel()) * efficiency).roundToInt())
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.artillexstudios.axminions.minions.miniontype
|
||||
|
||||
import com.artillexstudios.axapi.scheduler.Scheduler
|
||||
import com.artillexstudios.axminions.AxMinionsPlugin
|
||||
import com.artillexstudios.axminions.api.minions.Minion
|
||||
import com.artillexstudios.axminions.api.minions.miniontype.MinionType
|
||||
@ -24,7 +23,8 @@ class LumberMinionType : MinionType("lumber", AxMinionsPlugin.INSTANCE.getResour
|
||||
override fun onToolDirty(minion: Minion) {
|
||||
val minionImpl = minion as com.artillexstudios.axminions.minions.Minion
|
||||
minionImpl.setRange(getDouble("range", minion.getLevel()))
|
||||
val efficiency = 1.0 - (minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1)
|
||||
val tool = minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1
|
||||
val efficiency = 1.0 - if (tool > 0.9) 0.9 else tool
|
||||
minionImpl.setNextAction((getLong("speed", minion.getLevel()) * efficiency).roundToInt())
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ class LumberMinionType : MinionType("lumber", AxMinionsPlugin.INSTANCE.getResour
|
||||
|
||||
val loot = ArrayList<ItemStack>()
|
||||
LocationUtils.getAllBlocksInRadius(minion.getLocation(), minion.getRange(), false).fastFor { location ->
|
||||
MinionUtils.getTree(location.block).fastFor {
|
||||
MinionUtils.getTree(location.block).forEach {
|
||||
val down = it.getRelative(BlockFace.DOWN).type
|
||||
loot.addAll(it.getDrops(minion.getTool()))
|
||||
|
||||
|
@ -29,7 +29,8 @@ class MinerMinionType : MinionType("miner", AxMinionsPlugin.INSTANCE.getResource
|
||||
override fun onToolDirty(minion: Minion) {
|
||||
val minionImpl = minion as com.artillexstudios.axminions.minions.Minion
|
||||
minionImpl.setRange(getDouble("range", minion.getLevel()))
|
||||
val efficiency = 1.0 - (minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1)
|
||||
val tool = minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1
|
||||
val efficiency = 1.0 - if (tool > 0.9) 0.9 else tool
|
||||
minionImpl.setNextAction((getLong("speed", minion.getLevel()) * efficiency).roundToInt())
|
||||
}
|
||||
|
||||
@ -60,7 +61,7 @@ class MinerMinionType : MinionType("miner", AxMinionsPlugin.INSTANCE.getResource
|
||||
|
||||
if (isStoneGenerator) {
|
||||
val drops = location.block.getDrops(minion.getTool())
|
||||
drops.fastFor {
|
||||
drops.forEach {
|
||||
amount += it.amount
|
||||
}
|
||||
minion.addToContainerOrDrop(drops)
|
||||
@ -83,7 +84,7 @@ class MinerMinionType : MinionType("miner", AxMinionsPlugin.INSTANCE.getResource
|
||||
if (isStoneGenerator) {
|
||||
Scheduler.get().run {
|
||||
val drops = location.block.getDrops(minion.getTool())
|
||||
drops.fastFor {
|
||||
drops.forEach {
|
||||
amount += it.amount
|
||||
}
|
||||
minion.addToContainerOrDrop(drops)
|
||||
@ -99,7 +100,7 @@ class MinerMinionType : MinionType("miner", AxMinionsPlugin.INSTANCE.getResource
|
||||
|
||||
if (isStoneGenerator) {
|
||||
val drops = location.block.getDrops(minion.getTool())
|
||||
drops.fastFor {
|
||||
drops.forEach {
|
||||
amount += it.amount
|
||||
}
|
||||
minion.addToContainerOrDrop(drops)
|
||||
@ -116,8 +117,8 @@ class MinerMinionType : MinionType("miner", AxMinionsPlugin.INSTANCE.getResource
|
||||
|
||||
if (isStoneGenerator) {
|
||||
val drops = location.block.getDrops(minion.getTool())
|
||||
drops.fastFor {
|
||||
amount += it.amount
|
||||
drops.forEach { item ->
|
||||
amount += item.amount
|
||||
}
|
||||
minion.addToContainerOrDrop(drops)
|
||||
location.block.type = Material.AIR
|
||||
@ -133,7 +134,7 @@ class MinerMinionType : MinionType("miner", AxMinionsPlugin.INSTANCE.getResource
|
||||
|
||||
if (isStoneGenerator) {
|
||||
val drops = location.block.getDrops(minion.getTool())
|
||||
drops.fastFor {
|
||||
drops.forEach {
|
||||
amount += it.amount
|
||||
}
|
||||
minion.addToContainerOrDrop(drops)
|
||||
@ -143,6 +144,7 @@ class MinerMinionType : MinionType("miner", AxMinionsPlugin.INSTANCE.getResource
|
||||
}
|
||||
}
|
||||
|
||||
minion.setActions(minion.getActionAmount() + amount)
|
||||
minion.damageTool(amount)
|
||||
}
|
||||
}
|
@ -18,7 +18,8 @@ class SellerMinionType : MinionType("seller", AxMinionsPlugin.INSTANCE.getResour
|
||||
override fun onToolDirty(minion: Minion) {
|
||||
val minionImpl = minion as com.artillexstudios.axminions.minions.Minion
|
||||
minionImpl.setRange(getDouble("range", minion.getLevel()))
|
||||
val efficiency = 1.0 - (minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1)
|
||||
val tool = minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1
|
||||
val efficiency = 1.0 - if (tool > 0.9) 0.9 else tool
|
||||
minionImpl.setNextAction((getLong("speed", minion.getLevel()) * efficiency).roundToInt())
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,8 @@ class SlayerMinionType : MinionType("slayer", AxMinionsPlugin.INSTANCE.getResour
|
||||
override fun onToolDirty(minion: Minion) {
|
||||
val minionImpl = minion as com.artillexstudios.axminions.minions.Minion
|
||||
minionImpl.setRange(getDouble("range", minion.getLevel()))
|
||||
val efficiency = 1.0 - (minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1)
|
||||
val tool = minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1
|
||||
val efficiency = 1.0 - if (tool > 0.9) 0.9 else tool
|
||||
minionImpl.setNextAction((getLong("speed", minion.getLevel()) * efficiency).roundToInt())
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,6 @@ max-linking-distance: 30
|
||||
# This setting can be overwritten by setting the 'axminions.limit.<amount>' permission!
|
||||
default-minion-limit: 5
|
||||
|
||||
# If we should allow floating minions to be created
|
||||
allow-floating-minions: false
|
||||
|
||||
# If true, only the owner of this minion can break it
|
||||
# If false, everyone, who can break blocks at said location can break this minion
|
||||
# You should enable this, if you aren't using a supported protection plugin!
|
||||
@ -31,8 +28,7 @@ can-break-tools: true
|
||||
use-durability: true
|
||||
|
||||
database:
|
||||
# Can be H2 and SQLite.
|
||||
# For most setups, H2 is recommended
|
||||
# Can be H2 (SqLite support is planned)
|
||||
type: "H2"
|
||||
|
||||
hooks:
|
||||
|
@ -32,9 +32,9 @@ gui:
|
||||
name: "<#00CCFF><b>Upgrade minion"
|
||||
lore:
|
||||
- ""
|
||||
- " <gray>- <white>Level: <green><current_level> » <dark_green><next_level>"
|
||||
- " <gray>- <white>Range: <green><current_range> » <dark_green><next_range>"
|
||||
- " <gray>- <white>Speed: <green><current_speed> » <dark_green><next_speed>"
|
||||
- " <gray>- <white>Level: <green><level> » <dark_green><next_level>"
|
||||
- " <gray>- <white>Range: <green><range> » <dark_green><next_range>"
|
||||
- " <gray>- <white>Speed: <green><speed> » <dark_green><next_speed>"
|
||||
- ""
|
||||
- "<#00CCFF>Requirements:"
|
||||
- " <gray>- <white>Money: <#33FF33><price>$"
|
||||
|
@ -51,6 +51,7 @@ gui:
|
||||
lore:
|
||||
- ""
|
||||
- " <gray>- <white>Killed mobs: <#FF3333><actions>"
|
||||
- " <gray>- <white>Stored exp: <#CC00FF><storage>"
|
||||
- ""
|
||||
|
||||
upgrades:
|
||||
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user