Skip to content

Commit

Permalink
Merge branch 'beta' into backend/command-register-event
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
  • Loading branch information
j10a1n15 committed Oct 11, 2024
2 parents f0566e6 + 92291e6 commit 20f93da
Show file tree
Hide file tree
Showing 39 changed files with 396 additions and 202 deletions.
85 changes: 9 additions & 76 deletions src/main/java/at/hannibal2/skyhanni/api/event/EventHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,77 +8,25 @@ import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.LorenzUtils.inAnyIsland
import at.hannibal2.skyhanni.utils.StringUtils
import at.hannibal2.skyhanni.utils.chat.Text
import java.lang.invoke.LambdaMetafactory
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.util.function.Consumer

class EventHandler<T : SkyHanniEvent> private constructor(val name: String, private val isGeneric: Boolean) {

private val listeners: MutableList<Listener> = mutableListOf()

private var isFrozen = false
private var canReceiveCancelled = false
class EventHandler<T : SkyHanniEvent> private constructor(
val name: String,
private val listeners: List<EventListeners.Listener>,
private val canReceiveCancelled: Boolean,
) {

var invokeCount: Long = 0L
private set

constructor(event: Class<T>) : this(
constructor(event: Class<T>, listeners: List<EventListeners.Listener>) : this(
(event.name.split(".").lastOrNull() ?: event.name).replace("$", "."),
GenericSkyHanniEvent::class.java.isAssignableFrom(event)
listeners.sortedBy { it.options.priority }.toList(),
listeners.any { it.options.receiveCancelled }
)

fun addListener(method: Method, instance: Any, options: HandleEvent) {
if (isFrozen) throw IllegalStateException("Cannot add listener to frozen event handler")
val generic: Class<*>? = if (isGeneric) {
method.genericParameterTypes
.firstNotNullOfOrNull { it as? ParameterizedType }
?.let { it.actualTypeArguments.firstOrNull() as? Class<*> }
?: throw IllegalArgumentException("Generic event handler must have a generic type")
} else {
null
}
val name = "${method.declaringClass.name}.${method.name}${
method.parameterTypes.joinTo(
StringBuilder(),
prefix = "(",
postfix = ")",
separator = ", ",
transform = Class<*>::getTypeName
)
}"
listeners.add(Listener(name, createEventConsumer(name, instance, method), options, generic))
}

@Suppress("UNCHECKED_CAST")
private fun createEventConsumer(name: String, instance: Any, method: Method): Consumer<Any> {
try {
val handle = MethodHandles.lookup().unreflect(method)
return LambdaMetafactory.metafactory(
MethodHandles.lookup(),
"accept",
MethodType.methodType(Consumer::class.java, instance::class.java),
MethodType.methodType(Nothing::class.javaPrimitiveType, Object::class.java),
handle,
MethodType.methodType(Nothing::class.javaPrimitiveType, method.parameterTypes[0])
).target.bindTo(instance).invokeExact() as Consumer<Any>
} catch (e: Throwable) {
throw IllegalArgumentException("Method $name is not a valid consumer", e)
}
}

fun freeze() {
isFrozen = true
listeners.sortBy { it.options.priority }
canReceiveCancelled = listeners.any { it.options.receiveCancelled }
}

fun post(event: T, onError: ((Throwable) -> Unit)? = null): Boolean {
invokeCount++
if (this.listeners.isEmpty()) return false
if (!isFrozen) error("Cannot invoke event on unfrozen event handler")

if (SkyHanniEvents.isDisabledHandler(name)) return false

Expand Down Expand Up @@ -112,7 +60,7 @@ class EventHandler<T : SkyHanniEvent> private constructor(val name: String, priv
return event.isCancelled
}

private fun shouldInvoke(event: SkyHanniEvent, listener: Listener): Boolean {
private fun shouldInvoke(event: SkyHanniEvent, listener: EventListeners.Listener): Boolean {
if (SkyHanniEvents.isDisabledInvoker(listener.name)) return false
if (listener.options.onlyOnSkyblock && !LorenzUtils.inSkyBlock) return false
if (IslandType.ANY !in listener.onlyOnIslandTypes && !inAnyIsland(listener.onlyOnIslandTypes)) return false
Expand All @@ -126,19 +74,4 @@ class EventHandler<T : SkyHanniEvent> private constructor(val name: String, priv
}
return true
}

private class Listener(
val name: String,
val invoker: Consumer<Any>,
val options: HandleEvent,
val generic: Class<*>?,
) {
val onlyOnIslandTypes: Set<IslandType> = getIslands(options)

companion object {
private fun getIslands(options: HandleEvent): Set<IslandType> =
if (options.onlyOnIslands.isEmpty()) setOf(options.onlyOnIsland)
else options.onlyOnIslands.toSet()
}
}
}
78 changes: 78 additions & 0 deletions src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package at.hannibal2.skyhanni.api.event

import at.hannibal2.skyhanni.data.IslandType
import java.lang.invoke.LambdaMetafactory
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.util.function.Consumer

class EventListeners private constructor(val name: String, private val isGeneric: Boolean) {

private val listeners: MutableList<Listener> = mutableListOf()

constructor(event: Class<*>) : this(
(event.name.split(".").lastOrNull() ?: event.name).replace("$", "."),
GenericSkyHanniEvent::class.java.isAssignableFrom(event)
)

fun addListener(method: Method, instance: Any, options: HandleEvent) {
val generic: Class<*>? = if (isGeneric) {
method.genericParameterTypes
.firstNotNullOfOrNull { it as? ParameterizedType }
?.let { it.actualTypeArguments.firstOrNull() as? Class<*> }
?: throw IllegalArgumentException("Generic event handler must have a generic type")
} else {
null
}
val name = "${method.declaringClass.name}.${method.name}${
method.parameterTypes.joinTo(
StringBuilder(),
prefix = "(",
postfix = ")",
separator = ", ",
transform = Class<*>::getTypeName
)
}"
listeners.add(Listener(name, createEventConsumer(name, instance, method), options, generic))
}

/**
* Creates a consumer using LambdaMetafactory, this is the most efficient way to reflectively call
* a method from within code.
*/
@Suppress("UNCHECKED_CAST")
private fun createEventConsumer(name: String, instance: Any, method: Method): Consumer<Any> {
try {
val handle = MethodHandles.lookup().unreflect(method)
return LambdaMetafactory.metafactory(
MethodHandles.lookup(),
"accept",
MethodType.methodType(Consumer::class.java, instance::class.java),
MethodType.methodType(Nothing::class.javaPrimitiveType, Object::class.java),
handle,
MethodType.methodType(Nothing::class.javaPrimitiveType, method.parameterTypes[0])
).target.bindTo(instance).invokeExact() as Consumer<Any>
} catch (e: Throwable) {
throw IllegalArgumentException("Method $name is not a valid consumer", e)
}
}

fun getListeners(): List<Listener> = listeners

class Listener(
val name: String,
val invoker: Consumer<Any>,
val options: HandleEvent,
val generic: Class<*>?,
) {
val onlyOnIslandTypes: Set<IslandType> = getIslands(options)

companion object {
private fun getIslands(options: HandleEvent): Set<IslandType> =
if (options.onlyOnIslands.isEmpty()) setOf(options.onlyOnIsland)
else options.onlyOnIslands.toSet()
}
}
}
30 changes: 26 additions & 4 deletions src/main/java/at/hannibal2/skyhanni/api/event/SkyHanniEvents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import java.lang.reflect.Method
@SkyHanniModule
object SkyHanniEvents {

private val listeners: MutableMap<Class<*>, EventListeners> = mutableMapOf()
private val handlers: MutableMap<Class<*>, EventHandler<*>> = mutableMapOf()
private var disabledHandlers = emptySet<String>()
private var disabledHandlerInvokers = emptySet<String>()
Expand All @@ -21,12 +22,14 @@ object SkyHanniEvents {
registerMethod(it, instance)
}
}
handlers.values.forEach { it.freeze() }
}

@Suppress("UNCHECKED_CAST")
fun <T : SkyHanniEvent> getEventHandler(event: Class<T>): EventHandler<T> = handlers.getOrPut(event) {
EventHandler(event)
EventHandler(
event,
getEventClasses(event).mapNotNull { listeners[it] }.flatMap(EventListeners::getListeners)
)
} as EventHandler<T>

fun isDisabledHandler(handler: String): Boolean = handler in disabledHandlers
Expand All @@ -38,8 +41,8 @@ object SkyHanniEvents {
val options = method.getAnnotation(HandleEvent::class.java) ?: return
val event = method.parameterTypes[0]
if (!SkyHanniEvent::class.java.isAssignableFrom(event)) return
val handler = getEventHandler(event as Class<SkyHanniEvent>)
handler.addListener(method, instance, options)
listeners.getOrPut(event as Class<SkyHanniEvent>) { EventListeners(event) }
.addListener(method, instance, options)
}

@SubscribeEvent
Expand All @@ -61,4 +64,23 @@ object SkyHanniEvents {
}
}
}

