diff --git a/build.gradle.kts b/build.gradle.kts index d75705a..65447bc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -116,6 +116,7 @@ dependencies { } implementation("net.hypixel:mod-api:1.0.1") + modImplementation("net.hypixel:mod-api-forge:1.0.1.2") implementation("org.antlr:ST4:4.3.4") shade("com.squareup.okhttp3:okhttp:4.12.0") diff --git a/gradle.properties b/gradle.properties index d77f222..0c34979 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ mod_name=EnchAddons # Sets the id of your mod that mod loaders use to recognize it. mod_id=enchaddons # Sets the version of your mod. Make sure to update this when you make changes according to semver. -mod_version=0.3.0 +mod_version=0.4.0 # Sets the name of the jar file that you put in your 'mods' folder. mod_archives_name=EnchAddons diff --git a/src/main/java/net/skymoe/enchaddons/impl/mixin/EntityLivingBaseMixin.java b/src/main/java/net/skymoe/enchaddons/impl/mixin/EntityLivingBaseMixin.java new file mode 100644 index 0000000..61b78f6 --- /dev/null +++ b/src/main/java/net/skymoe/enchaddons/impl/mixin/EntityLivingBaseMixin.java @@ -0,0 +1,17 @@ +package net.skymoe.enchaddons.impl.mixin; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.DamageSource; +import net.skymoe.enchaddons.impl.mixincallback.EntityLivingBaseMixinCallbackKt; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(EntityLivingBase.class) +public class EntityLivingBaseMixin { + @Inject(method = "onDeath", at = @At("HEAD")) + private void onDeathPre(DamageSource cause, CallbackInfo ci) { + EntityLivingBaseMixinCallbackKt.onLivingEntityDeathPre((EntityLivingBase) (Object) this, cause); + } +} diff --git a/src/main/java/net/skymoe/enchaddons/impl/mixin/ForgeHooksClientMixin.java b/src/main/java/net/skymoe/enchaddons/impl/mixin/ForgeHooksClientMixin.java new file mode 100644 index 0000000..cddc30d --- /dev/null +++ b/src/main/java/net/skymoe/enchaddons/impl/mixin/ForgeHooksClientMixin.java @@ -0,0 +1,17 @@ +package net.skymoe.enchaddons.impl.mixin; + +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraftforge.client.ForgeHooksClient; +import net.skymoe.enchaddons.impl.mixincallback.ForgeHooksClientMixinCallback; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ForgeHooksClient.class) +public class ForgeHooksClientMixin { + @Inject(method = "dispatchRenderLast", at = @At("HEAD"), remap = false) + private static void onRenderWorldLast(RenderGlobal context, float partialTicks, CallbackInfo ci) { + ForgeHooksClientMixinCallback.INSTANCE.onRenderWorldLast(context, partialTicks); + } +} diff --git a/src/main/java/net/skymoe/enchaddons/impl/mixin/MinecraftMixin.java b/src/main/java/net/skymoe/enchaddons/impl/mixin/MinecraftMixin.java index 854855d..c6a7c23 100644 --- a/src/main/java/net/skymoe/enchaddons/impl/mixin/MinecraftMixin.java +++ b/src/main/java/net/skymoe/enchaddons/impl/mixin/MinecraftMixin.java @@ -3,17 +3,12 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.settings.GameSettings; -import net.skymoe.enchaddons.impl.EnchAddonsImpl; -import net.skymoe.enchaddons.impl.EnchAddonsImplKt; -import net.skymoe.enchaddons.impl.config.EnchAddonsConfig; -import net.skymoe.enchaddons.impl.config.EnchAddonsConfigKt; import net.skymoe.enchaddons.impl.mixincallback.MinecraftMixinCallbackKt; import org.spongepowered.asm.lib.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; 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; @Mixin(Minecraft.class) @@ -38,7 +33,7 @@ private void onUnloadWorldPre(CallbackInfo ci) { } @Inject(method = "runTick", at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;hideGUI:Z", opcode = Opcodes.PUTFIELD, shift = At.Shift.AFTER)) - private void runTickRedirect(CallbackInfo ci) { + private void onHideGUIPost(CallbackInfo ci) { MinecraftMixinCallbackKt.onHideGUIKeyDetect(gameSettings); } } diff --git a/src/main/java/net/skymoe/enchaddons/impl/mixin/NetHandlerPlayClientMixin.java b/src/main/java/net/skymoe/enchaddons/impl/mixin/NetHandlerPlayClientMixin.java index 9882aa3..cdda9ed 100644 --- a/src/main/java/net/skymoe/enchaddons/impl/mixin/NetHandlerPlayClientMixin.java +++ b/src/main/java/net/skymoe/enchaddons/impl/mixin/NetHandlerPlayClientMixin.java @@ -2,8 +2,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.network.NetHandlerPlayClient; -import net.minecraft.network.play.server.S02PacketChat; -import net.minecraft.network.play.server.S32PacketConfirmTransaction; +import net.minecraft.network.play.server.*; import net.skymoe.enchaddons.impl.mixincallback.NetHandlerPlayClientCallback; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -26,4 +25,22 @@ public void processPacket(S32PacketConfirmTransaction packetIn, CallbackInfo ci) // if (!gameController.isCallingFromMinecraftThread()) return; NetHandlerPlayClientCallback.INSTANCE.onS32PacketConfirmTransactionPre(packetIn); } + + @Inject(method = "handlePlayerListItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketThreadUtil;checkThreadAndEnqueue(Lnet/minecraft/network/Packet;Lnet/minecraft/network/INetHandler;Lnet/minecraft/util/IThreadListener;)V", shift = At.Shift.AFTER)) + public void processPacket(S38PacketPlayerListItem packetIn, CallbackInfo ci) { +// if (!gameController.isCallingFromMinecraftThread()) return; + NetHandlerPlayClientCallback.INSTANCE.onS38PacketPlayerListItemPre(packetIn); + } + + @Inject(method = "handleTeams", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketThreadUtil;checkThreadAndEnqueue(Lnet/minecraft/network/Packet;Lnet/minecraft/network/INetHandler;Lnet/minecraft/util/IThreadListener;)V", shift = At.Shift.AFTER)) + public void processPacket(S3EPacketTeams packetIn, CallbackInfo ci) { +// if (!gameController.isCallingFromMinecraftThread()) return; + NetHandlerPlayClientCallback.INSTANCE.onS3EPacketTeamsPre(packetIn); + } + + @Inject(method = "handleMaps", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketThreadUtil;checkThreadAndEnqueue(Lnet/minecraft/network/Packet;Lnet/minecraft/network/INetHandler;Lnet/minecraft/util/IThreadListener;)V", shift = At.Shift.AFTER)) + public void processPacket(S34PacketMaps packetIn, CallbackInfo ci) { +// if (!gameController.isCallingFromMinecraftThread()) return; + NetHandlerPlayClientCallback.INSTANCE.onS34PacketMapsPre(packetIn); + } } diff --git a/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/ChatEvent.kt b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/ChatEvent.kt index 5408438..0870ea7 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/ChatEvent.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/ChatEvent.kt @@ -1,15 +1,18 @@ package net.skymoe.enchaddons.event.minecraft +import net.minecraft.util.IChatComponent import net.skymoe.enchaddons.event.Event sealed interface ChatEvent : Event { - sealed interface Normal : ChatEvent { - val message: String - val messageRaw: String + val message: String + val messageRaw: String + val component: IChatComponent + sealed interface Normal : ChatEvent { data class Pre( override val message: String, override val messageRaw: String, + override val component: IChatComponent, ) : Normal } } diff --git a/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/LivingEntityEvent.kt b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/LivingEntityEvent.kt new file mode 100644 index 0000000..6ac3762 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/LivingEntityEvent.kt @@ -0,0 +1,14 @@ +package net.skymoe.enchaddons.event.minecraft + +import net.minecraft.entity.EntityLivingBase +import net.minecraft.util.DamageSource +import net.skymoe.enchaddons.event.Event + +sealed interface LivingEntityEvent : Event { + val entity: EntityLivingBase + + data class Death( + override val entity: EntityLivingBase, + val cause: DamageSource, + ) : LivingEntityEvent +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/MapEvent.kt b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/MapEvent.kt new file mode 100644 index 0000000..44e6b22 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/MapEvent.kt @@ -0,0 +1,10 @@ +package net.skymoe.enchaddons.event.minecraft + +import net.minecraft.network.play.server.S34PacketMaps +import net.skymoe.enchaddons.event.Event + +interface MapEvent : Event { + data class Pre( + val packet: S34PacketMaps, + ) : MapEvent +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/RenderEvent.kt b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/RenderEvent.kt index e3352ab..e333795 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/RenderEvent.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/RenderEvent.kt @@ -2,6 +2,7 @@ package net.skymoe.enchaddons.event.minecraft import net.minecraft.block.state.IBlockState import net.minecraft.client.gui.GuiScreen +import net.minecraft.client.renderer.RenderGlobal import net.minecraft.tileentity.TileEntity import net.minecraft.world.IBlockAccess import net.skymoe.enchaddons.event.Event @@ -9,6 +10,13 @@ import net.skymoe.enchaddons.util.asBlockPos import net.skymoe.enchaddons.util.math.Vec3I sealed interface RenderEvent : Event { + sealed interface World : RenderEvent { + data class Last( + val context: RenderGlobal, + val partialTicks: Float, + ) : World + } + sealed interface Render : RenderEvent { data object Pre : Render } @@ -43,4 +51,4 @@ sealed interface RenderEvent : Event { var mutableTileEntity: TileEntity? = tileEntity, ) : Block } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/TabListEvent.kt b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/TabListEvent.kt new file mode 100644 index 0000000..994a7de --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/TabListEvent.kt @@ -0,0 +1,12 @@ +package net.skymoe.enchaddons.event.minecraft + +import net.minecraft.network.play.server.S38PacketPlayerListItem +import net.skymoe.enchaddons.event.Event + +sealed interface TabListEvent : Event { + val packet: S38PacketPlayerListItem + + data class Pre( + override val packet: S38PacketPlayerListItem, + ) : TabListEvent +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/TeamEvent.kt b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/TeamEvent.kt new file mode 100644 index 0000000..8506ce7 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/event/minecraft/TeamEvent.kt @@ -0,0 +1,28 @@ +package net.skymoe.enchaddons.event.minecraft + +import net.minecraft.network.play.server.S3EPacketTeams +import net.skymoe.enchaddons.event.Event + +sealed interface TeamEvent : Event { + sealed interface Pre : TeamEvent { + data class Create( + val packet: S3EPacketTeams, + ) : Pre + + data class Remove( + val packet: S3EPacketTeams, + ) : Pre + + data class Update( + val packet: S3EPacketTeams, + ) : Pre + + data class AddPlayer( + val packet: S3EPacketTeams, + ) : Pre + + data class RemovePlayer( + val packet: S3EPacketTeams, + ) : Pre + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMap.kt b/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMap.kt new file mode 100644 index 0000000..e4faf0c --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMap.kt @@ -0,0 +1,169 @@ +package net.skymoe.enchaddons.feature.awesomemap + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import net.skymoe.enchaddons.api.setDefault +import net.skymoe.enchaddons.event.RegistryEventDispatcher +import net.skymoe.enchaddons.event.minecraft.* +import net.skymoe.enchaddons.event.register +import net.skymoe.enchaddons.feature.FeatureBase +import net.skymoe.enchaddons.feature.config.invoke +import net.skymoe.enchaddons.feature.ensureEnabled +import net.skymoe.enchaddons.feature.ensureSkyBlockMode +import net.skymoe.enchaddons.feature.featureInfo +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.* +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.RenderUtils +import net.skymoe.enchaddons.util.scope.longrun +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +val AWESOME_MAP_INFO = featureInfo("awesome_map", "Awesome Map") + +object AwesomeMap : FeatureBase(AWESOME_MAP_INFO) { + val scope = CoroutineScope(EmptyCoroutineContext) + private var runningTick = atomic(false) + + override fun registerEvents(dispatcher: RegistryEventDispatcher) { + dispatcher.run { + register { + Dungeon +// GuiRenderer + Location + DungeonScan + RenderUtils +// MapRender +// MapRenderList + MapUpdate + MimicDetector + PlayerTracker + RunInformation + ScanUtils + ScoreCalculation + WitherDoorESP + } + + register { + longrun { + ensureEnabled() + ensureSkyBlockMode("dungeon") + + scope.launch { + if (runningTick.getAndSet(true)) return@launch + try { + Dungeon.onTick() +// GuiRenderer.onTick() + Location.onTick() + } finally { + runningTick.value = false + } + } + } + } + + register { event -> + longrun { + ensureEnabled() + ensureSkyBlockMode("dungeon") + + Dungeon.onChat(event) + Location.onChat(event) + RunInformation.onChat(event) + } + } + +// register { event -> +// longrun { +// ensureEnabled() +// ensureSkyBlockMode("dungeon") +// +// glStateScope { +// NanoVGHelper.INSTANCE.setupAndDraw { vg -> +// GuiRenderer.onOverlay(vg) +// } +// } +// } +// } + + register { event -> + longrun { + ensureEnabled() + ensureSkyBlockMode("dungeon") + + Location.onWorldUnload(event) + Dungeon.onWorldLoad(event) + } + } + + register { event -> + longrun { + ensureEnabled() + ensureSkyBlockMode("dungeon") + + RunInformation.onTabList(event) + } + } + + register { event -> + longrun { + ensureEnabled() + ensureSkyBlockMode("dungeon") + + RunInformation.onEntityDeath(event) + } + } + + register { event -> + longrun { + ensureEnabled() + ensureSkyBlockMode("dungeon") + + RunInformation.onScoreboard(event) + } + } + + register { event -> + longrun { + ensureEnabled() + ensureSkyBlockMode("dungeon") + + MapUtils.onUpdateMapData(event) + } + } + + register { event -> + longrun { + ensureEnabled() + ensureSkyBlockMode("dungeon") + + WitherDoorESP.onRender(event) + } + } + + register { event -> + when (event) { + is AwesomeMapEvent.Score.Reach270 -> + config.notification.onScore270(logger) { + setDefault() + this["time"] = event.timeElapsed.toDuration(DurationUnit.SECONDS) + } + is AwesomeMapEvent.Score.Reach300 -> + config.notification.onScore300(logger) { + setDefault() + this["time"] = event.timeElapsed.toDuration(DurationUnit.SECONDS) + } + } + } + + register { event -> + config.notification.onMimicKilled(logger) { + setDefault() + this["time"] = event.timeElapsed.toDuration(DurationUnit.SECONDS) + } + } + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMapConfig.kt b/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMapConfig.kt new file mode 100644 index 0000000..3d4bccd --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMapConfig.kt @@ -0,0 +1,75 @@ +package net.skymoe.enchaddons.feature.awesomemap + +import net.skymoe.enchaddons.feature.config.FeatureConfig +import net.skymoe.enchaddons.feature.config.NotificationOption + +interface AwesomeMapConfig : FeatureConfig { + val autoScan: Boolean + val scanChatInfo: Boolean + + val legitMode: Boolean + + val mapRotateMode: Int + val mapCenter: Boolean + val mapHideInBoss: Boolean + val playerHeads: Int + val mapVanillaMarker: Boolean + val mapClip: Boolean + + val textScale: Float + val playerHeadScale: Float + val playerNameScale: Float + + val mapDarkenUndiscovered: Boolean + val mapDarkenPercent: Float + val mapGrayUndiscovered: Boolean + + val mapRoomNames: Int + val mapRoomSecrets: Int + val mapCenterRoomName: Boolean + val mapColorText: Boolean + val mapCheckmark: Boolean + val mapCenterCheckmark: Boolean + + val scoreElementEnabled: Boolean + val scoreAssumeSpirit: Boolean + val scoreMinimizedName: Boolean + val scoreHideInBoss: Boolean + + val scoreX: Int + val scoreY: Int + val scoreScale: Float + + val scoreTotalScore: Int + val scoreSecrets: Int + val scoreCrypts: Boolean + val scoreMimic: Boolean + val scoreDeaths: Boolean + val scorePuzzles: Int + + val mapShowRunInformation: Boolean + val runInformationScore: Boolean + val runInformationSecrets: Int + val runInformationCrypts: Boolean + val runInformationMimic: Boolean + val runInformationDeaths: Boolean + + val teamInfo: Boolean + + val witherDoorESP: Int + val witherDoorOutlineWidth: Float + val witherDoorOutline: Float + val witherDoorFill: Float + val forceSkyblock: Boolean + val paulBonus: Boolean + val renderBeta: Boolean + val customPrefix: String + + val notification: Notification +} + +interface Notification { + val onScore270: NotificationOption + val onScore300: NotificationOption + val onMimicKilled: NotificationOption +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMapEvent.kt b/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMapEvent.kt new file mode 100644 index 0000000..dc9c262 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/feature/awesomemap/AwesomeMapEvent.kt @@ -0,0 +1,21 @@ +package net.skymoe.enchaddons.feature.awesomemap + +import net.skymoe.enchaddons.event.Event + +sealed interface AwesomeMapEvent : Event { + val timeElapsed: Int + + sealed interface Score : AwesomeMapEvent { + data class Reach270( + override val timeElapsed: Int, + ) : Score + + data class Reach300( + override val timeElapsed: Int, + ) : Score + } + + data class MimicKilled( + override val timeElapsed: Int, + ) : AwesomeMapEvent +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/feature/dungeon/fastdraft/FastDraft.kt b/src/main/kotlin/net/skymoe/enchaddons/feature/dungeon/fastdraft/FastDraft.kt index 14cfcd3..1c88231 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/feature/dungeon/fastdraft/FastDraft.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/feature/dungeon/fastdraft/FastDraft.kt @@ -26,8 +26,6 @@ object FastDraft : FeatureBase(FAST_DRAFT_INFO) { longrun { ensureEnabled() - println(event.messageRaw) - puzzleFailPatterns.forEach { pattern -> pattern.matchEntire(event.messageRaw)?.let { matchResult -> if (config.chatHintEnabled) { diff --git a/src/main/kotlin/net/skymoe/enchaddons/feature/invincibilitytimer/InvincibilityTimer.kt b/src/main/kotlin/net/skymoe/enchaddons/feature/invincibilitytimer/InvincibilityTimer.kt index ba0eec0..27d7bda 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/feature/invincibilitytimer/InvincibilityTimer.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/feature/invincibilitytimer/InvincibilityTimer.kt @@ -10,9 +10,6 @@ import net.skymoe.enchaddons.event.register import net.skymoe.enchaddons.feature.FeatureBase import net.skymoe.enchaddons.feature.config.invoke import net.skymoe.enchaddons.feature.featureInfo -import net.skymoe.enchaddons.getLogger - -val logger = getLogger("Invincibility Timer") val INVINCIBILITY_TIMER_INFO = featureInfo("invincibility_timer", "Invincibility Timer") diff --git a/src/main/kotlin/net/skymoe/enchaddons/feature/teamspeakconnect/TeamSpeakConnect.kt b/src/main/kotlin/net/skymoe/enchaddons/feature/teamspeakconnect/TeamSpeakConnect.kt index 8c973bc..5e503c3 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/feature/teamspeakconnect/TeamSpeakConnect.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/feature/teamspeakconnect/TeamSpeakConnect.kt @@ -4,6 +4,7 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.SupervisorJob import net.skymoe.enchaddons.event.RegistryEventDispatcher import net.skymoe.enchaddons.event.minecraft.MinecraftEvent @@ -11,7 +12,6 @@ import net.skymoe.enchaddons.event.register import net.skymoe.enchaddons.feature.FeatureBase import net.skymoe.enchaddons.feature.featureInfo import net.skymoe.enchaddons.feature.teamspeakconnect.wrapper.* -import net.skymoe.enchaddons.getLogger import net.skymoe.enchaddons.impl.MOD_ID import net.skymoe.enchaddons.impl.MOD_NAME import net.skymoe.enchaddons.impl.MOD_VERSION @@ -20,8 +20,6 @@ import net.skymoe.enchaddons.util.tickCounter val TEAMSPEAK_CONNECT_INFO = featureInfo("teamspeak_connect", "TeamSpeak Connect") -val logger = getLogger("TeamSpeak Connect") - object TeamSpeakConnect : FeatureBase(TEAMSPEAK_CONNECT_INFO) { private var tsRemote: TeamSpeakRemoteAppWrapper? = null private var job: CompletableJob = SupervisorJob() diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/EnchAddonsImpl.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/EnchAddonsImpl.kt index f5c1a44..c8d49a4 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/EnchAddonsImpl.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/EnchAddonsImpl.kt @@ -3,6 +3,7 @@ package net.skymoe.enchaddons.impl import net.skymoe.enchaddons.EnchAddons import net.skymoe.enchaddons.event.minecraft.MinecraftEvent import net.skymoe.enchaddons.event.register +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap import net.skymoe.enchaddons.feature.config.FeatureConfig import net.skymoe.enchaddons.feature.dungeon.fastdraft.FastDraft import net.skymoe.enchaddons.feature.dynamickeybind.DynamicKeyBinding @@ -14,12 +15,19 @@ import net.skymoe.enchaddons.impl.api.APIImpl import net.skymoe.enchaddons.impl.cache.ResourceCacheImpl import net.skymoe.enchaddons.impl.config.EnchAddonsConfig import net.skymoe.enchaddons.impl.event.EventDispatcherImpl +import net.skymoe.enchaddons.impl.feature.awesomemap.AwesomeMapHUD import net.skymoe.enchaddons.impl.feature.dynamickeybinding.DynamicKeyBindingHUD import net.skymoe.enchaddons.impl.feature.dynamicspot.DynamicSpotHUD import net.skymoe.enchaddons.impl.feature.invincibilitytimer.InvincibilityTimerHUD import net.skymoe.enchaddons.impl.feature.teamspeakconnect.TeamSpeakConnectHUD import net.skymoe.enchaddons.impl.hypixel.loadHypixelModAPI +import net.skymoe.enchaddons.impl.nanovg.GUIEvent +import net.skymoe.enchaddons.impl.nanovg.NanoVGUIContext +import net.skymoe.enchaddons.impl.nanovg.Widget +import net.skymoe.enchaddons.impl.oneconfig.fuckOneConfigFont +import net.skymoe.enchaddons.impl.oneconfig.nvg import net.skymoe.enchaddons.theEA +import net.skymoe.enchaddons.util.math.Vec2D import kotlin.reflect.KClass lateinit var theEAImpl: EnchAddonsImpl @@ -49,6 +57,8 @@ class EnchAddonsImpl : EnchAddons { override var configVersion = 0 + var fuckedOneConfig = false + override fun getConfigImpl(type: KClass) = EnchAddonsConfig.getConfigImpl(type) init { @@ -72,8 +82,32 @@ class EnchAddonsImpl : EnchAddons { FastDraft + AwesomeMap + AwesomeMapHUD + eventDispatcher.register { loadHypixelModAPI } + + eventDispatcher.register { event -> + if (!fuckedOneConfig) { + object : Widget { + override fun draw(context: NanoVGUIContext) { + context.run { + nvg.fuckOneConfigFont(vg) + } + } + + override fun alphaScale(alpha: Double): Nothing = throw IllegalStateException() + + override fun scale( + scale: Double, + origin: Vec2D, + ): Nothing = throw IllegalStateException() + }.also(event.widgets::add) + + fuckedOneConfig = true + } + } } } diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/config/feature/AwesomeMapConfigImpl.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/config/feature/AwesomeMapConfigImpl.kt new file mode 100644 index 0000000..39b3118 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/config/feature/AwesomeMapConfigImpl.kt @@ -0,0 +1,813 @@ +package net.skymoe.enchaddons.impl.config.feature + +import cc.polyfrost.oneconfig.config.annotations.* +import cc.polyfrost.oneconfig.config.annotations.Number +import cc.polyfrost.oneconfig.config.core.OneColor +import net.skymoe.enchaddons.feature.awesomemap.AWESOME_MAP_INFO +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMapConfig +import net.skymoe.enchaddons.feature.awesomemap.Notification +import net.skymoe.enchaddons.impl.config.AdvancedHUD +import net.skymoe.enchaddons.impl.config.ConfigImpl +import net.skymoe.enchaddons.impl.config.adapter.Extract +import net.skymoe.enchaddons.impl.config.gui.GUIBackground +import net.skymoe.enchaddons.impl.config.impl.NotificationOptionImpl + +class AwesomeMapConfigImpl : + ConfigImpl(AWESOME_MAP_INFO), + AwesomeMapConfig { + @Switch( + name = "Auto Scan", + description = "Automatically scans when entering dungeon. Manual scan can be done with /fmap scan.", + category = "General", + subcategory = "Scanning", + ) + override var autoScan = true + + @Switch( + name = "Chat Info", + description = "Show dungeon overview information after scanning.", + category = "General", + subcategory = "Scanning", + ) + override var scanChatInfo = true + + @Switch( + name = "Enabled", + description = "Hides unopened rooms. Still uses scanning to identify all rooms.", + category = "General", + subcategory = "Legit Mode", + ) + override var legitMode = false + + @HUD( + name = "Map", + category = "Map", + ) + var hud = AdvancedHUD() + + @Extract( + category = "Map", + ) + var background = GUIBackground() + + @Number( + name = "Room Radius", + category = "Map", + subcategory = "Room Render", + min = 0F, + max = 10F, + ) + var mapRoomRadius = 4.0f + + @Number( + name = "L-Shape Room Inner Radius", + category = "Map", + subcategory = "Room Render", + min = 0F, + max = 10F, + ) + var mapLShapeRoomInnerRadius = 2.0f + + @Number( + name = "Room Connector Width", + category = "Map", + subcategory = "Room Render", + min = 0F, + max = 16F, + ) + var mapRoomConnector = 6.0f + + @Dropdown( + name = "Rotate Mode", + description = "Rotates map to specific mode.", + category = "Map", + subcategory = "Tweaks", + options = ["Off", "Player Direction", "Entrance Room At Bottom"], + ) + override var mapRotateMode = 2 + + @Switch( + name = "Center Map", + description = "Centers the map on the player if Rotate Map is enabled.", + category = "Map", + subcategory = "Tweaks", + ) + override var mapCenter = false + + @Switch( + name = "Hide In Boss", + description = "Hides the map in boss.", + category = "Map", + subcategory = "Tweaks", + ) + override var mapHideInBoss = false + + @Dropdown( + name = "Show Player Names", + description = "Show player name under player head", + category = "Map", + subcategory = "Tweaks", + options = ["Off", "Holding Leap", "Always"], + ) + override var playerHeads = 0 + + @Switch( + name = "Vanilla Head Marker", + description = "Uses the vanilla head marker for yourself.", + category = "Map", + subcategory = "Tweaks", + ) + override var mapVanillaMarker = false + + @Switch( + name = "Clip Map", + description = "Clip map which out of the HUD region.", + category = "Map", + subcategory = "Tweaks", + ) + override var mapClip = true + + @Slider( + name = "Map Text Scale", + description = "Scale of room names and secret counts relative to map size.", + category = "Map", + subcategory = "Size", + min = 0F, + max = 2F, + ) + override var textScale = 0.75f + + @Slider( + name = "Player Heads Scale", + description = "Scale of player heads relative to map size.", + category = "Map", + subcategory = "Size", + min = 0F, + max = 2F, + ) + override var playerHeadScale = 1f + + @Slider( + name = "Player Heads Radius", + description = "Radius of player heads.", + category = "Map", + subcategory = "Size", + min = 0F, + max = 10F, + ) + var playerHeadRadius = 2.0f + + @Slider( + name = "Player Name Scale", + description = "Scale of player names relative to head size.", + category = "Map", + subcategory = "Size", + min = 0F, + max = 2F, + ) + override var playerNameScale = .8f + + @Switch( + name = "Dark Undiscovered Rooms", + description = "Darkens unentered rooms.", + category = "Rooms", + subcategory = "Render", + ) + override var mapDarkenUndiscovered = true + + @Slider( + name = "Darken Multiplier", + description = "How much to darken undiscovered rooms.", + category = "Rooms", + subcategory = "Render", + min = 0F, + max = 1F, + ) + override var mapDarkenPercent = 0.4f + + @Switch( + name = "Gray Undiscovered Rooms", + description = "Grayscale unentered rooms.", + category = "Rooms", + subcategory = "Render", + ) + override var mapGrayUndiscovered = false + + @Dropdown( + name = "Room Names", + description = "Shows names of rooms on map.", + category = "Rooms", + subcategory = "Text", + options = ["None", "Puzzles / Trap", "All"], + ) + override var mapRoomNames = 2 + + @Dropdown( + name = "Room Secrets", + description = "Shows total secrets of rooms on map.", + category = "Rooms", + subcategory = "Text", + options = ["Off", "On", "Replace Checkmark"], + ) + override var mapRoomSecrets = 0 + + @Switch( + name = "Center Room Names", + description = "Center room names.", + subcategory = "Text", + category = "Rooms", + ) + override var mapCenterRoomName = true + + @Switch( + name = "Color Text", + description = "Colors name and secret count based on room state.", + subcategory = "Text", + category = "Rooms", + ) + override var mapColorText = true + + @Switch( + name = "Room Checkmark", + description = "Adds room checkmarks based on room state.", + category = "Rooms", + subcategory = "Checkmarks", + ) + override var mapCheckmark = true + + @Switch( + name = "Center Room Checkmarks", + description = "Center room checkmarks.", + subcategory = "Checkmarks", + category = "Rooms", + ) + override var mapCenterCheckmark = true + + @Color( + name = "Blood Door", + category = "Colors", + subcategory = "Doors", + allowAlpha = true, + ) + var colorBloodDoor = OneColor(231, 0, 0) + + @Color( + name = "Entrance Door", + category = "Colors", + subcategory = "Doors", + allowAlpha = true, + ) + var colorEntranceDoor = OneColor(20, 133, 0) + + @Color( + name = "Normal Door", + category = "Colors", + subcategory = "Doors", + allowAlpha = true, + ) + var colorRoomDoor = OneColor(92, 52, 14) + + @Color( + name = "Wither Door", + category = "Colors", + subcategory = "Doors", + allowAlpha = true, + ) + var colorWitherDoor = OneColor(0, 0, 0) + + @Color( + name = "Opened Wither Door", + category = "Colors", + subcategory = "Doors", + allowAlpha = true, + ) + var colorOpenWitherDoor = OneColor(92, 52, 14) + + @Color( + name = "Unopened Door", + category = "Colors", + subcategory = "Doors", + allowAlpha = true, + ) + var colorUnopenedDoor = OneColor(65, 65, 65) + + @Color( + name = "Blood Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorBlood = OneColor(255, 0, 0) + + @Color( + name = "Entrance Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorEntrance = OneColor(20, 133, 0) + + @Color( + name = "Fairy Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorFairy = OneColor(224, 0, 255) + + @Color( + name = "Miniboss Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorMiniboss = OneColor(254, 223, 0) + + @Color( + name = "Normal Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorRoom = OneColor(107, 58, 17) + + @Color( + name = "Mimic Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorRoomMimic = OneColor(186, 66, 52) + + @Color( + name = "Puzzle Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorPuzzle = OneColor(117, 0, 133) + + @Color( + name = "Rare Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorRare = OneColor(255, 203, 89) + + @Color( + name = "Trap Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorTrap = OneColor(216, 127, 51) + + @Color( + name = "Unopened Room", + category = "Colors", + subcategory = "Rooms", + allowAlpha = true, + ) + var colorUnopened = OneColor(65, 65, 65) + + @Color( + name = "Cleared Room", + category = "Colors", + subcategory = "Text", + allowAlpha = true, + ) + var colorTextCleared = OneColor(255, 255, 255) + + @Color( + name = "Uncleared Room", + category = "Colors", + subcategory = "Text", + allowAlpha = true, + ) + var colorTextUncleared = OneColor(170, 170, 170) + + @Color( + name = "Green Room", + category = "Colors", + subcategory = "Text", + allowAlpha = true, + ) + var colorTextGreen = OneColor(85, 255, 85) + + @Color( + name = "Failed Room", + category = "Colors", + subcategory = "Text", + allowAlpha = true, + ) + var colorTextFailed = OneColor(255, 255, 255) + + @Color( + name = "Cleared Room", + category = "Colors", + subcategory = "CheckMark", + allowAlpha = true, + ) + var colorCheckMarkCleared = OneColor(255, 255, 255) + + @Color( + name = "Green Room", + category = "Colors", + subcategory = "CheckMark", + allowAlpha = true, + ) + var colorCheckMarkGreen = OneColor(85, 255, 85) + + @Color( + name = "Failed Room", + category = "Colors", + subcategory = "CheckMark", + allowAlpha = true, + ) + var colorCheckMarkFailed = OneColor(255, 255, 255) + + @Switch( + name = "Show Score", + description = "Shows separate score element.", + category = "Score", + subcategory = "Toggle", + ) + override var scoreElementEnabled = false + + @Switch( + name = "Assume Spirit", + description = "Assume everyone has a legendary spirit pet.", + category = "Score", + subcategory = "Toggle", + ) + override var scoreAssumeSpirit = true + + @Switch( + name = "Minimized Text", + description = "Shortens description for score elements.", + category = "Score", + subcategory = "Toggle", + ) + override var scoreMinimizedName = false + + @Switch( + name = "Hide in Boss", + category = "Score", + subcategory = "Toggle", + ) + override var scoreHideInBoss = false + + @Number( + name = "Score Calc X", + category = "Score", + subcategory = "Size", + min = 0F, + max = 100F, + ) + override var scoreX = 10 + + @Number( + name = "Score Calc Y", + category = "Score", + subcategory = "Size", + min = 0F, + max = 100F, + ) + override var scoreY = 10 + + @Slider( + name = "Score Calc Size", + category = "Score", + subcategory = "Size", + min = 0.1F, + max = 4F, + ) + override var scoreScale = 1f + + @Dropdown( + name = "Score", + category = "Score", + subcategory = "Elements", + options = ["Off", "On", "Separate"], + ) + override var scoreTotalScore = 2 + + @Dropdown( + name = "Secrets", + category = "Score", + subcategory = "Elements", + options = ["Off", "Total", "Total and Missing"], + ) + override var scoreSecrets = 1 + + @Switch( + name = "Crypts", + category = "Score", + subcategory = "Elements", + ) + override var scoreCrypts = false + + @Switch( + name = "Mimic", + category = "Score", + subcategory = "Elements", + ) + override var scoreMimic = false + + @Switch( + name = "Deaths", + category = "Score", + subcategory = "Elements", + ) + override var scoreDeaths = false + + @Dropdown( + name = "Puzzles", + category = "Score", + subcategory = "Elements", + options = ["Off", "Total", "Completed and Total"], + ) + override var scorePuzzles = 0 + + @Switch( + name = "Show Run Information", + description = "Shows run information under map.", + category = "Run Information", + subcategory = "Toggle", + ) + override var mapShowRunInformation = true + + @Switch( + name = "Score", + category = "Run Information", + subcategory = "Elements", + ) + override var runInformationScore = true + + @Dropdown( + name = "Secrets", + category = "Run Information", + subcategory = "Elements", + options = ["Off", "Total", "Total and Missing"], + ) + override var runInformationSecrets = 1 + + @Switch( + name = "Crypts", + category = "Run Information", + subcategory = "Elements", + ) + override var runInformationCrypts = true + + @Switch( + name = "Mimic", + category = "Run Information", + subcategory = "Elements", + ) + override var runInformationMimic = true + + @Switch( + name = "Deaths", + category = "Run Information", + subcategory = "Elements", + ) + override var runInformationDeaths = true + + @Text( + name = "Score", + category = "Placeholder", + subcategory = "Full", + ) + var textScore = "Score" + + @Text( + name = "Secrets", + category = "Placeholder", + subcategory = "Full", + ) + var textSecrets = "Secrets" + + @Text( + name = "Crypts", + category = "Placeholder", + subcategory = "Full", + ) + var textCrypts = "Crypts" + + @Text( + name = "Mimic", + category = "Placeholder", + subcategory = "Full", + ) + var textMimic = "Mimic" + + @Text( + name = "Mimic Yes", + category = "Placeholder", + subcategory = "Full", + ) + var textMimicYes = "✔" + + @Text( + name = "Mimic No", + category = "Placeholder", + subcategory = "Full", + ) + var textMimicNo = "✘" + + @Text( + name = "Death", + category = "Placeholder", + subcategory = "Full", + ) + var textDeaths = "Deaths" + + @Text( + name = "Puzzles", + category = "Placeholder", + subcategory = "Full", + ) + var textPuzzles = "Puzzles" + + @Text( + name = "Score", + category = "Placeholder", + subcategory = "Minimized", + ) + var textMinimizedScore = "" + + @Text( + name = "Secrets", + category = "Placeholder", + subcategory = "Minimized", + ) + var textMinimizedSecrets = "" + + @Text( + name = "Crypts", + category = "Placeholder", + subcategory = "Minimized", + ) + var textMinimizedCrypts = "C" + + @Text( + name = "Mimic", + category = "Placeholder", + subcategory = "Minimized", + ) + var textMinimizedMimic = "M" + + @Text( + name = "Mimic Yes", + category = "Placeholder", + subcategory = "Minimized", + ) + var textMinimizedMimicYes = "✔" + + @Text( + name = "Mimic No", + category = "Placeholder", + subcategory = "Minimized", + ) + var textMinimizedMimicNo = "✘" + + @Text( + name = "Death", + category = "Placeholder", + subcategory = "Minimized", + ) + var textMinimizedDeaths = "D" + + @Text( + name = "Puzzles", + category = "Placeholder", + subcategory = "Minimized", + ) + var textMinimizedPuzzles = "P" + + @Switch( + name = "Show Team Info", + description = "Shows team member secrets and room times at end of run. Requires a valid API key.", + category = "Other Features", + ) + override var teamInfo = false + + @Dropdown( + name = "Wither Door ESP", + description = "Boxes unopened wither doors.", + category = "Wither Door", + options = ["Off", "First", "All"], + ) + override var witherDoorESP = 0 + + @Color( + name = "No Key Color", + category = "Wither Door", + allowAlpha = true, + ) + var witherDoorNoKeyColor = OneColor(255, 0, 0) + + @Color( + name = "Has Key Color", + category = "Wither Door", + allowAlpha = true, + ) + var witherDoorKeyColor = OneColor(0, 255, 0) + + @Slider( + name = "Door Outline Width", + category = "Wither Door", + min = 0f, + max = 10f, + ) + override var witherDoorOutlineWidth = 3f + + @Slider( + name = "Door Outline Opacity", + category = "Wither Door", + min = 0F, + max = 1F, + ) + override var witherDoorOutline = 1f + + @Slider( + name = "Door Fill Opacity", + category = "Wither Door", + min = 0F, + max = 1F, + ) + override var witherDoorFill = 0.25f + + @Switch( + name = "Force Skyblock", + description = "Disables in skyblock and dungeon checks. Don't enable unless you know what you're doing.", + category = "Debug", + ) + override var forceSkyblock = false + + @Switch( + name = "Paul Score", + description = "Assumes paul perk is active to give 10 bonus score.", + category = "Debug", + ) + override var paulBonus = false + + @Switch( + name = "Beta Rendering", + category = "Debug", + ) + override var renderBeta = false + + @Text( + name = "Custom Prefix", + category = "Other Features", + ) + override var customPrefix = "" + + class NotificationImpl : Notification { + @Transient + @Header( + text = "Notification On Score 270", + size = 2, + category = "Notification", + ) + val headerOnScore270 = false + + @Extract( + category = "Notification", + ) + override var onScore270: NotificationOptionImpl = NotificationOptionImpl() + + @Transient + @Header( + text = "Notification On Score 300", + size = 2, + category = "Notification", + ) + val headerOnScore300 = false + + @Extract( + category = "Notification", + ) + override var onScore300: NotificationOptionImpl = NotificationOptionImpl() + + @Transient + @Header( + text = "Notification On Mimic Killed", + size = 2, + category = "Notification", + ) + val headerOnMimicKilled = false + + @Extract( + category = "Notification", + ) + override var onMimicKilled: NotificationOptionImpl = NotificationOptionImpl() + } + + @Extract( + category = "Notification", + ) + override var notification = NotificationImpl() +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/config/gui/GUIBackground.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/config/gui/GUIBackground.kt index 7fa6a68..9c84c90 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/config/gui/GUIBackground.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/config/gui/GUIBackground.kt @@ -7,8 +7,6 @@ import cc.polyfrost.oneconfig.config.core.OneColor import net.skymoe.enchaddons.impl.nanovg.Transformation import net.skymoe.enchaddons.impl.nanovg.Widget import net.skymoe.enchaddons.impl.nanovg.widget.backgroundWidget -import net.skymoe.enchaddons.util.Colors -import net.skymoe.enchaddons.util.alphaScale import net.skymoe.enchaddons.util.math.Vec2D import net.skymoe.enchaddons.util.math.double @@ -23,16 +21,22 @@ class GUIBackground { name = "Background Color", size = 2, ) - var backgroundColorOption = OneColor(Colors.GRAY[9].rgb alphaScale 0.95) + var backgroundColorOption = OneColor(17, 18, 19, 204) @Number( name = "Rounded Corner Radius", min = 0.0F, max = Float.MAX_VALUE, - size = 1, + size = 2, ) open var radiusOption = 6.0F + @Switch( + name = "Show Shadow", + size = 1, + ) + var shadowEnabled = true + @Number( name = "Shadow Blur", min = 0.0F, @@ -72,6 +76,7 @@ class GUIBackground { backgroundColorOption.rgb, tr size minOf(radiusOption.double, size.x / 2.0 + paddingXOption, size.y / 2.0 + paddingYOption), tr size shadowBlur.double, + shadowEnabled, ), ) } diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/config/subcategory/DungeonConfig.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/config/subcategory/DungeonConfig.kt index ff02de7..f3a88ee 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/config/subcategory/DungeonConfig.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/config/subcategory/DungeonConfig.kt @@ -5,6 +5,7 @@ import net.skymoe.enchaddons.feature.config.FeatureConfig import net.skymoe.enchaddons.feature.featureInfo import net.skymoe.enchaddons.impl.config.ConfigCategory import net.skymoe.enchaddons.impl.config.ConfigImpl +import net.skymoe.enchaddons.impl.config.feature.AwesomeMapConfigImpl import net.skymoe.enchaddons.impl.config.feature.FastDraftConfigImpl import kotlin.reflect.KClass @@ -17,6 +18,9 @@ class DungeonConfig : @SubConfig var fastDraft = FastDraftConfigImpl() + @SubConfig + var awesomeMapConfig = AwesomeMapConfigImpl() + init { initializeMembers() } diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/event/EventDispatcherImpl.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/event/EventDispatcherImpl.kt index 0e4394d..dd601cc 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/event/EventDispatcherImpl.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/event/EventDispatcherImpl.kt @@ -7,8 +7,8 @@ import net.skymoe.enchaddons.util.general.AnyComparator import net.skymoe.enchaddons.util.general.UniqueHash import net.skymoe.enchaddons.util.general.inBox import net.skymoe.enchaddons.util.prepend -import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.TreeSet +import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.read import kotlin.concurrent.write import kotlin.reflect.KClass @@ -24,21 +24,20 @@ class EventDispatcherImpl : EventDispatcher { val priority: Int, val handler: EventHandler<*>, ) : Comparable { - override fun compareTo(other: RegistryEntry): Int { - return if (handler === other.handler) { + override fun compareTo(other: RegistryEntry): Int = + if (handler === other.handler) { 0 } else { - priority.compareTo(other.priority) + priority + .compareTo(other.priority) .takeIf { it != 0 } ?: anyComparator.compare(handler, other.handler) } - } - override fun equals(other: Any?): Boolean { - return (other as? RegistryEntry)?.let { - return handler === other.handler + override fun equals(other: Any?): Boolean = + (other as? RegistryEntry)?.let { + handler === other.handler } ?: false - } override fun hashCode() = handler.hashCode() } @@ -54,7 +53,11 @@ class EventDispatcherImpl : EventDispatcher { onlyHandlerCache.clear() } - override fun register(type: KClass, priority: Int, handler: EventHandler) { + override fun register( + type: KClass, + priority: Int, + handler: EventHandler, + ) { lock.write { clearCache() unregister(type, handler) @@ -62,7 +65,11 @@ class EventDispatcherImpl : EventDispatcher { } } - override fun registerOnly(type: KClass, priority: Int, handler: EventHandler) { + override fun registerOnly( + type: KClass, + priority: Int, + handler: EventHandler, + ) { lock.write { clearCache() unregisterOnly(type, handler) @@ -70,7 +77,10 @@ class EventDispatcherImpl : EventDispatcher { } } - override fun unregister(type: KClass, handler: EventHandler) { + override fun unregister( + type: KClass, + handler: EventHandler, + ) { lock.write { clearCache() val entry = RegistryEntry(0, handler) @@ -80,7 +90,10 @@ class EventDispatcherImpl : EventDispatcher { } } - override fun unregisterOnly(type: KClass, handler: EventHandler) { + override fun unregisterOnly( + type: KClass, + handler: EventHandler, + ) { lock.write { clearCache() val entry = RegistryEntry(0, handler) @@ -136,4 +149,4 @@ class EventDispatcherImpl : EventDispatcher { .cast() } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/AwesomeMapHUD.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/AwesomeMapHUD.kt new file mode 100644 index 0000000..75669df --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/AwesomeMapHUD.kt @@ -0,0 +1,45 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap + +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.feature.ensure +import net.skymoe.enchaddons.feature.ensureEnabled +import net.skymoe.enchaddons.feature.ensureSkyBlockMode +import net.skymoe.enchaddons.impl.config.EnchAddonsConfig +import net.skymoe.enchaddons.impl.config.feature.AwesomeMapConfigImpl +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location +import net.skymoe.enchaddons.impl.hud.FeatureHUDBase +import net.skymoe.enchaddons.impl.nanovg.Transformation +import net.skymoe.enchaddons.impl.nanovg.Widget +import net.skymoe.enchaddons.impl.oneconfig.NanoVGImageCache +import net.skymoe.enchaddons.util.math.Vec2D + +object AwesomeMapHUD : FeatureHUDBase(AwesomeMap, { config.hud }) { + override val width = 128.0 + override val height get() = if (config.mapShowRunInformation) 146.0 else 128.0 + + private val cache = NanoVGImageCache() + + override fun ensureShow() { + ensureEnabled() + ensureSkyBlockMode("dungeon") + ensureHUDEnabled() + ensure { + !(config.mapHideInBoss && Location.inBoss) + } + } + + override fun reset() { + cache.clear() + } + + override fun draw( + widgets: MutableList>, + box: Vec2D, + tr: Transformation, + ) { + config.background.addTo(AwesomeMapHUD.widgets, tr, size) + widgets.add( + AwesomeMapWidget(cache, tr pos (Vec2D(0.0, 0.0)), tr size 1.0, width, height, EnchAddonsConfig.dungeonConfig.awesomeMapConfig), + ) + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/AwesomeMapWidget.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/AwesomeMapWidget.kt new file mode 100644 index 0000000..4f87b48 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/AwesomeMapWidget.kt @@ -0,0 +1,550 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap + +import net.minecraft.util.ResourceLocation +import net.skymoe.enchaddons.impl.config.feature.AwesomeMapConfigImpl +import net.skymoe.enchaddons.impl.feature.awesomemap.core.DungeonPlayer +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.* +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.Dungeon +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.DungeonScan +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MapRender +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MapRender.legitRender +import net.skymoe.enchaddons.impl.feature.awesomemap.ui.ScoreElement +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.RenderUtils.darken +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.RenderUtils.grayScale +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.itemID +import net.skymoe.enchaddons.impl.nanovg.NanoVGUIContext +import net.skymoe.enchaddons.impl.nanovg.Transformation +import net.skymoe.enchaddons.impl.nanovg.Widget +import net.skymoe.enchaddons.impl.oneconfig.FONT_MEDIUM_Y_OFFSET +import net.skymoe.enchaddons.impl.oneconfig.NanoVGImageCache +import net.skymoe.enchaddons.impl.oneconfig.fontMedium +import net.skymoe.enchaddons.impl.oneconfig.fontSemiBold +import net.skymoe.enchaddons.impl.oneconfig.loadFonts +import net.skymoe.enchaddons.impl.oneconfig.nvg +import net.skymoe.enchaddons.util.MC +import net.skymoe.enchaddons.util.alphaScale +import net.skymoe.enchaddons.util.math.Vec2D +import net.skymoe.enchaddons.util.math.double +import net.skymoe.enchaddons.util.math.float +import net.skymoe.enchaddons.util.math.int +import net.skymoe.enchaddons.util.math.lerp +import net.skymoe.enchaddons.util.partialTicks +import net.skymoe.enchaddons.util.renderPos +import net.skymoe.enchaddons.util.toStyledSegments +import kotlin.math.PI + +data class AwesomeMapWidget( + private val cache: NanoVGImageCache, + private val pos: Vec2D, + private val mapScale: Double, + private val width: Double, + private val height: Double, + private val config: AwesomeMapConfigImpl, + private val mapAlpha: Double = 1.0, +) : Widget { + override fun draw(context: NanoVGUIContext) { + val realScale = mapScale + context.run { + nvg.save(vg) + + if (config.mapCenter || config.mapRotateMode != 0) { + nvg.save(vg) + } + + if (config.mapClip) { + nvg.scissor(vg, pos.x, pos.y, width * mapScale, height * mapScale) + } + + if (config.mapRotateMode != 0) { + nvg.translate(vg, Vec2D(pos.x + 64.0 * mapScale, pos.y + 64.0 * mapScale)) + nvg.rotate( + vg, + when (config.mapRotateMode) { + 1 -> -MC.thePlayer.rotationYaw + 180.0 + 2 -> MapRender.dynamicRotation.double + else -> -MC.thePlayer.rotationYaw + 180.0 + } * PI / 180.0, + ) + } + + if (config.mapCenter) { + nvg.translate( + vg, + ( + Vec2D( + (MC.thePlayer.renderPos.x - DungeonScan.START_X + 15) * MapUtils.coordMultiplier + MapUtils.startCorner.first, + (MC.thePlayer.renderPos.z - DungeonScan.START_Z + 15) * MapUtils.coordMultiplier + MapUtils.startCorner.second, + ) * -1.0 + Vec2D(64.0, 64.0) + ) * realScale, + ) + } + } + + val tr = (Transformation() + pos) * realScale + context.run { + if (config.mapRotateMode != 0) { + nvg.translate(vg, Vec2D(-pos.x - 64.0 * mapScale, -pos.y - 64.0 * mapScale)) + + if (config.mapCenter) { + nvg.translate(vg, Vec2D(-1.0, -1.0) * realScale) + } + } + + drawMap(tr) + if (!Location.inBoss) { + drawPlayerHeads(tr) + } + + if (config.mapClip) { + nvg.resetScissor(vg) + } + + if (config.mapCenter || config.mapRotateMode != 0) { + nvg.restore(vg) + } + + if (config.mapShowRunInformation) { + drawRunInfo(tr) + } + + nvg.restore(vg) + } + } + + private fun NanoVGUIContext.drawMap(tr: Transformation) { + val ttr = + tr + + Vec2D( + MapUtils.startCorner.first.double + 1, + MapUtils.startCorner.second.double + 1, + ) + + val uniqueRooms = mutableSetOf() + val doors = mutableSetOf>>() + + for (y in 0..10) { + for (x in 0..10) { + val tile = Dungeon.Info.dungeonList[y * 11 + x] + if (tile is Unknown) continue + if (legitRender && tile.state == RoomState.UNDISCOVERED) continue + if (tile is Room) tile.uniqueRoom?.also(uniqueRooms::add) + if (tile is Door) doors.add(tile to (x to y)) + } + } + + doors.forEach { (door, pos) -> + val (x, y) = pos + val yEven = y and 1 == 0 + val xOffset = (x shr 1) * (MapUtils.roomSize + MapUtils.connectorSize) + val yOffset = (y shr 1) * (MapUtils.roomSize + MapUtils.connectorSize) + val doorwayOffset = if (MapUtils.roomSize == 16) 5 else 6 + val width = 6 + var x1 = if (yEven) xOffset + MapUtils.roomSize else xOffset + var y1 = if (yEven) yOffset else yOffset + MapUtils.roomSize + if (yEven) y1 += doorwayOffset else x1 += doorwayOffset + helper.drawRect( + vg, + (ttr posX x1.double).float, + (ttr posY y1.double).float, + (ttr size (if (yEven) MapUtils.connectorSize else width).double).float, + (ttr size (if (yEven) width else MapUtils.connectorSize).double).float, + getTileColor(door), + ) + } + + uniqueRooms.forEach { uniqueRoom -> + uniqueRoom.roomShape.draw(this, ttr, getTileColor(uniqueRoom.mainRoom)) + if (legitRender && uniqueRoom.mainRoom.state.equalsOneOf(RoomState.UNDISCOVERED, RoomState.UNOPENED)) return@forEach + val checkPos = uniqueRoom.getCheckmarkPosition() + val namePos = uniqueRoom.getNamePosition() + + val xOffsetCheck = (checkPos.first / 2f) * (MapUtils.roomSize + MapUtils.connectorSize) + val yOffsetCheck = (checkPos.second / 2f) * (MapUtils.roomSize + MapUtils.connectorSize) + val xOffsetName = (namePos.first / 2f) * (MapUtils.roomSize + MapUtils.connectorSize) + val yOffsetName = (namePos.second / 2f) * (MapUtils.roomSize + MapUtils.connectorSize) + + nvg.save(vg) + nvg.translate( + vg, + Vec2D( + ttr posX (xOffsetCheck.double + MapUtils.roomSize / 2.0), + ttr posY (yOffsetCheck.double + MapUtils.roomSize / 2.0), + ), + ) + + if (config.mapRotateMode != 0) { + nvg.rotate( + vg, + when (config.mapRotateMode) { + 1 -> MC.thePlayer.rotationYaw + 180.0 + 2 -> -MapRender.dynamicRotation.double + else -> MC.thePlayer.rotationYaw + 180.0 + } * PI / 180.0, + ) + } + + if (config.mapCheckmark && config.mapRoomSecrets != 2) { + val state = uniqueRoom.mainRoom.state + + val color = + when (state) { + RoomState.CLEARED -> config.colorCheckMarkCleared.rgb + RoomState.FAILED -> config.colorCheckMarkFailed.rgb + RoomState.GREEN -> config.colorCheckMarkGreen.rgb + else -> 0 + } + + if (state == RoomState.FAILED) { + nvg.drawCrossMark( + vg, + ttr size -6.0, + ttr size -6.0, + ttr size 12.0, + ttr size 1.5, + color, + ) + } else { + nvg.drawCheckMark( + vg, + ttr size -6.0, + ttr size -6.0, + ttr size 12.0, + ttr size 1.5, + color, + ) + } + } + + val color = + ( + if (config.mapColorText) { + when (uniqueRoom.mainRoom.state) { + RoomState.GREEN -> config.colorTextGreen + RoomState.CLEARED -> config.colorTextCleared + RoomState.FAILED -> config.colorTextFailed + else -> config.colorTextUncleared + } + } else { + config.colorTextCleared + } + ).rgb alphaScale mapAlpha + + if (config.mapRoomSecrets == 2) { + nvg.drawTextSegments( + vg, + "${uniqueRoom.mainRoom.data.secrets}".toStyledSegments(), + .0, + ttr size FONT_MEDIUM_Y_OFFSET.double, + ttr size 16.0 * config.textScale, + fontMedium(), + color = color, + anchor = Vec2D(0.5, 0.5), + ) + } + + nvg.restore(vg) + } + + uniqueRooms.forEach { uniqueRoom -> + val namePos = uniqueRoom.getNamePosition() + val xOffsetName = (namePos.first / 2f) * (MapUtils.roomSize + MapUtils.connectorSize) + val yOffsetName = (namePos.second / 2f) * (MapUtils.roomSize + MapUtils.connectorSize) + + val color = + ( + if (config.mapColorText) { + when (uniqueRoom.mainRoom.state) { + RoomState.GREEN -> config.colorTextGreen + RoomState.CLEARED -> config.colorTextCleared + RoomState.FAILED -> config.colorTextFailed + else -> config.colorTextUncleared + } + } else { + config.colorTextCleared + } + ).rgb alphaScale mapAlpha + + val name = mutableListOf() + + if (config.mapRoomNames != 0 && + uniqueRoom.mainRoom.data.type.equalsOneOf( + RoomType.PUZZLE, + RoomType.TRAP, + ) || + config.mapRoomNames == 2 && + uniqueRoom.mainRoom.data.type.equalsOneOf( + RoomType.NORMAL, + RoomType.RARE, + RoomType.CHAMPION, + ) + ) { + name.addAll( + uniqueRoom.mainRoom.data.name + .split(" "), + ) + } + + if (uniqueRoom.mainRoom.data.type == RoomType.NORMAL && config.mapRoomSecrets == 1) { + name.add( + uniqueRoom.mainRoom.data.secrets + .toString(), + ) + } + + name.forEachIndexed { i, it -> + val size = config.textScale * 8.0 + + nvg.save(vg) + nvg.translate( + vg, + Vec2D( + ttr posX (xOffsetName + MapUtils.halfRoomSize).double, + ttr posY (yOffsetName + MapUtils.halfRoomSize).double, + ), + ) + + if (config.mapRotateMode != 0) { + nvg.rotate( + vg, + when (config.mapRotateMode) { + 1 -> MC.thePlayer.rotationYaw + 180.0 + 2 -> -MapRender.dynamicRotation.double + else -> MC.thePlayer.rotationYaw + 180.0 + } * PI / 180.0, + ) + } + + nvg.translate( + vg, + Vec2D( + .0, + ttr size -size * name.size / 2 + size * (i + 0.5), + ), + ) + + nvg.drawTextSegments( + vg, + it.toStyledSegments(), + 0.0, + 0.0, + ttr size size, + fontSemiBold(), + color = color, + anchor = Vec2D(0.5, 0.5), + ) + + nvg.restore(vg) + } + } + } + + private fun getTileColor(tile: Tile): Int { + 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() + } + } + return color.rgb alphaScale mapAlpha + } + + private fun NanoVGUIContext.drawPlayerHeads(tr: Transformation) { + try { + if (Dungeon.dungeonTeammates.isEmpty()) { + drawPlayerHead( + tr, + MC.thePlayer.name, + DungeonPlayer(MC.thePlayer.locationSkin, MC.thePlayer.uniqueID).apply { + yaw = MC.thePlayer.rotationYaw + }, + ) + } else { + Dungeon.dungeonTeammates.forEach { (name, teammate) -> + if (!teammate.dead) { + drawPlayerHead(tr, name, teammate) + } + } + } + } catch (_: ConcurrentModificationException) { + } + } + + private fun NanoVGUIContext.drawPlayerHead( + tr: Transformation, + name: String, + player: DungeonPlayer, + ) { + nvg.save(vg) + nvg.translate(vg, tr.offset) + nvg.scale(vg, Vec2D(tr.scale, tr.scale)) + nvg.translate(vg, Vec2D(1.0, 1.0)) + try { + val isLocalPlayer = player.isPlayer || name == MC.thePlayer.name + + // Translates to the player's location, which is updated every tick. + if (isLocalPlayer) { + nvg.translate( + vg, + Vec2D( + (MC.thePlayer.renderPos.x - DungeonScan.START_X + 15) * MapUtils.coordMultiplier + MapUtils.startCorner.first, + (MC.thePlayer.renderPos.z - DungeonScan.START_Z + 15) * MapUtils.coordMultiplier + MapUtils.startCorner.second, + ), + ) + } else { + nvg.translate( + vg, + Vec2D( + lerp(player.prevPosition.mapX, player.position.mapX, partialTicks), + lerp(player.prevPosition.mapZ, player.position.mapZ, partialTicks), + ), + ) + } + + // Apply head rotation + if (isLocalPlayer) { + nvg.rotate(vg, (MC.thePlayer.rotationYaw + 180.0) * PI / 180.0) + } else { + nvg.rotate(vg, (player.getRenderYaw() + 180.0) * PI / 180.0) + } + + // Apply scaling + nvg.scale(vg, Vec2D(1 / tr.scale, 1 / tr.scale)) + val ttr = Transformation() * tr.scale * config.playerHeadScale.double + + if (config.mapVanillaMarker && (player.isPlayer || name == MC.thePlayer.name)) { + nvg.drawRoundedTexture( + vg, + cache["marker"], + MC.textureManager.getTexture(ResourceLocation("funnymap", "marker.png")).glTextureId, + 0.0, + 0.0, + 1.0, + 1.0, + ttr posX -6.0, + ttr posY -6.0, + ttr size 12.0, + ttr size 12.0, + mapAlpha, + ttr size 0.0, + ) + } else { +// // Render black border around the player head +// renderRectBorder(-6.0, -6.0, 12.0, 12.0, 1.0, Color(0, 0, 0, 255)) + nvg.drawRoundedPlayerAvatar( + vg, + cache[player.uuid], + MC.textureManager.getTexture(player.skin).glTextureId, + hat = true, + scaleHat = true, + ttr posX -6.0, + ttr posY -6.0, + ttr size 12.0, + ttr size 12.0, + mapAlpha, + ttr size config.playerHeadRadius.double, + ) + } + + // Handle player names + if (config.playerHeads == 2 || + config.playerHeads == 1 && + MC.thePlayer.heldItem?.itemID.equalsOneOf( + "SPIRIT_LEAP", + "INFINITE_SPIRIT_LEAP", + "HAUNT_ABILITY", + ) + ) { + if (config.mapRotateMode != 1) { + nvg.save(vg) + val rotateAngle = + if (isLocalPlayer) { + -MC.thePlayer.rotationYaw.double + } else { + -player.getRenderYaw() + } + + if (config.mapRotateMode == 2) { + -MapRender.dynamicRotation.double + } else { + .0 + } + nvg.rotate(vg, (rotateAngle + 180.0) * PI / 180.0) + } + + nvg.drawTextSegments( + vg, + name.toStyledSegments(), + ttr posX 0.0, + ttr posY 8.0, + ttr size 8.0 * config.playerNameScale, + fontSemiBold(), + color = 0xFFFFFFFF.int alphaScale mapAlpha, + anchor = Vec2D(0.5, 0.0), + shadow = Vec2D(1 / 16.0, 1 / 16.0) to 0.25, + ) + + if (config.mapRotateMode != 1) { + nvg.restore(vg) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + nvg.restore(vg) + } + + private fun NanoVGUIContext.drawRunInfo(tr: Transformation) { + nvg.loadFonts(vg) + val ttr = tr + Vec2D(64.0, 130.0) + val lines = ScoreElement.runInformationLines() + val lineOne = lines.takeWhile { it != "split" }.joinToString(separator = " ") + val lineTwo = lines.takeLastWhile { it != "split" }.joinToString(separator = " ") + nvg.drawTextSegments( + vg, + lineOne.toStyledSegments(), + ttr posX 0.0, + ttr posY 0.0, + ttr size 8.0, + fontSemiBold(), + color = 0xFFFFFFFF.int alphaScale mapAlpha, + anchor = Vec2D(0.5, 0.0), + shadow = Vec2D(1 / 16.0, 1 / 16.0) to 0.25, + ) + nvg.drawTextSegments( + vg, + lineTwo.toStyledSegments(), + ttr posX 0.0, + ttr posY 9.0, + ttr size 8.0, + fontSemiBold(), + color = 0xFFFFFFFF.int alphaScale mapAlpha, + anchor = Vec2D(0.5, 0.0), + shadow = Vec2D(1 / 16.0, 1 / 16.0) to 0.25, + ) + } + + override fun alphaScale(alpha: Double): AwesomeMapWidget = + copy( + mapAlpha = alpha, + ) + + override fun scale( + scale: Double, + origin: Vec2D, + ): AwesomeMapWidget = + copy( + pos = lerp(origin, pos, scale), + mapScale = mapScale * scale, + ) +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/LRoomShape.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/LRoomShape.kt new file mode 100644 index 0000000..f431a30 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/LRoomShape.kt @@ -0,0 +1,38 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap + +import net.skymoe.enchaddons.impl.config.EnchAddonsConfig +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils +import net.skymoe.enchaddons.impl.nanovg.NanoVGUIContext +import net.skymoe.enchaddons.impl.nanovg.Transformation +import net.skymoe.enchaddons.impl.oneconfig.nvg +import net.skymoe.enchaddons.util.math.double + +data class LRoomShape( + val x: Int, + val y: Int, + val width: Int, + val height: Int, + val corner: Int, +) : RoomShape { + override fun draw( + context: NanoVGUIContext, + tr: Transformation, + color: Int, + ) { + context.run { + nvg.drawAccarc( + vg, + corner, + tr posX (x shr 1) * (MapUtils.roomSize + MapUtils.connectorSize).double, + tr posY (y shr 1) * (MapUtils.roomSize + MapUtils.connectorSize).double, + tr size width.double * (MapUtils.roomSize + MapUtils.connectorSize) - MapUtils.connectorSize, + tr size height.double * (MapUtils.roomSize + MapUtils.connectorSize) - MapUtils.connectorSize, + tr size MapUtils.roomSize.double, + tr size MapUtils.roomSize.double, + tr size EnchAddonsConfig.dungeonConfig.awesomeMapConfig.mapRoomRadius.double, + tr size EnchAddonsConfig.dungeonConfig.awesomeMapConfig.mapLShapeRoomInnerRadius.double, + color, + ) + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/RectRoomShape.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/RectRoomShape.kt new file mode 100644 index 0000000..41b4ddc --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/RectRoomShape.kt @@ -0,0 +1,33 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap + +import net.skymoe.enchaddons.impl.config.EnchAddonsConfig +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils +import net.skymoe.enchaddons.impl.nanovg.NanoVGUIContext +import net.skymoe.enchaddons.impl.nanovg.Transformation +import net.skymoe.enchaddons.util.math.double +import net.skymoe.enchaddons.util.math.float + +data class RectRoomShape( + val x: Int, + val y: Int, + val width: Int, + val height: Int, +) : RoomShape { + override fun draw( + context: NanoVGUIContext, + tr: Transformation, + color: Int, + ) { + context.run { + helper.drawRoundedRect( + vg, + (tr posX (x shr 1) * (MapUtils.roomSize + MapUtils.connectorSize).double).float, + (tr posY (y shr 1) * (MapUtils.roomSize + MapUtils.connectorSize).double).float, + (tr size width.double * (MapUtils.roomSize + MapUtils.connectorSize) - MapUtils.connectorSize).float, + (tr size height.double * (MapUtils.roomSize + MapUtils.connectorSize) - MapUtils.connectorSize).float, + color, + (tr size EnchAddonsConfig.dungeonConfig.awesomeMapConfig.mapRoomRadius.double).float, + ) + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/RoomShape.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/RoomShape.kt new file mode 100644 index 0000000..20a0423 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/RoomShape.kt @@ -0,0 +1,38 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap + +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.UniqueRoom +import net.skymoe.enchaddons.impl.nanovg.NanoVGUIContext +import net.skymoe.enchaddons.impl.nanovg.Transformation + +interface RoomShape { + fun draw( + context: NanoVGUIContext, + tr: Transformation, + color: Int, + ) +} + +val UniqueRoom.roomShape: RoomShape get() { + val minX = tiles.minOf { it.second.first } + val maxX = tiles.maxOf { it.second.first } + val minY = tiles.minOf { it.second.second } + val maxY = tiles.maxOf { it.second.second } + val w = (maxX - minX) / 2 + 1 + val h = (maxY - minY) / 2 + 1 + val nw = minX to minY + val ne = maxX to minY + val sw = minX to maxY + val se = maxX to maxY + val nwp = tiles.any { it.second == nw } + val nep = tiles.any { it.second == ne } + val swp = tiles.any { it.second == sw } + val sep = tiles.any { it.second == se } + return when { + nwp && nep && swp && sep -> RectRoomShape(minX, minY, w, h) + nwp && nep && swp && !sep -> LRoomShape(minX, minY, w, h, 0) + nwp && nep && !swp && sep -> LRoomShape(minX, minY, w, h, 1) + !nwp && nep && swp && sep -> LRoomShape(minX, minY, w, h, 2) + nwp && !nep && swp && sep -> LRoomShape(minX, minY, w, h, 3) + else -> UnknownRoomShape + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/UnknownRoomShape.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/UnknownRoomShape.kt new file mode 100644 index 0000000..669887d --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/UnknownRoomShape.kt @@ -0,0 +1,13 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap + +import net.skymoe.enchaddons.impl.nanovg.NanoVGUIContext +import net.skymoe.enchaddons.impl.nanovg.Transformation + +object UnknownRoomShape : RoomShape { + override fun draw( + context: NanoVGUIContext, + tr: Transformation, + color: Int, + ) { + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/commands/FunnyMapCommands.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/commands/FunnyMapCommands.kt new file mode 100644 index 0000000..2febff7 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/commands/FunnyMapCommands.kt @@ -0,0 +1,77 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.commands + +import net.minecraft.client.gui.GuiScreen +import net.minecraft.command.CommandBase +import net.minecraft.command.ICommandSender +import net.minecraft.util.BlockPos +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.Dungeon +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.DungeonScan +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.ScanUtils +import net.skymoe.enchaddons.util.LogLevel +import net.skymoe.enchaddons.util.MC +import net.skymoe.enchaddons.util.modMessage + +class FunnyMapCommands : CommandBase() { + private val commands = listOf("help", "scan", "roomdata") + + override fun getCommandName(): String = "funnymap" + + override fun getCommandAliases(): List = listOf("fmap", "fm") + + override fun getCommandUsage(sender: ICommandSender): String = "/$commandName" + + override fun getRequiredPermissionLevel(): Int = 0 + + override fun processCommand( + sender: ICommandSender, + args: Array, + ) { + if (args.isEmpty()) { +// display = Config.gui() + return + } + when (args[0]) { + // Help command + "help" -> { +// UChat.chat( +// """ +// #§b§l<§fFunnyMap Commands§b§l> +// # §b/funnymap §9> §3Opens the main mod GUI. §7(Alias: fm, fmap) +// # §b/§ffunnymap §bscan §9> §3Rescans the map. +// # §b/§ffunnymap §broomdata §9> §3Copies current room data or room core to clipboard. +// """.trimMargin("#"), +// ) + } + // Scans the dungeon + "scan" -> { + Dungeon.reset() + DungeonScan.scan() + } + // Copies room data or room core to clipboard + "roomdata" -> { + val pos = ScanUtils.getRoomCentre(MC.thePlayer.posX.toInt(), MC.thePlayer.posZ.toInt()) + val data = ScanUtils.getRoomData(pos.first, pos.second) + if (data != null) { + GuiScreen.setClipboardString(data.toString()) + modMessage("§aCopied room data to clipboard.", LogLevel.INFO) + } else { + GuiScreen.setClipboardString(ScanUtils.getCore(pos.first, pos.second).toString()) + modMessage("§cExisting room data not found. §aCopied room core to clipboard.", LogLevel.WARN) + } + } + // Unknown command help message + else -> modMessage("§cUnknown command. Use §b/§ffunnymap help §cfor a list of commands.", LogLevel.WARN) + } + } + + override fun addTabCompletionOptions( + sender: ICommandSender, + args: Array, + pos: BlockPos, + ): MutableList { + if (args.size == 1) { + return getListOfStringsMatchingLastWord(args, commands) + } + return mutableListOf() + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/DungeonPlayer.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/DungeonPlayer.kt new file mode 100644 index 0000000..92e85d6 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/DungeonPlayer.kt @@ -0,0 +1,86 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.launch +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.entity.player.EnumPlayerModelParts +import net.minecraft.util.ResourceLocation +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.Room +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.Dungeon +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.APIUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils +import net.skymoe.enchaddons.util.math.int +import net.skymoe.enchaddons.util.partialTicks +import java.util.UUID + +data class DungeonPlayer( + val skin: ResourceLocation, + var uuid: UUID?, +) { + var name = "" + + /** Minecraft formatting code for the player's name */ + var colorPrefix = 'f' + + /** The player's name with formatting code */ + val formattedName: String + get() = "§$colorPrefix$name" + + data class PlayerPosition( + var mapX: Double = .0, + var mapZ: Double = .0, + ) + + val position: PlayerPosition = PlayerPosition() + val prevPosition: PlayerPosition = PlayerPosition() + + var yaw = 0f + var prevYaw = 0f + + fun getRenderYaw(): Double { + val prevYaw = (prevYaw % 360 + 360) % 360 + val yaw = (yaw % 360 + 360) % 360 + var diff = yaw - prevYaw + if (diff > 180) diff = 360 - diff + if (diff < -180) diff = -360 - diff + return prevYaw + diff * partialTicks + } + + /** Has information from the player entity been loaded */ + var playerLoaded = false + var icon = "" + var renderHat = true + var dead = false + var isPlayer = false + + /** Stats for compiling player tracker information */ + var startingSecrets = 0 + var lastRoom = "" + var lastTime = 0L + var roomVisits: MutableList> = mutableListOf() + + /** Set player data that requires entity to be loaded */ + fun setData(player: EntityPlayer) { + renderHat = player.isWearing(EnumPlayerModelParts.HAT) + playerLoaded = true + AwesomeMap.scope.launch(Dispatchers.IO) { + val secrets = uuid?.let { APIUtils.getSecrets(it) } ?: 0 + Utils.runMinecraftThread { + startingSecrets = secrets + } + } + } + + /** Gets the player's room, used for room tracker */ + fun getCurrentRoom(): String { + if (dead) return "Dead" + if (Location.inBoss) return "Boss" + val x = (position.mapX.int - MapUtils.startCorner.first) / (MapUtils.roomSize + MapUtils.connectorSize) + val z = (position.mapZ.int - MapUtils.startCorner.second) / (MapUtils.roomSize + MapUtils.connectorSize) + return (Dungeon.Info.dungeonList.getOrNull(x * 2 + z * 22) as? Room)?.data?.name ?: "Error" + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/RoomData.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/RoomData.kt new file mode 100644 index 0000000..dbb3a50 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/RoomData.kt @@ -0,0 +1,16 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core + +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.RoomType + +data class RoomData( + val name: String, + var type: RoomType, + val cores: List, + val crypts: Int, + val secrets: Int, + val trappedChests: Int, +) { + companion object { + fun createUnknown(type: RoomType) = RoomData("Unknown", type, emptyList(), 0, 0, 0) + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Door.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Door.kt new file mode 100644 index 0000000..7f5a449 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Door.kt @@ -0,0 +1,40 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +import net.skymoe.enchaddons.impl.config.EnchAddonsConfig +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MapRender +import java.awt.Color + +class Door( + override val x: Int, + override val z: Int, + var type: DoorType, +) : Tile { + var opened = false + override var state: RoomState = RoomState.UNDISCOVERED + override val color: Color + get() = + if (MapRender.legitRender && state == RoomState.UNOPENED) { + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorUnopenedDoor + .toJavaColor() + } else { + when (type) { + DoorType.BLOOD -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorBloodDoor + .toJavaColor() + DoorType.ENTRANCE -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorEntranceDoor + .toJavaColor() + DoorType.WITHER -> + if (opened) { + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorOpenWitherDoor + .toJavaColor() + } else { + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorWitherDoor + .toJavaColor() + } + else -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorRoomDoor + .toJavaColor() + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/DoorType.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/DoorType.kt new file mode 100644 index 0000000..3252bfe --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/DoorType.kt @@ -0,0 +1,21 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +enum class DoorType { + BLOOD, + ENTRANCE, + NORMAL, + WITHER, + ; + + companion object { + fun fromMapColor(color: Int): DoorType? = + when (color) { + 18 -> BLOOD + 30 -> ENTRANCE + // Champion, Fairy, Puzzle, Trap, Unopened doors render as normal doors + 74, 82, 66, 62, 85, 63 -> NORMAL + 119 -> WITHER + else -> null + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Puzzle.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Puzzle.kt new file mode 100644 index 0000000..64adc21 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Puzzle.kt @@ -0,0 +1,26 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf + +enum class Puzzle( + val roomDataName: String, + val tabName: String = roomDataName, +) { + BOMB_DEFUSE("Bomb Defuse"), + BOULDER("Boulder"), + CREEPER_BEAMS("Creeper Beams"), + HIGHER_BLAZE("Higher Blaze", "Higher Or Lower"), + ICE_FILL("Ice Fill"), + ICE_PATH("Ice Path"), + LOWER_BLAZE("Lower Blaze", "Higher Or Lower"), + QUIZ("Quiz"), + TELEPORT_MAZE("Teleport Maze"), + THREE_WEIRDOS("Three Weirdos"), + TIC_TAC_TOE("Tic Tac Toe"), + WATER_BOARD("Water Board"), + ; + + companion object { + fun fromName(name: String): Puzzle? = entries.find { name.equalsOneOf(it.roomDataName, it.tabName) } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Room.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Room.kt new file mode 100644 index 0000000..e8af6bc --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Room.kt @@ -0,0 +1,55 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +import net.skymoe.enchaddons.impl.config.EnchAddonsConfig +import net.skymoe.enchaddons.impl.feature.awesomemap.core.RoomData +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MapRender +import java.awt.Color + +class Room( + override val x: Int, + override val z: Int, + var data: RoomData, +) : Tile { + var core = 0 + var isSeparator = false + var uniqueRoom: UniqueRoom? = null + override var state: RoomState = RoomState.UNDISCOVERED + override val color: Color + get() = + if (MapRender.legitRender && state == RoomState.UNOPENED) { + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorUnopened + .toJavaColor() + } else { + when (data.type) { + RoomType.BLOOD -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorBlood + .toJavaColor() + RoomType.CHAMPION -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorMiniboss + .toJavaColor() + RoomType.ENTRANCE -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorEntrance + .toJavaColor() + RoomType.FAIRY -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorFairy + .toJavaColor() + RoomType.PUZZLE -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorPuzzle + .toJavaColor() + RoomType.RARE -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorRare + .toJavaColor() + RoomType.TRAP -> + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorTrap + .toJavaColor() + else -> + if (uniqueRoom?.hasMimic == true) { + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorRoomMimic + .toJavaColor() + } else { + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.colorRoom + .toJavaColor() + } + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/RoomState.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/RoomState.kt new file mode 100644 index 0000000..1bca042 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/RoomState.kt @@ -0,0 +1,10 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +enum class RoomState { + GREEN, + CLEARED, + FAILED, + DISCOVERED, + UNOPENED, + UNDISCOVERED, +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/RoomType.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/RoomType.kt new file mode 100644 index 0000000..4cc6d11 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/RoomType.kt @@ -0,0 +1,27 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +enum class RoomType { + BLOOD, + CHAMPION, + ENTRANCE, + FAIRY, + NORMAL, + PUZZLE, + RARE, + TRAP, + ; + + companion object { + fun fromMapColor(color: Int): RoomType? = + when (color) { + 18 -> BLOOD + 74 -> CHAMPION + 30 -> ENTRANCE + 82 -> FAIRY + 63, 85 -> NORMAL + 66 -> PUZZLE + 62 -> TRAP + else -> null + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Tile.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Tile.kt new file mode 100644 index 0000000..ed8678a --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Tile.kt @@ -0,0 +1,10 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +import java.awt.Color + +interface Tile { + val x: Int + val z: Int + var state: RoomState + val color: Color +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/UniqueRoom.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/UniqueRoom.kt new file mode 100644 index 0000000..218ccd2 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/UniqueRoom.kt @@ -0,0 +1,139 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.Dungeon +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MapRender + +class UniqueRoom( + arrX: Int, + arrY: Int, + room: Room, +) { + var name: String + var topLeft = Pair(arrX, arrY) + private var center = Pair(arrX, arrY) + var mainRoom = room + get() = Dungeon.Info.dungeonList[topLeft.second * 11 + topLeft.first] as? Room ?: field + val tiles = mutableListOf(room to Pair(arrX, arrY)) + var hasMimic = false + + init { + if (room.data.name == "Unknown") { + name = "Unknown_${arrX}_$arrY" + } else { + name = room.data.name + init(arrX, arrY, room) + } + room.uniqueRoom = this + Dungeon.Info.uniqueRooms.add(this) + } + + fun init( + arrX: Int, + arrY: Int, + room: Room, + ) { + Dungeon.Info.cryptCount += room.data.crypts + Dungeon.Info.secretCount += room.data.secrets + when (room.data.type) { + RoomType.ENTRANCE -> + MapRender.dynamicRotation = + when { + arrY == 0 -> 180f + arrX == 0 -> -90f + arrX > arrY -> 90f + else -> 0f + } + + RoomType.TRAP -> Dungeon.Info.trapType = room.data.name.split(" ")[0] + RoomType.PUZZLE -> Puzzle.fromName(room.data.name)?.let { Dungeon.Info.puzzles.putIfAbsent(it, false) } + + else -> {} + } + } + + fun addTile( + x: Int, + y: Int, + tile: Room, + ) { + addToTiles(x, y, tile) + calculateCenter() + } + + fun addTiles(tiles: Iterable>) { + tiles.forEach { (x, y) -> + val room = Dungeon.Info.dungeonList[y * 11 + x] as? Room ?: return@forEach + if (room.uniqueRoom !== this) { + Dungeon.Info.uniqueRooms.remove(room.uniqueRoom) + addToTiles(x, y, room) + } + } + calculateCenter() + } + + private fun addToTiles( + x: Int, + y: Int, + tile: Room, + ) { + if (mainRoom.data.name == "Unknown") { + if (tile.data.name != "Unknown") { + init(x, y, tile) + name = tile.data.name + mainRoom.data = tile.data + } + } else if (tile.data.name == "Unknown") { + tile.data = mainRoom.data + } + + tile.uniqueRoom = this + + tiles.removeIf { it.first.x == tile.x && it.first.z == tile.z } + tiles.add(tile to Pair(x, y)) + + if (x < topLeft.first || (x == topLeft.first && y < topLeft.second)) { + topLeft = Pair(x, y) + mainRoom = tile + if (name.startsWith("Unknown")) { + name = "Unknown_${x}_$y" + } + } + } + + private fun calculateCenter() { + if (tiles.size == 1) { + center = tiles.first().second + return + } + + val positions = + tiles.mapNotNull { + it.second.takeIf { (arrX, arrZ) -> + arrX % 2 == 0 && arrZ % 2 == 0 + } + } + + if (positions.isEmpty()) return + + val xRooms = positions.groupBy { it.first }.entries.sortedByDescending { it.value.size } + val zRooms = positions.groupBy { it.second }.entries.sortedByDescending { it.value.size } + + center = + when { + zRooms.size == 1 || zRooms[0].value.size != zRooms[1].value.size -> { + xRooms.sumOf { it.key } / xRooms.size to zRooms[0].key + } + + xRooms.size == 1 || xRooms[0].value.size != xRooms[1].value.size -> { + xRooms[0].key to zRooms.sumOf { it.key } / zRooms.size + } + + else -> (xRooms[0].key + xRooms[1].key) / 2 to (zRooms[0].key + zRooms[1].key) / 2 + } + } + + fun getNamePosition(): Pair = if (AwesomeMap.config.mapCenterRoomName) center else topLeft + + fun getCheckmarkPosition(): Pair = if (AwesomeMap.config.mapCenterCheckmark) center else topLeft +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Unknown.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Unknown.kt new file mode 100644 index 0000000..d59fc78 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/core/map/Unknown.kt @@ -0,0 +1,11 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.core.map + +import java.awt.Color + +class Unknown( + override val x: Int, + override val z: Int, +) : Tile { + override val color: Color = Color(0, 0, 0, 0) + override var state: RoomState = RoomState.UNDISCOVERED +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/Dungeon.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/Dungeon.kt new file mode 100644 index 0000000..052379a --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/Dungeon.kt @@ -0,0 +1,148 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.minecraft.event.ClickEvent +import net.skymoe.enchaddons.event.minecraft.ChatEvent +import net.skymoe.enchaddons.event.minecraft.MinecraftEvent +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.impl.feature.awesomemap.core.DungeonPlayer +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.* +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.Dungeon.Info.ended +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.TabList +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import net.skymoe.enchaddons.util.LogLevel +import net.skymoe.enchaddons.util.buildComponent +import net.skymoe.enchaddons.util.modMessage + +object Dungeon { + val dungeonTeammates = mutableMapOf() + val espDoors = mutableListOf() + + private val keyGainRegex = + listOf( + Regex(".+ §r§ehas obtained §r§a§r§.+ Key§r§e!§r"), + Regex("§r§eA §r§a§r§.+ Key§r§e was picked up!§r"), + ) + private val keyUseRegex = + listOf( + Regex("§r§cThe §r§c§lBLOOD DOOR§r§c has been opened!§r"), + Regex("§r§a.+§r§a opened a §r§8§lWITHER §r§adoor!§r"), + ) + + fun onTick() { + if (shouldSearchMimic()) { + MimicDetector.findMimic()?.let { + if (AwesomeMap.config.scanChatInfo) { + buildComponent { + "Mimic Room: ".gray + it.red + }.also { component -> modMessage(component, LogLevel.INFO) } + } + Info.mimicFound = true + } + } + + if (!MapUtils.calibrated) { + MapUtils.calibrated = MapUtils.calibrateMap() + } + + if (MapUtils.mapDataUpdated) { + MapUpdate.updateRooms() + MapUtils.mapDataUpdated = false + } + + ScoreCalculation.updateScore() + + TabList.getDungeonTabList()?.let { + MapUpdate.updatePlayers(it) + RunInformation.updatePuzzleCount(it) + } + + if (DungeonScan.shouldScan) { + DungeonScan.scan() + } + } + + fun onChat(event: ChatEvent) { + if (event.component.siblings.any { + it.chatStyle?.chatClickEvent?.run { + action == ClickEvent.Action.RUN_COMMAND && value == "/showextrastats" + } == true + } + ) { + ended = true + if (AwesomeMap.config.teamInfo) { + PlayerTracker.onDungeonEnd() + } + } + + if (keyGainRegex.any { it.matches(event.component.formattedText) }) { + Info.keys++ + } + + if (keyUseRegex.any { it.matches(event.component.formattedText) }) { + Info.keys-- + } + + when (event.message) { + "Starting in 4 seconds." -> MapUpdate.preloadHeads() + "[NPC] Mort: Here, I found this map when I first entered the dungeon." -> { + MapUpdate.getPlayers() + Info.startTime = System.currentTimeMillis() + } + } + } + + fun onWorldLoad(event: MinecraftEvent.World.Unload) { + reset() + } + + fun reset() { + Info.reset() + dungeonTeammates.clear() + espDoors.clear() + PlayerTracker.roomClears.clear() + MapUtils.calibrated = false + MapUtils.mapData = null + DungeonScan.hasScanned = false + RunInformation.reset() + } + + private fun shouldSearchMimic() = !Info.mimicFound && Location.dungeonFloor.equalsOneOf(6, 7) && !AwesomeMap.config.legitMode + + object Info { + // 6 x 6 room grid, 11 x 11 with connections + val dungeonList = Array(121) { Unknown(0, 0) } + val uniqueRooms = mutableSetOf() + var roomCount = 0 + val puzzles = mutableMapOf() + + var trapType = "" + var witherDoors = 0 + var cryptCount = 0 + var secretCount = 0 + var mimicFound = false + + var startTime = 0L + var ended = false + var keys = 0 + + fun reset() { + dungeonList.fill(Unknown(0, 0)) + uniqueRooms.clear() + roomCount = 0 + puzzles.clear() + + trapType = "" + witherDoors = 0 + cryptCount = 0 + secretCount = 0 + mimicFound = false + + startTime = 0L + ended = false + keys = 0 + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/DungeonMap.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/DungeonMap.kt new file mode 100644 index 0000000..59a01db --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/DungeonMap.kt @@ -0,0 +1,111 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.skymoe.enchaddons.impl.feature.awesomemap.core.RoomData +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.* +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils + +class DungeonMap( + mapColors: ByteArray, +) { + private var centerColors: ByteArray = ByteArray(121) + private var sideColors: ByteArray = ByteArray(121) + private val cacheTiles: Array = Array(121) { null } + + init { + val halfRoom = MapUtils.roomSize / 2 + val halfTile = halfRoom + 2 + val startX = MapUtils.startCorner.first + halfRoom + val startY = MapUtils.startCorner.second + halfRoom + + for (y in 0..10) { + for (x in 0..10) { + val mapX = startX + x * halfTile + val mapY = startY + y * halfTile + + if (mapX >= 128 || mapY >= 128) continue + + centerColors[y * 11 + x] = mapColors[mapY * 128 + mapX] + + val sideIndex = + if (x % 2 == 0 && y % 2 == 0) { + val topX = mapX - halfRoom + val topY = mapY - halfRoom + topY * 128 + topX + } else { + val horizontal = y % 2 == 1 + if (horizontal) { + mapY * 128 + mapX - 4 + } else { + (mapY - 4) * 128 + mapX + } + } + + sideColors[y * 11 + x] = mapColors[sideIndex] + } + } + } + + fun getTile( + arrayX: Int, + arrayY: Int, + ): Tile { + val index = arrayY * 11 + arrayX + if (index !in cacheTiles.indices) return Unknown(0, 0) + if (cacheTiles[index] == null) { + val xPos = DungeonScan.START_X + arrayX * (DungeonScan.ROOM_SIZE shr 1) + val zPos = DungeonScan.START_Z + arrayY * (DungeonScan.ROOM_SIZE shr 1) + cacheTiles[index] = scanTile(arrayX, arrayY, xPos, zPos) + } + return cacheTiles[index] ?: Unknown(0, 0) + } + + private fun scanTile( + arrayX: Int, + arrayY: Int, + worldX: Int, + worldZ: Int, + ): Tile { + val centerColor = centerColors[arrayY * 11 + arrayX].toInt() + val sideColor = sideColors[arrayY * 11 + arrayX].toInt() + + if (centerColor == 0) return Unknown(worldX, worldZ) + + return if (arrayX % 2 == 0 && arrayY % 2 == 0) { + val type = RoomType.fromMapColor(sideColor) ?: return Unknown(worldX, worldZ) + Room(worldX, worldZ, RoomData.createUnknown(type)).apply { + state = + when (centerColor) { + 18 -> + when (type) { + RoomType.BLOOD -> RoomState.DISCOVERED + RoomType.PUZZLE -> RoomState.FAILED + else -> state + } + + 30 -> + when (type) { + RoomType.ENTRANCE -> RoomState.DISCOVERED + else -> RoomState.GREEN + } + + 34 -> RoomState.CLEARED + 85, 119 -> RoomState.UNOPENED + else -> RoomState.DISCOVERED + } + } + } else { + if (sideColor == 0) { + val type = DoorType.fromMapColor(centerColor) ?: return Unknown(worldX, worldZ) + Door(worldX, worldZ, type).apply { + state = if (centerColor == 85) RoomState.UNOPENED else RoomState.DISCOVERED + } + } else { + val type = RoomType.fromMapColor(sideColor) ?: return Unknown(worldX, worldZ) + Room(worldX, worldZ, RoomData.createUnknown(type)).apply { + state = RoomState.DISCOVERED + isSeparator = true + } + } + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/DungeonScan.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/DungeonScan.kt new file mode 100644 index 0000000..fc1855d --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/DungeonScan.kt @@ -0,0 +1,200 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.minecraft.init.Blocks +import net.minecraft.util.BlockPos +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.* +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.DungeonScan.scan +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location.dungeonFloor +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import net.skymoe.enchaddons.util.LogLevel +import net.skymoe.enchaddons.util.MC +import net.skymoe.enchaddons.util.buildComponent +import net.skymoe.enchaddons.util.modMessage +import kotlin.math.ceil + +/** + * Handles everything related to scanning the dungeon. Running [scan] will update the instance of [Dungeon]. + */ +object DungeonScan { + /** + * The size of each dungeon room in blocks. + */ + const val ROOM_SIZE = 32 + + /** + * The starting coordinates to start scanning (the north-west corner). + */ + const val START_X = -185 + const val START_Z = -185 + + private var lastScanTime = 0L + var isScanning = false + var hasScanned = false + + val shouldScan: Boolean + get() = + AwesomeMap.config.autoScan && + !isScanning && + !hasScanned && + System.currentTimeMillis() - lastScanTime >= 250 && + dungeonFloor != -1 + + fun scan() { + isScanning = true + var allChunksLoaded = true + + // Scans the dungeon in a 11x11 grid. + for (x in 0..10) { + for (z in 0..10) { + // Translates the grid index into world position. + val xPos = START_X + x * (ROOM_SIZE shr 1) + val zPos = START_Z + z * (ROOM_SIZE shr 1) + + if (!MC.theWorld.getChunkFromChunkCoords(xPos shr 4, zPos shr 4).isLoaded) { + // The room being scanned has not been loaded in. + allChunksLoaded = false + continue + } + + // This room has already been added in a previous scan. + if (Dungeon.Info.dungeonList[x + z * 11].run { + this !is Unknown && (this as? Room)?.data?.name != "Unknown" + } + ) { + continue + } + + scanRoom(xPos, zPos, z, x)?.let { + val prev = Dungeon.Info.dungeonList[z * 11 + x] + if (it is Room) { + if ((prev as? Room)?.uniqueRoom != null) { + prev.uniqueRoom?.addTile(x, z, it) + } else if (Dungeon.Info.uniqueRooms.none { unique -> unique.name == it.data.name }) { + UniqueRoom(x, z, it) + } + MapUpdate.roomAdded = true + } + Dungeon.Info.dungeonList[z * 11 + x] = it + MapRenderList.renderUpdated = true + } + } + } + + if (MapUpdate.roomAdded) { + MapUpdate.updateUniques() + } + + if (allChunksLoaded) { + if (AwesomeMap.config.scanChatInfo) { + val maxSecrets = ceil(Dungeon.Info.secretCount * ScoreCalculation.getSecretPercent()) + var maxBonus = 5 + if (dungeonFloor.equalsOneOf(6, 7)) maxBonus += 2 + if (ScoreCalculation.paul) maxBonus += 10 + val minSecrets = ceil(maxSecrets * (40 - maxBonus) / 40).toInt() + + buildComponent { + "Scan Finished!\n".green + "Puzzles (".green + "${Dungeon.Info.puzzles.size}" + .red + "):".green + Dungeon.Info.puzzles.entries.forEach { puzzle -> + "\n- ".aqua + puzzle.key.roomDataName.lightPurple + } + "\nTrap: ".gold + Dungeon.Info.trapType.green + "\nWither Doors: ".darkGray + "${Dungeon.Info.witherDoors - 1}".gray + "\nTotal Crypts: ".gray + "${Dungeon.Info.cryptCount}".gold + "\nTotal Secrets: ".gray + "${Dungeon.Info.secretCount}".aqua + "\nMinimum Secrets: ".gray + "$minSecrets".yellow + }.also { + modMessage(it, LogLevel.INFO) + } + } + Dungeon.Info.roomCount = + Dungeon.Info.dungeonList + .filter { it is Room && !it.isSeparator } + .size + hasScanned = true + } + + lastScanTime = System.currentTimeMillis() + isScanning = false + } + + private fun scanRoom( + x: Int, + z: Int, + row: Int, + column: Int, + ): Tile? { + val height = MC.theWorld.getChunkFromChunkCoords(x shr 4, z shr 4).getHeightValue(x and 15, z and 15) + if (height == 0) return null + + val rowEven = row and 1 == 0 + val columnEven = column and 1 == 0 + + return when { + // Scanning a room + rowEven && columnEven -> { + val roomCore = ScanUtils.getCore(x, z) + Room(x, z, ScanUtils.getRoomData(roomCore) ?: return null).apply { + core = roomCore + } + } + + // Can only be the center "block" of a 2x2 room. + !rowEven && !columnEven -> { + Dungeon.Info.dungeonList[column - 1 + (row - 1) * 11].let { + if (it is Room) { + Room(x, z, it.data).apply { + isSeparator = true + } + } else { + null + } + } + } + + // Doorway between rooms + // Old trap has a single block at 82 + height.equalsOneOf(74, 82) -> { + Door( + x, + z, + // Finds door type from door block + type = + when (MC.theWorld.getBlockState(BlockPos(x, 69, z)).block) { + Blocks.coal_block -> { + Dungeon.Info.witherDoors++ + DoorType.WITHER + } + + Blocks.monster_egg -> DoorType.ENTRANCE + Blocks.stained_hardened_clay -> DoorType.BLOOD + else -> DoorType.NORMAL + }, + ) + } + + // Connection between large rooms + else -> { + Dungeon.Info.dungeonList[if (rowEven) row * 11 + column - 1 else (row - 1) * 11 + column].let { + if (it !is Room) { + null + } else if (it.data.type == RoomType.ENTRANCE) { + Door(x, z, DoorType.ENTRANCE) + } else { + Room(x, z, it.data).apply { isSeparator = true } + } + } + } + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapRender.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapRender.kt new file mode 100644 index 0000000..a411a3c --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapRender.kt @@ -0,0 +1,17 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap + +object MapRender { + var dynamicRotation = 0f + var legitPeek = false + set(value) { + if (field != value && AwesomeMap.config.legitMode) { + MapRenderList.renderUpdated = true + } + field = value + } + + val legitRender: Boolean + get() = AwesomeMap.config.legitMode && !legitPeek +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapRenderList.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapRenderList.kt new file mode 100644 index 0000000..8f8645f --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapRenderList.kt @@ -0,0 +1,233 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.minecraft.client.renderer.GlStateManager +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.* +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MapRender.dynamicRotation +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MapRender.legitRender +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location.inBoss +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils.connectorSize +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils.halfRoomSize +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils.roomSize +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.RenderUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.RenderUtils.darken +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.RenderUtils.grayScale +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import org.lwjgl.opengl.GL11 + +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 (AwesomeMap.config.mapShowRunInformation) 142.0 else 128.0, +// AwesomeMap.config.mapBackground.toJavaColor(), +// ) +// RenderUtilsGL.renderRectBorder( +// 0.0, +// 0.0, +// 128.0, +// if (AwesomeMap.config.mapShowRunInformation) 142.0 else 128.0, +// AwesomeMap.config.mapBorderWidth.toDouble(), +// AwesomeMap.config.mapBorder.toJavaColor(), +// ) +// GL11.glEndList() +// } +// +// if (renderUpdated && AwesomeMap.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() +// } +// +// GlStateManager.pushMatrix() +// RenderUtils.preDraw() +// RenderUtilsGL.preDraw() +// +// if (borderGlList != -1) GL11.glCallList(borderGlList) +// +// if (AwesomeMap.config.mapRotate) { +// GlStateManager.pushMatrix() +// MapRender.setupRotate() +// } else if (AwesomeMap.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 (roomGlList != -1) GL11.glCallList(roomGlList) +// +// RenderUtilsGL.unbindTexture() +// RenderUtils.postDraw() +// RenderUtilsGL.postDraw() +// GlStateManager.popMatrix() +// +// if (!inBoss) { +// MapRender.renderPlayerHeads() +// } +// +// if (AwesomeMap.config.mapRotate) { +// GL11.glDisable(GL11.GL_SCISSOR_TEST) +// GlStateManager.popMatrix() +// } else if (AwesomeMap.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 (AwesomeMap.config.mapShowRunInformation) { +// MapRender.renderRunInformation() +// } +// } +// +// 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 (AwesomeMap.config.mapDarkenUndiscovered) { +// color = color.darken(1 - AwesomeMap.config.mapDarkenPercent) +// } +// if (AwesomeMap.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 (AwesomeMap.config.mapCheckmark != 0 && AwesomeMap.config.mapRoomSecrets != 2) { +// RenderUtilsGL.drawCheckmark(xPosCheck, yPosCheck, room.state) +// } +// +// val color = +// if (AwesomeMap.config.mapColorText) { +// when (room.state) { +// RoomState.GREEN -> AwesomeMap.config.colorTextGreen +// RoomState.CLEARED -> AwesomeMap.config.colorTextCleared +// RoomState.FAILED -> AwesomeMap.config.colorTextFailed +// else -> AwesomeMap.config.colorTextUncleared +// } +// } else { +// AwesomeMap.config.colorTextCleared +// } +// +// if (AwesomeMap.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.toJavaColor()) +// GlStateManager.popMatrix() +// } +// +// val name = mutableListOf() +// +// if (AwesomeMap.config.mapRoomNames != 0 && +// room.data.type.equalsOneOf( +// RoomType.PUZZLE, +// RoomType.TRAP, +// ) || +// AwesomeMap.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 && AwesomeMap.config.mapRoomSecrets == 1) { +// name.add(room.data.secrets.toString()) +// } +// // Offset + half of roomsize +// RenderUtilsGL.renderCenteredText( +// name, +// xPosName.toInt() + halfRoomSize, +// yPosName.toInt() + halfRoomSize, +// color.toJavaColor(), +// ) +// } +// GlStateManager.translate(-MapUtils.startCorner.first.toFloat(), -MapUtils.startCorner.second.toFloat(), 0f) +// } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapUpdate.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapUpdate.kt new file mode 100644 index 0000000..7a39346 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MapUpdate.kt @@ -0,0 +1,259 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.minecraft.client.network.NetworkPlayerInfo +import net.minecraft.init.Blocks +import net.minecraft.util.BlockPos +import net.minecraft.util.StringUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.core.DungeonPlayer +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.* +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils.mapX +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils.mapZ +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.MapUtils.yaw +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.TabList +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import net.skymoe.enchaddons.util.MC +import net.skymoe.enchaddons.util.currPos +import net.skymoe.enchaddons.util.math.double + +object MapUpdate { + var roomAdded = false + + fun preloadHeads() { + val tabEntries = TabList.getDungeonTabList() ?: return + for (i in listOf(5, 9, 13, 17, 1)) { + // Accessing the skin locations to load in skin + tabEntries[i].first.locationSkin + } + } + + fun getPlayers() { + val tabEntries = TabList.getDungeonTabList() ?: return + Dungeon.dungeonTeammates.clear() + var iconNum = 0 + for (i in listOf(5, 9, 13, 17, 1)) { + with(tabEntries[i]) { + val name = + StringUtils + .stripControlCodes(second) + .trim() + .substringAfterLast("] ") + .split(" ")[0] + if (name != "") { + Dungeon.dungeonTeammates[name] = + DungeonPlayer(first.locationSkin, TabList.getPlayerUUIDByName(name)).apply { + MC.theWorld.getPlayerEntityByName(name)?.let { setData(it) } + colorPrefix = second.substringBefore(name, "f").last() + this.name = name + icon = "icon-$iconNum" + } + iconNum++ + } + } + } + } + + fun updatePlayers(tabEntries: List>) { + if (Dungeon.dungeonTeammates.isEmpty()) return + // Update map icons + val time = System.currentTimeMillis() - Dungeon.Info.startTime + var iconNum = 0 + for (i in listOf(5, 9, 13, 17, 1)) { + val tabText = StringUtils.stripControlCodes(tabEntries[i].second).trim() + val name = tabText.substringAfterLast("] ").split(" ")[0] + if (name == "") continue + Dungeon.dungeonTeammates[name]?.run { + dead = tabText.contains("(DEAD)") + if (dead) { + icon = "" + } else { + icon = "icon-$iconNum" + iconNum++ + } + if (!playerLoaded) { + MC.theWorld.getPlayerEntityByName(name)?.let { setData(it) } + } + + val room = getCurrentRoom() + if (room != "Error" || time > 1000) { + if (lastRoom == "") { + lastRoom = room + } else if (lastRoom != room) { + roomVisits.add(Pair(time - lastTime, lastRoom)) + lastTime = time + lastRoom = room + } + } + } + } + + val decor = MapUtils.mapData?.mapDecorations ?: return + Dungeon.dungeonTeammates.forEach { (name, player) -> + decor.entries.find { (icon, _) -> icon == player.icon }?.let { (_, vec4b) -> + player.isPlayer = vec4b.func_176110_a().toInt() == 1 + + val entityPlayer = MC.theWorld?.getPlayerEntityByUUID(player.uuid) + + player.prevYaw = player.yaw + player.prevPosition.mapX = player.position.mapX + player.prevPosition.mapZ = player.position.mapZ + + if (entityPlayer != null) { + player.yaw = entityPlayer.rotationYaw + player.position.mapX = + (entityPlayer.currPos.x - DungeonScan.START_X + 15) * MapUtils.coordMultiplier + MapUtils.startCorner.first + player.position.mapZ = + (entityPlayer.currPos.z - DungeonScan.START_X + 15) * MapUtils.coordMultiplier + MapUtils.startCorner.second + } else { + player.yaw = vec4b.yaw + player.position.mapX = vec4b.mapX.double + player.position.mapZ = vec4b.mapZ.double + } + } + if (player.isPlayer || name == MC.thePlayer.name) { + player.yaw = MC.thePlayer.rotationYaw + player.position.mapX = + ((MC.thePlayer.posX - DungeonScan.START_X + 15) * MapUtils.coordMultiplier + MapUtils.startCorner.first) + .double + player.position.mapZ = + ((MC.thePlayer.posZ - DungeonScan.START_Z + 15) * MapUtils.coordMultiplier + MapUtils.startCorner.second) + .double + } + } + } + + fun updateRooms() { + if (Dungeon.Info.ended) return + val map = DungeonMap(MapUtils.mapData?.colors ?: return) + Dungeon.espDoors.clear() + + for (x in 0..10) { + for (z in 0..10) { + val room = Dungeon.Info.dungeonList[z * 11 + x] + val mapTile = map.getTile(x, z) + + 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 + } + room.state = mapTile.state + } + + 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 + } + } + + 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( + room.x shr 4, + room.z shr 4, + ).isLoaded && + MC.theWorld.getBlockState(BlockPos(room.x, 69, room.z)).block == Blocks.air + ) { + MapRenderList.renderUpdated = true + room.opened = true + } + + if (!room.opened) { + Dungeon.espDoors.add(room) + } + } + } + } + + if (roomAdded) { + updateUniques() + } + } + + fun updateUniques() { + val visited = BooleanArray(121) + for (x in 0..10) { + for (z in 0..10) { + val index = z * 11 + x + if (visited[index]) continue + visited[index] = true + + val room = Dungeon.Info.dungeonList[index] + if (room !is Room) continue + + val connected = getConnectedIndices(x, z) + var unique = room.uniqueRoom + if (unique == null || unique.name.startsWith("Unknown")) { + unique = connected + .firstOrNull { + (Dungeon.Info.dungeonList[it.second * 11 + it.first] as? Room)?.uniqueRoom?.name?.startsWith("Unknown") == false + }?.let { + (Dungeon.Info.dungeonList[it.second * 11 + it.first] as? Room)?.uniqueRoom + } ?: unique + } + + val finalUnique = unique ?: UniqueRoom(x, z, room) + + finalUnique.addTiles(connected) + + connected.forEach { + visited[it.second * 11 + it.first] = true + } + } + } + roomAdded = false + } + + private fun getConnectedIndices( + arrayX: Int, + arrayY: Int, + ): List> { + val tile = Dungeon.Info.dungeonList[arrayY * 11 + arrayX] + if (tile !is Room) return emptyList() + val directions = + listOf( + Pair(0, 1), + Pair(1, 0), + Pair(0, -1), + Pair(-1, 0), + ) + val connected = mutableListOf>() + val queue = mutableListOf(Pair(arrayX, arrayY)) + while (queue.isNotEmpty()) { + val current = queue.removeFirst() + if (connected.contains(current)) continue + connected.add(current) + directions.forEach { + val x = current.first + it.first + val y = current.second + it.second + if (x !in 0..10 || y !in 0..10) return@forEach + if (Dungeon.Info.dungeonList[y * 11 + x] is Room) { + queue.add(Pair(x, y)) + } + } + } + return connected + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MimicDetector.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MimicDetector.kt new file mode 100644 index 0000000..7ea47da --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/MimicDetector.kt @@ -0,0 +1,68 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.minecraft.entity.Entity +import net.minecraft.entity.monster.EntityZombie +import net.minecraft.tileentity.TileEntityChest +import net.minecraft.util.BlockPos +import net.skymoe.enchaddons.EA +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMapEvent +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.ScanUtils.getRoomFromPos +import net.skymoe.enchaddons.util.MC + +object MimicDetector { + var mimicOpenTime = 0L + var mimicPos: BlockPos? = null + + fun checkMimicDead() { + if (RunInformation.mimicKilled) return + if (mimicOpenTime == 0L) return + if (System.currentTimeMillis() - mimicOpenTime < 750) return + if (MC.thePlayer.getDistanceSq(mimicPos) < 400) { + if (MC.theWorld.loadedEntityList.none { + it is EntityZombie && + it.isChild && + it + .getCurrentArmor(3) + ?.getSubCompound("SkullOwner", false) + ?.getString("Id") == "bcb486a4-0cb5-35db-93f0-039fbdde03f0" + } + ) { + setMimicKilled() + } + } + } + + fun setMimicKilled() { + RunInformation.mimicKilled = true + AwesomeMapEvent + .MimicKilled(RunInformation.timeElapsed) + .also(EA.eventDispatcher) + } + + fun isMimic(entity: Entity): Boolean { + if (entity is EntityZombie && entity.isChild) { + for (i in 0..3) { + if (entity.getCurrentArmor(i) != null) return false + } + return true + } + return false + } + + fun findMimic(): String? { + MC.theWorld.loadedTileEntityList + .filter { it is TileEntityChest && it.chestType == 1 } + .groupingBy { getRoomFromPos(it.pos)?.data?.name } + .eachCount() + .forEach { (room, trappedChests) -> + Dungeon.Info.uniqueRooms + .find { it.name == room && it.mainRoom.data.trappedChests < trappedChests } + ?.let { + it.hasMimic = true + MapRenderList.renderUpdated = true + return it.name + } + } + return null + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/PlayerTracker.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/PlayerTracker.kt new file mode 100644 index 0000000..841ac9f --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/PlayerTracker.kt @@ -0,0 +1,174 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import net.minecraft.event.HoverEvent +import net.minecraft.util.ChatStyle +import net.minecraft.util.IChatComponent +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.impl.feature.awesomemap.core.DungeonPlayer +import net.skymoe.enchaddons.impl.feature.awesomemap.core.RoomData +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.Room +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.RoomState +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.RoomType +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.Tile +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.APIUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import net.skymoe.enchaddons.util.LogLevel +import net.skymoe.enchaddons.util.buildComponent +import net.skymoe.enchaddons.util.modMessage +import kotlin.time.Duration.Companion.milliseconds + +object PlayerTracker { + val roomClears: MutableMap> = mutableMapOf() + + fun roomStateChange( + room: Tile, + state: RoomState, + newState: RoomState, + ) { + if (room !is Room) return + if (newState.equalsOneOf(RoomState.CLEARED, RoomState.GREEN) && state != RoomState.CLEARED) { + val currentRooms = + Dungeon.dungeonTeammates.map { Pair(it.value.formattedName, it.value.getCurrentRoom()) } + roomClears[room.data] = + currentRooms.filter { it.first != "" && it.second == room.data.name }.map { it.first }.toSet() + } + } + + fun onDungeonEnd() { + val time = System.currentTimeMillis() - Dungeon.Info.startTime + Dungeon.dungeonTeammates.forEach { + it.value.roomVisits.add(Pair(time - it.value.lastTime, it.value.lastRoom)) + } + + AwesomeMap.scope.launch { + Dungeon.dungeonTeammates + .map { (_, player) -> + async(Dispatchers.IO) { + Triple( + player.formattedName, + player, + player.uuid?.let { APIUtils.getSecrets(it) } ?: 0, + ) + } + }.map { + val (name, player, secrets) = it.await() + getStatMessage(name, player, secrets) + }.forEach { + modMessage(it, LogLevel.INFO) + } + } + } + + fun getStatMessage( + name: String, + player: DungeonPlayer, + secrets: Int, + ): IChatComponent { + val allClearedRooms = roomClears.filter { it.value.contains(name) } + val soloClearedRooms = allClearedRooms.filter { it.value.size == 1 } + val max = allClearedRooms.size + val min = soloClearedRooms.size + + val secretsComponent = + buildComponent { + "${secrets - player.startingSecrets} ".aqua + "secrets".darkAqua + } + + val roomComponent = + buildComponent { + "${if (soloClearedRooms.size != allClearedRooms.size) "$min-$max" else max} ".aqua + "rooms cleared".darkAqua + }.apply { + chatStyle = + ChatStyle().setChatHoverEvent( + HoverEvent( + HoverEvent.Action.SHOW_TEXT, + buildComponent { + roomClears.entries + .filter { + it.key.type !in arrayOf(RoomType.BLOOD, RoomType.ENTRANCE, RoomType.FAIRY) && + it.value.contains(name) + }.joinToString( + separator = "\n", + prefix = "$name's §eRooms Cleared:\n", + ) { (room, players) -> + if (players.size == 1) { + "§6${room.name}" + } else { + "§6${room.name} §7with ${ + players.filter { it != name }.joinToString(separator = "§r, ") + }" + } + }.append + }, + ), + ) + } + + var lastTime = 0L + + val splitsComponent = + buildComponent { + "Splits".darkAqua + }.apply { + chatStyle = + ChatStyle().setChatHoverEvent( + HoverEvent( + HoverEvent.Action.SHOW_TEXT, + buildComponent { + player.roomVisits + .joinToString( + separator = "\n", + prefix = "$name's §eRoom Splits:\n", + ) { (elapsed, room) -> + val start = lastTime.milliseconds + lastTime += elapsed + val end = lastTime.milliseconds + "§b$start §7- §b$end §6$room" + }.append + }, + ), + ) + } + + val roomTimeComponent = + buildComponent { + "Times".darkAqua + }.apply { + chatStyle = + ChatStyle().setChatHoverEvent( + HoverEvent( + HoverEvent.Action.SHOW_TEXT, + buildComponent { + player.roomVisits + .groupBy { it.second } + .entries + .joinToString( + separator = "\n", + prefix = "$name's §eRoom Times:\n", + ) { (room, times) -> + "§6$room §a- §b${times.sumOf { it.first }.milliseconds}" + }.append + }, + ), + ) + } + + return buildComponent { + name.darkAqua + " > ".white + secretsComponent.append + " | ".gold + roomComponent.append + " | ".gold + splitsComponent.append + " | ".gold + roomTimeComponent.append + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/RunInformation.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/RunInformation.kt new file mode 100644 index 0000000..f4723c8 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/RunInformation.kt @@ -0,0 +1,218 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.minecraft.client.network.NetworkPlayerInfo +import net.minecraft.entity.monster.EntityZombie +import net.minecraft.network.play.server.S38PacketPlayerListItem +import net.skymoe.enchaddons.event.minecraft.ChatEvent +import net.skymoe.enchaddons.event.minecraft.LivingEntityEvent +import net.skymoe.enchaddons.event.minecraft.TabListEvent +import net.skymoe.enchaddons.event.minecraft.TeamEvent +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.Puzzle +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.Room +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.RoomState +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MimicDetector.setMimicKilled +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.ScoreCalculation.getBonusScore +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.removeFormatting +import kotlin.math.ceil + +/** + * Many parts of this code are modified from [Skytils](https://github.com/Skytils/SkytilsMod/blob/1.x/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/ScoreCalculation.kt). + */ +object RunInformation { + var deathCount = 0 + val completedPuzzles: Int + get() = Dungeon.Info.puzzles.count { it.value } + var totalPuzzles = 0 + var cryptsCount = 0 + var secretsFound = 0 + var secretPercentage = 0f + val secretTotal: Int + get() = (secretsFound / (secretPercentage + 0.0001f) + 0.5).toInt() + var minSecrets = 0 + var mimicKilled = false + private var completedRooms = 0 + val completedRoomsPercentage + get() = + (completedRooms + (if (!Location.inBoss) 1 else 0) + (if (!bloodDone) 1 else 0)) / + (if (totalRooms == 0) 36 else totalRooms).toFloat() + var bloodDone = false + private val totalRooms: Int + get() = (completedRooms / (clearedPercentage + 0.0001f) + 0.4).toInt() + private var clearedPercentage = 0f + var timeElapsed = 0 + + private val deathsRegex = Regex("§r§a§lTeam Deaths: §r§f(?\\d+)§r") + private val puzzleCountRegex = Regex("§r§b§lPuzzles: §r§f\\((?\\d)\\)§r") + private val failedPuzzleRegex = Regex("§r (?.+): §r§7\\[§r§c§l✖§r§7] §.+") + private val solvedPuzzleRegex = Regex("§r (?.+): §r§7\\[§r§a§l✔§r§7] §.+") + private val cryptsPattern = Regex("§r Crypts: §r§6(?\\d+)§r") + private val secretsFoundPattern = Regex("§r Secrets Found: §r§b(?\\d+)§r") + private val secretsFoundPercentagePattern = Regex("§r Secrets Found: §r§[ae](?[\\d.]+)%§r") + private val roomCompletedPattern = Regex("§r Completed Rooms: §r§d(?\\d+)§r") + private val dungeonClearedPattern = Regex("Cleared: (?\\d+)% \\(\\d+\\)") + private val timeElapsedPattern = Regex("Time Elapsed: (?:(?\\d+)h )?(?:(?\\d+)m )?(?:(?\\d+)s)?") + + fun reset() { + deathCount = 0 + totalPuzzles = 0 + cryptsCount = 0 + secretsFound = 0 + secretPercentage = 0f + mimicKilled = false + completedRooms = 0 + bloodDone = false + clearedPercentage = 0f + timeElapsed = 0 + + ScoreCalculation.message270 = false + ScoreCalculation.message300 = false + + MimicDetector.mimicPos = null + MimicDetector.mimicOpenTime = 0L + } + + fun onScoreboard(event: TeamEvent.Pre.Update) { + val maxSecrets = ceil(secretTotal * ScoreCalculation.getSecretPercent()) + + minSecrets = + ceil(maxSecrets * (40 - getBonusScore() + ScoreCalculation.getDeathDeduction()) / 40).toInt() + + val line = + event.packet.players + .joinToString( + " ", + prefix = event.packet.prefix, + postfix = event.packet.suffix, + ).removeFormatting() + + if (line.startsWith("Cleared: ")) { + val match = dungeonClearedPattern.matchEntire(line)?.groups ?: return + clearedPercentage = (match["percentage"]?.value?.toFloatOrNull()?.div(100f)) ?: clearedPercentage + } else if (line.startsWith("Time Elapsed:")) { + val match = timeElapsedPattern.matchEntire(line)?.groups ?: return + val hours = match["hrs"]?.value?.toIntOrNull() ?: 0 + val minutes = match["min"]?.value?.toIntOrNull() ?: 0 + val seconds = match["sec"]?.value?.toIntOrNull() ?: 0 + timeElapsed = hours * 3600 + minutes * 60 + seconds + } + } + + fun onChat(event: ChatEvent) { + if (mimicKilled) return + if (event.message.startsWith("Party > ") || (event.message.contains(":") && !event.message.contains(">"))) { + listOf("\$SKYTILS-DUNGEON-SCORE-MIMIC\$", "mimic dead", "mimic killed").forEach { + if (event.message.contains(it, true)) { + mimicKilled = true + return + } + } + + listOf("blaze done", "blaze puzzle finished").forEach { + if (event.message.contains(it, true)) { + val puzzle = + Dungeon.Info.puzzles.keys + .find { puzzle -> puzzle.tabName == "Higher Or Lower" } ?: return + Dungeon.Info.puzzles[puzzle] = true + val room = + Dungeon.Info.dungeonList.firstOrNull { tile -> + tile is Room && tile.data.name.equalsOneOf("Lower Blaze", "Higher Blaze") + } ?: return + PlayerTracker.roomStateChange(room, room.state, RoomState.CLEARED) + room.state = RoomState.CLEARED + } + } + } + } + + fun onEntityDeath(event: LivingEntityEvent) { + if (event.entity !is EntityZombie || mimicKilled) return + if (MimicDetector.isMimic(event.entity)) { + setMimicKilled() + } + } + + fun onTabList(event: TabListEvent.Pre) { + if (event.packet.action !in + arrayOf( + S38PacketPlayerListItem.Action.UPDATE_DISPLAY_NAME, + S38PacketPlayerListItem.Action.ADD_PLAYER, + ) + ) { + return + } + event.packet.entries.forEach { + val text = it?.displayName?.formattedText ?: it?.profile?.name ?: return@forEach + updateFromTabList(text) + } + } + + private fun updateFromTabList(text: String) { + when { + text.contains("Team Deaths:") -> { + deathCount = deathsRegex.firstResult(text)?.toIntOrNull() ?: deathCount + } + + text.contains("✔") -> { + val puzzleName = solvedPuzzleRegex.firstResult(text) ?: return + if (puzzleName == "???") return + val puzzle = + Dungeon.Info.puzzles.keys + .find { it.tabName == puzzleName } + if (puzzle == null) { + if (Dungeon.Info.puzzles.size < totalPuzzles) { + Puzzle.fromName(puzzleName)?.let { Dungeon.Info.puzzles.putIfAbsent(it, true) } + } + } else { + Dungeon.Info.puzzles[puzzle] = true + } + } + + text.contains("✖") -> { + val puzzleName = failedPuzzleRegex.firstResult(text) ?: return + if (puzzleName == "???") return + val puzzle = + Dungeon.Info.puzzles.keys + .find { it.tabName == puzzleName } + if (puzzle == null) { + if (Dungeon.Info.puzzles.size < totalPuzzles) { + Puzzle.fromName(puzzleName)?.let { Dungeon.Info.puzzles.putIfAbsent(it, false) } + } + } else { + Dungeon.Info.puzzles[puzzle] = false + } + } + + text.contains("Crypts:") -> { + cryptsCount = cryptsPattern.firstResult(text)?.toIntOrNull() ?: cryptsCount + } + + text.contains("Secrets Found:") -> { + if (text.contains("%")) { + secretPercentage = + secretsFoundPercentagePattern.firstResult(text)?.toFloatOrNull()?.div(100f) ?: secretPercentage + } else { + secretsFound = secretsFoundPattern.firstResult(text)?.toIntOrNull() ?: secretsFound + } + } + + text.contains("Completed Rooms") -> { + completedRooms = roomCompletedPattern.firstResult(text)?.toIntOrNull() ?: completedRooms + } + } + } + + fun updatePuzzleCount(tabList: List>) { + if (totalPuzzles != 0) return + val puzzleCount = tabList.find { it.second.contains("Puzzles:") }?.second ?: return + totalPuzzles = puzzleCountRegex.firstResult(puzzleCount)?.toIntOrNull() ?: totalPuzzles + } + + private fun Regex.firstResult(input: CharSequence): String? = + this + .matchEntire(input) + ?.groups + ?.get(1) + ?.value +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/ScanUtils.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/ScanUtils.kt new file mode 100644 index 0000000..33d3bdd --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/ScanUtils.kt @@ -0,0 +1,81 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import com.google.gson.Gson +import com.google.gson.JsonIOException +import com.google.gson.JsonSyntaxException +import com.google.gson.reflect.TypeToken +import net.minecraft.block.Block +import net.minecraft.util.BlockPos +import net.skymoe.enchaddons.impl.MOD_ID +import net.skymoe.enchaddons.impl.feature.awesomemap.core.RoomData +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.Room +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import net.skymoe.enchaddons.util.MC +import kotlin.math.roundToInt + +object ScanUtils { + val roomList: Set = + try { + Gson().fromJson( + javaClass.getResource("/assets/$MOD_ID/awesomemap/rooms.json")!!.readBytes().toString(Charsets.UTF_8), + object : TypeToken>() {}.type, + ) + } catch (e: JsonSyntaxException) { + println("Error parsing FunnyMap room data.") + setOf() + } catch (e: JsonIOException) { + println("Error reading FunnyMap room data.") + setOf() + } + + fun getRoomData( + x: Int, + z: Int, + ): RoomData? = getRoomData(getCore(x, z)) + + fun getRoomData(hash: Int): RoomData? = roomList.find { hash in it.cores } + + fun getRoomCentre( + posX: Int, + posZ: Int, + ): Pair { + val roomX = ((posX - DungeonScan.START_X) / 32f).roundToInt() + val roomZ = ((posZ - DungeonScan.START_Z) / 32f).roundToInt() + return Pair(roomX * 32 + DungeonScan.START_X, roomZ * 32 + DungeonScan.START_Z) + } + + fun getRoomFromPos(pos: BlockPos): Room? { + val x = ((pos.x - DungeonScan.START_X + 15) shr 5) + val z = ((pos.z - DungeonScan.START_Z + 15) shr 5) + val room = Dungeon.Info.dungeonList.getOrNull(x * 2 + z * 22) + return if (room is Room) room else null + } + + fun getCore( + x: Int, + z: Int, + ): Int { + val sb = StringBuilder(150) + val chunk = MC.theWorld.getChunkFromChunkCoords(x shr 4, z shr 4) + val height = chunk.getHeightValue(x and 15, z and 15).coerceIn(11..140) + sb.append(CharArray(140 - height) { '0' }) + var bedrock = 0 + for (y in height downTo 12) { + val id = Block.getIdFromBlock(chunk.getBlock(BlockPos(x, y, z))) + if (id == 0 && bedrock >= 2 && y < 69) { + sb.append(CharArray(y - 11) { '0' }) + break + } + + if (id == 7) { + bedrock++ + } else { + bedrock = 0 + if (id.equalsOneOf(5, 54, 146)) continue + } + + sb.append(id) + } + return sb.toString().hashCode() + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/ScoreCalculation.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/ScoreCalculation.kt new file mode 100644 index 0000000..b068746 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/ScoreCalculation.kt @@ -0,0 +1,129 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.skymoe.enchaddons.EA +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMapEvent +import net.skymoe.enchaddons.feature.config.invoke +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.RunInformation.completedRoomsPercentage +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.RunInformation.mimicKilled +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.RunInformation.secretPercentage +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.APIUtils +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location +import kotlin.math.roundToInt + +object ScoreCalculation { + val paul = APIUtils.hasBonusPaulScore() + get() = field || AwesomeMap.config.paulBonus + var score = 0 + var message300 = false + var message270 = false + + fun updateScore() { + score = getSkillScore() + getExplorationScore() + getSpeedScore(RunInformation.timeElapsed) + getBonusScore() + if (score >= 300 && !message300) { + message300 = true + message270 = true + AwesomeMapEvent.Score + .Reach300(RunInformation.timeElapsed) + .also(EA.eventDispatcher) + } else if (score >= 270 && !message270) { + message270 = true + AwesomeMapEvent.Score + .Reach270(RunInformation.timeElapsed) + .also(EA.eventDispatcher) + } + } + + fun getSkillScore(): Int { + val puzzleDeduction = (RunInformation.totalPuzzles - RunInformation.completedPuzzles) * 10 + val roomPercent = completedRoomsPercentage.coerceAtMost(1f) + return 20 + ((80 * roomPercent).toInt() - puzzleDeduction - getDeathDeduction()).coerceAtLeast(0) + } + + fun getDeathDeduction(): Int { + var deathDeduction = RunInformation.deathCount * 2 + if (AwesomeMap.config.scoreAssumeSpirit) deathDeduction -= 1 + return deathDeduction.coerceAtLeast(0) + } + + fun getExplorationScore(): Int { + val secretPercent = (secretPercentage / getSecretPercent()).coerceAtMost(1f) + val roomPercent = completedRoomsPercentage.coerceAtMost(1f) + return (60 * roomPercent + 40 * secretPercent).toInt() + } + + fun getSpeedScore(timeElapsed: Int): Int { + var score = 100 + val limit = getTimeLimit() + if (timeElapsed < limit) return score + val percentageOver = (timeElapsed - limit) * 100f / limit + score -= getSpeedDeduction(percentageOver).toInt() + return if (Location.dungeonFloor == 0) (score * 0.7).roundToInt() else score + } + + fun getBonusScore(): Int { + var score = 0 + score += RunInformation.cryptsCount.coerceAtMost(5) + if (mimicKilled) score += 2 + if (paul) score += 10 + return score + } + + fun getSecretPercent(): Float { + if (Location.masterMode) return 1f + return when (Location.dungeonFloor) { + 0 -> .3f + 1 -> .3f + 2 -> .4f + 3 -> .5f + 4 -> .6f + 5 -> .7f + 6 -> .85f + else -> 1f + } + } + + private fun getTimeLimit(): Int = + if (Location.masterMode) { + when (Location.dungeonFloor) { + 1, 2, 3, 4, 5 -> 480 + 6 -> 600 + else -> 840 + } + } else { + when (Location.dungeonFloor) { + 0 -> 1320 + 1, 2, 3, 5 -> 600 + 4, 6 -> 720 + else -> 840 + } + } + + /** + * This is a very ugly function, but it works. + * The formula on the wiki doesn't seem to work, this variation should never be more than 2 points off. + */ + private fun getSpeedDeduction(percentage: Float): Float { + var percentageOver = percentage + var deduction = 0f + + deduction += (percentageOver.coerceAtMost(20f) / 2f) + percentageOver -= 20f + if (percentageOver <= 0) return deduction + + deduction += (percentageOver.coerceAtMost(20f) / 3.5f) + percentageOver -= 20f + if (percentageOver <= 0) return deduction + + deduction += (percentageOver.coerceAtMost(10f) / 4f) + percentageOver -= 10f + if (percentageOver <= 0) return deduction + + deduction += (percentageOver.coerceAtMost(10f) / 5f) + percentageOver -= 10f + if (percentageOver <= 0) return deduction + + deduction += (percentageOver / 6f) + return deduction + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/WitherDoorESP.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/WitherDoorESP.kt new file mode 100644 index 0000000..2a374b1 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/features/dungeon/WitherDoorESP.kt @@ -0,0 +1,39 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon + +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.util.AxisAlignedBB +import net.skymoe.enchaddons.event.minecraft.RenderEvent +import net.skymoe.enchaddons.impl.config.EnchAddonsConfig +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.RoomState +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location.inBoss +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.RenderUtils.getInterpolatedPosition +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.RenderUtils3D +import net.skymoe.enchaddons.util.MC + +object WitherDoorESP { + fun onRender(event: RenderEvent.World.Last) { + if (inBoss || EnchAddonsConfig.dungeonConfig.awesomeMapConfig.witherDoorESP == 0) return + + val (x, y, z) = MC.renderViewEntity.getInterpolatedPosition(event.partialTicks) + GlStateManager.translate(-x, -y, -z) + Dungeon.espDoors.forEach { door -> + if (EnchAddonsConfig.dungeonConfig.awesomeMapConfig.witherDoorESP == 1 && door.state == RoomState.UNDISCOVERED) return@forEach + val aabb = AxisAlignedBB(door.x - 1.0, 69.0, door.z - 1.0, door.x + 2.0, 73.0, door.z + 2.0) + RenderUtils3D.drawBox( + aabb, + if (Dungeon.Info.keys > 0) { + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.witherDoorKeyColor + .toJavaColor() + } else { + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.witherDoorNoKeyColor + .toJavaColor() + }, + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.witherDoorOutlineWidth, + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.witherDoorOutline, + EnchAddonsConfig.dungeonConfig.awesomeMapConfig.witherDoorFill, + true, + ) + } + GlStateManager.translate(x, y, z) + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/ui/ScoreElement.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/ui/ScoreElement.kt new file mode 100644 index 0000000..b9eff0c --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/ui/ScoreElement.kt @@ -0,0 +1,185 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.ui + +import net.minecraft.client.gui.FontRenderer +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.impl.config.EnchAddonsConfig +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.RunInformation +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.ScoreCalculation +import net.skymoe.enchaddons.util.MC + +class ScoreElement { + companion object { + val fr: FontRenderer = MC.fontRendererObj + + fun getScoreLines(): List { + val list: MutableList = mutableListOf() + + when (AwesomeMap.config.scoreTotalScore) { + 1 -> list.add(getScore(AwesomeMap.config.scoreMinimizedName, false)) + 2 -> list.add(getScore(AwesomeMap.config.scoreMinimizedName, true)) + } + + when (AwesomeMap.config.scoreSecrets) { + 1 -> list.add(getSecrets(AwesomeMap.config.scoreMinimizedName, false)) + 2 -> list.add(getSecrets(AwesomeMap.config.scoreMinimizedName, true)) + } + + if (AwesomeMap.config.scoreCrypts) { + list.add(getCrypts(AwesomeMap.config.scoreMinimizedName)) + } + + if (AwesomeMap.config.scoreMimic) { + list.add(getMimic(AwesomeMap.config.scoreMinimizedName)) + } + + if (AwesomeMap.config.scoreDeaths) { + list.add(getDeaths(AwesomeMap.config.scoreMinimizedName)) + } + + when (AwesomeMap.config.scorePuzzles) { + 1 -> list.add(getPuzzles(AwesomeMap.config.scoreMinimizedName, false)) + 2 -> list.add(getPuzzles(AwesomeMap.config.scoreMinimizedName, true)) + } + + return list + } + + fun runInformationLines(): List { + val list: MutableList = mutableListOf() + + if (AwesomeMap.config.runInformationScore) { + list.add(getScore(minimized = false, expanded = false)) + } + + when (AwesomeMap.config.runInformationSecrets) { + 1 -> list.add(getSecrets(minimized = false, missing = false)) + 2 -> list.add(getSecrets(minimized = false, missing = true)) + } + + list.add("split") + + if (AwesomeMap.config.runInformationCrypts) { + list.add(getCrypts()) + } + + if (AwesomeMap.config.runInformationMimic) { + list.add(getMimic()) + } + + if (AwesomeMap.config.runInformationDeaths) { + list.add(getDeaths()) + } + + return list + } + + private fun getScore( + minimized: Boolean = false, + expanded: Boolean, + ): String { + val scoreColor = + when { + ScoreCalculation.score < 270 -> "§c" + ScoreCalculation.score < 300 -> "§e" + else -> "§a" + } + var line = + if (minimized) { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMinimizedScore}: " + } else { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textScore}: " + } + if (expanded) { + line += "§b${ScoreCalculation.getSkillScore()}§7/" + + "§a${ScoreCalculation.getExplorationScore()}§7/" + + "§3${ScoreCalculation.getSpeedScore(RunInformation.timeElapsed)}§7/" + + "§d${ScoreCalculation.getBonusScore()} §7: " + } + line += "$scoreColor${ScoreCalculation.score}" + + return line + } + + private fun getSecrets( + minimized: Boolean = false, + missing: Boolean, + ): String { + var line = + if (minimized) { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMinimizedSecrets}: " + } else { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textSecrets}: " + } + line += "§b${RunInformation.secretsFound}§7/" + if (missing) { + val missingSecrets = (RunInformation.minSecrets - RunInformation.secretsFound).coerceAtLeast(0) + line += "§e$missingSecrets§7/" + } + line += "§c${RunInformation.secretTotal}" + + return line + } + + private fun getCrypts(minimized: Boolean = false): String { + var line = + if (minimized) { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMinimizedCrypts}: " + } else { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textCrypts}: " + } + line += if (RunInformation.cryptsCount >= 5) "§a${RunInformation.cryptsCount}" else "§c${RunInformation.cryptsCount}" + return line + } + + private fun getMimic(minimized: Boolean = false): String { + var line = + if (minimized) { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMinimizedMimic}: " + } else { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMimic}: " + } + line += + if (RunInformation.mimicKilled) { + if (minimized) { + "§a${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMinimizedMimicYes}" + } else { + "§a${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMimicYes}" + } + } else { + if (minimized) { + "§c${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMinimizedMimicNo}" + } else { + "§c${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMimicNo}" + } + } + return line + } + + private fun getDeaths(minimized: Boolean = false): String { + var line = + if (minimized) { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMinimizedDeaths}: " + } else { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textDeaths}: " + } + line += "§c${RunInformation.deathCount}" + return line + } + + private fun getPuzzles( + minimized: Boolean = false, + total: Boolean, + ): String { + val color = if (RunInformation.completedPuzzles == RunInformation.totalPuzzles) "§a" else "§c" + var line = + if (minimized) { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textMinimizedPuzzles}: " + } else { + "§7${EnchAddonsConfig.dungeonConfig.awesomeMapConfig.textPuzzles}: " + } + line += "$color${RunInformation.completedPuzzles}" + if (total) line += "§7/$color${RunInformation.totalPuzzles}" + return line + } + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/APIUtils.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/APIUtils.kt new file mode 100644 index 0000000..524f007 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/APIUtils.kt @@ -0,0 +1,43 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import com.google.gson.JsonParser +import net.skymoe.enchaddons.util.scope.noexcept +import org.apache.http.client.methods.HttpGet +import org.apache.http.impl.client.HttpClients +import org.apache.http.util.EntityUtils +import java.util.UUID + +object APIUtils { + fun fetch(uri: String): String? { + HttpClients.createMinimal().use { + return noexcept { + val httpGet = HttpGet(uri) + return EntityUtils.toString(it.execute(httpGet).entity) + } + } + } + + fun getSecrets(uuid: UUID): Int { + repeat(3) { + val result = fetch("https://api.tenios.dev/secrets/$uuid")?.toIntOrNull() + if (result != null) return result + } + + return 0 + } + + fun hasBonusPaulScore(): Boolean { + val response = fetch("https://api.hypixel.net/resources/skyblock/election") ?: return false + val jsonObject = JsonParser().parse(response).toJsonObject() ?: return false + if (jsonObject.getJsonPrimitive("success")?.asBoolean == true) { + val mayor = jsonObject.getJsonObject("mayor") ?: return false + val name = mayor.getJsonPrimitive("name")?.asString + if (name == "Paul") { + return mayor.getJsonArray("perks")?.any { + it.toJsonObject()?.getJsonPrimitive("name")?.asString == "EZPZ" + } ?: false + } + } + return false + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/CheckmarkSet.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/CheckmarkSet.kt new file mode 100644 index 0000000..273ecd0 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/CheckmarkSet.kt @@ -0,0 +1,32 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import net.minecraft.client.renderer.texture.SimpleTexture +import net.minecraft.util.ResourceLocation +import net.skymoe.enchaddons.impl.feature.awesomemap.core.map.RoomState +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.MapRender +import net.skymoe.enchaddons.util.MC + +class CheckmarkSet( + val size: Int, + location: String, +) { + private val crossResource = ResourceLocation("funnymap", "$location/cross.png") + private val greenResource = ResourceLocation("funnymap", "$location/green_check.png") + private val questionResource = ResourceLocation("funnymap", "$location/question.png") + private val whiteResource = ResourceLocation("funnymap", "$location/white_check.png") + + init { + listOf(crossResource, greenResource, questionResource, whiteResource).forEach { + MC.textureManager.loadTexture(it, SimpleTexture(it)) + } + } + + fun getCheckmark(state: RoomState): ResourceLocation? = + when (state) { + RoomState.CLEARED -> whiteResource + RoomState.GREEN -> greenResource + RoomState.FAILED -> crossResource + RoomState.UNOPENED -> if (MapRender.legitRender) questionResource else null + else -> null + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/JsonUtils.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/JsonUtils.kt new file mode 100644 index 0000000..75159fe --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/JsonUtils.kt @@ -0,0 +1,18 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive + +fun JsonElement.toJsonArray(): JsonArray? = this as? JsonArray + +fun JsonElement.toJsonObject(): JsonObject? = this as? JsonObject + +fun JsonElement.toJsonPrimitive(): JsonPrimitive? = this as? JsonPrimitive + +fun JsonObject.getJsonArray(member: String): JsonArray? = this.get(member)?.toJsonArray() + +fun JsonObject.getJsonObject(member: String): JsonObject? = this.get(member)?.toJsonObject() + +fun JsonObject.getJsonPrimitive(member: String): JsonPrimitive? = this.get(member)?.toJsonPrimitive() diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/KotlinAdapter.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/KotlinAdapter.kt new file mode 100644 index 0000000..fb18e53 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/KotlinAdapter.kt @@ -0,0 +1,33 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import net.minecraftforge.fml.common.FMLModContainer +import net.minecraftforge.fml.common.ILanguageAdapter +import net.minecraftforge.fml.common.ModContainer +import net.minecraftforge.fml.relauncher.Side +import java.lang.reflect.Field +import java.lang.reflect.Method + +class KotlinAdapter : ILanguageAdapter { + override fun supportsStatics(): Boolean = false + + override fun getNewInstance( + container: FMLModContainer?, + objectClass: Class<*>, + classLoader: ClassLoader?, + factoryMarkedAnnotation: Method?, + ): Any = objectClass.kotlin.objectInstance ?: objectClass.getDeclaredConstructor().newInstance() + + override fun setProxy( + target: Field, + proxyTarget: Class<*>, + proxy: Any?, + ) { + target.set(proxyTarget.kotlin.objectInstance, proxy) + } + + override fun setInternalProxies( + mod: ModContainer?, + side: Side?, + loader: ClassLoader?, + ) {} +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Location.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Location.kt new file mode 100644 index 0000000..0e9d0a3 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Location.kt @@ -0,0 +1,108 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import net.skymoe.enchaddons.event.minecraft.ChatEvent +import net.skymoe.enchaddons.event.minecraft.MinecraftEvent +import net.skymoe.enchaddons.feature.awesomemap.AwesomeMap +import net.skymoe.enchaddons.util.MC + +object Location { + private var onHypixel = false + var inSkyblock = false + var island = Island.Unknown + val inDungeons + get() = island == Island.Dungeon + var dungeonFloor = -1 + var masterMode = false + var inBoss = false + + private var islandRegex = Regex("^§r§b§l(?:Area|Dungeon): §r§7(.+)§r\$") + + private val entryMessages = + listOf( + "[BOSS] Bonzo: Gratz for making it this far, but I'm basically unbeatable.", + "[BOSS] Scarf: This is where the journey ends for you, Adventurers.", + "[BOSS] The Professor: I was burdened with terrible news recently...", + "[BOSS] Thorn: Welcome Adventurers! I am Thorn, the Spirit! And host of the Vegan Trials!", + "[BOSS] Livid: Welcome, you've arrived right on time. I am Livid, the Master of Shadows.", + "[BOSS] Sadan: So you made it all the way here... Now you wish to defy me? Sadan?!", + ) + + private var tickCount = 0 + + fun onTick() { + if (MC.theWorld == null) return + tickCount++ + if (tickCount % 20 != 0) return + if (AwesomeMap.config.forceSkyblock) { + inSkyblock = true + island = Island.Dungeon + dungeonFloor = 7 + return + } + + inSkyblock = onHypixel && + MC.theWorld.scoreboard + ?.getObjectiveInDisplaySlot(1) + ?.name == "SBScoreboard" + + if (island == Island.Unknown) { + TabList + .getTabList() + .firstNotNullOfOrNull { islandRegex.find(it.second) } + ?.groupValues + ?.getOrNull(1) + ?.let { areaName -> + Island.entries.find { it.displayName == areaName }?.let { island = it } + } + } + + if (island == Island.Dungeon && dungeonFloor == -1) { + Scoreboard + .getLines() + .find { + Scoreboard.cleanLine(it).run { + contains("The Catacombs (") && !contains("Queue") + } + }?.let { + val line = it.substringBefore(")") + dungeonFloor = line.lastOrNull()?.digitToIntOrNull() ?: 0 + masterMode = line[line.length - 2] == 'M' + } + } + } + + fun onChat(event: ChatEvent) { + if (event.message.startsWith("[BOSS] Maxor: ")) inBoss = true + if (entryMessages.any { it == event.message }) inBoss = true + } + + fun onWorldUnload(event: MinecraftEvent.World.Unload) { + island = Island.Unknown + dungeonFloor = -1 + inBoss = false + } + + enum class Island( + val displayName: String, + ) { + PrivateIsland("Private Island"), + Garden("Garden"), + SpiderDen("Spider's Den"), + CrimsonIsle("Crimson Isle"), + TheEnd("The End"), + GoldMine("Gold Mine"), + DeepCaverns("Deep Caverns"), + DwarvenMines("Dwarven Mines"), + GlaciteMineshaft("Mineshaft"), + CrystalHollows("Crystal Hollows"), + FarmingIsland("The Farming Islands"), + ThePark("The Park"), + Dungeon("Catacombs"), + DungeonHub("Dungeon Hub"), + Hub("Hub"), + DarkAuction("Dark Auction"), + JerryWorkshop("Jerry's Workshop"), + Kuudra("Kuudra"), + Unknown("(Unknown)"), + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/MapUtils.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/MapUtils.kt new file mode 100644 index 0000000..a0e8790 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/MapUtils.kt @@ -0,0 +1,97 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import net.minecraft.item.ItemMap +import net.minecraft.item.ItemStack +import net.minecraft.util.Vec4b +import net.minecraft.world.storage.MapData +import net.skymoe.enchaddons.event.minecraft.MapEvent +import net.skymoe.enchaddons.impl.feature.awesomemap.features.dungeon.DungeonScan +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Location.inDungeons +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.equalsOneOf +import net.skymoe.enchaddons.util.MC + +object MapUtils { + val Vec4b.mapX + get() = (this.func_176112_b() + 128) shr 1 + + val Vec4b.mapZ + get() = (this.func_176113_c() + 128) shr 1 + + val Vec4b.yaw + get() = this.func_176111_d() * 22.5f + + var mapData: MapData? = null + var startCorner = Pair(5, 5) + var coordMultiplier = 0.625 + var roomSize = 16 + var halfRoomSize = roomSize / 2 + val connectorSize = 4 + var calibrated = false + var mapDataUpdated = false + + private fun getMapItem(): ItemStack? { + val map = MC.thePlayer?.inventory?.getStackInSlot(8) ?: return null + if (map.item !is ItemMap || !map.displayName.contains("Magical Map")) return null + return map + } + + fun onUpdateMapData(event: MapEvent.Pre) { + if (!inDungeons) return + Utils.runMinecraftThread { + val map = getMapItem() + if (map != null) { + mapData = (map.item as ItemMap).getMapData(map, MC.theWorld) + } + if (mapData == null) { + mapData = MapData("map_${event.packet.mapId}") + } + event.packet.setMapdataTo(mapData) + mapDataUpdated = true + } + } + + /** + * Calibrates map metrics based on the size and location of the entrance room. + */ + fun calibrateMap(): Boolean { + val (start, size) = findEntranceCorner() + if (size.equalsOneOf(16, 18)) { + roomSize = size + halfRoomSize = roomSize / 2 + startCorner = + when (Location.dungeonFloor) { + 0 -> Pair(22, 22) + 1 -> Pair(22, 11) + 2, 3 -> Pair(11, 11) + else -> { + val startX = start and 127 + val startZ = start shr 7 + Pair(startX % (roomSize + 4), startZ % (roomSize + 4)) + } + } + coordMultiplier = (roomSize + connectorSize).toDouble() / DungeonScan.ROOM_SIZE + return true + } + return false + } + + /** + * Finds the starting index of the entrance room as well as the size of the room. + */ + private fun findEntranceCorner(): Pair { + var start = 0 + var currLength = 0 + mapData?.colors?.forEachIndexed { index, byte -> + if (byte.toInt() == 30) { + if (currLength == 0) start = index + currLength++ + } else { + if (currLength >= 16) { + return Pair(start, currLength) + } + currLength = 0 + } + } + return Pair(start, currLength) + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/RenderUtils.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/RenderUtils.kt new file mode 100644 index 0000000..8f71900 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/RenderUtils.kt @@ -0,0 +1,22 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import net.minecraft.entity.Entity +import java.awt.Color +import kotlin.math.roundToInt + +object RenderUtils { + fun Color.grayScale(): Color { + val gray = (red * 0.299 + green * 0.587 + blue * 0.114).roundToInt() + return Color(gray, gray, gray, alpha) + } + + fun Color.darken(factor: Float): Color = + Color((red * factor).roundToInt(), (green * factor).roundToInt(), (blue * factor).roundToInt(), alpha) + + fun Entity.getInterpolatedPosition(partialTicks: Float): Triple = + Triple( + this.lastTickPosX + (this.posX - this.lastTickPosX) * partialTicks, + this.lastTickPosY + (this.posY - this.lastTickPosY) * partialTicks, + this.lastTickPosZ + (this.posZ - this.lastTickPosZ) * partialTicks, + ) +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/RenderUtils3D.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/RenderUtils3D.kt new file mode 100644 index 0000000..bb9ebde --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/RenderUtils3D.kt @@ -0,0 +1,152 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.client.renderer.vertex.DefaultVertexFormats +import net.minecraft.util.AxisAlignedBB +import net.skymoe.enchaddons.util.withAlpha +import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL11.GL_LINE_STRIP +import org.lwjgl.opengl.GL11.GL_QUADS +import java.awt.Color + +object RenderUtils3D { + private val tessellator: Tessellator = Tessellator.getInstance() + private val worldRenderer: WorldRenderer = tessellator.worldRenderer + + fun preDraw() { + GlStateManager.enableAlpha() + GlStateManager.enableBlend() + GlStateManager.disableDepth() + GlStateManager.disableLighting() + GlStateManager.disableTexture2D() + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + } + + fun postDraw() { + GlStateManager.disableBlend() + GlStateManager.enableDepth() + GlStateManager.enableTexture2D() + } + + fun drawBox( + aabb: AxisAlignedBB, + color: Color, + width: Float, + outline: Float, + fill: Float, + ignoreDepth: Boolean, + ) { + GlStateManager.pushMatrix() + preDraw() + GlStateManager.depthMask(!ignoreDepth) + GL11.glLineWidth(width) + + drawOutlinedAABB(aabb, color) + + drawFilledAABB(aabb, color.withAlpha(fill)) + + GlStateManager.depthMask(true) + postDraw() + GlStateManager.popMatrix() + } + + fun drawFilledAABB( + aabb: AxisAlignedBB, + color: Color, + ) { + color.bind() + + worldRenderer.begin(GL_QUADS, DefaultVertexFormats.POSITION) + + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + tessellator.draw() + } + + fun drawOutlinedAABB( + aabb: AxisAlignedBB, + color: Color, + ) { + color.bind() + + worldRenderer.begin(GL_LINE_STRIP, DefaultVertexFormats.POSITION) + + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.minZ).endVertex() + + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.minZ).endVertex() + + worldRenderer.pos(aabb.minX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.minX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.maxZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.maxY, aabb.minZ).endVertex() + worldRenderer.pos(aabb.maxX, aabb.minY, aabb.minZ).endVertex() + + tessellator.draw() + } + + fun Color.bind() { + GlStateManager.color(this.red / 255f, this.green / 255f, this.blue / 255f, this.alpha / 255f) + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Scoreboard.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Scoreboard.kt new file mode 100644 index 0000000..2b57e69 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Scoreboard.kt @@ -0,0 +1,18 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import net.minecraft.scoreboard.ScorePlayerTeam +import net.skymoe.enchaddons.impl.feature.awesomemap.utils.Utils.removeFormatting +import net.skymoe.enchaddons.util.MC + +object Scoreboard { + fun cleanLine(scoreboard: String): String = scoreboard.removeFormatting().filter { it.code in 32..126 } + + fun getLines(): List { + return MC.theWorld?.scoreboard?.run { + getSortedScores(getObjectiveInDisplaySlot(1) ?: return emptyList()) + .filter { it?.playerName?.startsWith("#") == false } + .let { if (it.size > 15) it.drop(15) else it } + .map { ScorePlayerTeam.formatPlayerName(getPlayersTeam(it.playerName), it.playerName) } + } ?: emptyList() + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/TabList.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/TabList.kt new file mode 100644 index 0000000..8a8dc9b --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/TabList.kt @@ -0,0 +1,46 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import com.google.common.collect.ComparisonChain +import com.google.common.collect.Ordering +import net.minecraft.client.network.NetworkPlayerInfo +import net.minecraft.world.WorldSettings +import net.skymoe.enchaddons.util.MC +import java.util.UUID + +object TabList { + private val tabListOrder = + Ordering.from { o1, o2 -> + if (o1 == null) return@from -1 + if (o2 == null) return@from 0 + return@from ComparisonChain + .start() + .compareTrueFirst( + o1.gameType != WorldSettings.GameType.SPECTATOR, + o2.gameType != WorldSettings.GameType.SPECTATOR, + ).compare( + o1.playerTeam?.registeredName ?: "", + o2.playerTeam?.registeredName ?: "", + ).compare(o1.gameProfile.name, o2.gameProfile.name) + .result() + } + + fun getTabList(): List> = + MC.thePlayer + ?.sendQueue + ?.playerInfoMap + ?.let { tabListOrder.immutableSortedCopy(it) } + ?.map { Pair(it, MC.ingameGUI.tabList.getPlayerName(it)) } ?: emptyList() + + fun getDungeonTabList(): List>? = + getTabList().let { + if (it.size > 18 && it[0].second.contains("§r§b§lParty §r§f(")) it else null + } + + fun getPlayerUUIDByName(name: String): UUID? = + getTabList() + .firstOrNull { + it.first.gameProfile.name == name + }?.first + ?.gameProfile + ?.id +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Utils.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Utils.kt new file mode 100644 index 0000000..257be65 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/feature/awesomemap/utils/Utils.kt @@ -0,0 +1,22 @@ +package net.skymoe.enchaddons.impl.feature.awesomemap.utils + +import net.minecraft.item.ItemStack +import net.minecraft.util.StringUtils +import net.skymoe.enchaddons.util.MC + +object Utils { + fun Any?.equalsOneOf(vararg other: Any): Boolean = other.any { this == it } + + fun runMinecraftThread(run: () -> Unit) { + if (!MC.isCallingFromMinecraftThread) { + MC.addScheduledTask(run) + } else { + run() + } + } + + fun String.removeFormatting(): String = StringUtils.stripControlCodes(this) + + val ItemStack.itemID: String + get() = this.getSubCompound("ExtraAttributes", false)?.getString("id") ?: "" +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/EntityLivingBaseMixinCallback.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/EntityLivingBaseMixinCallback.kt new file mode 100644 index 0000000..0e59f24 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/EntityLivingBaseMixinCallback.kt @@ -0,0 +1,15 @@ +package net.skymoe.enchaddons.impl.mixincallback + +import net.minecraft.entity.EntityLivingBase +import net.minecraft.util.DamageSource +import net.skymoe.enchaddons.EA +import net.skymoe.enchaddons.event.minecraft.LivingEntityEvent + +fun onLivingEntityDeathPre( + entity: EntityLivingBase, + cause: DamageSource, +) { + LivingEntityEvent + .Death(entity, cause) + .also(EA.eventDispatcher) +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/ForgeHooksClientMixinCallback.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/ForgeHooksClientMixinCallback.kt new file mode 100644 index 0000000..b5ab058 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/ForgeHooksClientMixinCallback.kt @@ -0,0 +1,16 @@ +package net.skymoe.enchaddons.impl.mixincallback + +import net.minecraft.client.renderer.RenderGlobal +import net.skymoe.enchaddons.EA +import net.skymoe.enchaddons.event.minecraft.RenderEvent + +object ForgeHooksClientMixinCallback { + fun onRenderWorldLast( + context: RenderGlobal, + partialTicks: Float, + ) { + RenderEvent.World + .Last(context, partialTicks) + .also(EA.eventDispatcher) + } +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/MinecraftMixinCallback.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/MinecraftMixinCallback.kt index 9f01929..8c0c1d9 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/MinecraftMixinCallback.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/MinecraftMixinCallback.kt @@ -8,6 +8,8 @@ import net.skymoe.enchaddons.impl.config.EnchAddonsConfig import net.skymoe.enchaddons.util.tickCounter fun startGamePost() { + MinecraftEvent.Load.Post + .also(EA.eventDispatcher) } fun onRunTickPre() { diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/NetHandlerPlayClientCallback.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/NetHandlerPlayClientCallback.kt index 0478046..6a926f8 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/NetHandlerPlayClientCallback.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/mixincallback/NetHandlerPlayClientCallback.kt @@ -2,9 +2,15 @@ package net.skymoe.enchaddons.impl.mixincallback import net.minecraft.network.play.server.S02PacketChat import net.minecraft.network.play.server.S32PacketConfirmTransaction +import net.minecraft.network.play.server.S34PacketMaps +import net.minecraft.network.play.server.S38PacketPlayerListItem +import net.minecraft.network.play.server.S3EPacketTeams import net.skymoe.enchaddons.EA import net.skymoe.enchaddons.event.hypixel.SkyblockEvent import net.skymoe.enchaddons.event.minecraft.ChatEvent +import net.skymoe.enchaddons.event.minecraft.MapEvent +import net.skymoe.enchaddons.event.minecraft.TabListEvent +import net.skymoe.enchaddons.event.minecraft.TeamEvent import net.skymoe.enchaddons.util.math.int import net.skymoe.enchaddons.util.trimStyle @@ -17,6 +23,7 @@ object NetHandlerPlayClientCallback { .Pre( packet.chatComponent.formattedText.trimStyle, packet.chatComponent.formattedText, + packet.chatComponent, ).also(EA.eventDispatcher) } } @@ -26,4 +33,27 @@ object NetHandlerPlayClientCallback { SkyblockEvent.ServerTick .also(EA.eventDispatcher) } + + fun onS38PacketPlayerListItemPre(packet: S38PacketPlayerListItem) { + TabListEvent + .Pre(packet) + .also(EA.eventDispatcher) + } + + fun onS3EPacketTeamsPre(packet: S3EPacketTeams) { + when (packet.action) { + 0 -> TeamEvent.Pre.Create(packet) + 1 -> TeamEvent.Pre.Remove(packet) + 2 -> TeamEvent.Pre.Update(packet) + 3 -> TeamEvent.Pre.AddPlayer(packet) + 4 -> TeamEvent.Pre.RemovePlayer(packet) + else -> null + }?.also(EA.eventDispatcher) + } + + fun onS34PacketMapsPre(packet: S34PacketMaps) { + MapEvent + .Pre(packet) + .also(EA.eventDispatcher) + } } diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/nanovg/widget/BackgroundWidget.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/nanovg/widget/BackgroundWidget.kt index bdec8d1..3335939 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/nanovg/widget/BackgroundWidget.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/nanovg/widget/BackgroundWidget.kt @@ -9,16 +9,23 @@ fun backgroundWidget( color: Int, radius: Double, blur: Double, -): ListWidget { - return ListWidget( - ShadowWidget( - pos - padding, - pos + size + padding, - blur, - 0.0, - radius, - 1.0, - ), + shadow: Boolean, +): ListWidget = + ListWidget( + *if (shadow) { + arrayOf( + ShadowWidget( + pos - padding, + pos + size + padding, + blur, + 0.0, + radius, + 1.0, + ), + ) + } else { + arrayOf() + }, RoundedRectWidget( pos - padding, pos + size + padding, @@ -26,4 +33,3 @@ fun backgroundWidget( radius, ), ) -} diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/oneconfig/NanoVGAccessor.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/oneconfig/NanoVGAccessor.kt index 5e8ad17..9a1c265 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/oneconfig/NanoVGAccessor.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/oneconfig/NanoVGAccessor.kt @@ -1,22 +1,55 @@ package net.skymoe.enchaddons.impl.oneconfig import cc.polyfrost.oneconfig.renderer.font.Font +import cc.polyfrost.oneconfig.renderer.font.Fonts +import net.skymoe.enchaddons.util.StyledSegment +import net.skymoe.enchaddons.util.math.Vec2D +import net.skymoe.enchaddons.util.math.int import net.skymoe.enchaddons.util.property.latelet var nvg: NanoVGAccessor by latelet() +private var fallbackFontLoaded = false private var fontLoaded = false +var fontFallbackInstance: Font by latelet() var fontMediumInstance: Font by latelet() var fontSemiBoldInstance: Font by latelet() val fontMedium = { fontMediumInstance } +const val FONT_MEDIUM_Y_OFFSET = 0.5 val fontSemiBold = { fontSemiBoldInstance } +const val FONT_SEMIBOLD_Y_OFFSET = 0.1 + +fun NanoVGAccessor.fuckOneConfigFont(vg: Long) { + if (!fallbackFontLoaded) { + fontFallbackInstance = loadFont(vg, "notosanssc/medium.ttf") + fallbackFontLoaded = true + } + + listOf( + Fonts.BOLD, + Fonts.SEMIBOLD, + Fonts.REGULAR, + Fonts.MEDIUM, + Fonts.MINECRAFT_BOLD, + Fonts.MINECRAFT_REGULAR, + ).forEach { font -> + setFallbackFont(vg, font, fontFallbackInstance) + } +} fun NanoVGAccessor.loadFonts(vg: Long) { + if (!fallbackFontLoaded) { + fontFallbackInstance = loadFont(vg, "notosanssc/medium.ttf") + fallbackFontLoaded = true + } + if (!fontLoaded) { fontMediumInstance = loadFont(vg, "montserrat/medium.ttf") + setFallbackFont(vg, fontMediumInstance, fontFallbackInstance) fontSemiBoldInstance = loadFont(vg, "montserrat/semibold.ttf") + setFallbackFont(vg, fontSemiBoldInstance, fontFallbackInstance) fontLoaded = true } } @@ -66,6 +99,12 @@ interface NanoVGAccessor { name: String, ): Font + fun setFallbackFont( + vg: Long, + base: Font, + fallback: Font, + ) + fun loadImageFromByteArray( vg: Long, image: ByteArray, @@ -76,6 +115,16 @@ interface NanoVGAccessor { images: Set, ) + fun scissor( + vg: Long, + x: Double, + y: Double, + width: Double, + height: Double, + ) + + fun resetScissor(vg: Long) + fun drawRoundedImage( vg: Long, image: Int, @@ -91,6 +140,24 @@ interface NanoVGAccessor { radius: Double, ) + fun drawCheckMark( + vg: Long, + x: Double, + y: Double, + size: Double, + lineWidth: Double, + color: Int, + ) + + fun drawCrossMark( + vg: Long, + x: Double, + y: Double, + size: Double, + lineWidth: Double, + color: Int, + ) + fun drawRingRectRounded( vg: Long, x: Double, @@ -129,4 +196,66 @@ interface NanoVGAccessor { alpha: Double, radius: Double, ) + + fun drawAccarc( + vg: Long, + lCorner: Int, + x: Double, + y: Double, + width: Double, + height: Double, + lWidth: Double, + lHeight: Double, + borderRadius: Double, + lRadius: Double, + color: Int, + ) + + fun drawTextSegments( + vg: Long, + segments: List, + x: Double, + y: Double, + size: Double, + font: Font, + anchor: Vec2D = Vec2D(0.0, 0.0), + color: Int = 0xFFFFFFFF.int, + colorMultiplier: Double = 1.0, + shadow: Pair? = null, + ) + + fun drawRoundedTexture( + vg: Long, + imageCache: NanoVGImageCacheEntry, + texture: Int, + imageXRel: Double, + imageYRel: Double, + imageWRel: Double, + imageHRel: Double, + x: Double, + y: Double, + width: Double, + height: Double, + alpha: Double, + radius: Double, + ) + + fun save(vg: Long) + + fun restore(vg: Long) + + fun translate( + vg: Long, + pos: Vec2D, + ) + + fun scale( + vg: Long, + factor: Vec2D, + ) + + fun rotate( + vg: Long, + angle: Double, + ) } diff --git a/src/main/kotlin/net/skymoe/enchaddons/impl/oneconfig/ciallo/NanoVGAccessorImpl.kt b/src/main/kotlin/net/skymoe/enchaddons/impl/oneconfig/ciallo/NanoVGAccessorImpl.kt index 5abfd71..29d07aa 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/impl/oneconfig/ciallo/NanoVGAccessorImpl.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/impl/oneconfig/ciallo/NanoVGAccessorImpl.kt @@ -5,8 +5,13 @@ import net.skymoe.enchaddons.impl.MOD_ID import net.skymoe.enchaddons.impl.oneconfig.NanoVGAccessor import net.skymoe.enchaddons.impl.oneconfig.NanoVGImageCacheEntry import net.skymoe.enchaddons.impl.oneconfig.nvg +import net.skymoe.enchaddons.util.StyledSegment import net.skymoe.enchaddons.util.convertARGBToDoubleArray +import net.skymoe.enchaddons.util.convertDoubleArrayToARGB +import net.skymoe.enchaddons.util.math.Vec2D +import net.skymoe.enchaddons.util.math.double import net.skymoe.enchaddons.util.math.float +import net.skymoe.enchaddons.util.math.int import net.skymoe.enchaddons.util.scope.withscope import net.skymoe.enchaddons.util.toBuffer import org.lwjgl.nanovg.NVGColor @@ -15,8 +20,10 @@ import org.lwjgl.nanovg.NanoVG.* import org.lwjgl.nanovg.NanoVGGL3.NVG_IMAGE_NODELETE import org.lwjgl.nanovg.NanoVGGL3.nvglCreateImageFromHandle import java.nio.ByteBuffer -import java.util.UUID -import kotlin.math.* +import java.util.* +import kotlin.math.PI +import kotlin.math.asin +import kotlin.math.min private fun NVGColor.fill(argb: Int): NVGColor { val (rv, gv, bv, av) = convertARGBToDoubleArray(argb) @@ -49,6 +56,14 @@ object NanoVGAccessorImpl : NanoVGAccessor { } } + override fun setFallbackFont( + vg: Long, + base: Font, + fallback: Font, + ) { + nvgAddFallbackFont(vg, base.name, fallback.name) + } + override fun deleteImages( vg: Long, images: Set, @@ -58,6 +73,59 @@ object NanoVGAccessorImpl : NanoVGAccessor { } } + override fun drawCheckMark( + vg: Long, + x: Double, + y: Double, + size: Double, + lineWidth: Double, + color: Int, + ) { + withscope { + val nvgColor = NVGColor.calloc().using().fill(color) + nvgBeginPath(vg) + nvgStrokeColor(vg, nvgColor) + nvgStrokeWidth(vg, lineWidth.float) + + nvgLineCap(vg, NVG_ROUND) + nvgLineJoin(vg, NVG_ROUND) + + nvgMoveTo(vg, (x + .17 * size).float, (y + .6325 * size).float) + nvgLineTo(vg, (x + .475 * size).float, (y + .8125 * size).float) + nvgLineTo(vg, (x + .85 * size).float, (y + .225 * size).float) + + nvgStroke(vg) + nvgClosePath(vg) + } + } + + override fun drawCrossMark( + vg: Long, + x: Double, + y: Double, + size: Double, + lineWidth: Double, + color: Int, + ) { + withscope { + val nvgColor = NVGColor.calloc().using().fill(color) + nvgBeginPath(vg) + nvgStrokeColor(vg, nvgColor) + nvgStrokeWidth(vg, lineWidth.float) + + nvgLineCap(vg, NVG_ROUND) + nvgLineJoin(vg, NVG_ROUND) + + nvgMoveTo(vg, (x + .2 * size).float, (y + .2 * size).float) + nvgLineTo(vg, (x + .8 * size).float, (y + .8 * size).float) + nvgMoveTo(vg, (x + .2 * size).float, (y + .8 * size).float) + nvgLineTo(vg, (x + .8 * size).float, (y + .2 * size).float) + + nvgStroke(vg) + nvgClosePath(vg) + } + } + override fun drawRingRectRounded( vg: Long, x: Double, @@ -100,7 +168,7 @@ object NanoVGAccessorImpl : NanoVGAccessor { nvgBeginPath(vg) nvgStrokeColor(vg, nvgColor) - nvgStrokeWidth(vg, lineWidth.toFloat()) + nvgStrokeWidth(vg, lineWidth.float) nvgLineCap(vg, NVG_ROUND) nvgLineJoin(vg, NVG_ROUND) @@ -108,7 +176,7 @@ object NanoVGAccessorImpl : NanoVGAccessor { val startX = x + width / 2.0 val startY = y - nvgMoveTo(vg, startX.toFloat(), startY.toFloat()) + nvgMoveTo(vg, startX.float, startY.float) // Helper function to draw segments fun drawSegment( @@ -119,16 +187,16 @@ object NanoVGAccessorImpl : NanoVGAccessor { 0 -> { // Top segment val segmentLength = width - 2 * radius val currentX = x + width / 2.0 + (segmentLength / 2) * segmentProgress - nvgLineTo(vg, currentX.toFloat(), y.toFloat()) + nvgLineTo(vg, currentX.float, y.float) } 1 -> { // Top-Right Corner val angle = (segmentProgress - 1.0) * PI / 2 nvgArc( vg, - (x + width - radius).toFloat(), - (y + radius).toFloat(), - radius.toFloat(), - (-PI / 2).toFloat(), + (x + width - radius).float, + (y + radius).float, + radius.float, + (-PI / 2).float, angle.float, NVG_CW, ) @@ -136,15 +204,15 @@ object NanoVGAccessorImpl : NanoVGAccessor { 2 -> { // Right segment val segmentLength = height - 2 * radius val currentY = y + radius + segmentLength * segmentProgress - nvgLineTo(vg, (x + width).toFloat(), currentY.toFloat()) + nvgLineTo(vg, (x + width).float, currentY.float) } 3 -> { // Bottom-Right Corner val angle = segmentProgress * PI / 2 nvgArc( vg, - (x + width - radius).toFloat(), - (y + height - radius).toFloat(), - radius.toFloat(), + (x + width - radius).float, + (y + height - radius).float, + radius.float, 0f, angle.float, NVG_CW, @@ -153,16 +221,16 @@ object NanoVGAccessorImpl : NanoVGAccessor { 4 -> { // Bottom segment val segmentLength = width - 2 * radius val currentX = x + width - radius - segmentLength * segmentProgress - nvgLineTo(vg, currentX.toFloat(), (y + height).toFloat()) + nvgLineTo(vg, currentX.float, (y + height).float) } 5 -> { // Bottom-Left Corner val angle = (segmentProgress + 1.0) * PI / 2 nvgArc( vg, - (x + radius).toFloat(), - (y + height - radius).toFloat(), - radius.toFloat(), - (PI / 2).toFloat(), + (x + radius).float, + (y + height - radius).float, + radius.float, + (PI / 2).float, angle.float, NVG_CW, ) @@ -170,16 +238,16 @@ object NanoVGAccessorImpl : NanoVGAccessor { 6 -> { // Left segment val segmentLength = height - 2 * radius val currentY = y + height - radius - segmentLength * segmentProgress - nvgLineTo(vg, x.toFloat(), currentY.toFloat()) + nvgLineTo(vg, x.float, currentY.float) } 7 -> { // Top-Left Corner val angle = (segmentProgress + 2.0) * PI / 2 nvgArc( vg, - (x + radius).toFloat(), - (y + radius).toFloat(), - radius.toFloat(), - PI.toFloat(), + (x + radius).float, + (y + radius).float, + radius.float, + PI.float, angle.float, NVG_CW, ) @@ -187,7 +255,7 @@ object NanoVGAccessorImpl : NanoVGAccessor { 8 -> { // Top segment (partial, completing the circle) val segmentLength = width / 2.0 - radius // Half of the width to the center val currentX = x + radius + segmentLength * segmentProgress - nvgLineTo(vg, currentX.toFloat(), y.toFloat()) + nvgLineTo(vg, currentX.float, y.float) } } } @@ -205,6 +273,24 @@ object NanoVGAccessorImpl : NanoVGAccessor { } } + override fun scissor( + vg: Long, + x: Double, + y: Double, + width: Double, + height: Double, + ) { + withscope { + nvgScissor(vg, x.float, y.float, width.float, height.float) + } + } + + override fun resetScissor(vg: Long) { + withscope { + nvgResetScissor(vg) + } + } + override fun drawRingArc( vg: Long, x: Double, @@ -355,4 +441,241 @@ object NanoVGAccessorImpl : NanoVGAccessor { ) } } + + override fun drawAccarc( + vg: Long, + lCorner: Int, + x: Double, + y: Double, + width: Double, + height: Double, + lWidth: Double, + lHeight: Double, + borderRadius: Double, + lRadius: Double, + color: Int, + ) { + nvgSave(vg) + nvgTranslate(vg, x.float, y.float) + + if (lCorner != 0) { + when (lCorner) { + 1 -> { + nvgTranslate(vg, width.float / 2, height.float / 2) + nvgRotate(vg, PI.float / 2) + nvgTranslate(vg, -width.float / 2, -height.float / 2) + drawAccarc( + vg, + 0, + 0.0, + 0.0, + height, + width, + lHeight, + lWidth, + borderRadius, + lRadius, + color, + ) + } + 2 -> { + nvgTranslate(vg, width.float / 2, height.float / 2) + nvgRotate(vg, PI.float) + nvgTranslate(vg, -width.float / 2, -height.float / 2) + drawAccarc( + vg, + 0, + 0.0, + 0.0, + width, + height, + lWidth, + lHeight, + borderRadius, + lRadius, + color, + ) + } + 3 -> { + nvgTranslate(vg, width.float / 2, height.float / 2) + nvgRotate(vg, -PI.float / 2) + nvgTranslate(vg, -width.float / 2, -height.float / 2) + drawAccarc( + vg, + 0, + 0.0, + 0.0, + height, + width, + lHeight, + lWidth, + borderRadius, + lRadius, + color, + ) + } + } + } else { + val w = width.float + val h = height.float + val lw = min(lWidth.float, w) + val lh = min(lHeight.float, h) + val br = minOf(borderRadius.float, lw / 2, lh / 2, w - lw, h - lh) + val lr = minOf(lRadius.float, w - lw, h - lh) + + withscope { + val nvgColor = NVGColor.calloc().using().fill(color) + + nvgBeginPath(vg) + + nvgMoveTo(vg, br, 0F) + nvgLineTo(vg, w - br, 0F) + nvgArcTo(vg, w, 0F, w, br, br) + nvgLineTo(vg, w, lh - br) + nvgArcTo(vg, w, lh, w - br, lh, br) + nvgLineTo(vg, lw + lr, lh) + nvgArcTo(vg, lw, lh, lw, lh + lr, lr) + nvgLineTo(vg, lw, h - br) + nvgArcTo(vg, lw, h, lw - br, h, br) + nvgLineTo(vg, br, h) + nvgArcTo(vg, 0F, h, 0F, h - br, br) + nvgLineTo(vg, 0F, br) + nvgArcTo(vg, 0F, 0F, br, 0F, br) + + nvgClosePath(vg) + nvgFillColor(vg, nvgColor) + nvgFill(vg) + } + } + + nvgRestore(vg) + } + + override fun drawTextSegments( + vg: Long, + segments: List, + x: Double, + y: Double, + size: Double, + font: Font, + anchor: Vec2D, + color: Int, + colorMultiplier: Double, + shadow: Pair?, + ) { + withscope { + shadow?.let { + drawTextSegments( + vg, + segments, + x + shadow.first.x * size, + y + shadow.first.y * size, + size, + font, + anchor, + color, + colorMultiplier * shadow.second, + null, + ) + } + val alphaBits = (color and 0xFF000000.int) or 0xFFFFFF + val nvgColor = NVGColor.calloc().using().fill(shadowColor(color, colorMultiplier)) + nvgFontSize(vg, size.float) + nvgFontFace(vg, font.name) + nvgTextAlign(vg, NVG_ALIGN_TOP or NVG_ALIGN_LEFT) + val totalWidth = segments.sumOf { nvgTextBounds(vg, 0.0f, 0.0f, it.text, FloatArray(4)).double } + val posX = x - totalWidth * anchor.x + val posY = y - size * anchor.y + var currentX = posX + segments.forEach { (text, color) -> + nvgFillColor( + vg, + color?.let { + val colorWithAlpha = color and alphaBits + NVGColor.calloc().using().fill(shadowColor(colorWithAlpha, colorMultiplier)) + } ?: nvgColor, + ) + currentX = nvgText(vg, currentX.float, posY.float, text).double + } + } + } + + override fun drawRoundedTexture( + vg: Long, + imageCache: NanoVGImageCacheEntry, + texture: Int, + imageXRel: Double, + imageYRel: Double, + imageWRel: Double, + imageHRel: Double, + x: Double, + y: Double, + width: Double, + height: Double, + alpha: Double, + radius: Double, + ) { + imageCache.cleanup(this, vg) + + val nvgImage = + imageCache.cache.getOrPut(texture) { + nvglCreateImageFromHandle(vg, texture, 64, 64, NVG_IMAGE_NEAREST or NVG_IMAGE_NODELETE) + .also { if (it == -1) return@drawRoundedTexture } + } + + drawRoundedImage( + vg, + nvgImage, + 0.0, + 0.0, + 1.0, + 1.0, + x, + y, + width, + height, + alpha, + radius, + ) + } + + private fun shadowColor( + color: Int, + multiplier: Double, + ): Int { + val rgba = convertARGBToDoubleArray(color) + rgba[0] = rgba[0] * multiplier + rgba[1] = rgba[1] * multiplier + rgba[2] = rgba[2] * multiplier + return convertDoubleArrayToARGB(*rgba) + } + + override fun save(vg: Long) { + nvgSave(vg) + } + + override fun restore(vg: Long) { + nvgRestore(vg) + } + + override fun translate( + vg: Long, + pos: Vec2D, + ) { + nvgTranslate(vg, pos.x.float, pos.y.float) + } + + override fun scale( + vg: Long, + factor: Vec2D, + ) { + nvgScale(vg, factor.x.float, factor.y.float) + } + + override fun rotate( + vg: Long, + angle: Double, + ) { + nvgRotate(vg, angle.float) + } } diff --git a/src/main/kotlin/net/skymoe/enchaddons/util/ColorUtil.kt b/src/main/kotlin/net/skymoe/enchaddons/util/ColorUtil.kt index 331aa33..b32d698 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/util/ColorUtil.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/util/ColorUtil.kt @@ -1,9 +1,24 @@ package net.skymoe.enchaddons.util import net.skymoe.enchaddons.util.math.int +import java.awt.Color import kotlin.math.abs import kotlin.math.min +fun Color.withAlpha(alpha: Int) = Color(this.red, this.green, this.blue, alpha) + +fun Color.withAlpha(alpha: Float) = Color(this.red, this.green, this.blue, (alpha * 255).toInt()) + +fun Color.invisible() = withAlpha(0) + +operator fun Color.component1() = this.red + +operator fun Color.component2() = this.green + +operator fun Color.component3() = this.blue + +operator fun Color.component4() = this.alpha + fun convertRGBToHSL(vararg rgb: Double): DoubleArray { val hsl = DoubleArray(if (rgb.size >= 4) 4 else 3) val maxRGB = maxOf(0.0, *rgb) @@ -57,14 +72,13 @@ fun convertHSLToRGB(vararg hsl: Double): DoubleArray { }.normalizeColor } -fun convertARGBToDoubleArray(argb: Int): DoubleArray { - return doubleArrayOf( +fun convertARGBToDoubleArray(argb: Int): DoubleArray = + doubleArrayOf( (argb ushr 16 and 0xFF) / 255.0, (argb ushr 8 and 0xFF) / 255.0, (argb and 0xFF) / 255.0, (argb ushr 24 and 0xFF) / 255.0, ).normalizeColor -} fun convertRGBToDoubleArray(argb: Int) = convertARGBToDoubleArray(argb or 0xFF000000.int) @@ -97,8 +111,7 @@ infix fun DoubleArray.blendColor(rgbaSrc: DoubleArray): DoubleArray { ).normalizeColor } -infix fun Int.blendColor(argbSrc: Int): Int { - return convertDoubleArrayToARGB( +infix fun Int.blendColor(argbSrc: Int): Int = + convertDoubleArrayToARGB( *convertARGBToDoubleArray(this) blendColor convertARGBToDoubleArray(argbSrc), ) -} diff --git a/src/main/kotlin/net/skymoe/enchaddons/util/EnumSetUtil.kt b/src/main/kotlin/net/skymoe/enchaddons/util/EnumSetUtil.kt new file mode 100644 index 0000000..e69de29 diff --git a/src/main/kotlin/net/skymoe/enchaddons/util/MinecraftUtil.kt b/src/main/kotlin/net/skymoe/enchaddons/util/MinecraftUtil.kt index 2d0c066..5569cd7 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/util/MinecraftUtil.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/util/MinecraftUtil.kt @@ -11,7 +11,6 @@ import net.minecraft.item.Item import net.minecraft.scoreboard.ScoreObjective import net.minecraft.scoreboard.ScorePlayerTeam import net.minecraft.util.BlockPos -import net.minecraft.util.ChatComponentText import net.minecraft.util.IChatComponent import net.minecraft.util.ResourceLocation import net.minecraft.util.Vec3i @@ -73,16 +72,14 @@ fun WorldRenderer.tex(vec: Vec2D): WorldRenderer = tex(vec.x, vec.y) fun WorldRenderer.color(color: Color): WorldRenderer = color(color.r.float, color.g.float, color.b.float, color.a.float) fun printChat(message: IChatComponent) { + logger.info("[PRINT CHAT] $message") MC.theWorld?.let { MC.ingameGUI.chatGUI.printChatMessage(message) } } fun printChat(message: String = "") { - logger.info("[PRINT CHAT] $message") - MC.theWorld?.let { - MC.ingameGUI.chatGUI.printChatMessage(ChatComponentText(message)) - } + printChat(message.asComponent()) } fun printChat(throwable: Throwable) = printChat(throwable.stackTraceMessage) diff --git a/src/main/kotlin/net/skymoe/enchaddons/util/ModMessage.kt b/src/main/kotlin/net/skymoe/enchaddons/util/ModMessage.kt new file mode 100644 index 0000000..a1785f3 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/util/ModMessage.kt @@ -0,0 +1,25 @@ +package net.skymoe.enchaddons.util + +import net.minecraft.util.IChatComponent + +enum class LogLevel( + val component: IChatComponent, +) { + INFO("§bEA§r §7»§r ".asComponent()), + WARN("§6EA§r §7»§r ".asComponent()), + ERROR("§cEA§r §7»§r ".asComponent()), +} + +fun modMessage( + message: String, + logLevel: LogLevel, +) { + modMessage(message.asComponent(), logLevel) +} + +fun modMessage( + message: IChatComponent, + logLevel: LogLevel, +) { + printChat(logLevel.component.createCopy().appendSibling(message)) +} diff --git a/src/main/kotlin/net/skymoe/enchaddons/util/StyledText.kt b/src/main/kotlin/net/skymoe/enchaddons/util/StyledText.kt new file mode 100644 index 0000000..65b0304 --- /dev/null +++ b/src/main/kotlin/net/skymoe/enchaddons/util/StyledText.kt @@ -0,0 +1,63 @@ +package net.skymoe.enchaddons.util + +import net.skymoe.enchaddons.util.math.int +import java.lang.StringBuilder + +data class StyledSegment( + val text: String, + val color: Int?, +) + +fun String.toStyledSegments(): List = + buildList { + val stringBuilder = StringBuilder() + var color: Int? = null + + fun setColor(newColor: Int?) { + if (color != newColor && stringBuilder.isNotEmpty()) { + add(StyledSegment(stringBuilder.toString(), color)) + stringBuilder.clear() + } + color = newColor + } + + run { + var i = 0 + while (i < length) { + when (val c = this@toStyledSegments[i]) { + '\u00A7' -> { + ++i + if (i < length) { + val code = this@toStyledSegments[i] + ++i + when (code.lowercaseChar()) { + 'r' -> setColor(null) + '0' -> setColor(0xFF000000.int) + '1' -> setColor(0xFF0000AA.int) + '2' -> setColor(0xFF00AA00.int) + '3' -> setColor(0xFF00AAAA.int) + '4' -> setColor(0xFFAA0000.int) + '5' -> setColor(0xFFAA00AA.int) + '6' -> setColor(0xFFFFAA00.int) + '7' -> setColor(0xFFAAAAAA.int) + '8' -> setColor(0xFF555555.int) + '9' -> setColor(0xFF5555FF.int) + 'a' -> setColor(0xFF55FF55.int) + 'b' -> setColor(0xFF55FFFF.int) + 'c' -> setColor(0xFFFF5555.int) + 'd' -> setColor(0xFFFF55FF.int) + 'e' -> setColor(0xFFFFFF55.int) + 'f' -> setColor(0xFFFFFFFF.int) + } + } + } + else -> { + ++i + stringBuilder.append(c) + } + } + } + } + + add(StyledSegment(stringBuilder.toString(), color)) + } diff --git a/src/main/kotlin/net/skymoe/enchaddons/util/TextComponentHelper.kt b/src/main/kotlin/net/skymoe/enchaddons/util/TextComponentHelper.kt index be3e394..28f68b7 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/util/TextComponentHelper.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/util/TextComponentHelper.kt @@ -24,6 +24,7 @@ class ComponentBuilderScope( data class Element( val text: String, var style: Style, + val component: IChatComponent? = null, ) { private fun modifyStyle(modifier: Style.() -> Style) = apply { style = modifier(style) } @@ -55,6 +56,8 @@ class ComponentBuilderScope( val String.append get() = toElement() + val IChatComponent.append get() = elements.add(Element("", parentStyle, this)) + val String.black get() = toElement().black val String.darkBlue get() = toElement().darkBlue val String.darkGreen get() = toElement().darkGreen @@ -81,6 +84,11 @@ class ComponentBuilderScope( fun build(): IChatComponent = "".asComponent().apply { elements.forEach { element -> + element.component?.let { + siblings.add(it) + return@forEach + } + siblings.add( element.text.asComponent().apply { chatStyle.color = element.style.color.formatting diff --git a/src/main/kotlin/net/skymoe/enchaddons/util/scope/WithScope.kt b/src/main/kotlin/net/skymoe/enchaddons/util/scope/WithScope.kt index 01fb414..2fcecff 100644 --- a/src/main/kotlin/net/skymoe/enchaddons/util/scope/WithScope.kt +++ b/src/main/kotlin/net/skymoe/enchaddons/util/scope/WithScope.kt @@ -1,16 +1,17 @@ package net.skymoe.enchaddons.util.scope +import java.lang.AutoCloseable + class WithScopeContext( private val resourceList: MutableList, private val cleanupList: MutableList<() -> Unit>, ) { - fun use(resource: T): T { - return resource.also { + fun use(resource: T): T = + resource.also { if (resource !in resourceList) { resourceList.add(it) } } - } fun use(cleanup: () -> Unit) { if (cleanup !in cleanupList) { diff --git a/src/main/resources/assets/enchaddons/awesomemap/default/cross.png b/src/main/resources/assets/enchaddons/awesomemap/default/cross.png new file mode 100644 index 0000000..5099d32 Binary files /dev/null and b/src/main/resources/assets/enchaddons/awesomemap/default/cross.png differ diff --git a/src/main/resources/assets/enchaddons/awesomemap/default/green_check.png b/src/main/resources/assets/enchaddons/awesomemap/default/green_check.png new file mode 100644 index 0000000..a17ca5a Binary files /dev/null and b/src/main/resources/assets/enchaddons/awesomemap/default/green_check.png differ diff --git a/src/main/resources/assets/enchaddons/awesomemap/default/question.png b/src/main/resources/assets/enchaddons/awesomemap/default/question.png new file mode 100644 index 0000000..f9278b0 Binary files /dev/null and b/src/main/resources/assets/enchaddons/awesomemap/default/question.png differ diff --git a/src/main/resources/assets/enchaddons/awesomemap/default/white_check.png b/src/main/resources/assets/enchaddons/awesomemap/default/white_check.png new file mode 100644 index 0000000..8aa2f15 Binary files /dev/null and b/src/main/resources/assets/enchaddons/awesomemap/default/white_check.png differ diff --git a/src/main/resources/assets/enchaddons/awesomemap/marker.png b/src/main/resources/assets/enchaddons/awesomemap/marker.png new file mode 100644 index 0000000..429eaf0 Binary files /dev/null and b/src/main/resources/assets/enchaddons/awesomemap/marker.png differ diff --git a/src/main/resources/assets/enchaddons/awesomemap/rooms.json b/src/main/resources/assets/enchaddons/awesomemap/rooms.json new file mode 100644 index 0000000..f392bc3 --- /dev/null +++ b/src/main/resources/assets/enchaddons/awesomemap/rooms.json @@ -0,0 +1,1309 @@ +[ + { + "name": "Admin", + "type": "NORMAL", + "cores": [ + -1989372370 + ], + "crypts": 34 + }, + { + "name": "Altar", + "type": "NORMAL", + "cores": [ + 1823952110, + 1004879775, + -1690727070 + ], + "crypts": 3, + "secrets": 6 + }, + { + "name": "Andesite", + "type": "NORMAL", + "cores": [ + -673246822 + ], + "secrets": 2 + }, + { + "name": "Archway", + "type": "NORMAL", + "cores": [ + -1447684689, + 1440798119 + ], + "crypts": 4, + "secrets": 3 + }, + { + "name": "Arrow Trap", + "type": "NORMAL", + "cores": [ + 332584803 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Atlas", + "type": "NORMAL", + "cores": [ + -685489527, + 745093020, + 308515062, + -38388847 + ], + "crypts": 5, + "secrets": 6 + }, + { + "name": "Balcony", + "type": "NORMAL", + "cores": [ + 1989395652, + 559495102 + ], + "secrets": 4 + }, + { + "name": "Banners", + "type": "NORMAL", + "cores": [ + -667603340 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Basement", + "type": "NORMAL", + "cores": [ + -204678789 + ], + "secrets": 1 + }, + { + "name": "Beams", + "type": "NORMAL", + "cores": [ + -2082251638 + ], + "crypts": 2, + "secrets": 2 + }, + { + "name": "Big Red Flag", + "type": "NORMAL", + "cores": [ + -1265317780 + ], + "crypts": 1, + "secrets": 2 + }, + { + "name": "Black Flag", + "type": "NORMAL", + "cores": [ + 153580070 + ], + "crypts": 1, + "secrets": 3 + }, + { + "name": "Blood", + "type": "BLOOD", + "cores": [ + -2130054003 + ] + }, + { + "name": "Blue Skulls", + "type": "NORMAL", + "cores": [ + -999204041 + ], + "crypts": 4, + "secrets": 1 + }, + { + "name": "Bomb Defuse", + "type": "PUZZLE", + "cores": [ + 86014075 + ] + }, + { + "name": "Boulder", + "type": "PUZZLE", + "cores": [ + -671152674, + 307825200 + ] + }, + { + "name": "Bridges", + "type": "NORMAL", + "cores": [ + -1604951086, + -1989950542 + ], + "crypts": 6, + "secrets": 6 + }, + { + "name": "Buttons", + "type": "NORMAL", + "cores": [ + -1359302282, + 160943502, + 823430452, + 2123177620 + ], + "crypts": 21, + "secrets": 5, + "trappedChests": 1 + }, + { + "name": "Cage", + "type": "NORMAL", + "cores": [ + 272954274 + ], + "secrets": 1 + }, + { + "name": "Cages", + "type": "NORMAL", + "cores": [ + -316384390 + ], + "secrets": 2 + }, + { + "name": "Carpets", + "type": "NORMAL", + "cores": [ + 988444684, + 1890757664, + 1186348038, + -1160833644 + ], + "crypts": 3, + "secrets": 1 + }, + { + "name": "Cathedral", + "type": "NORMAL", + "cores": [ + -2043617055, + 789869846, + -1212586959, + 1956475445 + ], + "crypts": 5, + "secrets": 8 + }, + { + "name": "Catwalk", + "type": "NORMAL", + "cores": [ + 1600132124, + -1010346133, + -1694830065 + ], + "crypts": 5, + "secrets": 6 + }, + { + "name": "Cell", + "type": "NORMAL", + "cores": [ + 1751890846 + ], + "secrets": 1 + }, + { + "name": "Chains", + "type": "NORMAL", + "cores": [ + 113272043 + ], + "secrets": 2 + }, + { + "name": "Chambers", + "type": "NORMAL", + "cores": [ + 1756685113, + 368708000, + 252800591 + ], + "crypts": 6, + "secrets": 5 + }, + { + "name": "Cobble Wall Pillar", + "type": "NORMAL", + "cores": [ + 259238824 + ], + "crypts": 1, + "secrets": 2 + }, + { + "name": "Creeper Beams", + "type": "PUZZLE", + "cores": [ + -755321869, + 573221447 + ] + }, + { + "name": "Criss-Cross", + "type": "NORMAL", + "cores": [ + 650161016, + 1002362494 + ], + "crypts": 6, + "secrets": 1 + }, + { + "name": "Crypt", + "type": "NORMAL", + "cores": [ + -845911506, + 331494915 + ], + "crypts": 2, + "secrets": 5 + }, + { + "name": "Deathmite", + "type": "NORMAL", + "cores": [ + -233562612, + -1901273450, + -1408070175 + ], + "crypts": 4, + "secrets": 6 + }, + { + "name": "Default", + "type": "CHAMPION", + "cores": [ + 652570347, + 1956609103 + ] + }, + { + "name": "Diagonal", + "type": "NORMAL", + "cores": [ + 2147431657, + 1144217017, + -464769223 + ], + "crypts": 3, + "secrets": 4 + }, + { + "name": "Dino Site", + "type": "NORMAL", + "cores": [ + -1113939414, + -1425445617, + -609789679 + ], + "crypts": 4, + "secrets": 4 + }, + { + "name": "Dip", + "type": "NORMAL", + "cores": [ + -1691958814 + ], + "crypts": 3, + "secrets": 2 + }, + { + "name": "Dome", + "type": "NORMAL", + "cores": [ + 462413746 + ], + "crypts": 2, + "secrets": 2 + }, + { + "name": "Doors", + "type": "NORMAL", + "cores": [ + 164990589, + 1033794268 + ], + "crypts": 7, + "secrets": 5 + }, + { + "name": "Double Diamond", + "type": "NORMAL", + "cores": [ + 1308341800 + ], + "secrets": 3 + }, + { + "name": "Dragon", + "type": "CHAMPION", + "cores": [ + 84632407 + ] + }, + { + "name": "Drop", + "type": "NORMAL", + "cores": [ + -615308028 + ], + "crypts": 6, + "secrets": 2 + }, + { + "name": "Dueces", + "type": "NORMAL", + "cores": [ + 1046920372 + ], + "crypts": 6, + "secrets": 3, + "trappedChests": 1 + }, + { + "name": "Duncan", + "type": "NORMAL", + "cores": [ + 544418695 + ], + "secrets": 1 + }, + { + "name": "End", + "type": "NORMAL", + "cores": [ + -1897192562 + ], + "crypts": 1, + "secrets": 2 + }, + { + "name": "Entrance", + "type": "ENTRANCE", + "cores": [ + 274652966, + -1092072828, + 1913969999 + ] + }, + { + "name": "Fairy", + "type": "FAIRY", + "cores": [ + 1484567748 + ] + }, + { + "name": "Flags", + "type": "NORMAL", + "cores": [ + -1267948931, + 284566079, + 1157102457, + 67929126 + ], + "crypts": 8, + "secrets": 7 + }, + { + "name": "Gold", + "type": "NORMAL", + "cores": [ + -1035453872, + 1451614295 + ], + "secrets": 1 + }, + { + "name": "Golden Oasis", + "type": "NORMAL", + "cores": [ + -1666473430 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Grand Library", + "type": "NORMAL", + "cores": [ + 749593273, + 1348435369 + ], + "crypts": 2, + "secrets": 4 + }, + { + "name": "Granite", + "type": "NORMAL", + "cores": [ + -456244067 + ], + "secrets": 2 + }, + { + "name": "Grass Ruin", + "type": "NORMAL", + "cores": [ + 568565222, + -786211724 + ], + "crypts": 4, + "secrets": 3 + }, + { + "name": "Gravel", + "type": "NORMAL", + "cores": [ + 841898152, + 1127962661, + 4304131 + ], + "crypts": 2, + "secrets": 6 + }, + { + "name": "Hall", + "type": "NORMAL", + "cores": [ + -2131538192 + ] + }, + { + "name": "Hallway", + "type": "NORMAL", + "cores": [ + -402028497, + 351544339, + -1723662179, + -1644671806 + ], + "crypts": 1, + "secrets": 3 + }, + { + "name": "Higher Blaze", + "type": "PUZZLE", + "cores": [ + 1103121487, + 23134049, + -243302881 + ], + "secrets": 1 + }, + { + "name": "Ice Fill", + "type": "PUZZLE", + "cores": [ + 327081838, + 1328525306, + 161828987 + ] + }, + { + "name": "Ice Path", + "type": "PUZZLE", + "cores": [ + 1073658098 + ] + }, + { + "name": "Jumping Skulls", + "type": "NORMAL", + "cores": [ + -1496765468 + ], + "secrets": 1 + }, + { + "name": "King Midas", + "type": "CHAMPION", + "cores": [ + -1678546839 + ], + "crypts": 1 + }, + { + "name": "Knight", + "type": "NORMAL", + "cores": [ + -1746428299 + ], + "secrets": 3 + }, + { + "name": "Lava Pit", + "type": "RARE", + "cores": [ + -1519947323 + ], + "secrets": 3 + }, + { + "name": "Lava Ravine", + "type": "NORMAL", + "cores": [ + -2040489612, + -294054018, + -2053308786 + ], + "crypts": 4, + "secrets": 6 + }, + { + "name": "Layers", + "type": "NORMAL", + "cores": [ + 1516049261, + 820948152, + 456930870, + -1910037748 + ], + "secrets": 8 + }, + { + "name": "Leaves", + "type": "NORMAL", + "cores": [ + -1967474423 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Locked Away", + "type": "NORMAL", + "cores": [ + 754378401 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Logs", + "type": "NORMAL", + "cores": [ + -1803705489 + ], + "secrets": 4 + }, + { + "name": "Long Hall", + "type": "NORMAL", + "cores": [ + -872480083 + ], + "crypts": 3, + "secrets": 3 + }, + { + "name": "Lots Of Floors", + "type": "NORMAL", + "cores": [ + 1449723216 + ], + "crypts": 1, + "secrets": 3 + }, + { + "name": "Lower Blaze", + "type": "PUZZLE", + "cores": [ + -1092103153, + -2027662369, + -1141175903 + ], + "secrets": 1 + }, + { + "name": "Mage", + "type": "NORMAL", + "cores": [ + -338946136, + 1089356068 + ], + "secrets": 4 + }, + { + "name": "Market", + "type": "NORMAL", + "cores": [ + 270637140, + -402574914, + -880417926 + ], + "crypts": 4, + "secrets": 5 + }, + { + "name": "Melon", + "type": "NORMAL", + "cores": [ + -325025964, + 1964904676, + 158528145 + ], + "crypts": 4, + "secrets": 7 + }, + { + "name": "Mines", + "type": "NORMAL", + "cores": [ + -361911912, + -39488099, + 1363618678, + 1227315161 + ], + "crypts": 11, + "secrets": 10 + }, + { + "name": "Mirror", + "type": "NORMAL", + "cores": [ + -1823353629 + ], + "secrets": 1 + }, + { + "name": "Mossy", + "type": "NORMAL", + "cores": [ + -1169615458, + 1432034198, + 1896208123 + ], + "crypts": 2, + "secrets": 4 + }, + { + "name": "Multicolored", + "type": "NORMAL", + "cores": [ + -671539463 + ], + "secrets": 1 + }, + { + "name": "Mural", + "type": "NORMAL", + "cores": [ + -1027066030 + ], + "secrets": 1 + }, + { + "name": "Museum", + "type": "NORMAL", + "cores": [ + -1957538226, + -2121384577, + -1797804860, + 1514395908 + ], + "crypts": 4, + "secrets": 5 + }, + { + "name": "Mushroom", + "type": "NORMAL", + "cores": [ + 1073109158 + ], + "secrets": 1 + }, + { + "name": "New Trap", + "type": "TRAP", + "cores": [ + -1358669872, + -1989128497 + ], + "crypts": 1, + "secrets": 3, + "trappedChests": 1 + }, + { + "name": "Old Trap", + "type": "TRAP", + "cores": [ + 1128554492 + ], + "crypts": 2, + "secrets": 4 + }, + { + "name": "Overgrown", + "type": "NORMAL", + "cores": [ + 1858897577 + ], + "secrets": 3 + }, + { + "name": "Overgrown Chains", + "type": "NORMAL", + "cores": [ + 1077887433 + ], + "crypts": 1, + "secrets": 2 + }, + { + "name": "Painting", + "type": "NORMAL", + "cores": [ + 474745227 + ], + "secrets": 2 + }, + { + "name": "Pedestal", + "type": "NORMAL", + "cores": [ + -1346033867, + 281551136 + ], + "crypts": 1, + "secrets": 5 + }, + { + "name": "Perch", + "type": "NORMAL", + "cores": [ + 27598620 + ], + "crypts": 1, + "secrets": 2 + }, + { + "name": "Pipes", + "type": "NORMAL", + "cores": [ + 952529534, + -901820944, + -664950032, + -1205767926 + ], + "crypts": 9, + "secrets": 7 + }, + { + "name": "Pirate", + "type": "NORMAL", + "cores": [ + -1175423677, + 1942933815, + 2002468229 + ], + "crypts": 2, + "secrets": 6 + }, + { + "name": "Pit", + "type": "NORMAL", + "cores": [ + -1862968316, + -258250108, + 265221970, + 430079089 + ], + "crypts": 4, + "secrets": 5 + }, + { + "name": "Pressure Plates", + "type": "NORMAL", + "cores": [ + 1172966775, + 681797038 + ], + "crypts": 6, + "secrets": 6 + }, + { + "name": "Prison Cell", + "type": "NORMAL", + "cores": [ + 1172045122 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Purple Flags", + "type": "NORMAL", + "cores": [ + 887024382, + 701310376 + ], + "crypts": 7, + "secrets": 5 + }, + { + "name": "Quad Lava", + "type": "NORMAL", + "cores": [ + 52900193 + ], + "secrets": 2 + }, + { + "name": "Quartz Knight", + "type": "NORMAL", + "cores": [ + -1639566599, + 153753389, + -129760550, + -742870398 + ], + "crypts": 9, + "secrets": 7 + }, + { + "name": "Quiz", + "type": "PUZZLE", + "cores": [ + 1928619293 + ] + }, + { + "name": "Raccoon", + "type": "NORMAL", + "cores": [ + 497881745 + ], + "crypts": 2, + "secrets": 4 + }, + { + "name": "Rail Track", + "type": "RARE", + "cores": [ + -701175845 + ], + "crypts": 3, + "secrets": 3 + }, + { + "name": "Rails", + "type": "NORMAL", + "cores": [ + 526587049, + -214948895, + -1778261714, + 1937419120 + ], + "crypts": 1, + "secrets": 9 + }, + { + "name": "Rare Overgrown", + "type": "RARE", + "cores": [ + -1643528240 + ], + "secrets": 3 + }, + { + "name": "Rare Pillars", + "type": "RARE", + "cores": [ + 1216268340 + ], + "secrets": 1 + }, + { + "name": "Red Blue", + "type": "NORMAL", + "cores": [ + 1011477602, + 1607395895, + -1794981292 + ], + "crypts": 1, + "secrets": 4 + }, + { + "name": "Red Green", + "type": "NORMAL", + "cores": [ + -1085327384 + ], + "crypts": 2, + "secrets": 3 + }, + { + "name": "Redstone Crypt", + "type": "NORMAL", + "cores": [ + -1054702517, + 127199896 + ], + "secrets": 3 + }, + { + "name": "Redstone Key", + "type": "NORMAL", + "cores": [ + 348655632 + ], + "crypts": 4, + "secrets": 3, + "trappedChests": 1 + }, + { + "name": "Redstone Warrior", + "type": "NORMAL", + "cores": [ + 1313090868, + 1819727964 + ], + "crypts": 4, + "secrets": 3 + }, + { + "name": "Ritual", + "type": "NORMAL", + "cores": [ + 758637731 + ], + "crypts": 1, + "secrets": 3 + }, + { + "name": "Sand Dragon", + "type": "RARE", + "cores": [ + -1497998508 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Sarcophagus", + "type": "NORMAL", + "cores": [ + 1986002687 + ], + "crypts": 1, + "secrets": 3 + }, + { + "name": "Scaffolding", + "type": "NORMAL", + "cores": [ + 2091387826 + ], + "secrets": 2 + }, + { + "name": "Shadow Assassin", + "type": "CHAMPION", + "cores": [ + -430117371 + ], + "crypts": 2 + }, + { + "name": "Silver Sword", + "type": "NORMAL", + "cores": [ + -224496952 + ], + "secrets": 1 + }, + { + "name": "Skull", + "type": "NORMAL", + "cores": [ + -46929855, + 1667732153 + ], + "secrets": 2 + }, + { + "name": "Slabs", + "type": "NORMAL", + "cores": [ + -1811244478 + ], + "crypts": 2, + "secrets": 2 + }, + { + "name": "Slime", + "type": "NORMAL", + "cores": [ + 1203689085, + 611794931, + -1050545277 + ], + "crypts": 1, + "secrets": 5, + "trappedChests": 1 + }, + { + "name": "Sloth", + "type": "NORMAL", + "cores": [ + -1390729196 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Small Stairs", + "type": "NORMAL", + "cores": [ + -1250912300 + ], + "crypts": 1, + "secrets": 2 + }, + { + "name": "Small Waterfall", + "type": "NORMAL", + "cores": [ + -1682285647 + ], + "crypts": 5, + "secrets": 2 + }, + { + "name": "Spider", + "type": "NORMAL", + "cores": [ + 365045229, + -1361285742, + 1778566373 + ], + "crypts": 3, + "secrets": 9 + }, + { + "name": "Spikes", + "type": "NORMAL", + "cores": [ + 881396995 + ], + "crypts": 2, + "secrets": 3 + }, + { + "name": "Staircase", + "type": "NORMAL", + "cores": [ + 1386509425 + ], + "crypts": 2, + "secrets": 3 + }, + { + "name": "Stairs", + "type": "NORMAL", + "cores": [ + -330702540, + -283979980, + -697693183, + 606731747 + ], + "crypts": 1, + "secrets": 4 + }, + { + "name": "Steps", + "type": "NORMAL", + "cores": [ + 1350621298 + ], + "crypts": 1, + "secrets": 1 + }, + { + "name": "Stone Window", + "type": "RARE", + "cores": [ + 477318192 + ], + "crypts": 1, + "secrets": 2 + }, + { + "name": "Supertall", + "type": "NORMAL", + "cores": [ + 1522346451, + -1376632689, + 291711773, + -46683467 + ], + "crypts": 6, + "secrets": 6 + }, + { + "name": "Teleport Maze", + "type": "PUZZLE", + "cores": [ + 487124604, + 2089453469 + ] + }, + { + "name": "Temple", + "type": "NORMAL", + "cores": [ + 1663174337, + 1480555517 + ], + "secrets": 3 + }, + { + "name": "Three Floors", + "type": "RARE", + "cores": [ + 633179672 + ], + "secrets": 1 + }, + { + "name": "Three Weirdos", + "type": "PUZZLE", + "cores": [ + -476788643 + ] + }, + { + "name": "Tic Tac Toe", + "type": "PUZZLE", + "cores": [ + 1958698161 + ], + "secrets": 1 + }, + { + "name": "Tombstone", + "type": "RARE", + "cores": [ + 1965783806 + ], + "secrets": 2 + }, + { + "name": "Tomioka", + "type": "NORMAL", + "cores": [ + -195263543 + ] + }, + { + "name": "Trinity", + "type": "RARE", + "cores": [ + 256380076 + ], + "crypts": 3, + "secrets": 4 + }, + { + "name": "Vinny 8 Ball", + "type": "RARE", + "cores": [ + -1169880205 + ], + "secrets": 1 + }, + { + "name": "Water", + "type": "NORMAL", + "cores": [ + -1849552977 + ], + "secrets": 2 + }, + { + "name": "Water Board", + "type": "PUZZLE", + "cores": [ + -109725212, + -353291158 + ] + }, + { + "name": "Waterfall", + "type": "NORMAL", + "cores": [ + 740310812, + 82232284, + 1379043687, + -1971268623 + ], + "crypts": 3, + "secrets": 8 + }, + { + "name": "Well", + "type": "NORMAL", + "cores": [ + 196766004, + 718434953, + 1955671195 + ], + "crypts": 5, + "secrets": 7 + }, + { + "name": "Withermancer", + "type": "NORMAL", + "cores": [ + -499989468, + -406356904, + -1645219026 + ], + "crypts": 6, + "secrets": 4 + }, + { + "name": "Wizard", + "type": "NORMAL", + "cores": [ + -23510667, + 1958624830, + 735485465 + ], + "crypts": 8, + "secrets": 4 + }, + { + "name": "Zodd", + "type": "NORMAL", + "cores": [ + 105458531 + ], + "crypts": 1, + "secrets": 1 + } +] diff --git a/src/main/resources/assets/enchaddons/font/notosanssc/OFL.txt b/src/main/resources/assets/enchaddons/font/notosanssc/OFL.txt new file mode 100644 index 0000000..d57ea9c --- /dev/null +++ b/src/main/resources/assets/enchaddons/font/notosanssc/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source' + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/resources/assets/enchaddons/font/notosanssc/medium.ttf b/src/main/resources/assets/enchaddons/font/notosanssc/medium.ttf new file mode 100644 index 0000000..2edd925 Binary files /dev/null and b/src/main/resources/assets/enchaddons/font/notosanssc/medium.ttf differ diff --git a/src/main/resources/mixins.enchaddons.json b/src/main/resources/mixins.enchaddons.json index c09d34d..339b3a6 100644 --- a/src/main/resources/mixins.enchaddons.json +++ b/src/main/resources/mixins.enchaddons.json @@ -17,6 +17,8 @@ "verbose": true, "mixins": [ "BasicOptionMixin", + "EntityLivingBaseMixin", + "ForgeHooksClientMixin", "NetHandlerPlayClientMixin" ] }