aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni
diff options
context:
space:
mode:
authorhannibal2 <24389977+hannibal00212@users.noreply.github.com>2023-05-15 15:57:52 +0200
committerhannibal2 <24389977+hannibal00212@users.noreply.github.com>2023-05-15 15:57:52 +0200
commit18e0b6fcaa1916d87ba176755d160c6d456292f1 (patch)
tree709cf6ac79fd04b2cc5febb7eca352f23f215301 /src/main/java/at/hannibal2/skyhanni
parent1097c46e471a81e41849287b64b0cb4b37a778c3 (diff)
downloadskyhanni-18e0b6fcaa1916d87ba176755d160c6d456292f1.tar.gz
skyhanni-18e0b6fcaa1916d87ba176755d160c6d456292f1.tar.bz2
skyhanni-18e0b6fcaa1916d87ba176755d160c6d456292f1.zip
Added Contest Time Needed
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/Garden.java11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt21
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt19
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestTimeNeeded.kt123
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt61
8 files changed, 253 insertions, 36 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
index bf86f5282..db90d9252 100644
--- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
+++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
@@ -28,6 +28,7 @@ import at.hannibal2.skyhanni.features.garden.composter.ComposterOverlay
import at.hannibal2.skyhanni.features.garden.composter.GardenComposterInventoryFeatures
import at.hannibal2.skyhanni.features.garden.contest.FarmingContestAPI
import at.hannibal2.skyhanni.features.garden.contest.JacobContestFFNeededDisplay
+import at.hannibal2.skyhanni.features.garden.contest.JacobContestTimeNeeded
import at.hannibal2.skyhanni.features.garden.contest.JacobFarmingContestsInventory
import at.hannibal2.skyhanni.features.garden.farming.*
import at.hannibal2.skyhanni.features.garden.inventory.*
@@ -261,6 +262,7 @@ class SkyHanniMod {
loadModule(GardenBurrowingSporesNotifier())
loadModule(WildStrawberryDyeNotification())
loadModule(JacobContestFFNeededDisplay())
+ loadModule(JacobContestTimeNeeded())
loadModule(GardenYawAndPitch())
loadModule(MovementSpeedDisplay())
loadModule(ChumBucketHider())
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/Garden.java b/src/main/java/at/hannibal2/skyhanni/config/features/Garden.java
index 4f4349f55..6be404745 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/Garden.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/Garden.java
@@ -1203,6 +1203,17 @@ public class Garden {
public Position farmingFortuneForContestPos = new Position(180, 156, false, true);
@Expose
+ @ConfigOption(
+ name = "Contest Time Needed",
+ desc = "Show the time and missing FF for every crop inside Jacob's Farming Contest inventory."
+ )
+ @ConfigEditorBoolean
+ public boolean jacobContextTimes = true;
+
+ @Expose
+ public Position jacobContextTimesPos = new Position(180, 156, false, true);
+
+ @Expose
@ConfigOption(name = "Always Finnegan", desc = "Forcefully set the Finnegan Farming Simulator perk to be active. This is useful if the auto mayor detection fails.")
@ConfigEditorBoolean
public boolean forcefullyEnabledAlwaysFinnegan = false;
diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt
index 64a2ecc46..1e027b6ab 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt
@@ -8,6 +8,7 @@ import at.hannibal2.skyhanni.features.garden.composter.ComposterAPI.getLevel
import at.hannibal2.skyhanni.utils.*
import at.hannibal2.skyhanni.utils.ItemUtils.name
import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList
+import at.hannibal2.skyhanni.utils.LorenzUtils.addSelector
import at.hannibal2.skyhanni.utils.LorenzUtils.round
import at.hannibal2.skyhanni.utils.LorenzUtils.sortedDesc
import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
@@ -273,24 +274,13 @@ class ComposterOverlay {
val fuelItem = currentFuelItem
if (organicMatterItem == "" || fuelItem == "") return
- val clickableList = mutableListOf<Any>()
- clickableList.add("§7Per ")
- for (type in TimeType.values()) {
- val display = type.display
- if (type == currentTimeType) {
- clickableList.add("§a[$display]")
- } else {
- clickableList.add("§e[")
- clickableList.add(Renderable.link("§e$display") {
- currentTimeType = type
- update()
- })
- clickableList.add("§e]")
- }
- clickableList.add(" ")
- }
- newList.add(clickableList)
-
+ newList.addSelector("§7Per ", TimeType.values(),
+ getName = { type -> type.display },
+ isCurrent = { it == currentTimeType },
+ onChange = {
+ currentTimeType = it
+ update()
+ })
val list = mutableListOf<Any>()
list.add("§7Using: ")
diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt
index 27a6d1515..3f943d037 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt
@@ -1,10 +1,12 @@
package at.hannibal2.skyhanni.features.garden.contest
import at.hannibal2.skyhanni.events.GuiContainerEvent
+import at.hannibal2.skyhanni.events.InventoryCloseEvent
import at.hannibal2.skyhanni.events.InventoryOpenEvent
import at.hannibal2.skyhanni.features.garden.CropType
import at.hannibal2.skyhanni.utils.ItemUtils.getLore
import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.LorenzUtils.sortedDesc
import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher
import io.github.moulberry.notenoughupdates.util.SkyBlockTime
import net.minecraft.item.ItemStack
@@ -29,6 +31,11 @@ object FarmingContestAPI {
inInventory = false
}
+ @SubscribeEvent
+ fun onInventoryClose(event: InventoryCloseEvent) {
+ inInventory = false
+ }
+
fun getSbTimeFor(text: String) = timePattern.matchMatcher(text) {
val month = group("month")
val monthNr = LorenzUtils.getSBMonthByName(month)
@@ -62,4 +69,18 @@ object FarmingContestAPI {
fun getContestAtTime(time: Long) = contests[time]
fun getContestsOfType(crop: CropType) = contests.values.filter { it.crop == crop }
+
+ fun calculateAverages(crop: CropType): Pair<Int, Map<ContestRank, Int>> {
+ var amount = 0
+ val map = mutableMapOf<ContestRank, Int>()
+ for (contest in getContestsOfType(crop).associateWith { it.time }.sortedDesc().keys) {
+ amount++
+ for ((rank, count) in contest.ranks) {
+ val old = map.getOrDefault(rank, 0)
+ map[rank] = count + old
+ }
+ if (amount == 10) break
+ }
+ return Pair(amount, map.mapValues { (_, counter) -> counter / amount })
+ }
} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt
index e7b356b63..7e45a9fe3 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt
@@ -12,7 +12,6 @@ import at.hannibal2.skyhanni.utils.ItemUtils.name
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList
import at.hannibal2.skyhanni.utils.LorenzUtils.round
-import at.hannibal2.skyhanni.utils.LorenzUtils.sortedDesc
import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems
import net.minecraft.item.ItemStack
@@ -65,7 +64,7 @@ class JacobContestFFNeededDisplay {
}
addAsSingletonList("")
- val (size, averages) = calculateAverages(crop)
+ val (size, averages) = FarmingContestAPI.calculateAverages(crop)
add(listOf("§7For the last §e$size ", crop.icon, "§7${crop.cropName} contests:"))
for (rank in ContestRank.values()) {
addAsSingletonList(getLine(rank, averages, crop))
@@ -116,26 +115,12 @@ class JacobContestFFNeededDisplay {
return " ${rank.displayName}§f: §6$farmingFortune FF §7(${counter.addSeparators()} crops)"
}
- private fun calculateAverages(crop: CropType): Pair<Int, Map<ContestRank, Int>> {
- var amount = 0
- val map = mutableMapOf<ContestRank, Int>()
- for (contest in FarmingContestAPI.getContestsOfType(crop).associateWith { it.time }.sortedDesc().keys) {
- amount++
- for ((rank, count) in contest.ranks) {
- val old = map.getOrDefault(rank, 0)
- map[rank] = count + old
- }
- if (amount == 10) break
- }
- return Pair(amount, map.mapValues { (_, counter) -> counter / amount })
- }
-
@SubscribeEvent
fun onRenderOverlay(event: GuiRenderEvent.ChestBackgroundRenderEvent) {
if (!isEnabled()) return
if (!FarmingContestAPI.inInventory) return
if (System.currentTimeMillis() > lastToolTipTime + 200) return
- config.farmingFortuneForContestPos.renderStringsAndItems(display, posLabel = "Estimated Item Value")
+ config.farmingFortuneForContestPos.renderStringsAndItems(display, posLabel = "Jacob Contest Crop Data")
}
fun isEnabled() = LorenzUtils.inSkyBlock && config.farmingFortuneForContest
diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestTimeNeeded.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestTimeNeeded.kt
new file mode 100644
index 000000000..94498ed69
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestTimeNeeded.kt
@@ -0,0 +1,123 @@
+package at.hannibal2.skyhanni.features.garden.contest
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.events.GuiRenderEvent
+import at.hannibal2.skyhanni.events.LateInventoryOpenEvent
+import at.hannibal2.skyhanni.features.garden.CropType
+import at.hannibal2.skyhanni.features.garden.FarmingFortuneDisplay.Companion.getLatestTrueFarmingFortune
+import at.hannibal2.skyhanni.features.garden.farming.GardenCropSpeed.getLatestBlocksPerSecond
+import at.hannibal2.skyhanni.features.garden.farming.GardenCropSpeed.getSpeed
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList
+import at.hannibal2.skyhanni.utils.LorenzUtils.addSelector
+import at.hannibal2.skyhanni.utils.LorenzUtils.round
+import at.hannibal2.skyhanni.utils.LorenzUtils.sorted
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems
+import at.hannibal2.skyhanni.utils.TimeUtils
+import at.hannibal2.skyhanni.utils.renderables.Renderable
+import net.minecraftforge.fml.common.eventhandler.EventPriority
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class JacobContestTimeNeeded {
+ private val config get() = SkyHanniMod.feature.garden
+ private var display = listOf<List<Any>>()
+ private var currentBracket = ContestRank.GOLD
+
+ @SubscribeEvent(priority = EventPriority.LOW)
+ fun onLateInventoryOpen(event: LateInventoryOpenEvent) {
+ if (FarmingContestAPI.inInventory) {
+ update()
+ }
+ }
+
+ private fun update() {
+ val sorted = mutableMapOf<CropType, Double>()
+ val map = mutableMapOf<CropType, Renderable>()
+ for (crop in CropType.values()) {
+ val speed = crop.getSpeed()
+ if (speed == null) {
+ sorted[crop] = Double.MAX_VALUE
+ map[crop] = Renderable.hoverTips(
+ "§9${crop.cropName} §cNo speed data!",
+ listOf("§cFarm ${crop.cropName} to show data!")
+ )
+ continue
+ }
+
+ val averages = FarmingContestAPI.calculateAverages(crop).second
+ if (averages.isEmpty()) {
+ sorted[crop] = Double.MAX_VALUE - 2
+ map[crop] = Renderable.hoverTips(
+ "§9${crop.cropName} §cNo contest data!",
+ listOf(
+ "§cOpen more pages or participate",
+ "§cin a ${crop.cropName} Contest to show data!"
+ )
+ )
+ continue
+ }
+ var showLine = ""
+ val brackets = mutableListOf<String>()
+ for ((bracket, amount) in averages) {
+ val timeInMinutes = amount.toDouble() / speed / 60
+ val formatDuration = TimeUtils.formatDuration((timeInMinutes * 60 * 1000).toLong())
+ val color = if (timeInMinutes < 20) "§b" else "§c"
+ if (bracket == currentBracket) {
+ sorted[crop] = timeInMinutes
+ }
+ var bracketText = "${bracket.displayName} $color$formatDuration"
+ if (timeInMinutes < 20) {
+ showLine = "§9${crop.cropName} §b$formatDuration"
+ } else {
+ showLine =
+ "§9${crop.cropName} §cNo ${currentBracket.displayName} §cMedal!"
+
+ val cropFF = crop.getLatestTrueFarmingFortune() ?: 0.0
+ val blocksPerSecond = crop.getLatestBlocksPerSecond() ?: 20.0
+ val cropsPerSecond = amount.toDouble() / blocksPerSecond / 60
+ val ffNeeded = cropsPerSecond * 100 / 20 / crop.baseDrops
+ val missing = (ffNeeded - cropFF).toInt()
+ bracketText += " §7(${missing.addSeparators()} more FF needed!)"
+ }
+ brackets.add(bracketText)
+ }
+ map[crop] = Renderable.hoverTips(showLine, buildList {
+ add("§7Time Needed for §9${crop.cropName} Medals§7:")
+ addAll(brackets)
+ add("")
+ val cropFF = crop.getLatestTrueFarmingFortune() ?: 0.0
+ add("§7Current FF: §e${(cropFF).addSeparators()}")
+ val bps = crop.getLatestBlocksPerSecond()?.round(1) ?: 0
+ add("§7Blocks/Second: §e${bps.addSeparators()}")
+
+ })
+ }
+
+ this.display = buildList {
+ addAsSingletonList("§e§lTime Needed for ${currentBracket.displayName} §eMedal!")
+
+ addSelector("§7Bracket: ", ContestRank.values(),
+ getName = { type -> type.name.lowercase() },
+ isCurrent = { it == currentBracket },
+ onChange = {
+ currentBracket = it
+ update()
+ })
+ addAsSingletonList("")
+ for (crop in sorted.sorted().keys) {
+ val text = map[crop]!!
+ add(listOf(crop.icon, text))
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onRenderOverlay(event: GuiRenderEvent.ChestBackgroundRenderEvent) {
+ if (!isEnabled()) return
+ if (!FarmingContestAPI.inInventory) return
+ config.jacobContextTimesPos.renderStringsAndItems(display, posLabel = "Jacob Contest Time Needed")
+ }
+
+ fun isEnabled() = LorenzUtils.inSkyBlock && config.jacobContextTimes
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
index 135335067..b7930bfd1 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
@@ -7,6 +7,7 @@ import at.hannibal2.skyhanni.features.dungeon.DungeonData
import at.hannibal2.skyhanni.test.TestBingo
import at.hannibal2.skyhanni.utils.StringUtils.removeColor
import at.hannibal2.skyhanni.utils.StringUtils.toDashlessUUID
+import at.hannibal2.skyhanni.utils.renderables.Renderable
import io.github.moulberry.moulconfig.observer.Observer
import io.github.moulberry.moulconfig.observer.Property
import io.github.moulberry.notenoughupdates.mixins.AccessorGuiEditSign
@@ -286,4 +287,29 @@ object LorenzUtils {
else -> "Special"
}
}
+
+ fun <T> MutableList<List<Any>>.addSelector(
+ prefix: String,
+ values: Array<T>,
+ getName: (T) -> String,
+ isCurrent: (T) -> Boolean,
+ onChange: (T) -> Unit,
+ ) {
+ val newList = mutableListOf<Any>()
+ newList.add(prefix)
+ for (entry in values) {
+ val display = getName(entry)
+ if (isCurrent(entry)) {
+ newList.add("§a[$display]")
+ } else {
+ newList.add("§e[")
+ newList.add(Renderable.link("§e$display") {
+ onChange(entry)
+ })
+ newList.add("§e]")
+ }
+ newList.add(" ")
+ }
+ add(newList)
+ }
} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt
index 4d8351083..9917799f7 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt
@@ -2,12 +2,14 @@ package at.hannibal2.skyhanni.utils.renderables
import at.hannibal2.skyhanni.config.core.config.gui.GuiPositionEditor
import at.hannibal2.skyhanni.data.ToolTipData
+import at.hannibal2.skyhanni.utils.LorenzColor
import at.hannibal2.skyhanni.utils.LorenzLogger
import at.hannibal2.skyhanni.utils.NEUItems.renderOnScreen
import io.github.moulberry.moulconfig.gui.GuiScreenElementWrapper
import io.github.moulberry.notenoughupdates.util.Utils
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.Gui
+import net.minecraft.client.gui.GuiScreen
import net.minecraft.client.gui.inventory.GuiEditSign
import net.minecraft.client.renderer.GlStateManager
import net.minecraft.item.ItemStack
@@ -16,9 +18,10 @@ import kotlin.math.max
interface Renderable {
val width: Int
+ val height: Int
fun isHovered(posX: Int, posY: Int) =
Utils.getMouseX() in (posX..posX + width)
- && Utils.getMouseY() in (posY..posY + 10) // TODO: adjust for variable height?
+ && Utils.getMouseY() in (posY..posY + height) // TODO: adjust for variable height?
/**
* N.B.: the offset is absolute, not relative to the position and should not be used for rendering
@@ -49,6 +52,7 @@ interface Renderable {
object : Renderable {
override val width: Int
get() = render.width
+ override val height = 10
private var wasDown = false
@@ -62,8 +66,58 @@ interface Renderable {
wasDown = isDown
render.render(posX, posY)
}
+ }
+
+ fun hoverTips(text: String, tips: List<String>, condition: () -> Boolean = { true }): Renderable {
+ val render = string(text)
+ return object : Renderable {
+ override val width: Int
+ get() = render.width
+ override val height = 11
+ override fun render(posX: Int, posY: Int) {
+ render.render(posX, posY)
+ if (isHovered(posX, posY)) {
+ if (condition() && shouldAllowLink(true)) {
+ renderToolTips(posX, posY, tips)
+ }
+ }
+ }
}
+ }
+
+ private fun renderToolTips(posX: Int, posY: Int, tips: List<String>, border: Int = 1) {
+ val x = Utils.getMouseX() - posX + 10
+ val startY = Utils.getMouseY() - posY - 10
+ var maxX = 0
+ var y = startY
+ val renderer = Minecraft.getMinecraft().fontRendererObj
+
+ GlStateManager.translate(0f, 0f, 2f)
+ for (line in tips) {
+ renderer.drawStringWithShadow(
+ "§f$line",
+ 1f + x,
+ 1f + y,
+ 0
+ )
+ val currentX = renderer.getStringWidth(line)
+ if (currentX > maxX) {
+ maxX = currentX
+ }
+ y += 10
+ }
+ GlStateManager.translate(0f, 0f, -1f)
+
+ GuiScreen.drawRect(
+ x - border,
+ startY - border,
+ x + maxX + 10 + border,
+ y + border,
+ LorenzColor.DARK_GRAY.toColor().rgb
+ )
+ GlStateManager.translate(0f, 0f, -1f)
+ }
private fun shouldAllowLink(debug: Boolean = false): Boolean {
val isGuiScreen = Minecraft.getMinecraft().currentScreen != null
@@ -94,6 +148,7 @@ interface Renderable {
fun underlined(renderable: Renderable) = object : Renderable {
override val width: Int
get() = renderable.width
+ override val height = 10
override fun render(posX: Int, posY: Int) {
Gui.drawRect(0, 10, width, 11, 0xFFFFFFFF.toInt())
@@ -106,6 +161,7 @@ interface Renderable {
object : Renderable {
override val width: Int
get() = max(hovered.width, unhovered.width)
+ override val height = 10
override fun render(posX: Int, posY: Int) {
if (isHovered(posX, posY) && condition() && shouldAllowLink())
@@ -118,6 +174,7 @@ interface Renderable {
fun itemStack(any: ItemStack, scale: Double = 1.0) = object : Renderable {
override val width: Int
get() = 12
+ override val height = 10
override fun render(posX: Int, posY: Int) {
any.renderOnScreen(0F, 0F, scaleMultiplier = scale)
@@ -127,6 +184,7 @@ interface Renderable {
fun string(string: String) = object : Renderable {
override val width: Int
get() = Minecraft.getMinecraft().fontRendererObj.getStringWidth(string)
+ override val height = 10
override fun render(posX: Int, posY: Int) {
Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow("§f$string", 1f, 1f, 0)
@@ -135,6 +193,7 @@ interface Renderable {
fun placeholder(width: Int) = object : Renderable {
override val width: Int = width
+ override val height = 10
override fun render(posX: Int, posY: Int) {
}