/**
* Returns a list of all super classes and the class itself up to [SkyHanniEvent].
*/
private fun getEventClasses(clazz: Class<*>): List<Class<*>> {
val classes = mutableListOf<Class<*>>()
classes.add(clazz)

var current = clazz
while (current.superclass != null) {
val superClass = current.superclass
if (superClass == SkyHanniEvent::class.java) break
if (superClass == GenericSkyHanniEvent::class.java) break
if (superClass == CancellableSkyHanniEvent::class.java) break
classes.add(superClass)
current = superClass
}
return classes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import at.hannibal2.skyhanni.config.FeatureToggle;
import at.hannibal2.skyhanni.features.chat.translation.TranslatableLanguage;
import at.hannibal2.skyhanni.utils.OSUtils;
import com.google.gson.annotations.Expose;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorButton;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDropdown;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorText;
import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
Expand All @@ -14,24 +16,34 @@ public class TranslatorConfig {
@Expose
@ConfigOption(
name = "Translate On Click",
desc = "Click on a message to translate it to English.\n" +
desc = "Click on a message to translate it to your language.\n" +
"Use §e/shcopytranslation§7 to translate from English.\n" +
"§cTranslation is not guaranteed to be 100% accurate."
)
@ConfigEditorBoolean
@FeatureToggle
public boolean translateOnClick = false;

@ConfigOption(name = "Language Name", desc = "The name of the language selected below. Note that languages marked as unknown might still be supported.")
@ConfigOption(name = "Your Language", desc = "The language that messages should be translated to.")
@Expose
@ConfigEditorDropdown
public Property<TranslatableLanguage> languageName = Property.of(TranslatableLanguage.ENGLISH);

@Expose
@ConfigOption(
name = "Language Code",
desc = "Enter a language code here to translate on chat click into another language. " +
"E.g. `es` for spanish or 'de' for german. Empty for english.")
desc = "If your language doesn't show in the dropdown, enter your language code here. " +
"E.g. 'es' for Spanish or 'de' for German. Empty will use English."
)
@ConfigEditorText
public Property<String> languageCode = Property.of("en");

@ConfigOption(
name = "List of Language Codes",
desc = "A list of Google Translate's suppored language codes."
)
@ConfigEditorButton(buttonText = "Open")
public Runnable langCodesURL = () -> OSUtils.openBrowser(
"https://cloud.google.com/translate/docs/languages#try-it-for-yourself"
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package at.hannibal2.skyhanni.config.features.mining;

import at.hannibal2.skyhanni.config.FeatureToggle;
import com.google.gson.annotations.Expose;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider;
import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;

public class CrystalHighlighterConfig {

@Expose
@ConfigOption(
name = "Highlight Crystal Nucleus barriers",
desc = "Draw visible bounding boxes around the Crystal Nucleus crystal barrier blocks."
)
@ConfigEditorBoolean
@FeatureToggle
public boolean enabled = true;

@Expose
@ConfigOption(
name = "Highlight Opacity",
desc = "Set the opacity of the highlighted boxes.\n§70§8: §7Transparent\n§7100§8: §7Solid"
)
@ConfigEditorSlider(minValue = 0, maxValue = 100, minStep = 1)
public int opacity = 60;

@Expose
@ConfigOption(
name = "Only Show During Hoppity's",
desc = "Only show the highlighted boxes during Hoppity's Hunt."
)
@ConfigEditorBoolean
public boolean onlyDuringHoppity = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public class MiningConfig {
@Accordion
public MineshaftPityDisplayConfig mineshaftPityDisplay = new MineshaftPityDisplayConfig();

@Expose
@ConfigOption(name = "Crystal Nucleus Crystal Highlights", desc = "")
@Accordion
public CrystalHighlighterConfig crystalHighlighter = new CrystalHighlighterConfig();

@Expose
@ConfigOption(name = "Highlight Commission Mobs", desc = "Highlight mobs that are part of active commissions.")
@ConfigEditorBoolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,6 @@ public String toString() {

@Expose
@ConfigLink(owner = EstimatedItemValueConfig.class, field = "enabled")
// TODO rename "position"
public Position itemPriceDataPos = new Position(140, 90, false, true);
}
14 changes: 7 additions & 7 deletions src/main/java/at/hannibal2/skyhanni/data/ItemClickData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ object ItemClickData {
packet is C08PacketPlayerBlockPlacement -> {
if (packet.placedBlockDirection != 255) {
val position = packet.position.toLorenzVec()
BlockClickEvent(ClickType.RIGHT_CLICK, position, packet.stack).postAndCatch()
BlockClickEvent(ClickType.RIGHT_CLICK, position, packet.stack).post()
} else {
ItemClickEvent(InventoryUtils.getItemInHand(), ClickType.RIGHT_CLICK).postAndCatch()
ItemClickEvent(InventoryUtils.getItemInHand(), ClickType.RIGHT_CLICK).post()
}
}

packet is C07PacketPlayerDigging && packet.status == C07PacketPlayerDigging.Action.START_DESTROY_BLOCK -> {
val position = packet.position.toLorenzVec()
val blockClickCancelled =
BlockClickEvent(ClickType.LEFT_CLICK, position, InventoryUtils.getItemInHand()).postAndCatch()
BlockClickEvent(ClickType.LEFT_CLICK, position, InventoryUtils.getItemInHand()).post()
ItemClickEvent(InventoryUtils.getItemInHand(), ClickType.LEFT_CLICK).also {
it.isCanceled = blockClickCancelled
}.postAndCatch()
if (blockClickCancelled) it.cancel()
}.post()
}

packet is C0APacketAnimation -> {
ItemClickEvent(InventoryUtils.getItemInHand(), ClickType.LEFT_CLICK).postAndCatch()
ItemClickEvent(InventoryUtils.getItemInHand(), ClickType.LEFT_CLICK).post()
}

packet is C02PacketUseEntity -> {
Expand All @@ -51,7 +51,7 @@ object ItemClickData {
else -> return
}
val clickedEntity = packet.getEntityFromWorld(Minecraft.getMinecraft().theWorld) ?: return
EntityClickEvent(clickType, clickedEntity, InventoryUtils.getItemInHand()).postAndCatch()
EntityClickEvent(clickType, clickedEntity, InventoryUtils.getItemInHand()).post()
}

else -> {
Expand Down
Loading

0 comments on commit 20f93da

Please sign in to comment.