aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/lorenz
diff options
context:
space:
mode:
authorLorenz <ESs95s3P5z8Pheb>2022-07-07 00:31:50 +0200
committerLorenz <ESs95s3P5z8Pheb>2022-07-07 00:31:50 +0200
commit99773d6a593c444151503de315f127bea6f74d49 (patch)
tree9ee1ae505e5f82aba62f10c882af85a3acd6e483 /src/main/java/at/lorenz
downloadskyhanni-99773d6a593c444151503de315f127bea6f74d49.tar.gz
skyhanni-99773d6a593c444151503de315f127bea6f74d49.tar.bz2
skyhanni-99773d6a593c444151503de315f127bea6f74d49.zip
init lorenz mod
Diffstat (limited to 'src/main/java/at/lorenz')
-rw-r--r--src/main/java/at/lorenz/mod/GuiContainerHook.kt61
-rw-r--r--src/main/java/at/lorenz/mod/HideNotClickableItems.kt444
-rw-r--r--src/main/java/at/lorenz/mod/bazaar/BazaarApi.kt59
-rw-r--r--src/main/java/at/lorenz/mod/bazaar/BazaarData.kt3
-rw-r--r--src/main/java/at/lorenz/mod/bazaar/BazaarDataGrabber.kt116
-rw-r--r--src/main/java/at/lorenz/mod/bazaar/BazaarOrderHelper.kt87
-rw-r--r--src/main/java/at/lorenz/mod/chat/ChatFilter.kt279
-rw-r--r--src/main/java/at/lorenz/mod/chat/ChatManager.kt47
-rw-r--r--src/main/java/at/lorenz/mod/chat/PlayerChatFilter.kt78
-rw-r--r--src/main/java/at/lorenz/mod/chat/PlayerMessageChannel.kt10
-rw-r--r--src/main/java/at/lorenz/mod/config/LorenzConfig.kt12
-rw-r--r--src/main/java/at/lorenz/mod/dungeon/DungeonChatFilter.kt259
-rw-r--r--src/main/java/at/lorenz/mod/events/GuiContainerEvent.kt54
-rw-r--r--src/main/java/at/lorenz/mod/events/LorenzChatEvent.kt5
-rw-r--r--src/main/java/at/lorenz/mod/events/LorenzEvent.kt20
-rw-r--r--src/main/java/at/lorenz/mod/events/PlayerSendChatEvent.kt11
-rw-r--r--src/main/java/at/lorenz/mod/mixins/MixinGuiContainer.java50
-rw-r--r--src/main/java/at/lorenz/mod/utils/APIUtil.kt116
-rw-r--r--src/main/java/at/lorenz/mod/utils/ItemUtil.kt211
-rw-r--r--src/main/java/at/lorenz/mod/utils/ItemUtils.kt39
-rw-r--r--src/main/java/at/lorenz/mod/utils/LorenzColor.kt27
-rw-r--r--src/main/java/at/lorenz/mod/utils/LorenzLogger.kt70
-rw-r--r--src/main/java/at/lorenz/mod/utils/LorenzUtils.kt91
-rw-r--r--src/main/java/at/lorenz/mod/utils/RenderUtil.kt20
24 files changed, 2169 insertions, 0 deletions
diff --git a/src/main/java/at/lorenz/mod/GuiContainerHook.kt b/src/main/java/at/lorenz/mod/GuiContainerHook.kt
new file mode 100644
index 000000000..55b30e964
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/GuiContainerHook.kt
@@ -0,0 +1,61 @@
+package at.lorenz.mod
+
+import at.lorenz.mod.events.GuiContainerEvent
+import at.lorenz.mod.events.GuiContainerEvent.CloseWindowEvent
+import at.lorenz.mod.events.GuiContainerEvent.SlotClickEvent
+import net.minecraft.client.gui.inventory.GuiContainer
+import net.minecraft.inventory.Slot
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo
+
+class GuiContainerHook(guiAny: Any) {
+
+ val gui: GuiContainer
+
+ init {
+ gui = guiAny as GuiContainer
+ }
+
+ fun closeWindowPressed(ci: CallbackInfo) {
+ if (CloseWindowEvent(gui, gui.inventorySlots).postAndCatch()) ci.cancel()
+ }
+
+ fun backgroundDrawn(mouseX: Int, mouseY: Int, partialTicks: Float, ci: CallbackInfo) {
+ GuiContainerEvent.BackgroundDrawnEvent(
+ gui,
+ gui.inventorySlots,
+ mouseX,
+ mouseY,
+ partialTicks
+ ).postAndCatch()
+ }
+
+ fun foregroundDrawn(mouseX: Int, mouseY: Int, partialTicks: Float, ci: CallbackInfo) {
+ GuiContainerEvent.ForegroundDrawnEvent(gui, gui.inventorySlots, mouseX, mouseY, partialTicks).postAndCatch()
+ }
+
+ fun onDrawSlot(slot: Slot, ci: CallbackInfo) {
+ if (GuiContainerEvent.DrawSlotEvent.Pre(
+ gui,
+ gui.inventorySlots,
+ slot
+ ).postAndCatch()
+ ) ci.cancel()
+ }
+
+ fun onDrawSlotPost(slot: Slot, ci: CallbackInfo) {
+ GuiContainerEvent.DrawSlotEvent.Post(gui, gui.inventorySlots, slot).postAndCatch()
+ }
+
+ fun onMouseClick(slot: Slot?, slotId: Int, clickedButton: Int, clickType: Int, ci: CallbackInfo) {
+ if (
+ SlotClickEvent(
+ gui,
+ gui.inventorySlots,
+ slot,
+ slotId,
+ clickedButton,
+ clickType
+ ).postAndCatch()
+ ) ci.cancel()
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/HideNotClickableItems.kt b/src/main/java/at/lorenz/mod/HideNotClickableItems.kt
new file mode 100644
index 000000000..3f94a1479
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/HideNotClickableItems.kt
@@ -0,0 +1,444 @@
+package at.lorenz.mod
+
+import at.lorenz.mod.bazaar.BazaarApi
+import at.lorenz.mod.config.LorenzConfig
+import at.lorenz.mod.utils.LorenzUtils.Companion.removeColorCodes
+import at.lorenz.mod.events.GuiContainerEvent
+import at.lorenz.mod.utils.ItemUtils
+import at.lorenz.mod.utils.ItemUtils.Companion.cleanName
+import at.lorenz.mod.utils.ItemUtils.Companion.getLore
+import at.lorenz.mod.utils.LorenzColor
+import at.lorenz.mod.utils.LorenzUtils
+import at.lorenz.mod.utils.RenderUtil.Companion.highlight
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.inventory.GuiChest
+import net.minecraft.client.renderer.GlStateManager
+import net.minecraft.inventory.ContainerChest
+import net.minecraft.item.ItemStack
+import net.minecraftforge.event.entity.player.ItemTooltipEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import org.lwjgl.opengl.GL11
+
+class HideNotClickableItems {
+
+ private var hideReason = ""
+
+ private var lastClickTime = 0L
+ private var bypassUntil = 0L
+
+ @SubscribeEvent
+ fun onBackgroundDrawn(event: GuiContainerEvent.BackgroundDrawnEvent) {
+ if (isDisabled()) return
+ if (event.gui !is GuiChest) return
+ val guiChest = event.gui
+ val chest = guiChest.inventorySlots as ContainerChest
+ val chestName = chest.lowerChestInventory.displayName.unformattedText.trim()
+
+ val lightingState = GL11.glIsEnabled(GL11.GL_LIGHTING)
+ GlStateManager.disableLighting()
+ GlStateManager.color(1f, 1f, 1f, 1f)
+
+ for (slot in chest.inventorySlots) {
+ if (slot == null) continue
+
+ if (slot.slotNumber == slot.slotIndex) continue
+ if (slot.stack == null) continue
+
+ if (hide(chestName, slot.stack)) {
+ slot highlight LorenzColor.GRAY
+ }
+ }
+
+ if (lightingState) GlStateManager.enableLighting()
+ }
+
+ @SubscribeEvent
+ fun onDrawSlot(event: GuiContainerEvent.DrawSlotEvent.Pre) {
+ if (isDisabled()) return
+ if (event.gui !is GuiChest) return
+ val guiChest = event.gui
+ val chest = guiChest.inventorySlots as ContainerChest
+ val chestName = chest.lowerChestInventory.displayName.unformattedText.trim()
+
+ val slot = event.slot
+ if (slot.slotNumber == slot.slotIndex) return
+ if (slot.stack == null) return
+
+ val stack = slot.stack
+
+ if (hide(chestName, stack)) {
+ event.isCanceled = true
+ }
+ }
+
+ @SubscribeEvent
+ fun onTooltip(event: ItemTooltipEvent) {
+ if (isDisabled()) return
+ if (event.toolTip == null) return
+ val guiChest = Minecraft.getMinecraft().currentScreen
+ if (guiChest !is GuiChest) return
+ val chest = guiChest.inventorySlots as ContainerChest
+ val chestName = chest.lowerChestInventory.displayName.unformattedText.trim()
+
+ val stack = event.itemStack
+ if (ItemUtils.getItemsInOpenChest().contains(stack)) return
+
+ if (hide(chestName, stack)) {
+ val first = event.toolTip[0]
+ event.toolTip.clear()
+ event.toolTip.add("§7" + first.removeColorCodes())
+ event.toolTip.add("")
+ if (hideReason == "") {
+ event.toolTip.add("§4No hide reason!")
+ LorenzUtils.warning("Not hide reason for not clickable item!")
+ } else {
+ event.toolTip.add("§c$hideReason")
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) {
+ if (isDisabled()) return
+ if (event.gui !is GuiChest) return
+ val guiChest = event.gui
+ val chest = guiChest.inventorySlots as ContainerChest
+ val chestName = chest.lowerChestInventory.displayName.unformattedText.trim()
+
+ val slot = event.slot ?: return
+
+ if (slot.slotNumber == slot.slotIndex) return
+ if (slot.stack == null) return
+
+ val stack = slot.stack
+
+ if (hide(chestName, stack)) {
+ event.isCanceled = true
+// SoundQueue.addToQueue("note.bass", 0.5f, 1f)
+
+ if (System.currentTimeMillis() > lastClickTime + 5_000) {
+ lastClickTime = System.currentTimeMillis()
+// EssentialAPI.getNotifications()
+// .push(
+// "§cThis item cannot be moved!",
+// "§eClick here §fto disable this feature for 10 seconds!\n" +
+// "§fOr disable it forever: §6/st §f+ '§6Hide Not Clickable Items§f'.",
+// 5f,
+// action = {
+// bypassUntil = System.currentTimeMillis() + 10_000
+// })
+ }
+ return
+ }
+ }
+
+ private fun isDisabled(): Boolean {
+ if (bypassUntil > System.currentTimeMillis()) return true
+
+ return !LorenzConfig.hideNotClickableItems
+ }
+
+ private fun hide(chestName: String, stack: ItemStack): Boolean {
+ hideReason = ""
+ return when {
+ hideNpcSell(chestName, stack) -> true
+ hideChestBackpack(chestName, stack) -> true
+ hideSalvage(chestName, stack) -> true
+ hideTrade(chestName, stack) -> true
+ hideBazaarOrAH(chestName, stack) -> true
+ hideAccessoryBag(chestName, stack) -> true
+ hideSackOfSacks(chestName, stack) -> true
+ hideFishingBag(chestName, stack) -> true
+ hidePotionBag(chestName, stack) -> true
+
+ else -> false
+ }
+ }
+
+ private fun hidePotionBag(chestName: String, stack: ItemStack): Boolean {
+ if (!chestName.startsWith("Potion Bag")) return false
+
+ val name = stack.cleanName()
+ if (isSkyBlockMenuItem(name)) {
+ hideReason = "The SkyBlock Menu cannot be put into the potion bag!"
+ return true
+ }
+
+ if (stack.cleanName().endsWith(" Potion")) return false
+
+ hideReason = "This item is not a potion!"
+ return true
+ }
+
+ private fun hideFishingBag(chestName: String, stack: ItemStack): Boolean {
+ if (!chestName.startsWith("Fishing Bag")) return false
+
+ val name = stack.cleanName()
+ if (isSkyBlockMenuItem(name)) {
+ hideReason = "The SkyBlock Menu cannot be put into the fishing bag!"
+ return true
+ }
+
+ if (stack.cleanName().endsWith(" Bait")) return false
+
+ hideReason = "This item is not a fishing bait!"
+ return true
+ }
+
+ private fun hideSackOfSacks(chestName: String, stack: ItemStack): Boolean {
+ if (!chestName.startsWith("Sack of Sacks")) return false
+
+ val name = stack.cleanName()
+ if (ItemUtils.isSack(name)) return false
+ if (isSkyBlockMenuItem(name)) return false
+
+ hideReason = "This item is not a sack!"
+ return true
+ }
+
+ private fun hideAccessoryBag(chestName: String, stack: ItemStack): Boolean {
+ if (!chestName.startsWith("Accessory Bag")) return false
+
+ if (stack.getLore().any { it.contains("ACCESSORY") }) return false
+ if (isSkyBlockMenuItem(stack.cleanName())) return false
+
+ hideReason = "This item is not an accessory!"
+ return true
+ }
+
+ private fun hideTrade(chestName: String, stack: ItemStack): Boolean {
+ if (!chestName.startsWith("You ")) return false
+
+ if (ItemUtils.isCoOpSoulBound(stack)) {
+ hideReason = "Coop-Soulbound items cannot be traded!"
+ return true
+ }
+
+ val name = stack.cleanName()
+
+ if (ItemUtils.isSack(name)) {
+ hideReason = "Sacks cannot be traded!"
+ return true
+ }
+
+ if (isSkyBlockMenuItem(name)) {
+ hideReason = "The SkyBlock Menu cannot be traded!"
+ return true
+ }
+
+ val result = when {
+ name.contains("Personal Deletor") -> true
+ name.contains("Day Crystal") -> true
+ name.contains("Night Crystal") -> true
+ name.contains("Cat Talisman") -> true
+ name.contains("Lynx Talisman") -> true
+ name.contains("Cheetah Talisman") -> true
+ else -> false
+ }
+
+ if (result) hideReason = "This item cannot be traded!"
+ return result
+ }
+
+ private fun hideNpcSell(chestName: String, stack: ItemStack): Boolean {
+ if (chestName != "Trades" && chestName != "Ophelia") return false
+
+ var name = stack.cleanName()
+ val size = stack.stackSize
+ val amountText = " x$size"
+ if (name.endsWith(amountText)) {
+ name = name.substring(0, name.length - amountText.length)
+ }
+
+ if (isSkyBlockMenuItem(name)) {
+ hideReason = "The SkyBlock Menu cannot be sold at the NPC!"
+ return true
+ }
+
+ if (!ItemUtils.isRecombobulated(stack)) {
+ when (name) {
+ "Health Potion VIII Splash Potion" -> return false
+ "Stone Button" -> return false
+ "Revive Stone" -> return false
+ "Premium Flesh" -> return false
+ "Defuse Kit" -> return false
+ "White Wool" -> return false
+ "Enchanted Wool" -> return false
+ "Training Weights" -> return false
+ "Journal Entry" -> return false
+
+ "Fairy's Galoshes" -> return false
+ }
+
+ if (name.startsWith("Music Disc")) return false
+ }
+
+ hideReason = "This item should not be sold at the NPC!"
+ return true
+ }
+
+ private fun hideChestBackpack(chestName: String, stack: ItemStack): Boolean {
+ if (!chestName.contains("Ender Chest") && !chestName.contains("Backpack")) return false
+
+ val name = stack.cleanName()
+
+ if (isSkyBlockMenuItem(name)) {
+ hideReason = "The SkyBlock Menu cannot be put into the storage!"
+ return true
+ }
+ if (ItemUtils.isSack(name)) {
+ hideReason = "Sacks cannot be put into the storage!"
+ return true
+ }
+
+ val result = when {
+ name.endsWith(" New Year Cake Bag") -> true
+ name == "Nether Wart Pouch" -> true
+ name == "Basket of Seeds" -> true
+ name == "Builder's Wand" -> true
+
+ else -> false
+ }
+
+ if (result) hideReason = "Bags cannot be put into the storage!"
+ return result
+ }
+
+ private fun hideSalvage(chestName: String, stack: ItemStack): Boolean {
+ if (chestName != "Salvage Item") return false
+
+ val name = stack.cleanName()
+
+ val armorSets = listOf(
+ "Zombie Knight",
+ "Heavy",
+ "Zombie Soldier",
+ "Skeleton Grunt",
+ "Skeleton Soldier",
+ "Zombie Commander",
+ "Skeleton Master",
+ "Sniper",
+ "Skeletor",
+ "Rotten",
+ )
+
+ val items = mutableListOf<String>()
+ for (armor in armorSets) {
+ items.add("$armor Helmet")
+ items.add("$armor Chestplate")
+ items.add("$armor Leggings")
+ items.add("$armor Boots")
+ }
+
+ items.add("Zombie Soldier Cutlass")
+ items.add("Silent Death")
+ items.add("Zombie Knight Sword")
+ items.add("Conjuring")
+ items.add("Dreadlord Sword")
+ items.add("Soulstealer Bow")
+ items.add("Machine Gun Bow")
+ items.add("Earth Shard")
+ items.add("Zombie Commander Whip")
+
+ for (item in items) {
+ if (name.endsWith(" $item")) {
+
+ if (ItemUtils.isRecombobulated(stack)) {
+ hideReason = "This item should not be salvaged! (Recombobulated)"
+ return true
+ }
+// val rarity = stack.getSkyBlockRarity()
+// if (rarity == ItemRarity.LEGENDARY || rarity == ItemRarity.MYTHIC) {
+// hideReason = "This item should not be salvaged! (Rarity)"
+// return true
+// }
+
+ return false
+ }
+ }
+
+ if (isSkyBlockMenuItem(name)) {
+ hideReason = "The SkyBlock Menu cannot be salvaged!"
+ return true
+ }
+
+ hideReason = "This item cannot be salvaged!"
+ return true
+ }
+
+ private fun hideBazaarOrAH(chestName: String, stack: ItemStack): Boolean {
+ val bazaarInventory = BazaarApi.isBazaarInventory(chestName)
+
+ val auctionHouseInventory =
+ chestName == "Co-op Auction House" || chestName == "Auction House" || chestName == "Create BIN Auction" || chestName == "Create Auction"
+ if (!bazaarInventory && !auctionHouseInventory) return false
+
+
+ val displayName = stack.displayName
+
+ if (isSkyBlockMenuItem(displayName.removeColorCodes())) {
+ if (bazaarInventory) hideReason = "The SkyBlock Menu is not a Bazaar Product!"
+ if (auctionHouseInventory) hideReason = "The SkyBlock Menu cannot be auctioned!"
+ return true
+ }
+
+ if (bazaarInventory != BazaarApi.isBazaarItem(displayName)) {
+ if (bazaarInventory) hideReason = "This item is not a Bazaar Product!"
+ if (auctionHouseInventory) hideReason = "Bazaar Products cannot be auctioned!"
+
+ return true
+ }
+
+ if (isNotAuctionable(stack)) return true
+
+ return false
+ }
+
+ private fun isNotAuctionable(stack: ItemStack): Boolean {
+ if (ItemUtils.isCoOpSoulBound(stack)) {
+ hideReason = "Coop-Soulbound items cannot be auctioned!"
+ return true
+ }
+
+ val name = stack.cleanName()
+
+ if (ItemUtils.isSack(name)) {
+ hideReason = "Sacks cannot be auctioned!"
+ return true
+ }
+
+ val result = when {
+ name.contains("Personal Deletor") -> true
+ name.contains("Day Crystal") -> true
+ name.contains("Night Crystal") -> true
+
+ name.contains("Cat Talisman") -> true
+ name.contains("Lynx Talisman") -> true
+ name.contains("Cheetah Talisman") -> true
+
+ name.contains("Hoe of Great Tilling") -> true
+ name.contains("Hoe of Greater Tilling") -> true
+ name.contains("InfiniDirt") -> true
+ name.contains("Prismapump") -> true
+ name.contains("Mathematical Hoe Blueprint") -> true
+ name.contains("Basket of Seeds") -> true
+ name.contains("Nether Wart Pouch") -> true
+
+ name.contains("Carrot Hoe") -> true
+ name.contains("Sugar Cane Hoe") -> true
+ name.contains("Nether Warts Hoe") -> true
+ name.contains("Potato Hoe") -> true
+ name.contains("Melon Dicer") -> true
+ name.contains("Pumpkin Dicer") -> true
+ name.contains("Coco Chopper") -> true
+ name.contains("Wheat Hoe") -> true
+
+ else -> false
+ }
+
+ if (result) hideReason = "This item cannot be auctioned!"
+ return result
+ }
+
+ private fun isSkyBlockMenuItem(name: String): Boolean = name == "SkyBlock Menu (Right Click)"
+}
diff --git a/src/main/java/at/lorenz/mod/bazaar/BazaarApi.kt b/src/main/java/at/lorenz/mod/bazaar/BazaarApi.kt
new file mode 100644
index 000000000..28ce228e2
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/bazaar/BazaarApi.kt
@@ -0,0 +1,59 @@
+package at.lorenz.mod.bazaar
+
+import at.lorenz.mod.utils.LorenzUtils
+
+class BazaarApi {
+
+ companion object {
+ private val bazaarMap = mutableMapOf<String, BazaarData>()
+
+ fun isBazaarInventory(inventoryName: String): Boolean {
+ if (inventoryName.contains(" ➜ ") && !inventoryName.contains("Museum")) return true
+ if (BazaarOrderHelper.isBazaarOrderInventory(inventoryName)) return true
+
+ return when (inventoryName) {
+ "Your Bazaar Orders" -> true
+ "How many do you want?" -> true
+ "How much do you want to pay?" -> true
+ "Confirm Buy Order" -> true
+ "Confirm Instant Buy" -> true
+ "At what price are you selling?" -> true
+ "Confirm Sell Offer" -> true
+ "Order options" -> true
+
+ else -> false
+ }
+ }
+
+ fun getCleanBazaarName(name: String): String {
+ if (name.endsWith(" Gemstone")) {
+ return name.substring(6)
+ }
+ if (name.startsWith("§")) {
+ return name.substring(2)
+ }
+
+ return name
+ }
+
+ fun getBazaarDataForName(name: String): BazaarData {
+ if (bazaarMap.containsKey(name)) {
+ val bazaarData = bazaarMap[name]
+ if (bazaarData != null) {
+ return bazaarData
+ }
+ LorenzUtils.error("Bazaar data is null for item '$name'")
+ }
+ throw Error("no bz data found for name '$name'")
+ }
+
+ fun isBazaarItem(name: String): Boolean {
+ val bazaarName = getCleanBazaarName(name)
+ return bazaarMap.containsKey(bazaarName)
+ }
+ }
+
+ init {
+ BazaarDataGrabber(bazaarMap).start()
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/bazaar/BazaarData.kt b/src/main/java/at/lorenz/mod/bazaar/BazaarData.kt
new file mode 100644
index 000000000..a9f75370c
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/bazaar/BazaarData.kt
@@ -0,0 +1,3 @@
+package at.lorenz.mod.bazaar
+
+data class BazaarData(val apiName: String, val itemName: String, val sellPrice: Double, val buyPrice: Double) \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/bazaar/BazaarDataGrabber.kt b/src/main/java/at/lorenz/mod/bazaar/BazaarDataGrabber.kt
new file mode 100644
index 000000000..78341e05c
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/bazaar/BazaarDataGrabber.kt
@@ -0,0 +1,116 @@
+package at.lorenz.mod.bazaar
+
+import at.lorenz.mod.utils.APIUtil
+import at.lorenz.mod.utils.LorenzUtils
+import at.lorenz.mod.utils.LorenzUtils.Companion.round
+import kotlin.concurrent.fixedRateTimer
+
+internal class BazaarDataGrabber(private var bazaarMap: MutableMap<String, BazaarData>) {
+
+ companion object {
+ private val itemNames = mutableMapOf<String, String>()
+
+ private var lastData = ""
+ var lastTime = 0L
+ var blockNoChange = false
+ var currentlyUpdating = false
+ }
+
+ private fun loadItemNames(): Boolean {
+ currentlyUpdating = true
+ try {
+ val itemsData = APIUtil.getJSONResponse("https://api.hypixel.net/resources/skyblock/items")
+ for (element in itemsData["items"].asJsonArray) {
+ val jsonObject = element.asJsonObject
+ val name = jsonObject["name"].asString
+ val id = jsonObject["id"].asString
+ itemNames[id] = name
+ }
+ currentlyUpdating = false
+ return true
+ } catch (e: Throwable) {
+ e.printStackTrace()
+ LorenzUtils.error("Error while trying to read bazaar item list from api: " + e.message)
+ currentlyUpdating = false
+ return false
+ }
+ }
+
+ fun start() {
+ fixedRateTimer(name = "lorenz-bazaar-update", period = 1000L) {
+ //TODO add
+// if (!LorenzUtils.inSkyBlock) {
+// return@fixedRateTimer
+// }
+
+ if (currentlyUpdating) {
+ LorenzUtils.error("Bazaar update took too long! Error?")
+ return@fixedRateTimer
+ }
+
+ if (itemNames.isEmpty()) {
+ if (!loadItemNames()) {
+ return@fixedRateTimer
+ }
+ }
+ checkIfUpdateNeeded()
+ }
+ }
+
+ private fun checkIfUpdateNeeded() {
+ if (lastData != "") {
+ if (System.currentTimeMillis() - lastTime > 9_000) {
+ blockNoChange = true
+ } else {
+ if (blockNoChange) {
+ return
+ }
+ }
+ }
+
+ currentlyUpdating = true
+ updateBazaarData()
+ currentlyUpdating = false
+ }
+
+ private fun updateBazaarData() {
+ val bazaarData = APIUtil.getJSONResponse("https://api.hypixel.net/skyblock/bazaar")
+ if (bazaarData.toString() != lastData) {
+ lastData = bazaarData.toString()
+ lastTime = System.currentTimeMillis()
+ }
+
+ val products = bazaarData["products"].asJsonObject
+
+ for (entry in products.entrySet()) {
+ val apiName = entry.key
+
+ if (apiName == "ENCHANTED_CARROT_ON_A_STICK") continue
+ if (apiName == "BAZAAR_COOKIE") continue
+
+ val itemData = entry.value.asJsonObject
+
+ val itemName = itemNames.getOrDefault(apiName, null)
+ if (itemName == null) {
+ LorenzUtils.error("Bazaar item name is null for '$apiName'! Restart to fix this problem!")
+ continue
+ }
+
+ val sellPrice: Double = try {
+ itemData["sell_summary"].asJsonArray[0].asJsonObject["pricePerUnit"].asDouble.round(1)
+ } catch (e: Exception) {
+// LorenzUtils.warning("Bazaar buy order for $itemName not found!")
+ 0.0
+ }
+ val buyPrice: Double = try {
+ itemData["buy_summary"].asJsonArray[0].asJsonObject["pricePerUnit"].asDouble.round(1)
+ } catch (e: Exception) {
+// LorenzUtils.warning("Bazaar sell offers for $itemName not found!")
+ 0.0
+ }
+
+ val data = BazaarData(apiName, itemName, sellPrice, buyPrice)
+ bazaarMap[itemName] = data
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/bazaar/BazaarOrderHelper.kt b/src/main/java/at/lorenz/mod/bazaar/BazaarOrderHelper.kt
new file mode 100644
index 000000000..0daa12b7d
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/bazaar/BazaarOrderHelper.kt
@@ -0,0 +1,87 @@
+package at.lorenz.mod.bazaar
+
+import at.lorenz.mod.config.LorenzConfig
+import at.lorenz.mod.events.GuiContainerEvent
+import at.lorenz.mod.utils.ItemUtils.Companion.getLore
+import at.lorenz.mod.utils.LorenzColor
+import at.lorenz.mod.utils.RenderUtil.Companion.highlight
+import net.minecraft.client.gui.inventory.GuiChest
+import net.minecraft.client.renderer.GlStateManager
+import net.minecraft.inventory.ContainerChest
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import org.lwjgl.opengl.GL11
+
+class BazaarOrderHelper {
+
+ companion object {
+ fun isBazaarOrderInventory(inventoryName: String): Boolean = when (inventoryName) {
+ "Your Bazaar Orders" -> true
+ "Co-op Bazaar Orders" -> true
+ else -> false
+ }
+ }
+
+ @SubscribeEvent
+ fun onBackgroundDrawn(event: GuiContainerEvent.BackgroundDrawnEvent) {
+ if (!LorenzConfig.lorenzBazaarOrderHelper) return
+ if (event.gui !is GuiChest) return
+ val guiChest = event.gui
+ val chest = guiChest.inventorySlots as ContainerChest
+ val inventoryName = chest.lowerChestInventory.displayName.unformattedText.trim()
+
+ if (!isBazaarOrderInventory(inventoryName)) return
+ val lightingState = GL11.glIsEnabled(GL11.GL_LIGHTING)
+ GlStateManager.disableLighting()
+ GlStateManager.color(1f, 1f, 1f, 1f)
+
+ out@ for (slot in chest.inventorySlots) {
+ if (slot == null) continue
+ if (slot.slotNumber != slot.slotIndex) continue
+ if (slot.stack == null) continue
+
+ val stack = slot.stack
+ val displayName = stack.displayName
+ val isSelling = displayName.startsWith("§6§lSELL§7: ")
+ val isBuying = displayName.startsWith("§a§lBUY§7: ")
+ if (!isSelling && !isBuying) continue
+
+ val text = displayName.split("§7: ")[1]
+ val name = BazaarApi.getCleanBazaarName(text)
+ val data = BazaarApi.getBazaarDataForName(name)
+ val buyPrice = data.buyPrice
+ val sellPrice = data.sellPrice
+
+ val itemLore = stack.getLore()
+ for (line in itemLore) {
+ if (line.startsWith("§7Filled:")) {
+ if (line.endsWith(" §a§l100%!")) {
+ slot highlight LorenzColor.GREEN
+ continue@out
+ }
+ }
+ }
+ for (line in itemLore) {
+ if (line.startsWith("§7Price per unit:")) {
+ var text = line.split(": §6")[1]
+ text = text.substring(0, text.length - 6)
+ text = text.replace(",", "")
+ val price = text.toDouble()
+ if (isSelling) {
+ if (buyPrice < price) {
+ slot highlight LorenzColor.GOLD
+ continue@out
+ }
+ } else {
+ if (sellPrice > price) {
+ slot highlight LorenzColor.GOLD
+ continue@out
+ }
+ }
+
+ }
+ }
+ }
+
+ if (lightingState) GlStateManager.enableLighting()
+ }
+}
diff --git a/src/main/java/at/lorenz/mod/chat/ChatFilter.kt b/src/main/java/at/lorenz/mod/chat/ChatFilter.kt
new file mode 100644
index 000000000..8e2160928
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/chat/ChatFilter.kt
@@ -0,0 +1,279 @@
+package at.lorenz.mod.chat
+
+import at.lorenz.mod.config.LorenzConfig
+import at.lorenz.mod.utils.LorenzUtils
+import at.lorenz.mod.utils.LorenzUtils.Companion.matchRegex
+import at.lorenz.mod.events.LorenzChatEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class ChatFilter {
+
+ @SubscribeEvent
+ fun onChatMessage(event: LorenzChatEvent) {
+ if (!LorenzConfig.chatFilter) return
+
+ val blockReason = block(event.message)
+ if (blockReason != "") {
+ event.blockedReason = blockReason
+ }
+ }
+
+ private fun block(message: String): String = when {
+ message.startsWith("§aYou are playing on profile: §e") -> "profile"//TODO move into own class
+ lobby(message) -> "lobby"
+ empty(message) -> "empty"
+ warping(message) -> "warping"
+ welcome(message) -> "welcome"
+ guild(message) -> "guild"
+ PlayerChatFilter.shouldBlock(message) -> "player_chat"
+ killCombo(message) -> "kill_combo"
+ bazaarAndAHMiniMessages(message) -> "bz_ah_minis"
+ watchdogAnnouncement(message) -> "watchdog"
+ slayer(message) -> "slayer"
+ slayerDrop(message) -> "slayer_drop"
+ uselessDrop(message) -> "useless_drop"
+ uselessNotification(message) -> "useless_notification"
+ party(message) -> "party"
+ money(message) -> "money"
+ winterIsland(message) -> "winter_island"
+ uselessWarning(message) -> "useless_warning"
+ friendJoin(message) -> "friend_join"
+
+
+ else -> ""
+ }
+
+ private fun friendJoin(message: String): Boolean {
+ return when {
+ message.matchRegex("§aFriend > §r(.*) §r§e(joined|left).") -> {
+ true
+ }
+ else -> false
+ }
+
+ }
+
+ private fun uselessNotification(message: String): Boolean {
+ return when {
+ message == "§eYour previous §r§6Plasmaflux Power Orb §r§ewas removed!" -> true
+
+ else -> false
+ }
+ }
+
+ private fun uselessWarning(message: String): Boolean = when {
+ message == "§cYou are sending commands too fast! Please slow down." -> true//TODO prevent in the future
+ message == "§cYou can't use this while in combat!" -> true
+ message == "§cYou can not modify your equipped armor set!" -> true
+ message == "§cPlease wait a few seconds between refreshing!" -> true
+ message == "§cThis item is not salvageable!" -> true//prevent in the future
+ message == "§cPlace a Dungeon weapon or armor piece above the anvil to salvage it!" -> true
+ message == "§cWhoa! Slow down there!" -> true
+ message == "§cWait a moment before confirming!" -> true
+ message == "§cYou need to be out of combat for 3 seconds before opening the SkyBlock Menu!" -> true//TODO prevent in the future
+
+ else -> false
+ }
+
+ private fun uselessDrop(message: String): Boolean {
+ when {
+ message.matchRegex("§6§lRARE DROP! §r§aEnchanted Ender Pearl (.*)") -> return true
+
+ message.matchRegex("§6§lRARE DROP! §r§fCarrot (.*)") -> return true
+ message.matchRegex("§6§lRARE DROP! §r§fPotato (.*)") -> return true
+
+ message.matchRegex("§6§lRARE DROP! §r§9Machine Gun Bow (.*)") -> return true
+ message.matchRegex("§6§lRARE DROP! §r§5Earth Shard (.*)") -> return true
+ message.matchRegex("§6§lRARE DROP! §r§5Zombie Lord Chestplate (.*)") -> return true
+ }
+
+ return false
+ }
+
+ private fun winterIsland(message: String): Boolean = when {
+ message.matchRegex(" §r§f☃ §r§7§r(.*) §r§7mounted a §r§fSnow Cannon§r§7!") -> true
+
+ else -> false
+ }
+
+ private fun money(message: String): Boolean {
+ if (isBazaar(message)) return true
+ if (isAuctionHouse(message)) return true
+
+ return false
+ }
+
+ private fun isAuctionHouse(message: String): Boolean {
+ if (message == "§b-----------------------------------------------------") return true
+ if (message == "§eVisit the Auction House to collect your item!") return true
+
+ return false
+ }
+
+ private fun isBazaar(message: String): Boolean {
+ if (message.matchRegex("§eBuy Order Setup! §r§a(.*)§r§7x (.*) §r§7for §r§6(.*) coins§r§7.")) return true
+ if (message.matchRegex("§eSell Offer Setup! §r§a(.*)§r§7x (.*) §r§7for §r§6(.*) coins§r§7.")) return true
+ if (message.matchRegex("§cCancelled! §r§7Refunded §r§6(.*) coins §r§7from cancelling buy order!")) return true
+ if (message.matchRegex("§cCancelled! §r§7Refunded §r§a(.*)§r§7x (.*) §r§7from cancelling sell offer!")) return true
+
+ return false
+ }
+
+ private fun party(message: String): Boolean {
+ if (message == "§9§m-----------------------------") return true
+ if (message == "§9§m-----------------------------------------------------") return true
+
+ return false
+ }
+
+ private fun slayerDrop(message: String): Boolean {
+ //Revenant
+ if (message.matchRegex("§b§lRARE DROP! §r§7\\(§r§f§r§9Revenant Viscera§r§7\\) (.*)")) return true
+ if (message.matchRegex("§b§lRARE DROP! §r§7\\(§r§f§r§7(.*)x §r§f§r§9Foul Flesh§r§7\\) (.*)")) return true
+ if (message.matchRegex("§b§lRARE DROP! §r§7\\(§r§f§r§9Foul Flesh§r§7\\) (.*)")) return true
+ if (message.matchRegex("§6§lRARE DROP! §r§5Golden Powder (.*)")) return true
+ if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§f§r§2(.*) Pestilence Rune I§r§7\\) (.*)")) {
+ LorenzUtils.debug("check regex for this blocked message!")
+ return true
+ }
+ if (message.matchRegex("§5§lVERY RARE DROP! §r§7\\(§r§f§r§5Revenant Catalyst§r§7\\) (.*)")) return true
+ if (message.matchRegex("§5§lVERY RARE DROP! §r§7\\(§r§f§r§9Undead Catalyst§r§7\\) (.*)")) return true
+
+ //Enderman
+ if (message.matchRegex("§b§lRARE DROP! §r§7\\(§r§f§r§7(.*)x §r§f§r§aTwilight Arrow Poison§r§7\\) (.*)")) return true
+ if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§fMana Steal I§r§7\\) (.*)")) return true
+ if (message.matchRegex("§5§lVERY RARE DROP! §r§7\\(§r§f§r§5Sinful Dice§r§7\\) (.*)")) return true
+ if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§f§r§9Null Atom§r§7\\) (.*)")) return true
+ if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§f§r§5Transmission Tuner§r§7\\) (.*)")) return true
+ if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§fMana Steal I§r§7\\) (.*)")) return true
+ if (message.matchRegex("§9§lVERY RARE DROP! §r§7\\(§r§f§r§5◆ Endersnake Rune I§r§7\\) (.*)")) return true
+ if (message.matchRegex("§d§lCRAZY RARE DROP! §r§7\\(§r§f§r§fPocket Espresso Machine§r§7\\) (.*)")) return true
+ if (message.matchRegex("§5§lVERY RARE DROP! §r§7\\(§r§f§r§5◆ End Rune I§r§7\\) (.*)")) return true
+
+ return false
+ }
+
+ private fun slayer(message: String): Boolean {
+ //start
+ if (message.matchRegex(" §r§5§lSLAYER QUEST STARTED!")) return true
+ if (message.matchRegex(" §5§l» §7Slay §c(.*) Combat XP §7worth of (.*)§7.")) return true
+
+ //end
+ if (message.matchRegex(" §r§a§lSLAYER QUEST COMPLETE!")) return true
+ if (message == " §r§6§lNICE! SLAYER BOSS SLAIN!") return true
+ if (message.matchRegex(" §r§e(.*)Slayer LVL 9 §r§5- §r§a§lLVL MAXED OUT!")) return true
+ if (message.matchRegex(" §r§5§l» §r§7Talk to Maddox to claim your (.*) Slayer XP!")) return true
+
+
+ if (message == "§eYou received kill credit for assisting on a slayer miniboss!") return true
+
+ if (message == "§e✆ Ring... ") return true
+ if (message == "§e✆ Ring... Ring... ") return true
+ if (message == "§e✆ Ring... Ring... Ring... ") return true
+
+ return false
+ }
+
+ private fun watchdogAnnouncement(message: String): Boolean = when {
+ message == "§4[WATCHDOG ANNOUNCEMENT]" -> true
+ message.matchRegex("§fWatchdog has banned §r§c§l(.*)§r§f players in the last 7 days.") -> true
+ message.matchRegex("§fStaff have banned an additional §r§c§l(.*)§r§f in the last 7 days.") -> true
+ message == "§cBlacklisted modifications are a bannable offense!" -> true
+ else -> false
+ }
+
+ private fun bazaarAndAHMiniMessages(message: String): Boolean = when (message) {
+ "§7Putting item in escrow...",
+ "§7Putting goods in escrow...",
+ "§7Putting coins in escrow...",
+
+ //Auction House
+ "§7Setting up the auction...",
+ "§7Processing purchase...",
+ "§7Claiming order...",
+ "§7Processing bid...",
+ "§7Claiming BIN auction...",
+
+ //Bazaar
+ "§7Submitting sell offer...",
+ "§7Submitting buy order...",
+ "§7Executing instant sell...",
+ "§7Executing instant buy...",
+
+ //Bank
+ "§8Depositing coins...",
+ "§8Withdrawing coins..." -> true
+ else -> false
+ }
+
+ private fun killCombo(message: String): Boolean {
+ //§a§l+5 Kill Combo §r§8+§r§b3% §r§b? Magic Find
+ return when {
+ message.matchRegex("§.§l\\+(.*) Kill Combo §r§8\\+(.*)") -> true
+ message.matchRegex("§cYour Kill Combo has expired! You reached a (.*) Kill Combo!") -> true
+ else -> false
+ }
+ }
+
+ private fun lobby(message: String): Boolean = when {
+ //player join
+ message.matchRegex("(.*) §6joined the lobby!") -> true
+ message.matchRegex(" §b>§c>§a>§r (.*) §6joined the lobby!§r §a<§c<§b<") -> true
+
+ //mystery box
+ message.matchRegex("§b✦ §r(.*) §r§7found a §r§e(.*) §r§bMystery Box§r§7!") -> true
+ message.matchRegex("§b✦ §r(.*) §r§7found (a|an) §r(.*) §r§7in a §r§aMystery Box§r§7!") -> true
+
+ //prototype
+ message.contains("§r§6§lWelcome to the Prototype Lobby§r") -> true
+ message == " §r§f§l➤ §r§6You have reached your Hype limit! Add Hype to Prototype Lobby minigames by right-clicking with the Hype Diamond!" -> true
+
+ //hypixel tournament notifications
+ message.contains("§r§e§6§lHYPIXEL§e is hosting a §b§lBED WARS DOUBLES§e tournament!") -> true
+ message.contains("§r§e§6§lHYPIXEL BED WARS DOUBLES§e tournament is live!") -> true
+
+ //other
+ message.contains("§aYou are still radiating with §bGenerosity§r§a!") -> true
+ else -> false
+ }
+
+ private fun guild(message: String): Boolean = when {
+ message.matchRegex("§2Guild > (.*) §r§e(joined|left).") -> true
+ message.matchRegex("§aYou earned §r§2(.*) GEXP §r§afrom playing SkyBlock!") -> true
+ message.matchRegex("§aYou earned §r§2(.*) GEXP §r§a\\+ §r§e(.*) Event EXP §r§afrom playing SkyBlock!") -> true
+ message == "§b§m-----------------------------------------------------" -> true
+ else -> false
+ }
+
+ private fun welcome(message: String): Boolean = message == "§eWelcome to §r§aHypixel SkyBlock§r§e!"
+
+ private fun warping(message: String): Boolean = when {
+ message.matchRegex("§7Sending to server (.*)\\.\\.\\.") -> true
+ message.matchRegex("§7Request join for Hub (.*)\\.\\.\\.") -> true
+ message.matchRegex("§7Request join for Dungeon Hub #(.*)\\.\\.\\.") -> true
+ message == "§7Warping..." -> true
+ message == "§7Warping you to your SkyBlock island..." -> true
+ message == "§7Warping using transfer token..." -> true
+
+ //visiting other players
+ message == "§7Finding player..." -> true
+ message == "§7Sending a visit request..." -> true
+
+ //warp portals on public islands (canvas room - flower house, election room - community center, void sepulture - the end)
+ message.matchRegex("§dWarped to (.*)§r§d!") -> true
+ else -> false
+ }
+
+ private fun empty(message: String): Boolean = when (message) {
+ "§8 §r§8 §r§1 §r§3 §r§3 §r§7 §r§8 ",
+
+ "§f §r§f §r§1 §r§0 §r§2 §r§4§r§f §r§f §r§2 §r§0 §r§4 §r§8§r§0§r§1§r§0§r§1§r§2§r§f§r§f§r§0§r§1§r§3§r§4§r§f§r§f§r§0§r§1§r§5§r§f§r§f§r§0§r§1§r§6§r§f§r§f§r§0§r§1§r§8§r§9§r§a§r§b§r§f§r§f§r§0§r§1§r§7§r§f§r§f§r§3 §r§9 §r§2 §r§0 §r§0 §r§1§r§3 §r§9 §r§2 §r§0 §r§0 §r§2§r§3 §r§9 §r§2 §r§0 §r§0 §r§3§r§0§r§0§r§1§r§f§r§e§r§0§r§0§r§2§r§f§r§e§r§0§r§0§r§3§r§4§r§5§r§6§r§7§r§8§r§f§r§e§r§3 §r§6 §r§3 §r§6 §r§3 §r§6 §r§e§r§3 §r§6 §r§3 §r§6 §r§3 §r§6 §r§d",
+
+ "§f §r§r§r§f §r§r§r§1 §r§r§r§0 §r§r§r§2 §r§r§r§f §r§r§r§f §r§r§r§2 §r§r§r§0 §r§r§r§4 §r§r§r§3 §r§r§r§9 §r§r§r§2 §r§r§r§0 §r§r§r§0 §r§r§r§3 §r§r§r§9 §r§r§r§2 §r§r§r§0 §r§r§r§0 §r§r§r§3 §r§r§r§9 §r§r§r§2 §r§r§r§0 §r§r§r§0 ",
+
+ "",
+ "§f",
+ "§c" -> true
+ else -> false
+ }
+}
diff --git a/src/main/java/at/lorenz/mod/chat/ChatManager.kt b/src/main/java/at/lorenz/mod/chat/ChatManager.kt
new file mode 100644
index 000000000..a85643e1b
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/chat/ChatManager.kt
@@ -0,0 +1,47 @@
+package at.lorenz.mod.chat
+
+import at.lorenz.mod.utils.LorenzLogger
+import at.lorenz.mod.events.LorenzChatEvent
+import at.lorenz.mod.utils.LorenzUtils
+import net.minecraftforge.client.event.ClientChatReceivedEvent
+import net.minecraftforge.fml.common.eventhandler.EventPriority
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class ChatManager {
+
+ private val loggerAll = LorenzLogger("chat/filter_all")
+ private val loggerFiltered = LorenzLogger("chat/filter_blocked")
+ private val loggerAllowed = LorenzLogger("chat/filter_allowed")
+ private val loggerFilteredTypes = mutableMapOf<String, LorenzLogger>()
+
+ @SubscribeEvent(priority = EventPriority.LOW, receiveCanceled = true)
+ fun onChatPacket(event: ClientChatReceivedEvent) {
+ val messageComponent = event.message
+
+ val message = LorenzUtils.stripVanillaMessage(messageComponent.formattedText)
+ if (event.type.toInt() == 2) {
+// val actionBarEvent = LorenzActionBarEvent(message)
+// actionBarEvent.postAndCatch()
+ } else {
+
+ val chatEvent = LorenzChatEvent(message, messageComponent)
+ chatEvent.postAndCatch()
+
+ val blockReason = chatEvent.blockedReason.uppercase()
+ if (blockReason != "") {
+ event.isCanceled = true
+ loggerFiltered.log("[$blockReason] $message")
+ loggerAll.log("[$blockReason] $message")
+ loggerFilteredTypes.getOrPut(blockReason) { LorenzLogger("chat/filter_blocked/$blockReason") }
+ .log(message)
+ return
+ }
+
+ if (!message.startsWith("§f{\"server\":\"")) {
+ loggerAllowed.log(message)
+ loggerAll.log("[allowed] $message")
+ }
+
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/chat/PlayerChatFilter.kt b/src/main/java/at/lorenz/mod/chat/PlayerChatFilter.kt
new file mode 100644
index 000000000..427b25e37
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/chat/PlayerChatFilter.kt
@@ -0,0 +1,78 @@
+package at.lorenz.mod.chat
+
+import at.lorenz.mod.utils.LorenzLogger
+import at.lorenz.mod.utils.LorenzUtils
+import at.lorenz.mod.utils.LorenzUtils.Companion.removeColorCodes
+import at.lorenz.mod.events.PlayerSendChatEvent
+
+class PlayerChatFilter {
+
+ companion object {
+ val loggerPlayerChat = LorenzLogger("chat/player")
+
+ fun shouldBlock(originalMessage: String): Boolean {
+ val split: List<String> = if (originalMessage.contains("§7§r§7: ")) {
+ originalMessage.split("§7§r§7: ")
+ } else if (originalMessage.contains("§f: ")) {
+ originalMessage.split("§f: ")
+ } else {
+ return false
+ }
+
+ var rawName = split[0]
+ val message = split[1]
+
+ val channel: PlayerMessageChannel
+ if (rawName.startsWith("§9Party §8> ")) {
+ channel = PlayerMessageChannel.PARTY
+ rawName = rawName.substring(12)
+ } else if (rawName.startsWith("§2Guild > ")) {
+ channel = PlayerMessageChannel.GUILD
+ rawName = rawName.substring(10)
+ } else if (rawName.startsWith("§bCo-op > ")) {
+ channel = PlayerMessageChannel.COOP
+ rawName = rawName.substring(10)
+ } else {
+ channel = PlayerMessageChannel.ALL
+ }
+
+ val nameSplit = rawName.split(" ")
+ val first = nameSplit[0]
+
+ val last = nameSplit.last()
+ val name = if (last.endsWith("]")) {
+ nameSplit[nameSplit.size - 2]
+ } else {
+ last
+ }
+
+ if (first != name) {
+ if (!first.contains("VIP") && !first.contains("MVP")) {
+ //TODO support yt + admin
+ return false
+ }
+ }
+
+ send(channel, name.removeColorCodes(), message.removeColorCodes())
+ return true
+ }
+
+ private fun send(channel: PlayerMessageChannel, name: String, message: String) {
+ loggerPlayerChat.log("[$channel] $name: $message")
+ val event = PlayerSendChatEvent(channel, name, message)
+ event.postAndCatch()
+
+ if (event.cancelledReason != "") {
+ loggerPlayerChat.log("cancelled: " + event.cancelledReason)
+ } else {
+ val finalMessage = event.message
+ if (finalMessage != message) {
+ loggerPlayerChat.log("message changed: $finalMessage")
+ }
+
+ val prefix = channel.prefix
+ LorenzUtils.chat("$prefix §b$name §f$finalMessage")
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/chat/PlayerMessageChannel.kt b/src/main/java/at/lorenz/mod/chat/PlayerMessageChannel.kt
new file mode 100644
index 000000000..0e44b88f0
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/chat/PlayerMessageChannel.kt
@@ -0,0 +1,10 @@
+package at.lorenz.mod.chat
+
+enum class PlayerMessageChannel(val prefix: String) {
+
+ ALL("§fA>"),
+ ALL_ADVERTISEMENT("§8A>"),
+ PARTY("§9P>"),
+ GUILD("§2G>"),
+ COOP("§bCC>"),
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/config/LorenzConfig.kt b/src/main/java/at/lorenz/mod/config/LorenzConfig.kt
new file mode 100644
index 000000000..67268b6ca
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/config/LorenzConfig.kt
@@ -0,0 +1,12 @@
+package at.lorenz.mod.config
+
+class LorenzConfig {
+
+ companion object {
+ val chatFilter = true
+ val dungeonHideAnnoyingMessages = true
+ val hideNotClickableItems = true
+
+ val lorenzBazaarOrderHelper = true
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/dungeon/DungeonChatFilter.kt b/src/main/java/at/lorenz/mod/dungeon/DungeonChatFilter.kt
new file mode 100644
index 000000000..86fc67cfd
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/dungeon/DungeonChatFilter.kt
@@ -0,0 +1,259 @@
+package at.lorenz.mod.dungeon
+
+import at.lorenz.mod.config.LorenzConfig
+import at.lorenz.mod.events.LorenzChatEvent
+import at.lorenz.mod.utils.LorenzUtils.Companion.matchRegex
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class DungeonChatFilter {
+
+ @SubscribeEvent
+ fun onChatMessage(event: LorenzChatEvent) {
+ if (!LorenzConfig.dungeonHideAnnoyingMessages) return
+
+ val blockReason = block(event.message)
+ if (blockReason != "") {
+ event.blockedReason = "dungeon_$blockReason"
+ }
+ }
+
+ private fun block(message: String): String {
+ when {
+ isPrepare(message) -> return "prepare"
+ isStart(message) -> return "start"
+ }
+
+ //TODO add
+// if (!LorenzUtils.inDungeon) return ""
+
+ return when {
+ isKey(message) -> "key"
+ isDoor(message) -> "door"
+ isPickup(message) -> "pickup"
+ isReminder(message) -> "reminder"
+ isBuff(message) -> "buff"
+ isNotPossible(message) -> "not_possible"
+ isDamage(message) -> "damage"
+ isAbility(message) -> "ability"
+ isPuzzle(message) -> "puzzle"
+ isBoss(message) -> "boss"
+ isEnd(message) -> "end"
+ //TODO add
+// DungeonMilestoneDisplay.isMilestoneMessage(message) -> "milestone"
+
+ else -> ""
+ }
+ }
+
+ private fun isDoor(message: String): Boolean = message == "§cThe §r§c§lBLOOD DOOR§r§c has been opened!"
+
+ private fun isBoss(message: String): Boolean {
+ when {
+ message.matchRegex("§([cd4])\\[BOSS] (.*)") -> {
+ when {
+ message.contains(" The Watcher§r§f: ") -> return true
+ message.contains(" Bonzo§r§f: ") -> return true
+ message.contains(" Scarf§r§f:") -> return true
+ message.contains("Professor§r§f") -> return true
+ message.contains(" Livid§r§f: ") || message.contains(" Enderman§r§f: ") -> return true
+ message.contains(" Thorn§r§f: ") -> return true
+ message.contains(" Sadan§r§f: ") -> return true
+ message.contains(" Maxor§r§c: ") -> return true
+ message.contains(" Storm§r§c: ") -> return true
+ message.contains(" Goldor§r§c: ") -> return true
+ message.contains(" Necron§r§c: ") -> return true
+ message.contains(" §r§4§kWither King§r§c:") -> return true
+
+ message.endsWith(" Necron§r§c: That is enough, fool!") -> return true
+ message.endsWith(" Necron§r§c: Adventurers! Be careful of who you are messing with..") -> return true
+ message.endsWith(" Necron§r§c: Before I have to deal with you myself.") -> return true
+ }
+ }
+
+ //M7 - Dragons
+ message == "§cThe Crystal withers your soul as you hold it in your hands!" -> return true
+ message == "§cIt doesn't seem like that is supposed to go there." -> return true
+ }
+ return false
+ }
+
+ private fun isEnd(message: String): Boolean = when {
+ message.matchRegex("(.*) §r§eunlocked §r§d(.*) Essence §r§8x(.*)§r§e!") -> true
+ message.matchRegex(" §r§d(.*) Essence §r§8x(.*)") -> true
+ message.endsWith(" Experience §r§b(Team Bonus)") -> true
+ else -> false
+ }
+
+ private fun isAbility(message: String): Boolean = when {
+ message == "§a§r§6Guided Sheep §r§ais now available!" -> true
+ message.matchRegex("§7Your Guided Sheep hit §r§c(.*) §r§7enemy for §r§c(.*) §r§7damage.") -> true
+ message == "§6Rapid Fire§r§a is ready to use! Press §r§6§lDROP§r§a to activate it!" -> true
+ message == "§6Castle of Stone§r§a is ready to use! Press §r§6§lDROP§r§a to activate it!" -> true
+
+
+ message.matchRegex("§a§lBUFF! §fYou were splashed by (.*) §fwith §r§cHealing VIII§r§f!") -> true
+ message.matchRegex("§aYou were healed for (.*) health by (.*)§a!") -> true
+ message.matchRegex("§aYou gained (.*) HP worth of absorption for 3s from §r(.*)§r§a!") -> true
+ message.matchRegex("§c(.*) §r§epicked up your (.*) Orb!") -> true
+ message.matchRegex("§cThis ability is on cooldown for (.*)s.") -> true
+ message.matchRegex("§a§l(.*) healed you for (.*) health!") -> true
+ message == "§aYou used your §r§6Mining Speed Boost §r§aPickaxe Ability!" -> true
+ message == "§cYour Mining Speed Boost has expired!" -> true
+ message == "§a§r§6Mining Speed Boost §r§ais now available!" -> true
+ message.matchRegex("§eYour bone plating reduced the damage you took by §r§c(.*)§r§e!") -> true
+ message.matchRegex("(.*) §r§eformed a tether with you!") -> true
+ message.matchRegex("§eYour tether with (.*) §r§ehealed you for §r§a(.*) §r§ehealth.") -> true
+ message.matchRegex("§7Your Implosion hit §r§c(.*) §r§7enemy for §r§c(.*) §r§7damage.") -> true
+
+ message.matchRegex("§eYour §r§6Spirit Pet §r§ehealed (.*) §r§efor §r§a(.*) §r§ehealth!") -> true
+ message.matchRegex("§eYour §r§6Spirit Pet §r§ehit (.*) enemy for §r§c(.*) §r§edamage.") -> true
+
+ message == "§dCreeper Veil §r§aActivated!" -> true
+ message == "§dCreeper Veil §r§cDe-activated!" -> true
+ message.matchRegex("§cYou need at least (.*) mana to activate this!") -> true
+
+ message.matchRegex(
+ "§eYou were healed for §r§a(.*)§r§e health by §r(.*)§r§e's §r§9Healing Bow§r§e and " + "gained §r§c\\+(.*) Strength§r§e for 10 seconds."
+ ) -> true
+ message.matchRegex("(.*)§r§a granted you §r§c(.*) §r§astrength for §r§e20 §r§aseconds!") -> true
+
+ message.matchRegex("§eYour fairy healed §r§ayourself §r§efor §r§a(.*) §r§ehealth!") -> true
+ message.matchRegex("§eYour fairy healed §r(.*) §r§efor §r§a(.*) §r§ehealth!") -> true
+ message.matchRegex("(.*) fairy healed you for §r§a(.*) §r§ehealth!") -> true
+
+ else -> false
+ }
+
+ private fun isDamage(message: String): Boolean = when {
+ message == "§cMute silenced you!" -> true
+ message.matchRegex("(.*) §r§aused §r(.*) §r§aon you!") -> true
+ message.matchRegex("§cThe (.*)§r§c struck you for (.*) damage!") -> true
+ message.matchRegex("§cThe (.*) hit you for (.*) damage!") -> true
+ message.matchRegex("§7(.*) struck you for §r§c(.*)§r§7 damage.") -> true
+ message.matchRegex("(.*) hit you for §r§c(.*)§r§7 damage.") -> true
+ message.matchRegex("(.*) hit you for §r§c(.*)§r§7 true damage.") -> true
+ message.matchRegex("§7(.*) exploded, hitting you for §r§c(.*)§r§7 damage.") -> true
+ message.matchRegex("(.*)§r§c hit you with §r(.*) §r§cfor (.*) damage!") -> true
+ message.matchRegex("(.*)§r§a struck you for §r§c(.*)§r§a damage!") -> true
+ message.matchRegex("(.*)§r§c struck you for (.*)!") -> true
+
+ //TODO abstract if more "burnt" messages are found
+ message.matchRegex("§7The Mage's Magma burnt you for §r§c(.*)§r§7 true damage.") -> true
+
+ message.matchRegex("§7Your (.*) hit §r§c(.*) §r§7(enemy|enemies) for §r§c(.*) §r§7damage.") -> true
+ else -> false
+ }
+
+ private fun isNotPossible(message: String): Boolean = when (message) {
+ "§cYou cannot hit the silverfish while it's moving!",
+ "§cYou cannot move the silverfish in that direction!",
+ "§cThere are blocks in the way!",
+ "§cThis chest has already been searched!",
+ "§cThis lever has already been used.",
+ "§cYou cannot do that in this room!",
+ "§cYou do not have the key for this door!",
+ "§cYou have already opened this dungeon chest!",
+ "§cYou cannot use abilities in this room!",
+ "§cA mystical force in this room prevents you from using that ability!" -> true
+
+ else -> false
+ }
+
+ private fun isBuff(message: String): Boolean = when {
+ message.matchRegex("§6§lDUNGEON BUFF! (.*) §r§ffound a §r§dBlessing of (.*)§r§f!(.*)") -> true
+ message.matchRegex("§6§lDUNGEON BUFF! §r§fYou found a §r§dBlessing of (.*)§r§f!(.*)") -> true
+ message.matchRegex("§6§lDUNGEON BUFF! §r§fA §r§dBlessing of (.*)§r§f was found! (.*)") -> true
+ message.matchRegex("§eA §r§a§r§dBlessing of (.*)§r§e was picked up!") -> true
+ message.matchRegex("(.*) §r§ehas obtained §r§a§r§dBlessing of (.*)§r§e!") -> true
+ message.matchRegex(" §r§7(Grants|Granted) you §r§a(.*) Strength §r§7and §r§a(.*) Crit Damage§r§7.") -> true
+ message.matchRegex(" §r§7(Grants|Granted) you §r§a(.*) Defense §r§7and §r§a+(.*) Damage§r§7.") -> true
+ message.matchRegex(" §r§7(Grants|Granted) you §r§a(.*) HP §r§7and §r§a+(.*)% §r§7health regeneration.") -> true
+ message.matchRegex(" §r§7(Grants|Granted) you §r§a(.*) Intelligence §r§7and §r§a+(.*)? Speed§r§7.") -> true
+ message.matchRegex(" §r§7Granted you §r§a+(.*) HP§r§7, §r§a(.*) Defense§r§7, §r§a(.*) Intelligence§r§7, and §r§a(.*) Strength§r§7.") -> true
+ message == "§a§lBUFF! §fYou have gained §r§cHealing V§r§f!" -> true
+ else -> false
+ }
+
+ private fun isPuzzle(message: String): Boolean = when {
+ message.matchRegex("§a§lPUZZLE SOLVED! (.*) §r§ewasn't fooled by §r§c(.*)§r§e! §r§4G§r§co§r§6o§r§ed§r§a §r§2j§r§bo§r§3b§r§5!") -> true
+ message.matchRegex("§a§lPUZZLE SOLVED! (.*) §r§etied Tic Tac Toe! §r§4G§r§co§r§6o§r§ed§r§a §r§2j§r§bo§r§3b§r§5!") -> true
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fThough I sit stationary in this prison that is §r§cThe Catacombs§r§f, my knowledge knows no bounds." -> true
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fProve your knowledge by answering 3 questions and I shall reward you in ways that transcend time!" -> true
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fAnswer incorrectly, and your moment of ineptitude will live on for generations." -> true
+
+// message == "§4[STATUE] Oruo the Omniscient§r§f: §r§f2 questions §r§fleft...and§r§f you will have proven your worth to me!" -> true
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§f2 questions left... Then you will have proven your worth to me!" -> true
+
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fOne more question!" -> true
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fI bestow upon you all the power of a hundred years!" -> true
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fYou've already proven enough to me! No need to press more of my buttons!" -> true
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fI've had enough of you and your party fiddling with my buttons. Scram!" -> true
+ message == "§4[STATUE] Oruo the Omniscient§r§f: §r§fEnough! My buttons are not to be pressed with such lack of grace!" -> true
+ message.matchRegex("§4\\[STATUE] Oruo the Omniscient§r§f: §r(.*) §r§fthinks the answer is §r§6 . §r(.*)§r§f! §r§fLock in your party's answer in my Chamber!") -> true
+ else -> false
+ }
+
+ private fun isKey(message: String): Boolean = when {
+ message.matchRegex("(.*) §r§ehas obtained §r§a§r§6§r§8Wither Key§r§e!") -> true
+ message.matchRegex("(.*) opened a §r§8§lWITHER §r§adoor!") -> true
+ message.matchRegex("(.*) §r§ehas obtained §r§a§r§c§r§cBlood Key§r§e!") -> true
+ message.matchRegex("(.*) §r§ehas obtained §r§a§r§9Beating Heart§r§e!") -> true
+ message == "§5A shiver runs down your spine..." -> true
+ message == "§eA §r§a§r§6§r§8Wither Key§r§e was picked up!" -> true
+ message == "§eA §r§a§r§c§r§cBlood Key§r§e was picked up!" -> true
+
+ else -> false
+ }
+
+ private fun isReminder(message: String): Boolean = when (message) {
+ "§e§lRIGHT CLICK §r§7on §r§7a §r§8WITHER §r§7door§r§7 to open it. This key can only be used to open §r§a1§r§7 door!",
+ "§e§lRIGHT CLICK §r§7on §r§7the §r§cBLOOD DOOR§r§7 to open it. This key can only be used to open §r§a1§r§7 door!" -> true
+
+ else -> false
+ }
+
+ private fun isPickup(message: String): Boolean = when {
+ message.matchRegex("(.*) §r§ehas obtained §r§a§r§9Superboom TNT§r§e!") -> true
+ message.matchRegex("(.*) §r§ehas obtained §r§a§r§9Superboom TNT §r§8x2§r§e!") -> true
+ message.matchRegex("§6§lRARE DROP! §r§9Hunk of Blue Ice §r§b\\(+(.*)% Magic Find!\\)") -> true
+ message.matchRegex("(.*) §r§ehas obtained §r§a§r§6Revive Stone§r§e!") -> true
+ message.matchRegex("(.*) §r§ffound a §r§dWither Essence§r§f! Everyone gains an extra essence!") -> true
+ message == "§fYou found a §r§dWither Essence§r§f! Everyone gains an extra essence!" -> true
+ message.matchRegex("§d(.*) the Fairy§r§f: You killed me! Take this §r§6Revive Stone §r§fso that my death is not in vain!") -> true
+ message.matchRegex("§d(.*) the Fairy§r§f: You killed me! I'll revive you so that my death is not in vain!") -> true
+ message.matchRegex("§d(.*) the Fairy§r§f: You killed me! I'll revive your friend §r(.*) §r§fso that my death is not in vain!") -> true
+ message.matchRegex("§d(.*) the Fairy§r§f: Have a great life!") -> true
+ message.matchRegex(
+ "§c(.*) §r§eYou picked up a Ability Damage Orb from (.*) §r§ehealing you for §r§c(.*) §r§eand granting you +§r§c(.*)% §r§eAbility Damage for §r§b10 §r§eseconds."
+ ) -> true
+ message.matchRegex(
+ "§c(.*) §r§eYou picked up a Damage Orb from (.*) §r§ehealing you for §r§c(.*) §r§eand granting you +§r§c(.*)% §r§eDamage for §r§b10 §r§eseconds."
+ ) -> true
+ message.matchRegex("(.*) §r§ehas obtained §r§a§r§9Premium Flesh§r§e!") -> true
+ message.matchRegex("§6§lRARE DROP! §r§9Beating Heart §r§b(.*)") -> true
+ else -> false
+ }
+
+ private fun isStart(message: String): Boolean = when {
+ message == "§e[NPC] §bMort§f: §rHere, I found this map when I first entered the dungeon." -> true
+ message == "§e[NPC] §bMort§f: §rYou should find it useful if you get lost." -> true
+ message == "§e[NPC] §bMort§f: §rGood luck." -> true
+ message == "§e[NPC] §bMort§f: §rTalk to me to change your class and ready up." -> true
+
+ //§a[Berserk] §r§fMelee Damage §r§c48%§r§f -> §r§a88%
+ //§a[Berserk] §r§fWalk Speed §r§c38§r§f -> §r§a68
+ message.matchRegex("§a(.*) §r§f(.*) §r§c(.*)§r§f -> §r§a(.*)") -> true
+ else -> false
+ }
+
+ private fun isPrepare(message: String): Boolean = when {
+ message == "§aYour active Potion Effects have been paused and stored. They will be restored when you leave Dungeons! You are not allowed to use existing Potion Effects while in Dungeons." -> true
+ message.matchRegex("(.*) has started the dungeon countdown. The dungeon will begin in 1 minute.") -> true
+ message.matchRegex("§e[NPC] §bMort§f: §rTalk to me to change your class and ready up.") -> true
+ message.matchRegex("(.*) §a is now ready!") -> true
+ message.matchRegex("§aDungeon starts in (.*) seconds.") -> true
+ message == "§aDungeon starts in 1 second." -> true
+ message == "§aYou can no longer consume or splash any potions during the remainder of this Dungeon run!" -> true
+ else -> false
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/events/GuiContainerEvent.kt b/src/main/java/at/lorenz/mod/events/GuiContainerEvent.kt
new file mode 100644
index 000000000..4f43ab505
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/events/GuiContainerEvent.kt
@@ -0,0 +1,54 @@
+package at.lorenz.mod.events
+
+import net.minecraft.client.gui.inventory.GuiContainer
+import net.minecraft.inventory.Container
+import net.minecraft.inventory.ContainerChest
+import net.minecraft.inventory.Slot
+import net.minecraftforge.fml.common.eventhandler.Cancelable
+
+abstract class GuiContainerEvent(open val gui: GuiContainer, open val container: Container) : LorenzEvent() {
+ val chestName: String by lazy {
+ if (container !is ContainerChest) error("Container is not a chest")
+ return@lazy (container as ContainerChest).lowerChestInventory.displayName.unformattedText.trim()
+ }
+
+ data class BackgroundDrawnEvent(
+ override val gui: GuiContainer,
+ override val container: Container,
+ val mouseX: Int,
+ val mouseY: Int,
+ val partialTicks: Float
+ ) : GuiContainerEvent(gui, container)
+
+ @Cancelable
+ data class CloseWindowEvent(override val gui: GuiContainer, override val container: Container) :
+ GuiContainerEvent(gui, container)
+
+ abstract class DrawSlotEvent(gui: GuiContainer, container: Container, open val slot: Slot) :
+ GuiContainerEvent(gui, container) {
+ @Cancelable
+ data class Pre(override val gui: GuiContainer, override val container: Container, override val slot: Slot) :
+ DrawSlotEvent(gui, container, slot)
+
+ data class Post(override val gui: GuiContainer, override val container: Container, override val slot: Slot) :
+ DrawSlotEvent(gui, container, slot)
+ }
+
+ data class ForegroundDrawnEvent(
+ override val gui: GuiContainer,
+ override val container: Container,
+ val mouseX: Int,
+ val mouseY: Int,
+ val partialTicks: Float
+ ) : GuiContainerEvent(gui, container)
+
+ @Cancelable
+ data class SlotClickEvent(
+ override val gui: GuiContainer,
+ override val container: Container,
+ val slot: Slot?,
+ val slotId: Int,
+ val clickedButton: Int,
+ val clickType: Int
+ ) : GuiContainerEvent(gui, container)
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/events/LorenzChatEvent.kt b/src/main/java/at/lorenz/mod/events/LorenzChatEvent.kt
new file mode 100644
index 000000000..3b0350918
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/events/LorenzChatEvent.kt
@@ -0,0 +1,5 @@
+package at.lorenz.mod.events
+
+import net.minecraft.util.IChatComponent
+
+class LorenzChatEvent(val message: String, val chatComponent: IChatComponent, var blockedReason: String = "") : LorenzEvent() \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/events/LorenzEvent.kt b/src/main/java/at/lorenz/mod/events/LorenzEvent.kt
new file mode 100644
index 000000000..facb18e2a
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/events/LorenzEvent.kt
@@ -0,0 +1,20 @@
+package at.lorenz.mod.events
+
+import at.lorenz.mod.utils.LorenzUtils
+import net.minecraftforge.common.MinecraftForge
+import net.minecraftforge.fml.common.eventhandler.Event
+
+abstract class LorenzEvent: Event() {
+ val eventName by lazy {
+ this::class.simpleName
+ }
+
+ fun postAndCatch(): Boolean {
+ return runCatching {
+ MinecraftForge.EVENT_BUS.post(this)
+ }.onFailure {
+ it.printStackTrace()
+ LorenzUtils.chat("§cLorenz Mod caught and logged an ${it::class.simpleName ?: "error"} at ${eventName}.")
+ }.getOrDefault(isCanceled)
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/events/PlayerSendChatEvent.kt b/src/main/java/at/lorenz/mod/events/PlayerSendChatEvent.kt
new file mode 100644
index 000000000..dd015e1cb
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/events/PlayerSendChatEvent.kt
@@ -0,0 +1,11 @@
+package at.lorenz.mod.events
+
+import at.lorenz.mod.chat.PlayerMessageChannel
+
+
+class PlayerSendChatEvent(
+ val channel: PlayerMessageChannel,
+ val playerName: String,
+ var message: String,
+ var cancelledReason: String = ""
+) : LorenzEvent() \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/mixins/MixinGuiContainer.java b/src/main/java/at/lorenz/mod/mixins/MixinGuiContainer.java
new file mode 100644
index 000000000..c9dc12fc5
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/mixins/MixinGuiContainer.java
@@ -0,0 +1,50 @@
+package at.lorenz.mod.mixins;
+
+import at.lorenz.mod.GuiContainerHook;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.inventory.GuiContainer;
+import net.minecraft.inventory.Slot;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(GuiContainer.class)
+public abstract class MixinGuiContainer extends GuiScreen {
+
+ @Unique
+ private final GuiContainerHook hook = new GuiContainerHook(this);
+
+ @Inject(method = "keyTyped", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/EntityPlayerSP;closeScreen()V", shift = At.Shift.BEFORE), cancellable = true)
+ private void closeWindowPressed(CallbackInfo ci) {
+ hook.closeWindowPressed(ci);
+ }
+
+ @Inject(method = "drawScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;color(FFFF)V", ordinal = 1))
+ private void backgroundDrawn(int mouseX, int mouseY, float partialTicks, CallbackInfo ci) {
+ hook.backgroundDrawn(mouseX, mouseY, partialTicks, ci);
+ }
+
+ @Inject(method = "drawScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/inventory/GuiContainer;drawGuiContainerForegroundLayer(II)V", shift = At.Shift.AFTER))
+ private void onForegroundDraw(int mouseX, int mouseY, float partialTicks, CallbackInfo ci) {
+ hook.foregroundDrawn(mouseX, mouseY, partialTicks, ci);
+ }
+
+ @Inject(method = "drawSlot", at = @At("HEAD"), cancellable = true)
+ private void onDrawSlot(Slot slot, CallbackInfo ci) {
+ hook.onDrawSlot(slot, ci);
+ }
+
+ @Inject(method = "drawSlot", at = @At("RETURN"), cancellable = true)
+ private void onDrawSlotPost(Slot slot, CallbackInfo ci) {
+ hook.onDrawSlotPost(slot, ci);
+ }
+
+
+ @Inject(method = "handleMouseClick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/PlayerControllerMP;windowClick(IIIILnet/minecraft/entity/player/EntityPlayer;)Lnet/minecraft/item/ItemStack;"), cancellable = true)
+ private void onMouseClick(Slot slot, int slotId, int clickedButton, int clickType, CallbackInfo ci) {
+ hook.onMouseClick(slot, slotId, clickedButton, clickType, ci);
+ }
+
+}
diff --git a/src/main/java/at/lorenz/mod/utils/APIUtil.kt b/src/main/java/at/lorenz/mod/utils/APIUtil.kt
new file mode 100644
index 000000000..88d459ada
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/APIUtil.kt
@@ -0,0 +1,116 @@
+package at.lorenz.mod.utils
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import org.apache.http.client.config.RequestConfig
+import org.apache.http.client.methods.HttpGet
+import org.apache.http.impl.client.HttpClientBuilder
+import org.apache.http.impl.client.HttpClients
+import org.apache.http.message.BasicHeader
+import org.apache.http.util.EntityUtils
+import scala.util.parsing.json.JSONArray
+import scala.util.parsing.json.JSONObject
+import java.awt.image.BufferedImage
+import java.net.HttpURLConnection
+import java.net.URL
+import java.security.cert.X509Certificate
+import javax.imageio.ImageIO
+
+
+object APIUtil {
+ private val parser = JsonParser()
+
+// val sslContext = SSLContexts.custom()
+// .loadTrustMaterial { chain, authType ->
+// isValidCert(chain, authType)
+// }
+// .build()
+// val sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
+// .setSslContext(sslContext)
+// .build()
+
+// val cm = PoolingHttpClientConnectionManagerBuilder.create()
+// .setSSLSocketFactory(sslSocketFactory)
+
+ val builder: HttpClientBuilder =
+ HttpClients.custom().setUserAgent("LorenzMod")
+// .setConnectionManagerShared(true)
+// .setConnectionManager(cm.build())
+ .setDefaultHeaders(
+ mutableListOf(
+ BasicHeader("Pragma", "no-cache"),
+ BasicHeader("Cache-Control", "no-cache")
+ )
+ )
+ .setDefaultRequestConfig(
+ RequestConfig.custom()
+// .setConnectTimeout(Timeout.ofMinutes(1))
+// .setResponseTimeout(Timeout.ofMinutes(1))
+ .build()
+ )
+ .useSystemProperties()
+
+ /**
+ * Taken from Elementa under MIT License
+ * @link https://github.com/Sk1erLLC/Elementa/blob/master/LICENSE
+ */
+ fun URL.getImage(): BufferedImage {
+ val connection = this.openConnection() as HttpURLConnection
+
+ connection.requestMethod = "GET"
+ connection.useCaches = true
+ connection.addRequestProperty("User-Agent", "LorenzMod")
+ connection.doOutput = true
+
+ return ImageIO.read(connection.inputStream)
+ }
+
+ fun getJSONResponse(urlString: String): JsonObject {
+ val client = builder.build()
+ try {
+ client.execute(HttpGet(urlString)).use { response ->
+ val entity = response.entity
+ if (entity != null) {
+ val retSrc = EntityUtils.toString(entity)
+ return parser.parse(retSrc) as JsonObject
+ // parsing JSON
+// val result = JSONObject(retSrc) //Convert String to JSON Object
+// val tokenList: JSONArray = result.getJSONArray("names")
+// val oj: JSONObject = tokenList.getJSONObject(0)
+// val token: String = oj.getString("name")
+ }
+ }
+ } catch (ex: Throwable) {
+ ex.printStackTrace()
+ LorenzUtils.error("Skytils ran into an ${ex::class.simpleName ?: "error"} whilst fetching a resource. See logs for more details.")
+ } finally {
+ client.close()
+ }
+ return JsonObject()
+ }
+
+// fun getArrayResponse(urlString: String): JsonArray {
+// val client = builder.build()
+// try {
+// client.execute(HttpGet(urlString)).use { response ->
+//// response.entity.content
+// response.entity.content { entity ->
+// val obj = parser.parse(EntityUtils.toString(entity)).asJsonArray
+// EntityUtils.consume(entity)
+// return obj
+// }
+// }
+// } catch (ex: Throwable) {
+// LorenzUtils.error("Skytils ran into an ${ex::class.simpleName ?: "error"} whilst fetching a resource. See logs for more details.")
+// ex.printStackTrace()
+// } finally {
+// client.close()
+// }
+// return JsonArray()
+// }
+
+ private fun isValidCert(chain: Array<X509Certificate>, authType: String): Boolean {
+ return chain.any { it.issuerDN.name == "CN=R3, O=Let's Encrypt, C=US" }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/ItemUtil.kt b/src/main/java/at/lorenz/mod/utils/ItemUtil.kt
new file mode 100644
index 000000000..fc0409e31
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/ItemUtil.kt
@@ -0,0 +1,211 @@
+package at.lorenz.mod.utils
+
+import net.minecraft.init.Items
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NBTTagCompound
+import net.minecraft.nbt.NBTTagList
+import net.minecraft.nbt.NBTTagString
+import net.minecraftforge.common.util.Constants
+import java.util.*
+
+object ItemUtil {
+ private val PET_PATTERN = "§7\\[Lvl \\d+] (?<color>§[0-9a-fk-or]).+".toRegex()
+ const val NBT_INTEGER = 3
+ private const val NBT_STRING = 8
+ private const val NBT_LIST = 9
+ private const val NBT_COMPOUND = 10
+
+ /**
+ * Returns the display name of a given item
+ * @author Mojang
+ * @param item the Item to get the display name of
+ * @return the display name of the item
+ */
+ @JvmStatic
+ fun getDisplayName(item: ItemStack): String {
+ var s = item.item.getItemStackDisplayName(item)
+ if (item.tagCompound != null && item.tagCompound.hasKey("display", 10)) {
+ val nbtTagCompound = item.tagCompound.getCompoundTag("display")
+ if (nbtTagCompound.hasKey("Name", 8)) {
+ s = nbtTagCompound.getString("Name")
+ }
+ }
+ return s
+ }
+
+ /**
+ * Returns the Skyblock Item ID of a given Skyblock item
+ *
+ * @author BiscuitDevelopment
+ * @param item the Skyblock item to check
+ * @return the Skyblock Item ID of this item or `null` if this isn't a valid Skyblock item
+ */
+ @JvmStatic
+ fun getSkyBlockItemID(item: ItemStack?): String? {
+ if (item == null) {
+ return null
+ }
+ val extraAttributes = getExtraAttributes(item) ?: return null
+ return if (!extraAttributes.hasKey("id", NBT_STRING)) {
+ null
+ } else extraAttributes.getString("id")
+ }
+
+ /**
+ * Returns the `ExtraAttributes` compound tag from the item's NBT data.
+ *
+ * @author BiscuitDevelopment
+ * @param item the item to get the tag from
+ * @return the item's `ExtraAttributes` compound tag or `null` if the item doesn't have one
+ */
+ @JvmStatic
+ fun getExtraAttributes(item: ItemStack?): NBTTagCompound? {
+ return if (item == null || !item.hasTagCompound()) {
+ null
+ } else item.getSubCompound("ExtraAttributes", false)
+ }
+
+ /**
+ * Returns the Skyblock Item ID of a given Skyblock Extra Attributes NBT Compound
+ *
+ * @author BiscuitDevelopment
+ * @param extraAttributes the NBT to check
+ * @return the Skyblock Item ID of this item or `null` if this isn't a valid Skyblock NBT
+ */
+ @JvmStatic
+ fun getSkyBlockItemID(extraAttributes: NBTTagCompound?): String? {
+ if (extraAttributes != null) {
+ val itemId = extraAttributes.getString("id")
+ if (itemId.isNotEmpty()) {
+ return itemId
+ }
+ }
+ return null
+ }
+
+ /**
+ * Returns a string list containing the nbt lore of an ItemStack, or
+ * an empty list if this item doesn't have a lore. The returned lore
+ * list is unmodifiable since it has been converted from an NBTTagList.
+ *
+ * @author BiscuitDevelopment
+ * @param itemStack the ItemStack to get the lore from
+ * @return the lore of an ItemStack as a string list
+ */
+ @JvmStatic
+ fun getItemLore(itemStack: ItemStack): List<String> {
+ if (itemStack.hasTagCompound() && itemStack.tagCompound.hasKey("display", NBT_COMPOUND)) {
+ val display = itemStack.tagCompound.getCompoundTag("display")
+ if (display.hasKey("Lore", NBT_LIST)) {
+ val lore = display.getTagList("Lore", NBT_STRING)
+ val loreAsList = ArrayList<String>(lore.tagCount())
+ for (lineNumber in 0 until lore.tagCount()) {
+ loreAsList.add(lore.getStringTagAt(lineNumber))
+ }
+ return Collections.unmodifiableList(loreAsList)
+ }
+ }
+ return emptyList()
+ }
+
+// @JvmStatic
+// fun hasRightClickAbility(itemStack: ItemStack): Boolean {
+// for (line in getItemLore(itemStack)) {
+// val stripped = line.stripControlCodes()
+// if (stripped.startsWith("Item Ability:") && stripped.endsWith("RIGHT CLICK")) return true
+// }
+// return false
+// }
+
+// /**
+// * Returns the rarity of a given Skyblock item
+// * Modified
+// * @author BiscuitDevelopment
+// * @param item the Skyblock item to check
+// * @return the rarity of the item if a valid rarity is found, `null` if no rarity is found, `null` if item is `null`
+// */
+// fun getRarity(item: ItemStack?): ItemRarity {
+// if (item == null || !item.hasTagCompound()) {
+// return ItemRarity.NONE
+// }
+// val display = item.getSubCompound("display", false)
+// if (display == null || !display.hasKey("Lore")) {
+// return ItemRarity.NONE
+// }
+// val lore = display.getTagList("Lore", Constants.NBT.TAG_STRING)
+// val name = display.getString("Name")
+//
+// // Determine the item's rarity
+// for (i in (lore.tagCount() - 1) downTo 0) {
+// val currentLine = lore.getStringTagAt(i)
+// val rarityMatcher = RARITY_PATTERN.find(currentLine)
+// if (rarityMatcher != null) {
+// val rarity = rarityMatcher.groups["rarity"]?.value ?: continue
+// ItemRarity.values().find {
+// it.rarityName == rarity.stripControlCodes().substringAfter("SHINY ")
+// }?.let {
+// return it
+// }
+// }
+// }
+// val petRarityMatcher = PET_PATTERN.find(name)
+// if (petRarityMatcher != null) {
+// val color = petRarityMatcher.groupValues.getOrNull(1) ?: return ItemRarity.NONE
+// return ItemRarity.byBaseColor(color) ?: ItemRarity.NONE
+// }
+//
+// // If the item doesn't have a valid rarity, return null
+// return ItemRarity.NONE
+// }
+
+ fun isPet(item: ItemStack?): Boolean {
+ if (item == null || !item.hasTagCompound()) {
+ return false
+ }
+ val display = item.getSubCompound("display", false)
+ if (display == null || !display.hasKey("Lore")) {
+ return false
+ }
+ val name = display.getString("Name")
+
+ return PET_PATTERN.matches(name)
+ }
+
+ fun setSkullTexture(item: ItemStack, texture: String, SkullOwner: String): ItemStack {
+ val textureTagCompound = NBTTagCompound()
+ textureTagCompound.setString("Value", texture)
+
+ val textures = NBTTagList()
+ textures.appendTag(textureTagCompound)
+
+ val properties = NBTTagCompound()
+ properties.setTag("textures", textures)
+
+ val skullOwner = NBTTagCompound()
+ skullOwner.setString("Id", SkullOwner)
+ skullOwner.setTag("Properties", properties)
+
+ val nbtTag = NBTTagCompound()
+ nbtTag.setTag("SkullOwner", skullOwner)
+
+ item.tagCompound = nbtTag
+ return item
+ }
+
+ fun getSkullTexture(item: ItemStack): String? {
+ if (item.item != Items.skull) return null
+ val nbt = item.tagCompound
+ if (!nbt.hasKey("SkullOwner")) return null
+ return nbt.getCompoundTag("SkullOwner").getCompoundTag("Properties")
+ .getTagList("textures", Constants.NBT.TAG_COMPOUND).getCompoundTagAt(0).getString("Value")
+ }
+
+ fun ItemStack.setLore(lines: List<String>): ItemStack {
+ setTagInfo("display", getSubCompound("display", true).apply {
+ setTag("Lore", NBTTagList().apply {
+ for (line in lines) appendTag(NBTTagString(line))
+ })
+ })
+ return this
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/ItemUtils.kt b/src/main/java/at/lorenz/mod/utils/ItemUtils.kt
new file mode 100644
index 000000000..697e57393
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/ItemUtils.kt
@@ -0,0 +1,39 @@
+package at.lorenz.mod.utils
+
+import at.lorenz.mod.utils.LorenzUtils.Companion.removeColorCodes
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.inventory.GuiChest
+import net.minecraft.item.ItemStack
+
+class ItemUtils {
+
+ companion object {
+ fun ItemStack.cleanName() = this.displayName.removeColorCodes()
+
+ fun getItemsInOpenChest(): List<ItemStack> {
+ val list = mutableListOf<ItemStack>()
+ val guiChest = Minecraft.getMinecraft().currentScreen as GuiChest
+ val inventorySlots = guiChest.inventorySlots.inventorySlots
+ val skipAt = inventorySlots.size - 9 * 4
+ var i = 0
+ for (slot in inventorySlots) {
+ val stack = slot.stack
+ if (stack != null) {
+ list.add(stack)
+ }
+ i++
+ if (i == skipAt) break
+ }
+ return list
+ }
+
+ fun isSack(name: String): Boolean = name.endsWith(" Sack")
+
+ fun ItemStack.getLore() = ItemUtil.getItemLore(this)
+
+ fun isCoOpSoulBound(stack: ItemStack): Boolean = stack.getLore().any { it.contains("Co-op Soulbound") }
+
+ fun isRecombobulated(stack: ItemStack): Boolean = stack.getLore().any { it.contains("§k") }
+
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/LorenzColor.kt b/src/main/java/at/lorenz/mod/utils/LorenzColor.kt
new file mode 100644
index 000000000..e60d6d8d1
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/LorenzColor.kt
@@ -0,0 +1,27 @@
+package at.lorenz.mod.utils
+
+import java.awt.Color
+
+enum class LorenzColor(private var chatColorCode: Char, private val color: Color) {
+ BLACK('0', Color(0, 0, 0)),
+ DARK_BLUE('1', Color(0, 0, 170)),
+ DARK_GREEN('2', Color(0, 170, 0)),
+ DARK_AQUA('3', Color(0, 170, 170)),
+ DARK_RED('4', Color(170, 0, 0)),
+ DARK_PURPLE('5', Color(170, 0, 170)),
+ GOLD('6', Color(255, 170, 0)),
+ GRAY('7', Color(170, 170, 170)),
+ DARK_GRAY('8', Color(85, 85, 85)),
+ BLUE('9', Color(85, 85, 255)),
+ GREEN('a', Color(85, 255, 85)),
+ AQUA('b', Color(85, 255, 255)),
+ RED('c', Color(255, 85, 85)),
+ LIGHT_PURPLE('d', Color(255, 85, 255)),
+ YELLOW('e', Color(255, 255, 85)),
+ WHITE('f', Color(255, 255, 255)),
+ ;
+
+ fun getChatColor(): String = "§$chatColorCode"
+
+ fun toColor(): Color = color
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/LorenzLogger.kt b/src/main/java/at/lorenz/mod/utils/LorenzLogger.kt
new file mode 100644
index 000000000..1b7337224
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/LorenzLogger.kt
@@ -0,0 +1,70 @@
+package at.lorenz.mod.utils
+
+import at.lorenz.mod.utils.LorenzUtils.Companion.formatCurrentTime
+import java.io.File
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.logging.FileHandler
+import java.util.logging.Formatter
+import java.util.logging.LogRecord
+import java.util.logging.Logger
+
+class LorenzLogger(filePath: String) {
+ private val format = SimpleDateFormat("HH:mm:ss")
+ private val fileName = "$PREFIX_PATH$filePath.log"
+
+ companion object {
+ private var PREFIX_PATH: String
+
+ init {
+ val format = SimpleDateFormat("yyyy_MM_dd/HH_mm_ss").formatCurrentTime()
+ PREFIX_PATH = "mods/LorenzAddons/logs/$format/"
+ }
+ }
+
+ private lateinit var logger: Logger
+
+ private fun getLogger(): Logger {
+ if (::logger.isInitialized) {
+ return logger
+ }
+
+ val initLogger = initLogger()
+ this.logger = initLogger
+ return initLogger
+ }
+
+ private fun initLogger(): Logger {
+ val logger = Logger.getLogger("" + System.nanoTime())
+ try {
+ createParent(File(fileName))
+ val handler = FileHandler(fileName)
+ handler.encoding ="utf-8"
+ logger.addHandler(handler)
+ handler.formatter = object : Formatter() {
+ override fun format(logRecord: LogRecord): String {
+ val message = logRecord.message
+ return format.formatCurrentTime() + " $message\n"
+ }
+ }
+ } catch (e: SecurityException) {
+ e.printStackTrace()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ return logger
+ }
+
+ private fun createParent(file: File) {
+ val parent = file.parentFile
+ if (parent != null) {
+ if (!parent.isDirectory) {
+ parent.mkdirs()
+ }
+ }
+ }
+
+ fun log(text: String?) {
+ getLogger().info(text)
+ }
+}
diff --git a/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt b/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt
new file mode 100644
index 000000000..021c055d7
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt
@@ -0,0 +1,91 @@
+package at.lorenz.mod.utils
+
+import net.minecraft.client.Minecraft
+import net.minecraft.util.ChatComponentText
+import org.intellij.lang.annotations.Language
+import java.text.SimpleDateFormat
+
+class LorenzUtils {
+
+ companion object {
+ const val DEBUG_PREFIX = "[Debug] §7"
+
+ fun debug(message: String) {
+ internaChat(DEBUG_PREFIX + message)
+ }
+
+ fun warning(message: String) {
+ internaChat("§cWarning! $message")
+ }
+
+ fun error(message: String) {
+ internaChat("§4$message")
+ }
+
+ fun chat(message: String) {
+ internaChat(message)
+ }
+
+ private fun internaChat(message: String) {
+ val thePlayer = Minecraft.getMinecraft().thePlayer
+ thePlayer.addChatMessage(ChatComponentText(message))
+ }
+
+ fun String.matchRegex(@Language("RegExp") regex: String): Boolean = regex.toRegex().matches(this)
+
+ fun String.removeColorCodes(): String {
+ val builder = StringBuilder()
+ var skipNext = false
+ for (c in this.toCharArray()) {
+ if (c == '§') {
+ skipNext = true
+ continue
+ }
+ if (skipNext) {
+ skipNext = false
+ continue
+ }
+ builder.append(c)
+ }
+
+ return builder.toString()
+ }
+
+ fun SimpleDateFormat.formatCurrentTime(): String = this.format(System.currentTimeMillis())
+
+ fun stripVanillaMessage(originalMessage: String): String {
+ var message = originalMessage
+
+ while (message.startsWith("§r")) {
+ message = message.substring(2)
+ }
+ while (message.endsWith("§r")) {
+ message = message.substring(0, message.length - 2)
+ }
+
+// if (!message.startsWith(LorenzUtils.DEBUG_PREFIX + "chat api got (123)")) {
+// if (message.matchRegex("(.*)§r§7 \\((.{1,3})\\)")) {
+// val indexOf = message.lastIndexOf("(")
+//// LorenzAddons.testLogger.log("chat api got (123)!")
+//// LorenzAddons.testLogger.log("before: '$message'")
+// message = message.substring(0, indexOf - 5)
+//// LorenzAddons.testLogger.log("after: '$message'")
+//// LorenzAddons.testLogger.log("")
+//// LorenzUtils.debug("chat api got (123)")
+//// } else if (message.endsWith("§r§7 (2)")) {
+////// LorenzAddons.testLogger.log("other variant: '$message'")
+////// LorenzAddons.testLogger.log("")
+//// LorenzUtils.debug("chat api got WRONG (123)")
+// }
+// }
+
+ return message
+ }
+
+ fun Double.round(decimals: Int): Double {
+ var multiplier = 1.0
+ repeat(decimals) { multiplier *= 10 }
+ return kotlin.math.round(this * multiplier) / multiplier
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/lorenz/mod/utils/RenderUtil.kt b/src/main/java/at/lorenz/mod/utils/RenderUtil.kt
new file mode 100644
index 000000000..e633461a4
--- /dev/null
+++ b/src/main/java/at/lorenz/mod/utils/RenderUtil.kt
@@ -0,0 +1,20 @@
+package at.lorenz.mod.utils
+
+import net.minecraft.client.gui.Gui
+import net.minecraft.inventory.Slot
+
+class RenderUtil {
+
+ companion object {
+
+ infix fun Slot.highlight(color: LorenzColor) {
+ Gui.drawRect(
+ this.xDisplayPosition,
+ this.yDisplayPosition,
+ this.xDisplayPosition + 16,
+ this.yDisplayPosition + 16,
+ color.toColor().rgb
+ )
+ }
+ }
+} \ No newline at end of file