From 4126a8aac1c76ee90393d3fb4ff8084ab5d3abee Mon Sep 17 00:00:00 2001 From: Harry282 Date: Sat, 27 Apr 2024 17:50:58 -0400 Subject: [PATCH] Beta rendering system --- src/main/kotlin/funnymap/FunnyMap.kt | 14 +- src/main/kotlin/funnymap/config/Config.kt | 7 + .../funnymap/features/dungeon/DungeonScan.kt | 1 + .../funnymap/features/dungeon/MapRender.kt | 12 +- .../features/dungeon/MapRenderList.kt | 220 ++++++++++++++++++ .../funnymap/features/dungeon/MapUpdate.kt | 6 + src/main/kotlin/funnymap/ui/MapElement.kt | 7 +- src/main/kotlin/funnymap/utils/RenderUtils.kt | 10 +- .../kotlin/funnymap/utils/RenderUtilsGL.kt | 126 ++++++++++ .../funnymap/utils/SimpleFontRenderer.kt | 155 ++++++++++++ 10 files changed, 545 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/funnymap/features/dungeon/MapRenderList.kt create mode 100644 src/main/kotlin/funnymap/utils/RenderUtilsGL.kt create mode 100644 src/main/kotlin/funnymap/utils/SimpleFontRenderer.kt diff --git a/src/main/kotlin/funnymap/FunnyMap.kt b/src/main/kotlin/funnymap/FunnyMap.kt index 1272f9e..ea4bd35 100755 --- a/src/main/kotlin/funnymap/FunnyMap.kt +++ b/src/main/kotlin/funnymap/FunnyMap.kt @@ -2,20 +2,19 @@ package funnymap import funnymap.commands.FunnyMapCommands import funnymap.config.Config -import funnymap.features.dungeon.Dungeon -import funnymap.features.dungeon.MapRender -import funnymap.features.dungeon.RunInformation -import funnymap.features.dungeon.WitherDoorESP +import funnymap.features.dungeon.* import funnymap.ui.GuiRenderer import funnymap.utils.Location import funnymap.utils.UpdateChecker import gg.essential.api.EssentialAPI +import gg.essential.vigilance.gui.SettingsGui import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiScreen import net.minecraft.client.settings.KeyBinding import net.minecraftforge.client.ClientCommandHandler +import net.minecraftforge.client.event.GuiOpenEvent import net.minecraftforge.common.MinecraftForge import net.minecraftforge.fml.client.registry.ClientRegistry import net.minecraftforge.fml.common.Mod @@ -105,4 +104,11 @@ object FunnyMap { MapRender.legitPeek = !MapRender.legitPeek } } + + @SubscribeEvent + fun onGuiClose(event: GuiOpenEvent) { + if (event.gui == null && mc.currentScreen is SettingsGui) { + MapRenderList.renderUpdated = true + } + } } diff --git a/src/main/kotlin/funnymap/config/Config.kt b/src/main/kotlin/funnymap/config/Config.kt index 334ebd1..033fdf9 100755 --- a/src/main/kotlin/funnymap/config/Config.kt +++ b/src/main/kotlin/funnymap/config/Config.kt @@ -764,6 +764,13 @@ object Config : Vigilant(File("./config/funnymap/config.toml"), "Funny Map", sor ) var paulBonus = false + @Property( + name = "Beta Rendering", + type = PropertyType.SWITCH, + category = "Debug" + ) + var renderBeta = true + @Property( name = "Custom Prefix", type = PropertyType.TEXT, diff --git a/src/main/kotlin/funnymap/features/dungeon/DungeonScan.kt b/src/main/kotlin/funnymap/features/dungeon/DungeonScan.kt index 75f5a62..3b2c0b4 100644 --- a/src/main/kotlin/funnymap/features/dungeon/DungeonScan.kt +++ b/src/main/kotlin/funnymap/features/dungeon/DungeonScan.kt @@ -68,6 +68,7 @@ object DungeonScan { MapUpdate.roomAdded = true } Dungeon.Info.dungeonList[z * 11 + x] = it + MapRenderList.renderUpdated = true } } } diff --git a/src/main/kotlin/funnymap/features/dungeon/MapRender.kt b/src/main/kotlin/funnymap/features/dungeon/MapRender.kt index 2cb86ed..470a7e0 100755 --- a/src/main/kotlin/funnymap/features/dungeon/MapRender.kt +++ b/src/main/kotlin/funnymap/features/dungeon/MapRender.kt @@ -22,6 +22,12 @@ import java.awt.Color object MapRender { var dynamicRotation = 0f var legitPeek = false + set(value) { + if (field != value && Config.legitMode) { + MapRenderList.renderUpdated = true + } + field = value + } val legitRender: Boolean get() = Config.legitMode && !legitPeek @@ -79,7 +85,7 @@ object MapRender { } } - private fun setupRotate() { + fun setupRotate() { val scale = ScaledResolution(mc).scaleFactor GL11.glEnable(GL11.GL_SCISSOR_TEST) GL11.glScissor( @@ -226,7 +232,7 @@ object MapRender { GlStateManager.popMatrix() } - private fun renderPlayerHeads() { + fun renderPlayerHeads() { try { if (Dungeon.dungeonTeammates.isEmpty()) { RenderUtils.drawPlayerHead(mc.thePlayer.name, DungeonPlayer(mc.thePlayer.locationSkin).apply { @@ -267,7 +273,7 @@ object MapRender { ) } - private fun renderRunInformation() { + fun renderRunInformation() { GlStateManager.pushMatrix() GlStateManager.translate(64f, 128f, 0f) GlStateManager.scale(2.0 / 3.0, 2.0 / 3.0, 1.0) diff --git a/src/main/kotlin/funnymap/features/dungeon/MapRenderList.kt b/src/main/kotlin/funnymap/features/dungeon/MapRenderList.kt new file mode 100644 index 0000000..98fe719 --- /dev/null +++ b/src/main/kotlin/funnymap/features/dungeon/MapRenderList.kt @@ -0,0 +1,220 @@ +package funnymap.features.dungeon + +import funnymap.FunnyMap.mc +import funnymap.config.Config +import funnymap.core.map.* +import funnymap.features.dungeon.MapRender.dynamicRotation +import funnymap.features.dungeon.MapRender.legitRender +import funnymap.utils.Location.inBoss +import funnymap.utils.MapUtils +import funnymap.utils.MapUtils.connectorSize +import funnymap.utils.MapUtils.halfRoomSize +import funnymap.utils.MapUtils.roomSize +import funnymap.utils.RenderUtils.darken +import funnymap.utils.RenderUtils.grayScale +import funnymap.utils.RenderUtilsGL +import funnymap.utils.Utils.equalsOneOf +import net.minecraft.client.renderer.GlStateManager +import org.lwjgl.opengl.GL11 +import java.awt.Color + +object MapRenderList { + var renderUpdated = false + private var borderGlList = -1 + private var roomGlList = -1 + + fun updateRenderMap() { + if (borderGlList == -1) { + borderGlList = GL11.glGenLists(1) + GL11.glNewList(borderGlList, GL11.GL_COMPILE) + RenderUtilsGL.renderRect( + 0.0, 0.0, 128.0, if (Config.mapShowRunInformation) 142.0 else 128.0, Config.mapBackground + ) + RenderUtilsGL.renderRectBorder( + 0.0, + 0.0, + 128.0, + if (Config.mapShowRunInformation) 142.0 else 128.0, + Config.mapBorderWidth.toDouble(), + Config.mapBorder + ) + GL11.glEndList() + } + + if (renderUpdated && Config.renderBeta) { + if (roomGlList >= 0) { + GL11.glDeleteLists(roomGlList, 1) + roomGlList = -1 + } + roomGlList = GL11.glGenLists(1) + renderUpdated = false + + GL11.glNewList(roomGlList, GL11.GL_COMPILE) + renderRooms() + renderText() + GL11.glEndList() + + } + } + + fun renderMap() { + if (roomGlList == -1 || borderGlList == -1 || renderUpdated) { + updateRenderMap() + } + + mc.mcProfiler.startSection("border") + + if (borderGlList != -1) GL11.glCallList(borderGlList) + + mc.mcProfiler.endSection() + + if (Config.mapRotate) { + GlStateManager.pushMatrix() + MapRender.setupRotate() + } else if (Config.mapDynamicRotate) { + GlStateManager.translate(64.0, 64.0, 0.0) + GlStateManager.rotate(dynamicRotation, 0f, 0f, 1f) + GlStateManager.translate(-64.0, -64.0, 0.0) + } + + mc.mcProfiler.startSection("rooms") + + if (roomGlList != -1) GL11.glCallList(roomGlList) + + if (!inBoss) { + mc.mcProfiler.endStartSection("heads") + MapRender.renderPlayerHeads() + } + + mc.mcProfiler.endSection() + + if (Config.mapRotate) { + GL11.glDisable(GL11.GL_SCISSOR_TEST) + GlStateManager.popMatrix() + } else if (Config.mapDynamicRotate) { + GlStateManager.translate(64.0, 64.0, 0.0) + GlStateManager.rotate(-dynamicRotation, 0f, 0f, 1f) + GlStateManager.translate(-64.0, -64.0, 0.0) + } + + if (Config.mapShowRunInformation) { + mc.mcProfiler.startSection("footer") + MapRender.renderRunInformation() + mc.mcProfiler.endSection() + } + } + + private fun renderRooms() { + GlStateManager.translate(MapUtils.startCorner.first.toFloat(), MapUtils.startCorner.second.toFloat(), 0f) + + var yPos = 0 + var yStep = 0 + + for (y in 0..10) { + val yEven = y % 2 == 0 + yPos += yStep + yStep = if (yEven) roomSize else connectorSize + var xPos = 0 + var xStep = 0 + for (x in 0..10) { + val xEven = x % 2 == 0 + xPos += xStep + xStep = if (xEven) roomSize else connectorSize + + val tile = Dungeon.Info.dungeonList[y * 11 + x] + if (tile is Unknown) continue + if (legitRender && tile.state == RoomState.UNDISCOVERED) continue + + var color = tile.color + + if (tile.state.equalsOneOf(RoomState.UNDISCOVERED, RoomState.UNOPENED) && + !legitRender && Dungeon.Info.startTime != 0L + ) { + if (Config.mapDarkenUndiscovered) { + color = color.darken(1 - Config.mapDarkenPercent) + } + if (Config.mapGrayUndiscovered) { + color = color.grayScale() + } + } + + when (tile) { + is Room -> { + RenderUtilsGL.renderRect(xPos, yPos, xStep, yStep, color) + if (legitRender && tile.state == RoomState.UNOPENED) { + RenderUtilsGL.drawCheckmark(xPos.toFloat(), yPos.toFloat(), tile.state) + } + } + + is Door -> { + val doorOffset = if (roomSize == 16) 5 else 6 + if (xEven) { + RenderUtilsGL.renderRect(xPos + doorOffset, yPos, xStep - doorOffset * 2, yStep, color) + } else { + RenderUtilsGL.renderRect(xPos, yPos + doorOffset, xStep, yStep - doorOffset * 2, color) + } + } + } + } + } + GlStateManager.translate(-MapUtils.startCorner.first.toFloat(), -MapUtils.startCorner.second.toFloat(), 0f) + } + + private fun renderText() { + GlStateManager.translate(MapUtils.startCorner.first.toFloat(), MapUtils.startCorner.second.toFloat(), 0f) + + Dungeon.Info.uniqueRooms.forEach { unique -> + val room = unique.mainRoom + if (legitRender && room.state.equalsOneOf(RoomState.UNDISCOVERED, RoomState.UNOPENED)) return@forEach + val checkPos = unique.getCheckmarkPosition() + val namePos = unique.getNamePosition() + val xPosCheck = (checkPos.first / 2f) * (roomSize + connectorSize) + val yPosCheck = (checkPos.second / 2f) * (roomSize + connectorSize) + val xPosName = (namePos.first / 2f) * (roomSize + connectorSize) + val yPosName = (namePos.second / 2f) * (roomSize + connectorSize) + + if (Config.mapCheckmark != 0 && Config.mapRoomSecrets != 2) { + RenderUtilsGL.drawCheckmark(xPosCheck, yPosCheck, room.state) + } + + val color = if (Config.mapColorText) when (room.state) { + RoomState.GREEN -> Color(0x55ff55) + RoomState.CLEARED, RoomState.FAILED -> Color(0xffffff) + else -> Color(0xaaaaaa) + } else Color(0xffffff) + + if (Config.mapRoomSecrets == 2) { + GlStateManager.pushMatrix() + GlStateManager.translate( + xPosCheck + halfRoomSize, yPosCheck + 2 + halfRoomSize, 0f + ) + GlStateManager.scale(2f, 2f, 1f) + RenderUtilsGL.renderCenteredText(listOf(room.data.secrets.toString()), 0, 0, color) + GlStateManager.popMatrix() + } + + val name = mutableListOf() + + if (Config.mapRoomNames != 0 && room.data.type.equalsOneOf( + RoomType.PUZZLE, + RoomType.TRAP + ) || Config.mapRoomNames == 2 && room.data.type.equalsOneOf( + RoomType.NORMAL, RoomType.RARE, RoomType.CHAMPION + ) + ) { + name.addAll(room.data.name.split(" ")) + } + if (room.data.type == RoomType.NORMAL && Config.mapRoomSecrets == 1) { + name.add(room.data.secrets.toString()) + } + // Offset + half of roomsize + RenderUtilsGL.renderCenteredText( + name, + xPosName.toInt() + halfRoomSize, + yPosName.toInt() + halfRoomSize, + color + ) + } + GlStateManager.translate(-MapUtils.startCorner.first.toFloat(), -MapUtils.startCorner.second.toFloat(), 0f) + } +} diff --git a/src/main/kotlin/funnymap/features/dungeon/MapUpdate.kt b/src/main/kotlin/funnymap/features/dungeon/MapUpdate.kt index 911f83f..43fbd50 100755 --- a/src/main/kotlin/funnymap/features/dungeon/MapUpdate.kt +++ b/src/main/kotlin/funnymap/features/dungeon/MapUpdate.kt @@ -110,12 +110,14 @@ object MapUpdate { if (mapTile is Unknown) continue if (room is Unknown) { + MapRenderList.renderUpdated = true roomAdded = true Dungeon.Info.dungeonList[z * 11 + x] = mapTile continue } if (mapTile.state.ordinal < room.state.ordinal) { + MapRenderList.renderUpdated = true PlayerTracker.roomStateChange(room, room.state, mapTile.state) if (room is Room && room.data.type == RoomType.BLOOD && mapTile.state == RoomState.GREEN) { RunInformation.bloodDone = true @@ -125,12 +127,14 @@ object MapUpdate { if (mapTile is Room && room is Room) { if (room.data.type != mapTile.data.type && mapTile.data.type != RoomType.NORMAL) { + MapRenderList.renderUpdated = true room.data.type = mapTile.data.type } } if (mapTile is Door && room is Door) { if (mapTile.type == DoorType.WITHER && room.type != DoorType.WITHER) { + MapRenderList.renderUpdated = true room.type = mapTile.type } } @@ -138,6 +142,7 @@ object MapUpdate { if (room is Door && room.type.equalsOneOf(DoorType.ENTRANCE, DoorType.WITHER, DoorType.BLOOD)) { if (mapTile is Door && mapTile.type == DoorType.WITHER) { if (room.opened) { + MapRenderList.renderUpdated = true room.opened = false } } else if (!room.opened && mc.theWorld.getChunkFromChunkCoords( @@ -146,6 +151,7 @@ object MapUpdate { ).isLoaded && mc.theWorld.getBlockState(BlockPos(room.x, 69, room.z)).block == Blocks.air ) { + MapRenderList.renderUpdated = true room.opened = true } diff --git a/src/main/kotlin/funnymap/ui/MapElement.kt b/src/main/kotlin/funnymap/ui/MapElement.kt index 3601430..b43f97f 100644 --- a/src/main/kotlin/funnymap/ui/MapElement.kt +++ b/src/main/kotlin/funnymap/ui/MapElement.kt @@ -2,6 +2,7 @@ package funnymap.ui import funnymap.config.Config import funnymap.features.dungeon.MapRender +import funnymap.features.dungeon.MapRenderList import funnymap.utils.Location class MapElement : MovableGuiElement() { @@ -16,7 +17,11 @@ class MapElement : MovableGuiElement() { override var y2: Int = (y + h * scale).toInt() override fun render() { - MapRender.renderMap() + if (Config.renderBeta) { + MapRenderList.renderMap() + } else { + MapRender.renderMap() + } } override fun shouldRender(): Boolean { diff --git a/src/main/kotlin/funnymap/utils/RenderUtils.kt b/src/main/kotlin/funnymap/utils/RenderUtils.kt index 1dee60e..902c4c2 100644 --- a/src/main/kotlin/funnymap/utils/RenderUtils.kt +++ b/src/main/kotlin/funnymap/utils/RenderUtils.kt @@ -27,9 +27,9 @@ object RenderUtils { private val tessellator: Tessellator = Tessellator.getInstance() private val worldRenderer: WorldRenderer = tessellator.worldRenderer - private val neuCheckmarks = CheckmarkSet(10, "neu") - private val defaultCheckmarks = CheckmarkSet(16, "default") - private val legacyCheckmarks = CheckmarkSet(8, "legacy") + val neuCheckmarks = CheckmarkSet(10, "neu") + val defaultCheckmarks = CheckmarkSet(16, "default") + val legacyCheckmarks = CheckmarkSet(8, "legacy") private val mapIcons = ResourceLocation("funnymap", "marker.png") private fun preDraw() { @@ -47,14 +47,14 @@ object RenderUtils { GlStateManager.enableTexture2D() } - private fun addQuadVertices(x: Double, y: Double, w: Double, h: Double) { + fun addQuadVertices(x: Double, y: Double, w: Double, h: Double) { worldRenderer.pos(x, y + h, 0.0).endVertex() worldRenderer.pos(x + w, y + h, 0.0).endVertex() worldRenderer.pos(x + w, y, 0.0).endVertex() worldRenderer.pos(x, y, 0.0).endVertex() } - private fun drawTexturedQuad(x: Double, y: Double, width: Double, height: Double) { + fun drawTexturedQuad(x: Double, y: Double, width: Double, height: Double) { worldRenderer.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX) worldRenderer.pos(x, y + height, 0.0).tex(0.0, 1.0).endVertex() worldRenderer.pos(x + width, y + height, 0.0).tex(1.0, 1.0).endVertex() diff --git a/src/main/kotlin/funnymap/utils/RenderUtilsGL.kt b/src/main/kotlin/funnymap/utils/RenderUtilsGL.kt new file mode 100644 index 0000000..daa15da --- /dev/null +++ b/src/main/kotlin/funnymap/utils/RenderUtilsGL.kt @@ -0,0 +1,126 @@ +package funnymap.utils + +import funnymap.FunnyMap.mc +import funnymap.config.Config +import funnymap.core.map.RoomState +import funnymap.features.dungeon.MapRender +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.client.renderer.texture.SimpleTexture +import net.minecraft.client.renderer.vertex.DefaultVertexFormats +import net.minecraft.util.ResourceLocation +import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL11.GL_QUADS +import java.awt.Color + +object RenderUtilsGL { + + private val tessellator: Tessellator = Tessellator.getInstance() + private val worldRenderer: WorldRenderer = tessellator.worldRenderer + + fun preDraw() { + GL11.glEnable(GL11.GL_ALPHA_TEST) + GL11.glEnable(GL11.GL_BLEND) + GL11.glDisable(GL11.GL_DEPTH_TEST) + GL11.glDisable(GL11.GL_LIGHTING) + GL11.glDisable(GL11.GL_TEXTURE_2D) + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA) + } + + fun postDraw() { + GL11.glDisable(GL11.GL_BLEND) + GL11.glEnable(GL11.GL_DEPTH_TEST) + GL11.glEnable(GL11.GL_TEXTURE_2D) + } + + fun renderRect(x: Number, y: Number, w: Number, h: Number, color: Color) { + if (color.alpha == 0) return + preDraw() + color.bind() + + worldRenderer.begin(GL_QUADS, DefaultVertexFormats.POSITION) + RenderUtils.addQuadVertices(x.toDouble(), y.toDouble(), w.toDouble(), h.toDouble()) + tessellator.draw() + + postDraw() + } + + fun renderRectBorder(x: Double, y: Double, w: Double, h: Double, thickness: Double, color: Color) { + if (color.alpha == 0) return + preDraw() + color.bind() + + worldRenderer.begin(GL_QUADS, DefaultVertexFormats.POSITION) + RenderUtils.addQuadVertices(x - thickness, y, thickness, h) + RenderUtils.addQuadVertices(x - thickness, y - thickness, w + thickness * 2, thickness) + RenderUtils.addQuadVertices(x + w, y, thickness, h) + RenderUtils.addQuadVertices(x - thickness, y + h, w + thickness * 2, thickness) + tessellator.draw() + + postDraw() + } + + fun renderCenteredText(text: List, x: Int, y: Int, color: Color) { + if (text.isEmpty()) return + GlStateManager.pushMatrix() + GlStateManager.translate(x.toFloat(), y.toFloat(), 0f) + GlStateManager.scale(Config.textScale, Config.textScale, 1f) + + if (Config.mapRotate) { + GlStateManager.rotate(mc.thePlayer.rotationYaw + 180f, 0f, 0f, 1f) + } else if (Config.mapDynamicRotate) { + GlStateManager.rotate(-MapRender.dynamicRotation, 0f, 0f, 1f) + } + + val fontHeight = SimpleFontRenderer.FONT_HEIGHT + 1 + val yTextOffset = text.size * fontHeight / -2f + + text.withIndex().forEach { (index, text) -> + SimpleFontRenderer.drawString( + text, + SimpleFontRenderer.getStringWidth(text) / -2f, + yTextOffset + index * fontHeight, + color, + true + ) + } + + GlStateManager.popMatrix() + } + + fun drawCheckmark(x: Float, y: Float, state: RoomState) { + val (checkmark, size) = when (Config.mapCheckmark) { + 1 -> RenderUtils.defaultCheckmarks.getCheckmark(state) to RenderUtils.defaultCheckmarks.size.toDouble() + 2 -> RenderUtils.neuCheckmarks.getCheckmark(state) to RenderUtils.neuCheckmarks.size.toDouble() + 3 -> RenderUtils.legacyCheckmarks.getCheckmark(state) to RenderUtils.legacyCheckmarks.size.toDouble() + else -> return + } + if (checkmark != null) { + GL11.glColor4f(1f, 1f, 1f, 1f) + GL11.glEnable(GL11.GL_ALPHA_TEST) + GL11.glEnable(GL11.GL_TEXTURE_2D) + checkmark.bind() + + RenderUtils.drawTexturedQuad( + x + (MapUtils.roomSize - size) / 2, + y + (MapUtils.roomSize - size) / 2, + size, + size + ) + } + } + + private fun Color.bind() { + GL11.glColor4f(this.red / 255f, this.green / 255f, this.blue / 255f, this.alpha / 255f) + } + + fun ResourceLocation.bind() { + var tex = mc.textureManager.getTexture(this) + if (tex == null) { + tex = SimpleTexture(this) + mc.textureManager.loadTexture(this, tex) + } + GL11.glBindTexture(GL11.GL_TEXTURE_2D, tex.glTextureId) + } +} diff --git a/src/main/kotlin/funnymap/utils/SimpleFontRenderer.kt b/src/main/kotlin/funnymap/utils/SimpleFontRenderer.kt new file mode 100644 index 0000000..4ae26b1 --- /dev/null +++ b/src/main/kotlin/funnymap/utils/SimpleFontRenderer.kt @@ -0,0 +1,155 @@ +package funnymap.utils + +import funnymap.FunnyMap.mc +import funnymap.utils.RenderUtilsGL.bind +import net.minecraft.client.renderer.texture.SimpleTexture +import net.minecraft.client.renderer.texture.TextureUtil +import net.minecraft.client.resources.IResourceManager +import net.minecraft.client.resources.IResourceManagerReloadListener +import net.minecraft.util.ResourceLocation +import org.lwjgl.opengl.GL11 +import java.awt.Color +import java.awt.image.BufferedImage +import java.io.IOException +import kotlin.math.max + +object SimpleFontRenderer : IResourceManagerReloadListener { + private val locationFontTexture = ResourceLocation("textures/font/ascii.png") + private var charWidth: IntArray = IntArray(256) + private var posX: Float = 0f + private var posY: Float = 0f + const val FONT_HEIGHT: Int = 9 + private const val VALID_CHARS = + "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000" + + init { + readFontTexture() + } + + override fun onResourceManagerReload(resourceManager: IResourceManager) { + readFontTexture() + } + + private fun readFontTexture() { + val bufferedImage: BufferedImage = try { + TextureUtil.readBufferedImage(mc.resourceManager.getResource(locationFontTexture).inputStream) + } catch (e: IOException) { + throw RuntimeException(e) + } + val w = bufferedImage.width + val h = bufferedImage.height + val arr = IntArray(w * h) + bufferedImage.getRGB(0, 0, w, h, arr, 0, w) + val charHeight = h / 16 + val charWidth = w / 16 + val scale = 8.0f / charWidth.toFloat() + charIndex@ for (charIndex in 0..255) { + if (charIndex == 32) { + this.charWidth[charIndex] = 4 + } + val column = charIndex % 16 + val row = charIndex / 16 + for (scanX in charWidth - 1 downTo 0) { + val x = column * charWidth + scanX + var found = false + for (scanY in 0 until charHeight) { + val y = row * charHeight + scanY + if ((arr[x + y * w] shr 24 and 0xFF) != 0) { + found = true + break + } + } + if (found) { + this.charWidth[charIndex] = (0.5 + (scanX + 1) * scale).toInt() + 1 + continue@charIndex + } + } + this.charWidth[charIndex] = (0.5 + scale).toInt() + 1 + } + + if (mc.textureManager.getTexture(locationFontTexture) == null) { + mc.textureManager.loadTexture(locationFontTexture, SimpleTexture(locationFontTexture)) + } + } + + private fun renderChar(ch: Char): Int { + if (ch == ' ') return 4 + val i = VALID_CHARS.indexOf(ch) + return if (i != -1) this.renderDefaultChar(i) else 0 + } + + private fun renderDefaultChar(ch: Int): Int { + val texX = (ch % 16 * 8) / 128.0f + val texY = (ch / 16 * 8) / 128.0f + val width = charWidth[ch] - 1.01f + val height = 7.99f + val texWidth = width / 128.0f + val texHeight = height / 128.0f + + GL11.glBegin(GL11.GL_TRIANGLE_STRIP) + GL11.glTexCoord2f(texX, texY) + GL11.glVertex3f(posX, posY, 0.0f) + GL11.glTexCoord2f(texX, texY + texHeight) + GL11.glVertex3f(posX, posY + height, 0.0f) + GL11.glTexCoord2f(texX + texWidth, texY) + GL11.glVertex3f(posX + width, posY, 0.0f) + GL11.glTexCoord2f(texX + texWidth, texY + texHeight) + GL11.glVertex3f(posX + width, posY + height, 0.0f) + GL11.glEnd() + + return charWidth[ch] + } + + fun drawString(text: String, x: Float, y: Float, color: Color, dropShadow: Boolean): Int { + var i: Int + GL11.glEnable(GL11.GL_ALPHA_TEST) + GL11.glEnable(GL11.GL_TEXTURE_2D) + locationFontTexture.bind() + + if (dropShadow) { + i = renderString(text, x + 1.0f, y + 1.0f, color, true) + i = max(i, renderString(text, x, y, color, false)) + } else { + i = renderString(text, x, y, color, false) + } + + GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f) + return i + } + + private fun renderStringAtPos(text: String) { + text.forEach { + posX += renderChar(it) + } + } + + private fun renderString(text: String, x: Float, y: Float, color: Color, dropShadow: Boolean): Int { + if (dropShadow) { + GL11.glColor4f( + color.red * .25f / 255f, + color.green * .25f / 255f, + color.blue * .25f / 255f, + color.alpha / 255f + ) + } else { + GL11.glColor4f(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f) + } + posX = x + posY = y + renderStringAtPos(text) + return posX.toInt() + } + + fun getStringWidth(text: String): Int { + return text.sumOf { getCharWidth(it) } + } + + fun getCharWidth(character: Char): Int { + if (character == ' ') return 4 + val i = VALID_CHARS.indexOf(character) + if (character > '\u0000' && i != -1) { + return charWidth[i] + } + return 0 + } +}