Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/main/kotlin/org/polyfrost/polyhitbox/api/HitboxColors.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.polyfrost.polyhitbox.api

import net.minecraft.world.entity.Entity
import org.slf4j.LoggerFactory

enum class HitboxElement {
SIDE,
OUTLINE,
EYE_HEIGHT,
VIEW_RAY,
}

enum class HitboxCondition {
HOVERED,
IFRAME,
}

interface HitboxColorContext {
val entity: Entity
val element: HitboxElement

fun has(condition: HitboxCondition): Boolean
}

fun interface HitboxColorProvider {
fun color(context: HitboxColorContext, argb: Int): Int
}

object HitboxColors {

private val logger = LoggerFactory.getLogger("PolyHitbox")

@Volatile
private var providers: Array<HitboxColorProvider> = emptyArray()

@JvmStatic
fun register(provider: HitboxColorProvider) {
synchronized(this) {
if (providers.none { it === provider }) providers += provider
}
}

@JvmStatic
fun unregister(provider: HitboxColorProvider) {
synchronized(this) {
providers = providers.filterNot { it === provider }.toTypedArray()
}
}

internal fun snapshot(): Array<HitboxColorProvider> = providers

internal fun disable(provider: HitboxColorProvider, error: Throwable) {
unregister(provider)
logger.error("Unregistered hitbox color provider {} because it threw.", provider.javaClass.name, error)
}
}
45 changes: 41 additions & 4 deletions src/main/kotlin/org/polyfrost/polyhitbox/render/HitboxRenderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import org.polyfrost.polyhitbox.api.HitboxColorContext
import org.polyfrost.polyhitbox.api.HitboxColorProvider
import org.polyfrost.polyhitbox.api.HitboxColors
import org.polyfrost.polyhitbox.api.HitboxCondition
import org.polyfrost.polyhitbox.api.HitboxElement
import org.polyfrost.polyhitbox.config.HitboxCategory
import org.polyfrost.polyhitbox.config.HitboxConfig
import org.polyfrost.polyhitbox.config.ModConfig
Expand Down Expand Up @@ -51,6 +56,7 @@ object HitboxRenderer {
private var viewer: Player? = null
private var selfInFirstPerson: Entity? = null
private var vanillaToggle = false
private var colorProviders: Array<HitboxColorProvider> = emptyArray()

private var offX = 0.0
private var offY = 0.0
Expand Down Expand Up @@ -140,6 +146,7 @@ object HitboxRenderer {
viewer = player
selfInFirstPerson = if (mc.options.cameraType.isFirstPerson) mc.cameraEntity else null
vanillaToggle = vanillaHitboxesEnabled()
colorProviders = HitboxColors.snapshot()
return true
}

Expand Down Expand Up @@ -203,6 +210,36 @@ object HitboxRenderer {
private fun pick(iframe: Boolean, hover: Boolean, iframeArgb: Int, hoverArgb: Int, baseArgb: Int): Int =
if (iframe) iframeArgb else if (hover) hoverArgb else baseArgb

private class Context(
override val entity: Entity,
override val element: HitboxElement,
private val hovered: Boolean,
private val iframe: Boolean,
) : HitboxColorContext {
override fun has(condition: HitboxCondition): Boolean = when (condition) {
HitboxCondition.HOVERED -> hovered
HitboxCondition.IFRAME -> iframe
}
}

private fun tint(entity: Entity, element: HitboxElement, hover: Boolean, iframe: Boolean, argb: Int): Int {
val providers = colorProviders
if (providers.isEmpty()) return argb
val context = Context(entity, element, hover, iframe)
var result = argb
var failed = false
for (provider in providers) {
try {
result = provider.color(context, result)
} catch (error: Throwable) {
HitboxColors.disable(provider, error)
failed = true
}
}
if (failed) colorProviders = HitboxColors.snapshot()
return result
}

private fun drawEntity(vc: VertexConsumer, entity: Entity, config: HitboxConfig) {
val delta = partialTicks.toDouble()
val px = Mth.lerp(delta, entity.xo, entity.x)
Expand All @@ -223,20 +260,20 @@ object HitboxRenderer {
val iframe = config.iframeColor && inIframes(entity)

if (config.showSide) {
val c = pick(iframe, hover, config.sideIframeArgb, config.sideHoverArgb, config.sideArgb)
val c = tint(entity, HitboxElement.SIDE, hover, iframe, pick(iframe, hover, config.sideIframeArgb, config.sideHoverArgb, config.sideArgb))
fillBox(vc, minX, minY, minZ, maxX, maxY, maxZ, c)
}
if (config.showOutline) {
val c = pick(iframe, hover, config.outlineIframeArgb, config.outlineHoverArgb, config.outlineArgb)
val c = tint(entity, HitboxElement.OUTLINE, hover, iframe, pick(iframe, hover, config.outlineIframeArgb, config.outlineHoverArgb, config.outlineArgb))
styledBox(vc, config, minX, minY, minZ, maxX, maxY, maxZ, c, config.outlineThickness)
}
if (config.showEyeHeight) {
val c = pick(iframe, hover, config.eyeHeightIframeArgb, config.eyeHeightHoverArgb, config.eyeHeightArgb)
val c = tint(entity, HitboxElement.EYE_HEIGHT, hover, iframe, pick(iframe, hover, config.eyeHeightIframeArgb, config.eyeHeightHoverArgb, config.eyeHeightArgb))
val eyeY = minY + entity.eyeHeight
styledBox(vc, config, minX, eyeY - 0.01, minZ, maxX, eyeY + 0.01, maxZ, c, config.eyeHeightThickness)
}
if (config.showViewRay) {
val c = pick(iframe, hover, config.viewRayIframeArgb, config.viewRayHoverArgb, config.viewRayArgb)
val c = tint(entity, HitboxElement.VIEW_RAY, hover, iframe, pick(iframe, hover, config.viewRayIframeArgb, config.viewRayHoverArgb, config.viewRayArgb))
val eyeY = minY + entity.eyeHeight
val view = entity.getViewVector(partialTicks)
val ax = px - camX
Expand Down