Skip to content

Commit

Permalink
Add highlight and outline feature for rare sea creatures (#439)
Browse files Browse the repository at this point in the history
Add highlight and outline feature for rare sea creatures #439
  • Loading branch information
brandonwamboldt authored Sep 7, 2023
1 parent 5d2f4ea commit 6be7873
Show file tree
Hide file tree
Showing 13 changed files with 735 additions and 0 deletions.
1 change: 1 addition & 0 deletions OPEN_SOURCE_SOFTWARE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ SkyHanni would not be possible without the following open source software:
| [Forge1.8.9Template](https://github.com/romangraef/Forge1.8.9Template) | [Unlicensed](https://github.com/romangraef/Forge1.8.9Template/blob/master/LICENSE) |
| [SoopyV2](https://github.com/Soopyboo32/SoopyV2) | [GPL 3.0](https://github.com/Soopyboo32/SoopyV2/blob/master/LICENSE) |
| [DiscordIPC](https://github.com/jagrosh/DiscordIPC) | [Apache 2.0](https://github.com/jagrosh/DiscordIPC/blob/master/LICENSE) |
| [SkyBlockAddons](https://github.com/BiscuitDevelopment/SkyblockAddons/) | [MIT](https://github.com/BiscuitDevelopment/SkyblockAddons/blob/e9fd003f359b357f52b7430c24e64a4c8192a868/LICENSE) |
3 changes: 3 additions & 0 deletions src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ import at.hannibal2.skyhanni.features.summonings.SummoningSoulsName
import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper
import at.hannibal2.skyhanni.test.*
import at.hannibal2.skyhanni.test.command.CopyNearbyParticlesCommand
import at.hannibal2.skyhanni.utils.EntityOutlineRenderer
import at.hannibal2.skyhanni.utils.MinecraftConsoleFilter.Companion.initLogging
import at.hannibal2.skyhanni.utils.NEUVersionCheck.checkIfNeuIsLoaded
import at.hannibal2.skyhanni.utils.TabListData
Expand Down Expand Up @@ -140,6 +141,7 @@ class SkyHanniMod {
loadModule(HypixelData())
loadModule(DungeonData())
loadModule(ScoreboardData())
loadModule(SeaCreatureFeatures())
loadModule(SeaCreatureManager())
loadModule(ItemRenderBackground())
loadModule(EntityData())
Expand Down Expand Up @@ -171,6 +173,7 @@ class SkyHanniMod {
loadModule(TitleData())
loadModule(BlockData())
loadModule(DefaultConfigFeatures)
loadModule(EntityOutlineRenderer)

// APIs
loadModule(BazaarApi())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ public static class FishedItemName {

}

@Expose
@ConfigOption(name = "Rare Sea Creature Highlight", desc = "Highlight rare sea creatures in blue color.")
@ConfigEditorBoolean
@FeatureToggle
public boolean rareSeaCreatureHighlight = false;

@Expose
@ConfigOption(
name = "Shark Fish Counter",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,12 @@ public static class FollowingLineConfig {
@FeatureToggle
public boolean configButtonOnPause = true;

@Expose
@ConfigOption(name = "Entity Outlines", desc = "Enable entity outlines for applicable features.")
@ConfigEditorBoolean
@FeatureToggle
public boolean enableEntityOutlines = true;

@Expose
public Position inventoryLoadPos = new Position(394, 124, false, true);

Expand Down
118 changes: 118 additions & 0 deletions src/main/java/at/hannibal2/skyhanni/events/RenderEntityOutlineEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package at.hannibal2.skyhanni.events

import net.minecraft.client.Minecraft
import net.minecraft.entity.Entity
import net.minecraft.entity.item.EntityArmorStand
import net.minecraft.entity.item.EntityItemFrame
import java.util.function.Consumer

class RenderEntityOutlineEvent(theType: Type?, potentialEntities: HashSet<Entity>?) :
LorenzEvent() {

/**
* The phase of the event (see [Type]
*/
var type: Type? = null

/**
* The entities to outline. This is progressively cumulated from [.entitiesToChooseFrom]
*/
var entitiesToOutline: HashMap<Entity, Int>? = null

/**
* The entities we can outline. Note that this set and [.entitiesToOutline] are disjoint at all times.
*/
var entitiesToChooseFrom: HashSet<Entity>? = null

/**
* Constructs the event, given the type and optional entities to outline.
*
*
* This will modify {@param potentialEntities} internally, so make a copy before passing it if necessary.
*
* @param theType of the event (see [Type]
*/
init {
type = theType
entitiesToChooseFrom = potentialEntities
if (potentialEntities != null) {
entitiesToOutline = HashMap(potentialEntities.size)
}
}

/**
* Conditionally queue entities around which to render entities
* Selects from the pool of [.entitiesToChooseFrom] to speed up the predicate testing on subsequent calls.
* Is more efficient (theoretically) than calling [.queueEntityToOutline] for each entity because lists are handled internally.
*
*
* This function loops through all entities and so is not very efficient.
* It's advisable to encapsulate calls to this function with global checks (those not dependent on an individual entity) for efficiency purposes.
*
* @param outlineColor a function to test
*/
fun queueEntitiesToOutline(outlineColor: ((entity: Entity) -> Int?)? = null) {
if (outlineColor == null) {
return
}
if (entitiesToChooseFrom == null) {
computeAndCacheEntitiesToChooseFrom()
}
val itr: MutableIterator<Entity> = entitiesToChooseFrom!!.iterator()
while (itr.hasNext()) {
val e: Entity = itr.next()
val i: Int? = outlineColor(e)
if (i != null) {
entitiesToOutline!![e] = i
itr.remove()
}
}
}

/**
* Adds a single entity to the list of the entities to outline
*
* @param entity the entity to add
* @param outlineColor the color with which to outline
*/
fun queueEntityToOutline(entity: Entity?, outlineColor: Int) {
if (entity == null) {
return
}
if (entitiesToChooseFrom == null) {
computeAndCacheEntitiesToChooseFrom()
}
if (!entitiesToChooseFrom!!.contains(entity)) {
return
}
entitiesToOutline!![entity] = outlineColor
entitiesToChooseFrom!!.remove(entity)
}

/**
* Used for on-the-fly generation of entities. Driven by event handlers in a decentralized fashion
*/
private fun computeAndCacheEntitiesToChooseFrom() {
val entities: List<Entity> = Minecraft.getMinecraft().theWorld.getLoadedEntityList()
// Only render outlines around non-null entities within the camera frustum
entitiesToChooseFrom = HashSet(entities.size)
// Only consider entities that aren't invisible armorstands to increase FPS significantly
entities.forEach(Consumer<Entity> { e: Entity? ->
if (e != null && !(e is EntityArmorStand && e.isInvisible()) && e !is EntityItemFrame) {
entitiesToChooseFrom!!.add(e)
}
})
entitiesToOutline = HashMap(entitiesToChooseFrom!!.size)
}

/**
* The phase of the event.
* [.XRAY] means that this directly precedes entities whose outlines are rendered through walls (Vanilla 1.9+)
* [.NO_XRAY] means that this directly precedes entities whose outlines are rendered only when visible to the client
*/
enum class Type {
XRAY,
NO_XRAY
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package at.hannibal2.skyhanni.features.fishing

import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.events.EntityMaxHealthUpdateEvent
import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent
import at.hannibal2.skyhanni.events.RenderEntityOutlineEvent
import at.hannibal2.skyhanni.events.withAlpha
import at.hannibal2.skyhanni.features.damageindicator.DamageIndicatorManager
import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper
import at.hannibal2.skyhanni.utils.*
import at.hannibal2.skyhanni.utils.EntityUtils.getSkinTexture
import at.hannibal2.skyhanni.utils.EntityUtils.hasMaxHealth
import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer
import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth
import at.hannibal2.skyhanni.utils.LorenzUtils.editCopy
import net.minecraft.client.Minecraft
import net.minecraft.client.entity.EntityOtherPlayerMP
import net.minecraft.client.gui.FontRenderer
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityLivingBase
import net.minecraft.entity.item.EntityItem
import net.minecraft.entity.monster.EntityGuardian
import net.minecraft.entity.monster.EntitySkeleton
import net.minecraft.entity.monster.EntityZombie
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.scoreboard.ScorePlayerTeam
import net.minecraft.scoreboard.Team.EnumVisible
import net.minecraftforge.client.event.RenderWorldLastEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import java.awt.Color


class SeaCreatureFeatures {
private val config get() = SkyHanniMod.feature.fishing
private var rareSeaCreatures = listOf<EntityLivingBase>()

@SubscribeEvent
fun onEntityHealthUpdate(event: EntityMaxHealthUpdateEvent) {
if (!isEnabled()) return
val entity = event.entity as? EntityLivingBase ?: return
if (DamageIndicatorManager.isBoss(entity)) return

val maxHealth = event.maxHealth
for (creatureType in RareSeaCreatureType.entries) {
if (EntityGuardian::class.java.isInstance(entity) && entity.baseMaxHealth > 1000) {
LorenzUtils.consoleLog("FISHING CREATURE NAME: ${entity.baseMaxHealth} ${(entity as EntityGuardian).customNameTag}")
}

if (!creatureType.health.any { entity.hasMaxHealth(it, false, maxHealth) }) continue
if (!creatureType.clazz.isInstance(entity)) continue

if (EntityPlayer::class.java.isInstance(entity)) {
LorenzUtils.consoleLog("FISHING CREATURE NAME: ${(entity as EntityPlayer).customNameTag}")
}

if (creatureType.nametag.isNotBlank() && EntityPlayer::class.java.isInstance(entity) && (entity as EntityPlayer).name != creatureType.nametag) {
continue
}

rareSeaCreatures = rareSeaCreatures.editCopy { add(entity) }
RenderLivingEntityHelper.setEntityColor(entity, LorenzColor.RED.toColor().withAlpha(50))
{ config.rareSeaCreatureHighlight }
RenderLivingEntityHelper.setNoHurtTime(entity) { config.rareSeaCreatureHighlight }
}
}

@SubscribeEvent
fun onWorldChange(event: LorenzWorldChangeEvent) {
rareSeaCreatures = emptyList()
}

@SubscribeEvent
fun onRenderEntityOutlines(event: RenderEntityOutlineEvent) {
if (isEnabled() && config.rareSeaCreatureHighlight && event.type === RenderEntityOutlineEvent.Type.XRAY) {
event.queueEntitiesToOutline(getEntityOutlineColor)
}
}

private fun isEnabled() = LorenzUtils.inSkyBlock && !LorenzUtils.inDungeons && !LorenzUtils.inKuudraFight

private val getEntityOutlineColor: (entity: Entity) -> Int? = { e ->
if (EntityLivingBase::class.java.isInstance(e) && e in rareSeaCreatures && e.distanceToPlayer() < 30) {
LorenzColor.GREEN.toColor().rgb
} else {
null
}
}

enum class RareSeaCreatureType(val clazz: Class<out EntityLivingBase>, val nametag: String, vararg val health: Int) {
WATER_HYDRA(EntityZombie::class.java, "Water Hydra", 500_000, 1_500_000),
SEA_EMPEROR(EntityGuardian::class.java, "The Sea Emperors", 750_000, 800_000, 2_250_000, 2_400_000),
ZOMBIE_MINER(EntityPlayer::class.java, "", 2_000_000, 6_000_000),
PHANTOM_FISHERMAN(EntityPlayer::class.java, "Phantom Fisher", 1_000_000, 3_000_000),
GRIM_REAPER(EntityPlayer::class.java, "Grim Reaper", 3_000_000, 9_000_000),
YETI(EntityPlayer::class.java, "", 2_000_000, 6_000_000),
NUTCRACKER(EntityPlayer::class.java, "", 4_000_000, 12_000_000),
GREAT_WHITE_SHARK(EntityPlayer::class.java, "GWS ", 1_500_000, 4_500_000),
;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package at.hannibal2.skyhanni.mixins.hooks

import at.hannibal2.skyhanni.utils.EntityOutlineRenderer
import at.hannibal2.skyhanni.utils.RenderUtils
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.culling.ICamera
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable

class RenderGlobalHook {
fun renderEntitiesOutlines(camera: ICamera?, partialTicks: Float): Boolean {
val vec = RenderUtils.exactLocation(Minecraft.getMinecraft().renderViewEntity, partialTicks)
return EntityOutlineRenderer.renderEntityOutlines(camera!!, partialTicks, vec)
}

fun shouldRenderEntityOutlines(cir: CallbackInfoReturnable<Boolean?>) {
if (EntityOutlineRenderer.shouldRenderEntityOutlines()) {
cir.returnValue = true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package at.hannibal2.skyhanni.mixins.hooks

import at.hannibal2.skyhanni.utils.EntityOutlineRenderer
import net.minecraft.client.renderer.GlStateManager
import net.minecraft.entity.EntityLivingBase

class RendererLivingEntityHook {
fun setOutlineColor(red: Float, green: Float, blue: Float, alpha: Float, entity: EntityLivingBase) {
val color = EntityOutlineRenderer.getCustomOutlineColor(entity);

if (color != null) {
val colorRed = (color shr 16 and 255).toFloat() / 255.0f
val colorGreen = (color shr 8 and 255).toFloat() / 255.0f
val colorBlue = (color and 255).toFloat() / 255.0f
GlStateManager.color(colorRed, colorGreen, colorBlue, alpha);
} else {
GlStateManager.color(red, green, blue, alpha);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package at.hannibal2.skyhanni.mixins.transformers;

import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.shader.Framebuffer;
import net.minecraft.client.shader.ShaderGroup;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

@Mixin(RenderGlobal.class)
public interface CustomRenderGlobal {
@Accessor("entityOutlineFramebuffer")
Framebuffer getEntityOutlineFramebuffer_skyhanni();

@Accessor("entityOutlineShader")
ShaderGroup getEntityOutlineShader_skyhanni();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package at.hannibal2.skyhanni.mixins.transformers;

import at.hannibal2.skyhanni.mixins.hooks.RenderGlobalHook;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderGlobal;
import at.hannibal2.skyhanni.utils.EntityOutlineRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.culling.ICamera;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(RenderGlobal.class)
public abstract class MixinRenderGlobal {

@Shadow
abstract boolean isRenderEntityOutlines();

@Unique
private final RenderGlobalHook skyHanni$hook = new RenderGlobalHook();

@Redirect(method = "renderEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;isRenderEntityOutlines()Z"))
public boolean renderEntitiesOutlines(RenderGlobal self, Entity renderViewEntity, ICamera camera, float partialTicks) {
return skyHanni$hook.renderEntitiesOutlines(camera, partialTicks) && this.isRenderEntityOutlines();
}

@Inject(method = "isRenderEntityOutlines", at = @At(value = "HEAD"), cancellable = true)
public void isRenderEntityOutlinesWrapper(CallbackInfoReturnable<Boolean> cir) {
skyHanni$hook.shouldRenderEntityOutlines(cir);
}

@Inject(method = "renderEntityOutlineFramebuffer", at = @At(value = "RETURN"))
public void afterFramebufferDraw(CallbackInfo callbackInfo) {
GlStateManager.enableDepth();
}
}
Loading

0 comments on commit 6be7873

Please sign in to comment.