HTTPS
https://git.nageyqma.icu/nageyqma/odin_next.git
SSH
git@git.nageyqma.icu:nageyqma/odin_next.git
HTTPS 链接可直接 git clone;推送代码请使用 SSH。
仅展示默认分支 next 最近 100 条提交。
fix readme syntax查看变更README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) @@ -1,6 +1,6 @@ -#odin-next +# odin-next --- This is a branch to add support for latest minecraft version. -# OdinFabric \ No newline at end of file +# OdinFabric |
26.3查看变更README.md | 5 +++++ gradle.properties | 10 +++++----- src/main/java/com/odtheking/mixin/mixins/EntityMixin.java | 12 +++++++----- src/main/java/com/odtheking/mixin/mixins/EntityRendererMixin.java | 2 +- src/main/java/com/odtheking/mixin/mixins/MouseHandlerMixin.java | 2 +- src/main/java/com/odtheking/mixin/mixins/YggdrasilSignatureIgnoreMixin.java | 4 ++-- src/main/kotlin/com/odtheking/odin/clickgui/ClickGUI.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/KeybindSetting.kt | 5 +++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragonsEnum.kt | 4 +++- src/main/kotlin/com/odtheking/odin/features/impl/nether/PearlWaypoints.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/render/Etherwarp.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/IrisCompat.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/render/CustomRenderPipelines.kt | 12 ++++++------ src/main/kotlin/com/odtheking/odin/utils/render/CustomRenderType.kt | 3 --- src/main/kotlin/com/odtheking/odin/utils/render/ItemRenderer.kt | 5 +---- src/main/kotlin/com/odtheking/odin/utils/render/RenderUtils.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/utils/render/RoundedRectRenderer.kt | 8 ++++---- src/main/resources/odin.accesswidener | 3 +-- 18 files changed, 46 insertions(+), 43 deletions(-) @@ -1 +1,6 @@ +#odin-next +--- + +This is a branch to add support for latest minecraft version. + # OdinFabric \ No newline at end of file @@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx4G org.gradle.parallel=true #Fabric Properties -minecraft_version=26.2 -minecraft_version_range=~26.2 +minecraft_version=26.3 +minecraft_version_range=~26.3 loader_version=0.19.3 loom_version=1.16-SNAPSHOT # Mod Properties @@ -15,11 +15,11 @@ maven_group=com.odtheking archives_base_name=odin-fabric # Mod Dependencies fabric_kotlin_version=1.13.12+kotlin.2.4.0 -fabric_api_version=0.154.2+26.2 -modmenu_version=20.0.0-beta.4 +fabric_api_version=0.161.0+26.3 +modmenu_version=21.0.0 devauth_version=1.2.2 commodore_version=1.0.1 -iris=1.11.1+26.2-fabric +iris=1.11.6+26.3-fabric # Other Properties kotlin_version=2.4.0 kotlin.code.style=official @@ -6,6 +6,7 @@ import com.odtheking.odin.events.EntityEvent; import com.odtheking.odin.features.impl.dungeon.Highlight; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.InterpolationHandler; +import net.minecraft.world.entity.PositionPath; import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -25,14 +26,15 @@ public abstract class EntityMixin { } @WrapOperation( - method = "moveOrInterpolateTo(Ljava/util/Optional;Ljava/util/Optional;Ljava/util/Optional;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/InterpolationHandler;interpolateTo(Lnet/minecraft/world/phys/Vec3;FF)V") + method = "moveOrInterpolateTo(Lnet/minecraft/world/entity/PositionPath;FFZ)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/InterpolationHandler;interpolateTo(Lnet/minecraft/world/entity/PositionPath;FFZ)Z") ) - private void onMoveOrInterpolateTo(InterpolationHandler instance, Vec3 position, float yRot, float xRot, Operation<Void> original) { - original.call(instance, position, yRot, xRot); + private boolean onMoveOrInterpolateTo(InterpolationHandler instance, PositionPath path, float yRot, float xRot, boolean flag, Operation<Boolean> original) { + boolean result = original.call(instance, path, yRot, xRot, flag); Entity entity = (Entity)(Object)this; - new EntityEvent.Move(entity, position, yRot, xRot, entity.onGround()).postAndCatch(); + new EntityEvent.Move(entity, path.endPosition(), yRot, xRot, entity.onGround()).postAndCatch(); + return result; } @Inject(method = "setOnGround", at = @At("TAIL")) @@ -16,7 +16,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; public abstract class EntityRendererMixin<T extends Entity> { @Inject(method = "shouldRender", at = @At("HEAD"), cancellable = true) - private void onRender(T entity, Frustum culler, double camX, double camY, double camZ, CallbackInfoReturnable<Boolean> cir) { + private void onRender(T entity, Frustum culler, double camX, double camY, double camZ, float partialTicks, CallbackInfoReturnable<Boolean> cir) { if (!HidePlayers.shouldRenderPlayer(entity)) cir.setReturnValue(false); if (RenderOptimizer.hideEntityDeathAnimation() && entity instanceof LivingEntity livingEntity && livingEntity.isDeadOrDying()) @@ -34,7 +34,7 @@ public class MouseHandlerMixin { @Inject(method = "releaseMouse", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getWindow()Lcom/mojang/blaze3d/platform/Window;")) private void odin$correctCursorPosition(CallbackInfo ci) { if (OdinMod.getMc().gui.screen() instanceof ContainerScreen && NoCursorReset.shouldHookMouse()) { - InputConstants.grabOrReleaseMouse(OdinMod.getMc().getWindow(), InputConstants.CURSOR_NORMAL, this.beforeX, this.beforeY); + InputConstants.releaseMouse(OdinMod.getMc().getWindow(), this.beforeX, this.beforeY); this.xpos = this.beforeX; this.ypos = this.beforeY; } @@ -1,13 +1,13 @@ package com.odtheking.mixin.mixins; import com.mojang.authlib.properties.Property; -import com.mojang.authlib.yggdrasil.YggdrasilServicesKeyInfo; +import com.mojang.authlib.services.MinecraftServicesKeyInfo; 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.CallbackInfoReturnable; -@Mixin(value = YggdrasilServicesKeyInfo.class, remap = false) +@Mixin(value = MinecraftServicesKeyInfo.class, remap = false) public class YggdrasilSignatureIgnoreMixin { @Inject(method = "validateProperty", at = @At("HEAD"), cancellable = true, remap = false) @@ -1,5 +1,6 @@ package com.odtheking.odin.clickgui +import com.mojang.blaze3d.platform.InputConstants import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.clickgui.widget.PanelWidget import com.odtheking.odin.clickgui.widget.SearchBarWidget @@ -17,7 +18,6 @@ import net.minecraft.client.input.KeyEvent import net.minecraft.client.input.MouseButtonEvent import net.minecraft.network.chat.Component import net.minecraft.util.FormattedCharSequence -import org.lwjgl.glfw.GLFW object ClickGUI : Screen(Component.literal("Click GUI")) { @@ -129,7 +129,7 @@ object ClickGUI : Screen(Component.literal("Click GUI")) { } override fun keyPressed(event: KeyEvent): Boolean { - if (event.key == GLFW.GLFW_KEY_F && event.hasControlDownWithQuirk()) { + if (event.key == InputConstants.KEY_F && event.hasControlDownWithQuirk()) { focusSearch() return true } @@ -24,7 +24,7 @@ class KeybindSetting( desc: String ) : RenderableSetting<InputConstants.Key>(name, desc), Saving { - constructor(name: String, defaultKeyCode: Int, desc: String = "") : this(name, InputConstants.Type.KEYSYM.getOrCreate(defaultKeyCode), desc) + constructor(name: String, defaultKeyCode: Int, desc: String = "") : this(name, InputConstants.Type.KEYBOARD.getOrCreate(defaultKeyCode), desc) override var value: InputConstants.Key get() = mapping?.let { KeyMappingHelper.getBoundKeyOf(it) } ?: pending @@ -138,6 +138,7 @@ class KeybindSetting( } } - fun InputConstants.Key.isDown(): Boolean = InputConstants.isKeyDown(mc.window, value) + fun InputConstants.Key.isDown(): Boolean = + type == InputConstants.Type.KEYBOARD && value >= 0 && InputConstants.isKeyDown(value) } } @@ -101,7 +101,9 @@ fun handleSpawnPacket(particle: ClientboundLevelParticlesPacket) { particle.xDist != 2f || particle.yDist != 3f || particle.zDist != 2f || - particle.maxSpeed != 0f || + particle.xMaxSpeed != 0f || + particle.yMaxSpeed != 0f || + particle.zMaxSpeed != 0f || particle.x % 1 != 0.0 || particle.z % 1 != 0.0 ) return @@ -46,7 +46,7 @@ object PearlWaypoints : Module( drawFilledBox(AABB.ofSize(result.upAngle, 0.12, 0.12, 0.12), dynamicWaypointsColor) drawFilledBox(AABB.ofSize(result.flatAngle, 0.12, 0.12, 0.12), dynamicWaypointsColor) } - drawWireFrameBox(AABB(BlockPos(lineup.supply.dropOffSpot.above())), dynamicWaypointsColor) + drawWireFrameBox(AABB(lineup.supply.dropOffSpot.above()), dynamicWaypointsColor) } } } @@ -245,7 +245,7 @@ object Etherwarp : Module( is SugarCaneBlock -> true is MushroomBlock -> true is NetherWartBlock -> true - is RedStoneWireBlock, is ComparatorBlock, is RepeaterBlock -> true + is RedstoneWireBlock, is ComparatorBlock, is RepeaterBlock -> true is SmallDripleafBlock, is BigDripleafStemBlock -> true is DoublePlantBlock -> true is LeverBlock -> true @@ -1,6 +1,6 @@ package com.odtheking.odin.utils -import com.mojang.blaze3d.pipeline.RenderPipeline +import com.mojang.renderpearl.api.pipeline.RenderPipeline import com.odtheking.odin.utils.render.CustomRenderType import net.fabricmc.loader.api.FabricLoader import net.irisshaders.iris.api.v0.IrisApi @@ -1,12 +1,12 @@ package com.odtheking.odin.utils.render -import com.mojang.blaze3d.PrimitiveTopology -import com.mojang.blaze3d.pipeline.BlendFunction -import com.mojang.blaze3d.pipeline.ColorTargetState -import com.mojang.blaze3d.pipeline.DepthStencilState -import com.mojang.blaze3d.pipeline.RenderPipeline -import com.mojang.blaze3d.platform.CompareOp import com.mojang.blaze3d.vertex.DefaultVertexFormat +import com.mojang.renderpearl.api.pipeline.BlendFunction +import com.mojang.renderpearl.api.pipeline.ColorTargetState +import com.mojang.renderpearl.api.pipeline.CompareOp +import com.mojang.renderpearl.api.pipeline.DepthStencilState +import com.mojang.renderpearl.api.pipeline.PrimitiveTopology +import com.mojang.renderpearl.api.pipeline.RenderPipeline import net.minecraft.client.renderer.RenderPipelines import net.minecraft.resources.Identifier @@ -1,7 +1,6 @@ package com.odtheking.odin.utils.render import net.minecraft.client.renderer.rendertype.LayeringTransform -import net.minecraft.client.renderer.rendertype.OutputTarget import net.minecraft.client.renderer.rendertype.RenderSetup import net.minecraft.client.renderer.rendertype.RenderType @@ -13,14 +12,12 @@ object CustomRenderType { "lines-esp", RenderSetup.builder(CustomRenderPipelines.LINES_ESP) .setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING) - .setOutputTarget(OutputTarget.ITEM_ENTITY_TARGET) .createRenderSetup() ) val LINES_TRANSLUCENT_ESP: RenderType = RenderType.create( "lines-translucent-esp", RenderSetup.builder(CustomRenderPipelines.LINES_TRANSLUCENT_ESP) - .setOutputTarget(OutputTarget.ITEM_ENTITY_TARGET) .createRenderSetup() ) @@ -2,9 +2,8 @@ package com.odtheking.odin.utils.render import com.mojang.blaze3d.platform.Lighting import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.textures.FilterMode -import com.mojang.blaze3d.textures.GpuTextureView import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.renderpearl.api.textures.FilterMode import com.odtheking.odin.OdinMod.mc import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.navigation.ScreenRectangle @@ -27,11 +26,9 @@ import java.util.* class ItemStateRenderer : PictureInPictureRenderer<ItemStateRenderer.State>() { - private var textureView: GpuTextureView? = null private var lastState: State? = null override fun renderToTexture(renderState: State, poseStack: PoseStack, collector: SubmitNodeCollector) { - textureView = RenderSystem.outputColorTextureOverride lastState = renderState poseStack.scale(1f, -1f, -1f) @@ -199,8 +199,8 @@ fun RenderExtractEvent.drawText(text: String, pos: Vec3, scale: Float, depth: Bo if (displayMode == Font.DisplayMode.SEE_THROUGH) { context.submitNodeCollector().submitCustom(SubmitRenderPhases.AFTER_TERRAIN, TextFeatureRenderer.Submit( - Matrix4f(poseStack.last().pose()), x, 0f, string, - true, displayMode, LightCoordsUtil.FULL_BRIGHT, -1, 0, 0 + Matrix4f(poseStack.last().pose()), displayMode, LightCoordsUtil.FULL_BRIGHT, + TextFeatureRenderer.Content.Text(x, 0f, string, true, -1, 0, 0) ) ) } else context.submitNodeCollector().submitText(poseStack, x, 0f, string, true, displayMode, LightCoordsUtil.FULL_BRIGHT, -1, 0, 0) @@ -1,12 +1,12 @@ package com.odtheking.odin.utils.render -import com.mojang.blaze3d.GpuFormat -import com.mojang.blaze3d.pipeline.RenderPipeline import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.textures.FilterMode import com.mojang.blaze3d.vertex.DefaultVertexFormat import com.mojang.blaze3d.vertex.VertexConsumer -import com.mojang.blaze3d.vertex.VertexFormat +import com.mojang.renderpearl.api.GpuFormat +import com.mojang.renderpearl.api.pipeline.RenderPipeline +import com.mojang.renderpearl.api.textures.FilterMode +import com.mojang.renderpearl.api.vertex.VertexFormat import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.utils.render.CustomRenderPipelines.PIPELINE_ROUND_RECT import com.odtheking.odin.utils.render.CustomRenderPipelines.PIPELINE_ROUND_RECT_SHADOW @@ -5,5 +5,4 @@ accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScr accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScreen topPos I accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScreen imageWidth I accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScreen imageHeight I -accessible class com/mojang/blaze3d/opengl/GlDevice -accessible field com/mojang/blaze3d/systems/GpuDevice backend Lcom/mojang/blaze3d/systems/GpuDeviceBackend; \ No newline at end of file +accessible field net/minecraft/client/gui/render/pip/PictureInPictureRenderer textureView Lcom/mojang/renderpearl/api/textures/GpuTextureView; \ No newline at end of file |
| 26.2 (#162) * Cleanup rounded rectangle rendering
* Started on project FVKGUI mostly functional maybe
* 26.2 (only opengl) early
* Migrated ClickGUI to the Minecraft backend to support different rendering engines
* Fixed missing import
* Reworked 3d rendering to work with proper minecraft API
* Added animation for settings appearing from dependency hiding
* Changed ColorSetting design and added color favorites system
* Fix color favorite box positions
* A couple fixes
* Fixed merge and port
* Bump version
* Removed forgotten gui scale descale
* Fixed import
* Fixed BetterPartyFinder not checking the correct floor
* Fixed DungeonMap websocket not checking for player names
* Save options on main thread (#159)
This fixes some warnings about C2ME supposedly automatically redirecting it to main thread. Not sure if it would give a different error or just cause some unsafe behaviour without C2ME.
* Cleaned up module settings
Made secret interact event higher skull range
Fixed beacon texture rendering
Fixed SelectorSetting tag position
Added 4th color to numbers
* Fixed dragon spray notification being wrong
---------
Co-authored-by: Mustafa <mustafaoncel92@gmail.com> 查看变更build.gradle.kts | 10 --
gradle.properties | 11 +-
src/main/java/com/odtheking/mixin/mixins/ExperienceBarRendererMixin.java | 4 +-
src/main/java/com/odtheking/mixin/mixins/FontMixin.java | 6 +-
src/main/java/com/odtheking/mixin/mixins/GUIMixin.java | 20 +++
src/main/java/com/odtheking/mixin/mixins/{GuiMixin.java => HUDMixin.java} | 6 +-
src/main/java/com/odtheking/mixin/mixins/MinecraftMixin.java | 9 --
src/main/java/com/odtheking/mixin/mixins/MouseHandlerMixin.java | 2 +-
src/main/java/com/odtheking/mixin/mixins/ScreenEffectRendererMixin.java | 6 +-
src/main/kotlin/com/odtheking/odin/OdinMod.kt | 17 +--
src/main/kotlin/com/odtheking/odin/clickgui/ClickGUI.kt | 237 ++++++++++++++---------------
src/main/kotlin/com/odtheking/odin/clickgui/GuiTheme.kt | 30 ++++
src/main/kotlin/com/odtheking/odin/clickgui/HudManager.kt | 124 ++++++++-------
src/main/kotlin/com/odtheking/odin/clickgui/Panel.kt | 154 -------------------
src/main/kotlin/com/odtheking/odin/clickgui/SearchBar.kt | 59 --------
src/main/kotlin/com/odtheking/odin/clickgui/settings/ModuleButton.kt | 116 --------------
src/main/kotlin/com/odtheking/odin/clickgui/settings/RenderableSetting.kt | 77 ++++++----
src/main/kotlin/com/odtheking/odin/clickgui/settings/Setting.kt | 61 +++-----
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/ActionSetting.kt | 48 +++---
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/BooleanSetting.kt | 64 ++------
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/ColorSetting.kt | 405 +++++++++++++++++++++++++++++--------------------
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/DropdownSetting.kt | 50 +++----
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/HUDElement.kt | 68 +++++++--
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/HUDSetting.kt | 118 ++++++---------
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/KeybindSetting.kt | 137 +++++++++++------
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/ListSetting.kt | 4 +-
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/MapSetting.kt | 6 +-
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/NumberSetting.kt | 209 +++++++++++++-------------
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/SelectorSetting.kt | 197 ++++++++++++------------
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/StringSetting.kt | 90 ++++++-----
src/main/kotlin/com/odtheking/odin/clickgui/widget/ComponentHelpers.kt | 73 +++++++++
src/main/kotlin/com/odtheking/odin/clickgui/widget/ModuleWidget.kt | 130 ++++++++++++++++
src/main/kotlin/com/odtheking/odin/clickgui/widget/OdinWidget.kt | 115 ++++++++++++++
src/main/kotlin/com/odtheking/odin/clickgui/widget/PanelWidget.kt | 152 +++++++++++++++++++
src/main/kotlin/com/odtheking/odin/clickgui/widget/SearchBarWidget.kt | 59 ++++++++
src/main/kotlin/com/odtheking/odin/commands/DevCommand.kt | 3 +-
src/main/kotlin/com/odtheking/odin/commands/DungeonWaypointCommand.kt | 7 +-
src/main/kotlin/com/odtheking/odin/commands/MainCommand.kt | 4 +-
src/main/kotlin/com/odtheking/odin/commands/WaypointCommand.kt | 16 +-
src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 7 +-
src/main/kotlin/com/odtheking/odin/events/EventDispatcher.kt | 8 +-
src/main/kotlin/com/odtheking/odin/features/Category.kt | 16 +-
src/main/kotlin/com/odtheking/odin/features/Module.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt | 18 +--
src/main/kotlin/com/odtheking/odin/features/impl/boss/ArrowAlign.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/ArrowsDevice.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/DragonCheck.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/DragonPriority.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/ExtraStats.kt | 28 ++--
src/main/kotlin/com/odtheking/odin/features/impl/boss/InactiveWaypoints.kt | 101 +++++++++----
src/main/kotlin/com/odtheking/odin/features/impl/boss/KingRelics.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/LividSolver.kt | 26 ++--
src/main/kotlin/com/odtheking/odin/features/impl/boss/MelodyMessage.kt | 44 +++---
src/main/kotlin/com/odtheking/odin/features/impl/boss/SimonSays.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSimulator.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 71 +++++----
src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalTimes.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalTitles.kt | 3 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/TerracottaTimer.kt | 5 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragons.kt | 29 ++--
src/main/kotlin/com/odtheking/odin/features/impl/boss/termGUI/CustomTermGui.kt | 11 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/MelodySim.kt | 13 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/NumbersSim.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/PanesSim.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/RubixSim.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/StartGUI.kt | 17 ++-
src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/TermSimGUI.kt | 11 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BetterPartyFinder.kt | 14 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BloodCamp.kt | 30 ++--
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DoorHighlight.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DungeonQueue.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Highlight.kt | 7 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/InvincibilityTimer.kt | 32 ++--
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/LeapMenu.kt | 80 +++++-----
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MageBeam.kt | 8 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 186 +++++++----------------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PositionalMessages.kt | 15 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/RoomClear.kt | 5 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/SecretClicked.kt | 13 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypointEditor.kt | 20 ++-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypointHud.kt | 5 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypoints.kt | 49 ++----
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/TextPromtScreen.kt | 4 +-
.../kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/WaypointPackSelectorScreen.kt | 14 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 34 +++--
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 8 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 5 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BeamsSolver.kt | 10 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BlazeSolver.kt | 5 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BoulderSolver.kt | 5 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/IceFillSolver.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 35 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/QuizSolver.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/TPMazeSolver.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/WaterSolver.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/WeirdosSolver.kt | 5 +-
src/main/kotlin/com/odtheking/odin/features/impl/nether/BuildHelper.kt | 6 +-
src/main/kotlin/com/odtheking/odin/features/impl/nether/KuudraInfo.kt | 6 +-
src/main/kotlin/com/odtheking/odin/features/impl/nether/KuudraTracker.kt | 29 ++--
src/main/kotlin/com/odtheking/odin/features/impl/nether/Misc.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/nether/PearlWaypoints.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/nether/SupplyHelper.kt | 9 +-
src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt | 8 +-
src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 44 +++---
src/main/kotlin/com/odtheking/odin/features/impl/render/Etherwarp.kt | 23 +--
src/main/kotlin/com/odtheking/odin/features/impl/render/GyroWand.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/render/HidePlayers.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/render/PerformanceHUD.kt | 10 +-
src/main/kotlin/com/odtheking/odin/features/impl/render/PlayerSize.kt | 10 +-
src/main/kotlin/com/odtheking/odin/features/impl/render/RenderOptimizer.kt | 8 +-
src/main/kotlin/com/odtheking/odin/features/impl/render/RenderTest.kt | 18 +--
src/main/kotlin/com/odtheking/odin/features/impl/render/Shenanigans.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/render/Waypoints.kt | 29 +---
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/ChatCommands.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/LoadoutKeybinds.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/NoCursorReset.kt | 6 +-
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PetKeybinds.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PlayerDisplay.kt | 6 +-
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/SlotBinds.kt | 31 ++--
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/Splits.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/SpringBoots.kt | 4 +-
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/WardrobeKeybinds.kt | 2 +-
src/main/kotlin/com/odtheking/odin/utils/ChatUtils.kt | 4 +-
src/main/kotlin/com/odtheking/odin/utils/PlayerUtils.kt | 8 +-
src/main/kotlin/com/odtheking/odin/utils/Utils.kt | 8 +-
src/main/kotlin/com/odtheking/odin/utils/VecUtils.kt | 4 +-
src/main/kotlin/com/odtheking/odin/utils/render/CustomRenderPipelines.kt | 59 ++++++--
src/main/kotlin/com/odtheking/odin/utils/render/CustomRenderType.kt | 24 ++-
src/main/kotlin/com/odtheking/odin/utils/render/DrawContextRenderer.kt | 130 ----------------
src/main/kotlin/com/odtheking/odin/utils/render/DrawContextUtils.kt | 23 ---
src/main/kotlin/com/odtheking/odin/utils/render/ItemRenderer.kt | 16 +-
src/main/kotlin/com/odtheking/odin/utils/render/RenderUtils.kt | 416 +++++++++++++++++++--------------------------------
src/main/kotlin/com/odtheking/odin/utils/render/RoundRectPIPRenderer.kt | 182 ----------------------
src/main/kotlin/com/odtheking/odin/utils/render/RoundedRect.kt | 114 ++++++++++++++
src/main/kotlin/com/odtheking/odin/utils/render/RoundedRectRenderer.kt | 172 +++++++++++++++++++++
src/main/kotlin/com/odtheking/odin/utils/render/Scissor.kt | 64 ++++++++
src/main/kotlin/com/odtheking/odin/utils/skyblock/KuudraUtils.kt | 2 +-
src/main/kotlin/com/odtheking/odin/utils/skyblock/LocationUtils.kt | 2 +-
src/main/kotlin/com/odtheking/odin/utils/skyblock/SplitsManager.kt | 4 +-
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 17 ++-
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonUtils.kt | 3 +-
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/TerminalUtils.kt | 8 +-
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/MelodyHandler.kt | 8 +-
.../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/NumbersHandler.kt | 16 +-
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/PanesHandler.kt | 3 +-
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/RubixHandler.kt | 2 +-
.../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/SelectAllHandler.kt | 2 +-
.../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/StartsWithHandler.kt | 2 +-
.../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/TerminalHandler.kt | 7 +-
src/main/kotlin/com/odtheking/odin/utils/ui/HoverHandler.kt | 23 ---
src/main/kotlin/com/odtheking/odin/utils/ui/MouseUtils.kt | 20 ---
src/main/kotlin/com/odtheking/odin/utils/ui/TextInputHandler.kt | 331 ----------------------------------------
src/main/kotlin/com/odtheking/odin/utils/ui/animations/Animation.kt | 40 -----
src/main/kotlin/com/odtheking/odin/utils/ui/animations/Animations.kt | 13 ++
src/main/kotlin/com/odtheking/odin/utils/ui/animations/ColorAnimation.kt | 22 ---
src/main/kotlin/com/odtheking/odin/utils/ui/animations/EaseInOutAnimation.kt | 19 ---
src/main/kotlin/com/odtheking/odin/utils/ui/animations/EaseOutAnimation.kt | 16 --
src/main/kotlin/com/odtheking/odin/utils/ui/animations/Easing.kt | 17 +++
src/main/kotlin/com/odtheking/odin/utils/ui/animations/Fade.kt | 47 ++++++
src/main/kotlin/com/odtheking/odin/utils/ui/animations/LinearAnimation.kt | 13 --
src/main/kotlin/com/odtheking/odin/utils/ui/animations/Tween.kt | 35 +++++
src/main/kotlin/com/odtheking/odin/utils/ui/rendering/Font.kt | 32 ----
src/main/kotlin/com/odtheking/odin/utils/ui/rendering/Gradient.kt | 6 -
src/main/kotlin/com/odtheking/odin/utils/ui/rendering/Image.kt | 54 -------
src/main/kotlin/com/odtheking/odin/utils/ui/rendering/NVGPIPRenderer.kt | 102 -------------
src/main/kotlin/com/odtheking/odin/utils/ui/rendering/NVGRenderer.kt | 403 -------------------------------------------------
src/main/resources/assets/odin/HueGradient.png | Bin 229 -> 0 bytes
src/main/resources/assets/odin/MovementIcon.svg | 1 -
src/main/resources/assets/odin/chevron.svg | 3 -
src/main/resources/assets/odin/font.ttf | Bin 343244 -> 0 bytes
src/main/resources/assets/odin/icon.png | Bin 807 -> 218 bytes
src/main/resources/assets/odin/lang/en_us.json | 3 +
src/main/resources/assets/odin/map/cross.png | Bin 250 -> 141 bytes
src/main/resources/assets/odin/map/green_check.png | Bin 166 -> 120 bytes
src/main/resources/assets/odin/map/question.png | Bin 168 -> 103 bytes
src/main/resources/assets/odin/map/white_check.png | Bin 189 -> 120 bytes
src/main/resources/assets/odin/shaders/core/round_rect.fsh | 48 +-----
src/main/resources/assets/odin/shaders/core/round_rect.vsh | 29 +++-
src/main/resources/assets/odin/shaders/core/round_rect_shadow.fsh | 15 ++
src/main/resources/assets/odin/shaders/core/round_rect_textured.fsh | 17 +++
src/main/resources/assets/odin/shaders/include/round_rect.glsl | 46 ++++++
src/main/resources/assets/odin/shaders/pipeline/round_rect.json | 4 -
src/main/resources/assets/odin/textures/chevron.png | Bin 0 -> 288 bytes
src/main/resources/assets/odin/textures/huegradient.png | Bin 0 -> 197 bytes
src/main/resources/assets/odin/textures/job.png | Bin 428093 -> 307585 bytes
src/main/resources/assets/odin/textures/logo.png | Bin 288721 -> 235849 bytes
src/main/resources/assets/odin/textures/movementicon.png | Bin 0 -> 3320 bytes
src/main/resources/odin.mixins.json | 3 +-
189 files changed, 3124 insertions(+), 3820 deletions(-)@@ -32,16 +32,6 @@ dependencies { compileOnly("com.terraformersmc:modmenu:${property("modmenu_version")}") - property("minecraft_lwjgl_version").let { lwjglVersion -> - implementation("org.lwjgl:lwjgl-nanovg:$lwjglVersion") - include("org.lwjgl:lwjgl-nanovg:$lwjglVersion") - - listOf("windows", "linux", "macos", "macos-arm64").forEach { os -> - implementation("org.lwjgl:lwjgl-nanovg:$lwjglVersion:natives-$os") - include("org.lwjgl:lwjgl-nanovg:$lwjglVersion:natives-$os") - } - } - compileOnly("maven.modrinth:iris:${property("iris")}") } @@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx4G org.gradle.parallel=true #Fabric Properties -minecraft_version=26.1.2 -minecraft_version_range=~26.1 +minecraft_version=26.2 +minecraft_version_range=~26.2 loader_version=0.19.3 loom_version=1.16-SNAPSHOT # Mod Properties @@ -15,12 +15,11 @@ maven_group=com.odtheking archives_base_name=odin-fabric # Mod Dependencies fabric_kotlin_version=1.13.12+kotlin.2.4.0 -fabric_api_version=0.151.0+26.1.2 -modmenu_version=18.0.0-beta.1 +fabric_api_version=0.154.2+26.2 +modmenu_version=20.0.0-beta.4 devauth_version=1.2.2 commodore_version=1.0.1 -iris=1.10.9+26.1-fabric +iris=1.11.1+26.2-fabric # Other Properties kotlin_version=2.4.0 -minecraft_lwjgl_version=3.4.1 kotlin.code.style=official @@ -4,13 +4,13 @@ import com.odtheking.odin.features.impl.skyblock.OverlayType; import com.odtheking.odin.features.impl.skyblock.PlayerDisplay; import net.minecraft.client.DeltaTracker; import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.gui.contextualbar.ExperienceBarRenderer; +import net.minecraft.client.gui.contextualbar.ExperienceBar; 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(ExperienceBarRenderer.class) +@Mixin(ExperienceBar.class) public class ExperienceBarRendererMixin { @Inject(method = "extractBackground", at = @At("HEAD"), cancellable = true) @@ -25,9 +25,9 @@ public class FontMixin { } @ModifyVariable(method = "width(Ljava/lang/String;)I", at = @At("HEAD"), argsOnly = true, name = "str") - private String onWidthString(String str) { - if (!CustomNameReplacer.isEnabled()) return str; - return CustomNameReplacer.replaceStringIfNeeded(str); + private String onWidthString(String text) { + if (!CustomNameReplacer.isEnabled()) return text; + return CustomNameReplacer.replaceStringIfNeeded(text); } @ModifyVariable(method = "width(Lnet/minecraft/network/chat/FormattedText;)I", at = @At("HEAD"), argsOnly = true, name = "text") @@ -0,0 +1,20 @@ +package com.odtheking.mixin.mixins; + +import com.odtheking.odin.events.ScreenCloseEvent; +import com.odtheking.odin.events.ScreenEvent; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.screens.Screen; +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(Gui.class) +public class GUIMixin { + + @Inject(method = "setScreen", at = @At("TAIL")) + private void setScreen(Screen screen, CallbackInfo ci) { + if (screen != null) new ScreenEvent.Open(screen).postAndCatch(); + else ScreenCloseEvent.INSTANCE.postAndCatch(); + } +} \ No newline at end of file @@ -3,16 +3,16 @@ package com.odtheking.mixin.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.odtheking.odin.features.impl.skyblock.OverlayType; import com.odtheking.odin.features.impl.skyblock.PlayerDisplay; -import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.Hud; import net.minecraft.world.entity.player.Player; 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(Gui.class) -public class GuiMixin { +@Mixin(Hud.class) +public class HUDMixin { @Inject(method = "extractArmor", at = @At("HEAD"), cancellable = true) private static void cancelArmorBar(GuiGraphicsExtractor graphics, Player player, int yLineBase, int numHealthRows, int healthRowHeight, int xLeft, CallbackInfo ci) { @@ -3,12 +3,9 @@ package com.odtheking.mixin.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.odtheking.odin.events.BlockInteractEvent; import com.odtheking.odin.events.EntityInteractEvent; -import com.odtheking.odin.events.ScreenCloseEvent; -import com.odtheking.odin.events.ScreenEvent; import com.odtheking.odin.features.impl.boss.TerminalSolver; import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; @@ -43,10 +40,4 @@ public abstract class MinecraftMixin { if (TerminalUtils.getCurrentTerm() != null && TerminalSolver.getTermSize() != (Integer) original) return TerminalSolver.getTermSize(); return original; } - - @Inject(method = "setScreen", at = @At(value = "HEAD")) - private void onSetScreen(Screen screen, CallbackInfo ci) { - if (screen != null) new ScreenEvent.Open(screen).postAndCatch(); - else ScreenCloseEvent.INSTANCE.postAndCatch(); - } } \ No newline at end of file @@ -33,7 +33,7 @@ public class MouseHandlerMixin { @Inject(method = "releaseMouse", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getWindow()Lcom/mojang/blaze3d/platform/Window;")) private void odin$correctCursorPosition(CallbackInfo ci) { - if (OdinMod.getMc().screen instanceof ContainerScreen && NoCursorReset.shouldHookMouse()) { + if (OdinMod.getMc().gui.screen() instanceof ContainerScreen && NoCursorReset.shouldHookMouse()) { InputConstants.grabOrReleaseMouse(OdinMod.getMc().getWindow(), InputConstants.CURSOR_NORMAL, this.beforeX, this.beforeY); this.xpos = this.beforeX; this.ypos = this.beforeY; @@ -2,8 +2,8 @@ package com.odtheking.mixin.mixins; import com.mojang.blaze3d.vertex.PoseStack; import com.odtheking.odin.features.impl.render.RenderOptimizer; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.ScreenEffectRenderer; +import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -13,8 +13,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ScreenEffectRenderer.class) public class ScreenEffectRendererMixin { - @Inject(method = "renderFire", at = @At("HEAD"), cancellable = true) - private static void onRenderFireOverlay(PoseStack poseStack, MultiBufferSource bufferSource, TextureAtlasSprite sprite, CallbackInfo ci) { + @Inject(method = "submitFire", at = @At("HEAD"), cancellable = true) + private static void onRenderFireOverlay(PoseStack poseStack, SubmitNodeCollector submitNodeCollector, TextureAtlasSprite sprite, CallbackInfo ci) { if (RenderOptimizer.shouldDisableFireOverlay()) ci.cancel(); } } @@ -10,13 +10,10 @@ import com.odtheking.odin.utils.IrisCompatability import com.odtheking.odin.utils.ServerUtils import com.odtheking.odin.utils.handlers.TickTasks import com.odtheking.odin.utils.render.ItemStateRenderer -import com.odtheking.odin.utils.render.RenderBatchManager -import com.odtheking.odin.utils.render.RoundRectPIPRenderer import com.odtheking.odin.utils.skyblock.* import com.odtheking.odin.utils.skyblock.dungeon.DungeonListener import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils -import com.odtheking.odin.utils.ui.rendering.NVGPIPRenderer import com.odtheking.odin.utils.ui.widget.CustomGUIImpl import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -72,20 +69,10 @@ object OdinMod : ClientModInitializer { ActionBarListener, ServerUtils, EventDispatcher, DungeonListener, PartyUtils, TerminalUtils, DungeonUtils, SplitsManager, DungeonScan, - IrisCompatability, RenderBatchManager, + IrisCompatability, ModuleManager, CustomGUIImpl, Shenanigans, ).forEach { EventBus.subscribe(it) } - PictureInPictureRendererRegistry.register { context -> - NVGPIPRenderer(context.bufferSource()) - } - - PictureInPictureRendererRegistry.register { context -> - RoundRectPIPRenderer(context.bufferSource()) - } - - PictureInPictureRendererRegistry.register { context -> - ItemStateRenderer(context.bufferSource()) - } + PictureInPictureRendererRegistry.register { ItemStateRenderer() } } } \ No newline at end of file @@ -1,177 +1,162 @@ package com.odtheking.odin.clickgui import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.clickgui.settings.impl.ColorSetting +import com.odtheking.odin.clickgui.widget.PanelWidget +import com.odtheking.odin.clickgui.widget.SearchBarWidget import com.odtheking.odin.features.Category import com.odtheking.odin.features.ModuleManager import com.odtheking.odin.features.impl.render.ClickGUIModule -import com.odtheking.odin.utils.Color +import com.odtheking.odin.utils.Color.Companion.hsbMax import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.ui.HoverHandler -import com.odtheking.odin.utils.ui.animations.EaseOutAnimation -import com.odtheking.odin.utils.ui.rendering.NVGPIPRenderer -import com.odtheking.odin.utils.ui.rendering.NVGRenderer +import com.odtheking.odin.utils.render.roundedRectOutlined +import com.odtheking.odin.utils.ui.animations.Animations import net.minecraft.client.gui.GuiGraphicsExtractor +import net.minecraft.client.gui.components.events.GuiEventListener import net.minecraft.client.gui.screens.Screen -import net.minecraft.client.input.CharacterEvent import net.minecraft.client.input.KeyEvent import net.minecraft.client.input.MouseButtonEvent import net.minecraft.network.chat.Component -import kotlin.math.sign -import com.odtheking.odin.utils.ui.mouseX as odinMouseX -import com.odtheking.odin.utils.ui.mouseY as odinMouseY +import net.minecraft.util.FormattedCharSequence +import org.lwjgl.glfw.GLFW -/** - * Renders all the modules. - */ object ClickGUI : Screen(Component.literal("Click GUI")) { - private val panels: ArrayList<Panel> = arrayListOf<Panel>().apply { - if (Category.categories.any { (category, _) -> ClickGUIModule.panelSetting[category] == null }) ClickGUIModule.resetPositions() - for ((_, category) in Category.categories) add(Panel(category)) + var searchString = "" + private set + + private val panels: MutableList<PanelWidget> by lazy { + if (Category.categories.keys.any { ClickGUIModule.panelSetting[it] == null }) ClickGUIModule.resetPositions() + Category.categories.values.mapTo(mutableListOf()) { PanelWidget(it) } } - private var openAnim = EaseOutAnimation(500) - val gray38 = Color(38, 38, 38) - val gray26 = Color(26, 26, 26) + private val searchBar by lazy { + SearchBarWidget { text -> + searchString = text + panels.forEach { it.resetScroll() } + } + } - override fun extractRenderState(context: GuiGraphicsExtractor, mouseX: Int, mouseY: Int, deltaTicks: Float) { - NVGPIPRenderer.draw(context, 0, 0, context.guiWidth(), context.guiHeight()) { - val scaledMouseX = odinMouseX / ClickGUIModule.getStandardGuiScale() - val scaledMouseY = odinMouseY / ClickGUIModule.getStandardGuiScale() + private var scale: Float = 1f - NVGRenderer.scale(ClickGUIModule.getStandardGuiScale(), ClickGUIModule.getStandardGuiScale()) + val virtualWidth get() = (mc.window.guiScaledWidth / scale).toInt() + val virtualHeight get() = (mc.window.guiScaledHeight / scale).toInt() - SearchBar.draw( - mc.window.screenWidth / (2f * ClickGUIModule.getStandardGuiScale()) - 175f, - (mc.window.screenHeight - 110f) / ClickGUIModule.getStandardGuiScale() - 20f, - scaledMouseX, - scaledMouseY - ) + override fun children(): List<GuiEventListener> = panels.asReversed() + searchBar - if (openAnim.isAnimating()) { - val scale = openAnim.get(0f, 1f) + override fun extractRenderState(graphics: GuiGraphicsExtractor, mouseX: Int, mouseY: Int, partialTick: Float) { + val scaledX = (mouseX / scale).toInt() + val scaledY = (mouseY / scale).toInt() - val centerX = context.guiWidth().toFloat() - val centerY = context.guiHeight().toFloat() - NVGRenderer.translate(centerX, centerY) - NVGRenderer.scale(scale, scale) - NVGRenderer.translate(-centerX, -centerY) - } + graphics.pose().pushMatrix() + graphics.pose().scale(scale, scale) - val draggedPanel = panels.firstOrNull { it.dragging } - for (panel in panels) { - if (panel != draggedPanel) panel.draw(scaledMouseX, scaledMouseY) - } + searchBar.extractRenderState(graphics, scaledX, scaledY, partialTick) + for (panel in panels) panel.extractRenderState(graphics, scaledX, scaledY, partialTick) + drawDescription(graphics) - draggedPanel?.draw(scaledMouseX, scaledMouseY) + graphics.pose().popMatrix() + super.extractRenderState(graphics, mouseX, mouseY, partialTick) + } - desc.render() - } - super.extractRenderState(context, mouseX, mouseY, deltaTicks) + fun setDescription(text: String, x: Int, y: Int) { + description = text + descriptionX = x + descriptionY = y } - override fun mouseScrolled( - mouseX: Double, - mouseY: Double, - horizontalAmount: Double, - verticalAmount: Double - ): Boolean { - val actualAmount = (verticalAmount.sign * 16).toInt() - for (i in panels.size - 1 downTo 0) { - if (panels[i].handleScroll(actualAmount)) return true + private var description = "" + private var descriptionX = 0 + private var descriptionY = 0 + + private var lines: List<FormattedCharSequence> = emptyList() + private var wrappedText = "" + private var boxWidth = 0 + + private fun drawDescription(graphics: GuiGraphicsExtractor) { + if (description.isEmpty()) return + + if (description != wrappedText) { + wrappedText = description + lines = font.split(Component.literal(description), DESCRIPTION_WIDTH) + boxWidth = (lines.maxOfOrNull { font.width(it) } ?: 0) + DESCRIPTION_PADDING * 2 } - return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) - } - override fun mouseClicked( - mouseButtonEvent: MouseButtonEvent, - bl: Boolean - ): Boolean { - val scaledMouseX = odinMouseX / ClickGUIModule.getStandardGuiScale() - val scaledMouseY = odinMouseY / ClickGUIModule.getStandardGuiScale() - SearchBar.mouseClicked(scaledMouseX, scaledMouseY, mouseButtonEvent) - for (i in panels.size - 1 downTo 0) { - if (panels[i].mouseClicked(scaledMouseX, scaledMouseY, mouseButtonEvent)) return true + val boxHeight = lines.size * font.lineHeight + DESCRIPTION_PADDING * 2 + val boxX = descriptionX.coerceIn(0, (virtualWidth - boxWidth).coerceAtLeast(0)) + val boxY = descriptionY.coerceIn(0, (virtualHeight - boxHeight).coerceAtLeast(0)) + + graphics.roundedRectOutlined( + boxX, boxY, boxX + boxWidth, boxY + boxHeight, + GuiTheme.surface.rgba, GuiTheme.accent.hsbMax().rgba, 1.5f, GuiTheme.RADIUS + ) + lines.forEachIndexed { index, line -> + graphics.text( + font, line, boxX + DESCRIPTION_PADDING, + boxY + DESCRIPTION_PADDING + index * font.lineHeight, Colors.WHITE.rgba, false + ) } - return super.mouseClicked(mouseButtonEvent, bl) + + description = "" } - override fun mouseReleased(mouseButtonEvent: MouseButtonEvent): Boolean { - SearchBar.mouseReleased() - for (i in panels.size - 1 downTo 0) { - panels[i].mouseReleased(mouseButtonEvent) - } - return super.mouseReleased(mouseButtonEvent) + private fun MouseButtonEvent.intoGuiSpace(): MouseButtonEvent = + scale.let { MouseButtonEvent(x() / it, y() / it, buttonInfo()) } + + override fun mouseClicked(event: MouseButtonEvent, doubleClick: Boolean): Boolean { + val scaled = event.intoGuiSpace() + bringToFront(scaled) + return super.mouseClicked(scaled, doubleClick) } - override fun charTyped(characterEvent: CharacterEvent): Boolean { - SearchBar.keyTyped(characterEvent) - for (i in panels.size - 1 downTo 0) { - if (panels[i].keyTyped(characterEvent)) return true - } - return super.charTyped(characterEvent) + override fun mouseReleased(event: MouseButtonEvent): Boolean = super.mouseReleased(event.intoGuiSpace()) + + override fun mouseDragged(event: MouseButtonEvent, dragX: Double, dragY: Double): Boolean { + val factor = scale + return super.mouseDragged(event.intoGuiSpace(), dragX / factor, dragY / factor) } - override fun keyPressed(keyEvent: KeyEvent): Boolean { - SearchBar.keyPressed(keyEvent) - for (i in panels.size - 1 downTo 0) { - if (panels[i].keyPressed(keyEvent)) return true - } - return super.keyPressed(keyEvent) + override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean { + val factor = scale + return super.mouseScrolled(mouseX / factor, mouseY / factor, scrollX, scrollY) } override fun init() { - openAnim.start() - super.init() + Animations.settle() + + scale = ClickGUIModule.clickGuiScale.toFloat() / mc.window.guiScale + searchBar.place(virtualWidth / 2 - SearchBarWidget.WIDTH / 2, virtualHeight - SEARCH_BAR_BOTTOM_MARGIN) + focusSearch() } - override fun onClose() { - for (panel in panels.filter { it.panelSetting.extended }.reversed()) { - for (moduleButton in panel.moduleButtons.filter { it.extended }) { - for (setting in moduleButton.representableSettings) { - if (setting is ColorSetting) setting.section = null - setting.listening = false - } - } + override fun keyPressed(event: KeyEvent): Boolean { + if (event.key == GLFW.GLFW_KEY_F && event.hasControlDownWithQuirk()) { + focusSearch() + return true } + return focused?.keyPressed(event) == true || super.keyPressed(event) + } + + private fun focusSearch() { + setInitialFocus(searchBar) + searchBar.selectAll() + } + override fun onClose() { + panels.forEach { it.release() } ModuleManager.saveConfigurations() super.onClose() } override fun isPauseScreen(): Boolean = false - private var desc = Description("", 0f, 0f, HoverHandler(150)) - - /** Sets the description without creating a new data class which isn't optimal */ - fun setDescription(text: String, x: Float, y: Float, hoverHandler: HoverHandler) { - desc.text = text - desc.x = x - desc.y = y - desc.hoverHandler = hoverHandler - } - - data class Description(var text: String, var x: Float, var y: Float, var hoverHandler: HoverHandler) { - - fun render() { - if (text.isEmpty() || hoverHandler.percent() < 100) return - val area = NVGRenderer.wrappedTextBounds(text, 300f, 16f, NVGRenderer.defaultFont) - NVGRenderer.rect(x, y, area[2] - area[0] + 16f, area[3] - area[1] + 16f, gray38.rgba, 5f) - NVGRenderer.hollowRect( - x, - y, - area[2] - area[0] + 16f, - area[3] - area[1] + 16f, - 1.5f, - ClickGUIModule.clickGUIColor.rgba, - 5f - ) - NVGRenderer.drawWrappedString(text, x + 8f, y + 8f, 300f, 16f, Colors.WHITE.rgba, NVGRenderer.defaultFont) - } + private fun bringToFront(event: MouseButtonEvent) { + val clicked = panels.lastOrNull { it.isMouseOver(event.x(), event.y()) } ?: return + if (panels.last() === clicked) return + panels.remove(clicked) + panels.add(clicked) } - val movementImage = NVGRenderer.createImage("/assets/odin/MovementIcon.svg") - val hueImage = NVGRenderer.createImage("/assets/odin/HueGradient.png") - val chevronImage = NVGRenderer.createImage("/assets/odin/chevron.svg") -} \ No newline at end of file + private const val SEARCH_BAR_BOTTOM_MARGIN = 45 + private const val DESCRIPTION_WIDTH = 200 + private const val DESCRIPTION_PADDING = 8 +} @@ -0,0 +1,30 @@ +package com.odtheking.odin.clickgui + +import com.odtheking.odin.features.impl.render.ClickGUIModule +import com.odtheking.odin.utils.Color +import com.odtheking.odin.utils.Color.Companion.brighter +import com.odtheking.odin.utils.Colors +import net.minecraft.util.ARGB + +object GuiTheme { + + const val ROW_WIDTH = 160 + const val ROW_HEIGHT = 21 + const val RADIUS = 5f + const val CAP = 5 + const val PADDING = 6 + + const val PANEL_BLUR = 12f + + fun textY(y: Int, height: Int): Int = y + (height - 8) / 2 + + val background: Color get() = Colors.gray26 + val surface: Color get() = Colors.gray38 + val accent: Color get() = ClickGUIModule.clickGUIColor + + val shadow: Color = Color(0, 0, 0, 0.4f) +} + +internal fun blend(from: Int, to: Int, progress: Float): Int = ARGB.srgbLerp(progress, from, to) +internal fun Color.hoverTint(hover: Float, factor: Float = 1.3f): Int = + if (hover <= 0f) rgba else blend(rgba, brighter(factor).rgba, hover) \ No newline at end of file @@ -2,86 +2,70 @@ package com.odtheking.odin.clickgui import com.mojang.blaze3d.platform.InputConstants import com.odtheking.odin.OdinMod.mc +import com.odtheking.odin.clickgui.settings.impl.HUDSetting import com.odtheking.odin.clickgui.settings.impl.HudElement import com.odtheking.odin.features.ModuleManager import com.odtheking.odin.features.ModuleManager.hudSettingsCache import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.ui.animations.Animations import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.screens.Screen import net.minecraft.client.input.KeyEvent import net.minecraft.client.input.MouseButtonEvent import net.minecraft.network.chat.Component import kotlin.math.sign -import com.odtheking.odin.utils.ui.mouseX as odinMouseX -import com.odtheking.odin.utils.ui.mouseY as odinMouseY object HudManager : Screen(Component.literal("HUD Manager")) { private var dragging: HudElement? = null - - private var deltaX = 0f - private var deltaY = 0f + private var grabX = 0 + private var grabY = 0 override fun init() { - for (hud in hudSettingsCache) { - if (hud.isEnabled) { - hud.value.x = hud.value.x.coerceIn(0, (mc.window.screenWidth - (hud.value.width * hud.value.scale)).toInt()) - hud.value.y = hud.value.y.coerceIn(0, (mc.window.screenHeight - (hud.value.height * hud.value.scale)).toInt()) - } + Animations.settle() + for (setting in hudSettingsCache) { + if (setting.isEnabled) setting.hud.clampToScreen() } - super.init() } override fun extractRenderState(guiGraphics: GuiGraphicsExtractor, mouseX: Int, mouseY: Int, deltaTicks: Float) { super.extractRenderState(guiGraphics, mouseX, mouseY, deltaTicks) - dragging?.let { - it.x = (odinMouseX + deltaX).coerceIn(0f, (mc.window.screenWidth - (it.width * it.scale))).toInt() - it.y = (odinMouseY + deltaY).coerceIn(0f, (mc.window.screenHeight - (it.height * it.scale))).toInt() + dragging?.let { element -> + element.x = mouseX - grabX + element.y = mouseY - grabY + element.clampToScreen() } - guiGraphics.pose().pushMatrix() - val sf = mc.window.guiScale - guiGraphics.pose().scale(1f / sf, 1f / sf) - - for (hud in hudSettingsCache) { - if (hud.isEnabled) hud.value.draw(guiGraphics, true) + for (setting in hudSettingsCache) { + if (setting.isEnabled) setting.hud.draw(guiGraphics, true, mouseX, mouseY) } - hudSettingsCache.firstOrNull { it.isEnabled && it.value.isHovered() }?.let { hoveredHud -> - guiGraphics.pose().pushMatrix() - guiGraphics.pose().translate( - (hoveredHud.value.x + hoveredHud.value.width * hoveredHud.value.scale + 10f), - hoveredHud.value.y.toFloat(), + hovered(mouseX, mouseY)?.let { setting -> + val element = setting.hud + val labelX = element.x + element.scaledWidth + LABEL_GAP + guiGraphics.text(font, setting.name, labelX, element.y, Colors.WHITE.rgba, true) + guiGraphics.textWithWordWrap( + font, Component.literal(setting.description), + labelX, element.y + font.lineHeight + 1, LABEL_WIDTH, Colors.WHITE.rgba ) - guiGraphics.pose().scale(2f, 2f) - guiGraphics.text(mc.font, hoveredHud.name, 0, 0, Colors.WHITE.rgba) - guiGraphics.textWithWordWrap(mc.font, Component.literal(hoveredHud.description), 0, 10, 150, Colors.WHITE.rgba) - guiGraphics.pose().popMatrix() } - - guiGraphics.pose().popMatrix() } override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean { - val actualAmount = verticalAmount.sign.toFloat() * 0.2f - hudSettingsCache.firstOrNull { it.isEnabled && it.value.isHovered() }?.let { hovered -> - hovered.value.scale = (hovered.value.scale + actualAmount).coerceIn(1f, 10f) - return true - } - return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) + val hovered = hovered(mouseX.toInt(), mouseY.toInt()) ?: return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) + resize(hovered.hud, verticalAmount.sign.toFloat() * SCROLL_STEP) + return true } override fun mouseClicked(mouseButtonEvent: MouseButtonEvent, bl: Boolean): Boolean { - hudSettingsCache.firstOrNull { it.isEnabled && it.value.isHovered() }?.let { hovered -> - dragging = hovered.value + val hovered = hovered(mouseButtonEvent.x().toInt(), mouseButtonEvent.y().toInt()) + ?: return super.mouseClicked(mouseButtonEvent, bl) - deltaX = (hovered.value.x - odinMouseX) - deltaY = (hovered.value.y - odinMouseY) - return true - } - - return super.mouseClicked(mouseButtonEvent, bl) + dragging = hovered.hud + grabX = mouseButtonEvent.x().toInt() - hovered.hud.x + grabY = mouseButtonEvent.y().toInt() - hovered.hud.y + return true } override fun mouseReleased(mouseButtonEvent: MouseButtonEvent): Boolean { @@ -90,32 +74,56 @@ object HudManager : Screen(Component.literal("HUD Manager")) { } override fun keyPressed(keyEvent: KeyEvent): Boolean { - hudSettingsCache.firstOrNull { it.isEnabled && it.value.isHovered() }?.let { hovered -> + val (mouseX, mouseY) = mousePosition() + hovered(mouseX, mouseY)?.let { setting -> + val element = setting.hud when (keyEvent.key) { - InputConstants.KEY_EQUALS -> hovered.value.scale = (hovered.value.scale + 0.1f).coerceIn(1f, 10f) - InputConstants.KEY_MINUS -> hovered.value.scale = (hovered.value.scale - 0.1f).coerceIn(1f, 10f) - InputConstants.KEY_RIGHT -> hovered.value.x += 10 - InputConstants.KEY_LEFT -> hovered.value.x -= 10 - InputConstants.KEY_UP -> hovered.value.y -= 10 - InputConstants.KEY_DOWN -> hovered.value.y += 10 + InputConstants.KEY_EQUALS -> resize(element, KEY_STEP) + InputConstants.KEY_MINUS -> resize(element, -KEY_STEP) + InputConstants.KEY_RIGHT -> element.x += NUDGE + InputConstants.KEY_LEFT -> element.x -= NUDGE + InputConstants.KEY_UP -> element.y -= NUDGE + InputConstants.KEY_DOWN -> element.y += NUDGE + else -> return super.keyPressed(keyEvent) } + element.clampToScreen() + return true } - return super.keyPressed(keyEvent) } override fun onClose() { + dragging = null ModuleManager.saveConfigurations() super.onClose() } + override fun isPauseScreen(): Boolean = false + fun resetHUDS() { - hudSettingsCache.forEach { - it.value.x = 10 - it.value.y = 10 - it.value.scale = 2f + for (setting in hudSettingsCache) { + setting.hud.x = 10 + setting.hud.y = 10 + setting.hud.scale = 1f } } - override fun isPauseScreen(): Boolean = false + private fun hovered(mouseX: Int, mouseY: Int): HUDSetting? = + hudSettingsCache.firstOrNull { it.isEnabled && it.hud.isHovered(mouseX, mouseY) } + + private fun resize(element: HudElement, amount: Float) { + element.scale = (element.scale + amount).coerceIn(HudElement.MIN_SCALE, HudElement.MAX_SCALE) + element.clampToScreen() + } + + private fun mousePosition(): Pair<Int, Int> { + val scale = mc.window.guiScale.coerceAtLeast(1) + return (mc.mouseHandler.xpos() / scale).toInt() to (mc.mouseHandler.ypos() / scale).toInt() + } + + private const val LABEL_GAP = 10 + private const val LABEL_WIDTH = 150 + private const val SCROLL_STEP = 0.1f + private const val KEY_STEP = 0.1f + private const val NUDGE = 5 } \ No newline at end of file @@ -1,154 +0,0 @@ -package com.odtheking.odin.clickgui - -import com.odtheking.odin.clickgui.ClickGUI.gray26 -import com.odtheking.odin.clickgui.settings.ModuleButton -import com.odtheking.odin.features.Category -import com.odtheking.odin.features.ModuleManager -import com.odtheking.odin.features.impl.render.ClickGUIModule -import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.ui.isAreaHovered -import com.odtheking.odin.utils.ui.rendering.NVGRenderer -import net.minecraft.client.input.CharacterEvent -import net.minecraft.client.input.KeyEvent -import net.minecraft.client.input.MouseButtonEvent -import kotlin.math.floor - -/** - * Renders all the panels. - * - * Backend made by Aton, with some changes - * Design mostly made by Stivais - * - * @author Stivais, Aton - * @see [ModuleButton] - */ -class Panel(private val category: Category) { - - val panelSetting = ClickGUIModule.panelSetting[category.name] ?: throw IllegalStateException("Panel setting for category $category is not initialized") - val moduleButtons = ModuleManager.modulesByCategory[category] - ?.sortedByDescending { NVGRenderer.textWidth(it.name, 16f, NVGRenderer.defaultFont) } - ?.map { ModuleButton(it, this@Panel) } ?: listOf() - private val lastModuleButton by lazy { moduleButtons.lastOrNull() } - - private val textWidth = NVGRenderer.textWidth(category.name, 22f, NVGRenderer.defaultFont) - private var previousHeight = 0f - private var scrollOffset = 0f - var dragging = false - private set - private var deltaX = 0f - private var deltaY = 0f - - fun draw(mouseX: Float, mouseY: Float) { - if (dragging) { - panelSetting.x = floor(deltaX + mouseX) - panelSetting.y = floor(deltaY + mouseY) - } - - NVGRenderer.dropShadow( - panelSetting.x, - panelSetting.y, - WIDTH, - previousHeight, - 10f, - 3f, - 5f - ) - - NVGRenderer.drawHalfRoundedRect(panelSetting.x, panelSetting.y, WIDTH, HEIGHT, gray26.rgba, 5f, true) - NVGRenderer.text( - category.name, - panelSetting.x + WIDTH / 2f - textWidth / 2, - panelSetting.y + HEIGHT / 2f - 11, - 22f, - Colors.WHITE.rgba, - NVGRenderer.defaultFont - ) - - if (scrollOffset != 0f) NVGRenderer.pushScissor( - panelSetting.x, - panelSetting.y + HEIGHT, - WIDTH, - previousHeight - HEIGHT + 10f - ) - - var startY = scrollOffset + HEIGHT - if (panelSetting.extended) { - for (button in moduleButtons) { - if (!button.module.name.contains(SearchBar.currentSearch, true)) continue - startY += button.draw(panelSetting.x, startY + panelSetting.y) - } - } - previousHeight = startY - - NVGRenderer.drawHalfRoundedRect(panelSetting.x, panelSetting.y + startY, WIDTH, 10f, if (lastModuleButton?.module?.enabled == true) ClickGUIModule.clickGUIColor.rgba else gray26.rgba, 5f, false) - if (scrollOffset != 0f) NVGRenderer.popScissor() - } - - fun handleScroll(amount: Int): Boolean { - if (!isMouseOverExtended) return false - scrollOffset = (scrollOffset + amount).coerceIn((-previousHeight + scrollOffset + 72f).coerceAtMost(0f), 0f) - return true - } - - fun mouseClicked(mouseX: Float, mouseY: Float, click: MouseButtonEvent): Boolean { - if (isAreaHovered(panelSetting.x, panelSetting.y, WIDTH, HEIGHT, true)) { - if (click.button() == 0) { - deltaX = (panelSetting.x - mouseX) - deltaY = (panelSetting.y - mouseY) - dragging = true - return true - } else if (click.button() == 1) { - panelSetting.extended = !panelSetting.extended - return true - } - } else if (isMouseOverExtended) { - return moduleButtons.reversed().any { - if (!it.module.name.contains(SearchBar.currentSearch, true)) return@any false - it.mouseClicked(mouseX, mouseY, click) - } - } - return false - } - - fun mouseReleased(click: MouseButtonEvent) { - dragging = false - - if (panelSetting.extended) - moduleButtons.reversed().forEach { - if (!it.module.name.contains(SearchBar.currentSearch, true)) return@forEach - it.mouseReleased(click) - } - } - - fun keyTyped(input: CharacterEvent): Boolean { - if (!panelSetting.extended) return false - - return moduleButtons.reversed().any { - if (!it.module.name.contains(SearchBar.currentSearch, true)) return@any false - it.keyTyped(input) - } - } - - fun keyPressed(input: KeyEvent): Boolean { - if (!panelSetting.extended) return false - - return moduleButtons.reversed().any { - if (!it.module.name.contains(SearchBar.currentSearch, true)) return@any false - it.keyPressed(input) - } - } - - private inline val isMouseOverExtended - get() = panelSetting.extended && isAreaHovered( - panelSetting.x, - panelSetting.y, - WIDTH, - previousHeight.coerceAtLeast(HEIGHT), - true - ) - - companion object { - const val WIDTH = 240f - const val HEIGHT = 32f - } -} \ No newline at end of file @@ -1,59 +0,0 @@ -package com.odtheking.odin.clickgui - -import com.odtheking.odin.clickgui.ClickGUI.gray38 -import com.odtheking.odin.features.impl.render.ClickGUIModule -import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.ui.TextInputHandler -import com.odtheking.odin.utils.ui.rendering.NVGRenderer -import net.minecraft.client.input.CharacterEvent -import net.minecraft.client.input.KeyEvent -import net.minecraft.client.input.MouseButtonEvent - -object SearchBar { - - var currentSearch = "" - private set(value) { - if (value == field || value.length > 16) return - field = value - searchWidth = NVGRenderer.textWidth(value, 20f, NVGRenderer.defaultFont) - } - - private var placeHolderWidth = NVGRenderer.textWidth("Search here...", 20f, NVGRenderer.defaultFont) - private var searchWidth = NVGRenderer.textWidth(currentSearch, 20f, NVGRenderer.defaultFont) - - private val textInputHandler = TextInputHandler( - textProvider = { currentSearch }, - textSetter = { currentSearch = it } - ) - - fun draw(x: Float, y: Float, mouseX: Float, mouseY: Float) { - NVGRenderer.dropShadow(x, y, 350f, 40f, 10f, 0.75f, 9f) - NVGRenderer.rect(x, y, 350f, 40f, gray38.rgba, 9f) - NVGRenderer.hollowRect(x, y, 350f, 40f, 3f, ClickGUIModule.clickGUIColor.rgba, 9f) - - val textY = y + 10f - - if (currentSearch.isEmpty()) NVGRenderer.text("Search here...", x + 175f - placeHolderWidth / 2, textY, 20f, Colors.WHITE.rgba, NVGRenderer.defaultFont) - textInputHandler.x = (x + 175f - searchWidth / 2 - if (currentSearch.isEmpty()) placeHolderWidth / 2 + 2f else 0f).coerceAtLeast(x) - textInputHandler.y = textY - 1 - textInputHandler.width = 250f - textInputHandler.height = 22f - textInputHandler.draw(mouseX, mouseY) - } - - fun mouseClicked(mouseX: Float, mouseY: Float, click: MouseButtonEvent): Boolean { - return textInputHandler.mouseClicked(mouseX, mouseY, click) - } - - fun mouseReleased() { - textInputHandler.mouseReleased() - } - - fun keyPressed(input: KeyEvent): Boolean { - return textInputHandler.keyPressed(input) - } - - fun keyTyped(input: CharacterEvent): Boolean { - return textInputHandler.keyTyped(input) - } -} \ No newline at end of file @@ -1,116 +0,0 @@ -package com.odtheking.odin.clickgui.settings - -import com.odtheking.odin.clickgui.ClickGUI -import com.odtheking.odin.clickgui.ClickGUI.gray26 -import com.odtheking.odin.clickgui.Panel -import com.odtheking.odin.features.Module -import com.odtheking.odin.features.impl.rend …… diff 过长已截断 …… |
More accurate rotation axis for the player marker (#161)查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) @@ -192,7 +192,10 @@ fun GuiGraphicsExtractor.renderPlayers() { fill(-5, -5, 5, 5, DungeonUtils.currentDungeonPlayer.clazz.color.rgba) PlayerFaceExtractor.extractRenderState(this, skin, -4, -4, 8) } - } else blit(RenderPipelines.GUI_TEXTURED, marker, -2, -3, 2f, 0f, 5, 7, 8, 8) + } else { + pose().translate(-0.5f, -0.5f) + blit(RenderPipelines.GUI_TEXTURED, marker, -2, -4, 2f, 0f, 5, 7, 8, 8) + } pose().popMatrix() } |
Version bump查看变更gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -10,7 +10,7 @@ loom_version=1.16-SNAPSHOT mod_id=odin mod_name=Odin mod_description=A Hypixel Skyblock QoL mod -mod_version=0.3.3 +mod_version=0.3.4 maven_group=com.odtheking archives_base_name=odin-fabric # Mod Dependencies |
Made core logging keep cores unique查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonScan.kt | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) @@ -82,7 +82,7 @@ object DungeonMap : Module( val synced = try { gson.fromJson(message, DungeonRoom::class.java) } catch (_: Exception) { return@onMessage } if (synced.data == null) return@onMessage - val room = DungeonScan.rooms.find { it.data == synced.data } ?: return@onMessage + val room = DungeonScan.rooms.find { it.data == synced.data || it.tiles == synced.tiles } ?: return@onMessage if ((room.foundSecrets ?: -1) < (synced.foundSecrets ?: -1)) room.foundSecrets = synced.foundSecrets room.walkedInto = true @@ -2,6 +2,7 @@ package com.odtheking.odin.features.impl.dungeon.map import com.google.gson.reflect.TypeToken import com.odtheking.odin.OdinMod.mc +import com.odtheking.odin.OdinMod.scope import com.odtheking.odin.events.FloorEnterEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.MapUpdateEvent @@ -14,6 +15,8 @@ import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.IVec2 import com.odtheking.odin.utils.JsonResourceLoader.defaultGson import com.odtheking.odin.utils.skyblock.dungeon.Floor +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import java.io.File import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList @@ -43,10 +46,18 @@ object DungeonScan { defaultGson.fromJson<MutableMap<String, MutableList<Map<Int, List<String>>>>>(roomCoresFile.readText(), object : TypeToken<MutableMap<String, MutableList<Map<Int, List<String>>>>>() {}.type) }.getOrNull() ?: mutableMapOf() + private val roomCoresIODispatcher = Dispatchers.IO.limitedParallelism(1) + fun recordRoomCore(name: String, core: Int, blocks: List<String>) { - roomCores.getOrPut(name) { mutableListOf() }.add(mapOf(core to blocks)) - roomCoresFile.parentFile.mkdirs() - roomCoresFile.writeText(defaultGson.toJson(roomCores)) + val cores = roomCores.getOrPut(name) { mutableListOf() } + if (cores.any { it.containsKey(core) }) return + cores.add(mapOf(core to blocks)) + + val json = defaultGson.toJson(roomCores) + scope.launch(roomCoresIODispatcher) { + roomCoresFile.parentFile.mkdirs() + roomCoresFile.writeText(json) + } } init { |
Merge remote-tracking branch 'origin/main'查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) |
More reliable DungeonMap websocket parsing查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalTimes.kt | 22 +++++----------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 14 ++------------ 2 files changed, 7 insertions(+), 29 deletions(-) @@ -38,8 +38,6 @@ object TerminalTimes : Module( private var sectionTimer = 0L private var currentTick = 0L private var phaseTimer = 0L - private var lastSectionTime = 0f - private var lastPhaseTime = 0f init { on<TerminalEvent.Solve> { @@ -47,33 +45,23 @@ object TerminalTimes : Module( pbs.time(terminal.type.name, (System.currentTimeMillis() - terminal.timeOpened) / 1000f, "s§7!", "§a${terminal.type.termName}${if (mc.screen is TermSimGUI) " §7(termsim)" else ""} §7solved in §6", sendMessage = terminalTimes) } - on<MessageEvent.Chat> { + on<MessageEvent.ModifyChat> { if (!terminalSplits) return@on - terminalCompleteRegex.find(message)?.destructured?.let { (name, activated, type, current, total) -> - lastSectionTime = sectionTimer.seconds - lastPhaseTime = phaseTimer.seconds - - if ((current == total && gateBlown) || (current.toIntOrNull() ?: return@on) < completed.first) resetSection() - else completed = Pair(current.toIntOrNull() ?: return@on, total.toIntOrNull() ?: return@on) - return@on - } when { gateDestroyedRegex.matches(message) -> if (completed.first == completed.second) resetSection() else gateBlown = true - goldorRegex.matches(message) -> resetSection(true) - coreOpeningRegex.matches(message) -> { resetSection() modMessage("§bTimes: §a${times.joinToString(" §8| ") { "§a${it}s" }}§8, §bTotal: §a${phaseTimer.seconds}s") } } - } - on<MessageEvent.ModifyChat> { - if (!terminalSplits) return@on terminalCompleteRegex.find(message)?.destructured?.let { (name, activated, type, current, total) -> - component = Component.literal("§6$name §a$activated a $type! (§c${current}§a/${total}) §8(§7${lastSectionTime}s §8| §7${lastPhaseTime}s§8)") + component = Component.literal("§6$name §a$activated a $type! (§c${current}§a/${total}) §8(§7${sectionTimer.seconds}s §8| §7${phaseTimer.seconds}s§8)") + + if ((current == total && gateBlown) || (current.toIntOrNull() ?: return@on) < completed.first) resetSection() + else completed = Pair(current.toIntOrNull() ?: return@on, total.toIntOrNull() ?: return@on) } } @@ -82,18 +82,8 @@ object DungeonMap : Module( val synced = try { gson.fromJson(message, DungeonRoom::class.java) } catch (_: Exception) { return@onMessage } if (synced.data == null) return@onMessage - val existingRoom = DungeonScan.rooms.find { it.topLeft == synced.topLeft } - if (existingRoom?.data != null && existingRoom.data != synced.data) return@onMessage - - val room = existingRoom ?: synced.also { new -> - DungeonScan.rooms.add(new) - for ((x, z) in new.tiles) { - if (x !in 0..5 || z !in 0..5) continue - DungeonScan.tiles[x + z * 6].room = new - } - } - - if (room.data == null) room.data = synced.data + val room = DungeonScan.rooms.find { it.data == synced.data } ?: return@onMessage + if ((room.foundSecrets ?: -1) < (synced.foundSecrets ?: -1)) room.foundSecrets = synced.foundSecrets room.walkedInto = true } |
| m3 ff timer to tick timers (#158) * added fire freeze to tick timers since skyblockers ig just wasnt tick alligned to server, even though their naming schemes make it seem like it
wasted too much time creating a simulation method to account for fall time just for it to be that :/
* removed useless comments and imports and added the detail its for m3 into the desc
* reverted back to main so it doesnt get thrown into the pr
---------
Co-authored-by: David28121 <ravinthedavin@gmail.com> 查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) @@ -28,6 +28,7 @@ object TickTimers : Module( private val stormEndRegex = Regex("^\\[BOSS] Storm: I should have known that I stood no chance\\.$") private val stormStartRegex = Regex("^\\[BOSS] Storm: Pathetic Maxor, just like expected\\.$") private val stormPyRegex = Regex("^\\[BOSS] Storm: (ENERGY HEED MY CALL|THUNDER LET ME BE YOUR CATALYST)!$") + private val professorFireFreezeRegex = Regex("^\\[BOSS] The Professor: Oh\\? You found my Guardians' one weakness\\?$") private var necronTime = -1 @@ -100,6 +101,17 @@ object TickTimers : Module( else 0 to 0 } + private var fireFreezeTime = -1 + private const val FIREFREEZEACTIVATIONTIME = 100 + + private val fireFreezeHud by HUD("Fire Freeze Hud", "Displays a timer for when to use fire freeze in M3."){ + val timeTillFreeze = fireFreezeTime - FIREFREEZEACTIVATIONTIME + if (it) textDim(formatTimer(50, 106, "Fire Freeze:"), 0, 0, Colors.MINECRAFT_DARK_RED) + else if (timeTillFreeze > 0) textDim(formatTimer(timeTillFreeze, 106, "Fire Freeze:"), 0, 0, Colors.MINECRAFT_DARK_RED) + else if (fireFreezeTime >= 0) textDim("Use Fire Freeze", 0, 0, Colors.MINECRAFT_DARK_RED) + else 0 to 0 + } + init { on<MessageEvent.Chat> { when { @@ -124,6 +136,7 @@ object TickTimers : Module( pyTriggered = true pyTickTime = 95 } + message.matches(professorFireFreezeRegex) -> fireFreezeTime = 206 } } @@ -140,6 +153,7 @@ object TickTimers : Module( if (pyTickTime >= 0) pyTickTime-- if (necronTime >= 0) necronTime-- if (stormTick >= 0) stormTick++ + if (fireFreezeTime >= 0) fireFreezeTime-- } on<LevelEvent.Load> { @@ -152,6 +166,7 @@ object TickTimers : Module( necronTime = -1 secretsCounter = 0 stormTick = -1 + fireFreezeTime = -1 } } |
Fixed PositionalMessages throwing NPE with old format查看变更src/main/kotlin/com/odtheking/odin/commands/PosMsgCommand.kt | 12 ++++++------ src/main/kotlin/com/odtheking/odin/features/impl/boss/DragonCheck.kt | 6 ++---- src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragons.kt | 3 ++- src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragonsEnum.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PositionalMessages.kt | 8 ++++---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 7 +++++-- src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 9 +++++---- src/main/kotlin/com/odtheking/odin/utils/network/WebSocketConnection.kt | 4 ++-- 8 files changed, 28 insertions(+), 25 deletions(-) @@ -35,10 +35,10 @@ val posMsgCommand = Commodore("posmsg") { suggests { listOf("true", "false") } } - runs { x: Double, y: Double, z: Double, delay: Int, distance: Double, color: String, send: Boolean, message: GreedyString -> + runs { x: Double, y: Double, z: Double, delay: Int, distance: Double, color: String, dontSend: Boolean, message: GreedyString -> if (posMessageStrings.any { it.message == message.string }) return@runs val color = getColorFromString(color) ?: return@runs modMessage("Unknown color $color") - posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, null, null, null, delay, distance, color, message.string, send)) + posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, null, null, null, delay, distance, color, message.string, dontSend)) modMessage("Message \"${message}\" added at $x, $y, $z, with ${delay}t delay, triggered up to $distance blocks away.") ModuleManager.saveConfigurations() } @@ -52,10 +52,10 @@ val posMsgCommand = Commodore("posmsg") { suggests { listOf("true", "false") } } - runs { x: Double, y: Double, z: Double, x2: Double, y2: Double, z2: Double, delay: Int, color: String, send: Boolean, message: GreedyString -> + runs { x: Double, y: Double, z: Double, x2: Double, y2: Double, z2: Double, delay: Int, color: String, dontSend: Boolean, message: GreedyString -> if (posMessageStrings.any { it.message == message.string }) return@runs val color = getColorFromString(color) ?: return@runs modMessage("Unknown color $color") - posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, x2, y2, z2, delay, null, color, message.string, send)) + posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, x2, y2, z2, delay, null, color, message.string, dontSend)) modMessage("Message \"${message}\" added in $x, $y, $z, $x2, $y2, $z2, with ${delay}t delay.") ModuleManager.saveConfigurations() } @@ -71,7 +71,7 @@ val posMsgCommand = Commodore("posmsg") { input } suggests { - posMessageStrings.map { it.message }.distinct() + posMessageStrings.mapNotNull { it.message }.distinct() } } @@ -91,7 +91,7 @@ val posMsgCommand = Commodore("posmsg") { literal("list").runs { val output = posMessageStrings.withIndex().joinToString(separator = "\n") { (index, it) -> - "${index + 1}: ${it.x}, ${it.y}, ${it.z}, ${it.x2}, ${it.y2}, ${it.z2}, ${it.delay}, ${it.distance}, ${it.color.hex()}, send=${it.send}, \"${it.message}\"" + "${index + 1}: ${it.x}, ${it.y}, ${it.z}, ${it.x2}, ${it.y2}, ${it.z2}, ${it.delay}, ${it.distance}, ${it.color.hex()}, send=${it.dontSend}, \"${it.message}\"" } modMessage(if (posMessageStrings.isEmpty()) "Positional Message list is empty!" else "Positonal Message list:\n$output") } @@ -3,7 +3,6 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.EntityEvent import com.odtheking.odin.utils.modMessage -import net.minecraft.world.entity.EntityType import net.minecraft.world.entity.boss.enderdragon.EnderDragon import net.minecraft.world.entity.decoration.ArmorStand import net.minecraft.world.item.Items @@ -25,12 +24,11 @@ object DragonCheck { } fun dragonSpawn(event: EntityEvent.Add) { - if (event.entity.type == EntityType.ENDER_DRAGON) - WitherDragonsEnum.entries.find { it.aabbDimensions.contains(event.entity.position()) }?.setAlive(event.entity.uuid) + WitherDragonsEnum.entries.find { it.aabbDimensions.contains(event.entity.position()) }?.setAlive(event.entity.uuid) } fun dragonSprayed(event: EntityEvent.SetItemSlot) { - if (event.stack.item == Items.PACKED_ICE ) return + if (event.stack.item == Items.PACKED_ICE) return val sprayedEntity = event.entity as? ArmorStand ?: return WitherDragonsEnum.entries.forEach { dragon -> @@ -17,6 +17,7 @@ import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.skyblock.dungeon.M7Phases import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket +import net.minecraft.world.entity.EntityType object WitherDragons : Module( name = "Wither Dragons", @@ -72,7 +73,7 @@ object WitherDragons : Module( } on<EntityEvent.Add> { - if (DungeonUtils.getF7Phase() == M7Phases.P5) DragonCheck.dragonSpawn(this) + if (DungeonUtils.getF7Phase() == M7Phases.P5 && entity.type == EntityType.ENDER_DRAGON) DragonCheck.dragonSpawn(this) } on<EntityEvent.SetData> { @@ -38,8 +38,8 @@ enum class WitherDragonsEnum( Blue(BlockPos(84, 14, 94), BlockPos(79, 23, 94), AABB(71.5, 13.0, 82.5, 96.5, 26.0, 107.5), 'b', Colors.MINECRAFT_AQUA, 82.0..88.0, 91.0..97.0), Purple(BlockPos(56, 14, 125), BlockPos(56, 22, 120), AABB(45.5, 13.0, 113.5, 68.5, 23.0, 136.5), '5', Colors.MINECRAFT_DARK_PURPLE, 53.0..59.0, 122.0..128.0); - fun setAlive(entityId: UUID?) { - if (entityId != null) this.entityUUID = entityId + fun setAlive(entityUUID: UUID?) { + if (entityUUID != null) this.entityUUID = entityUUID if (state == WitherDragonState.ALIVE || state != WitherDragonState.SPAWNING) return state = WitherDragonState.ALIVE @@ -30,7 +30,7 @@ object PositionalMessages : Module( private val displayMessage by BooleanSetting("Show Message", true, desc = "Whether or not to display the message in the box.").withDependency { showPositions } private val messageSize by NumberSetting("Message Size", 1f, 0.1f, 4f, 0.1f, desc = "The size at which to display the message in the box.").withDependency { showPositions && displayMessage } - data class PosMessage(val x: Double, val y: Double, val z: Double, val x2: Double?, val y2: Double?, val z2: Double?, val delay: Int, val distance: Double?, val color: Color, val message: String, val send: Boolean) { + data class PosMessage(val x: Double, val y: Double, val z: Double, val x2: Double?, val y2: Double?, val z2: Double?, val delay: Int, val distance: Double?, val color: Color, val message: String?, val dontSend: Boolean) { @Transient private var _center: Vec3? = null val center: Vec3 @@ -59,7 +59,7 @@ object PositionalMessages : Module( onSend<ServerboundMovePlayerPacket> { if (onlyDungeons && !DungeonUtils.inBoss) return@onSend posMessageStrings.forEach { posMessage -> - if (posMessage.send && posMessage !in sentMessages) posMessage.x2?.let { handleInString(posMessage) } ?: handleAtString(posMessage) + if (!posMessage.dontSend && posMessage !in sentMessages) posMessage.x2?.let { handleInString(posMessage) } ?: handleAtString(posMessage) } } @@ -68,10 +68,10 @@ object PositionalMessages : Module( posMessageStrings.forEach { posMessage -> if (posMessage.distance != null) { drawCylinder(posMessage.center, posMessage.distance.toFloat(), cylinderHeight, color = posMessage.color, depth = true) - if (displayMessage) drawText(posMessage.message, Vec3(posMessage.x, posMessage.y + 1, posMessage.z), messageSize, true) + if (displayMessage) posMessage.message?.let { drawText(it, Vec3(posMessage.x, posMessage.y + 1, posMessage.z), messageSize, true) } } else { drawWireFrameBox(posMessage.box ?: return@forEach, posMessage.color, depth = true) - if (displayMessage) drawText(posMessage.message, posMessage.center.add(0.0, 1.0, 0.0), messageSize, true) + if (displayMessage) posMessage.message?.let { drawText(it, posMessage.center.add(0.0, 1.0, 0.0), messageSize, true) } } } } @@ -78,11 +78,14 @@ object DungeonMap : Module( val syncSocket = webSocket { onMessage { message -> - if (!allowWebsocket) return@onMessage + if (!allowWebsocket || !DungeonUtils.inDungeons) return@onMessage val synced = try { gson.fromJson(message, DungeonRoom::class.java) } catch (_: Exception) { return@onMessage } if (synced.data == null) return@onMessage - val room = DungeonScan.rooms.find { it.topLeft == synced.topLeft } ?: synced.also { new -> + val existingRoom = DungeonScan.rooms.find { it.topLeft == synced.topLeft } + if (existingRoom?.data != null && existingRoom.data != synced.data) return@onMessage + + val room = existingRoom ?: synced.also { new -> DungeonScan.rooms.add(new) for ((x, z) in new.tiles) { if (x !in 0..5 || z !in 0..5) continue @@ -7,10 +7,7 @@ import com.odtheking.odin.clickgui.ClickGUI import com.odtheking.odin.clickgui.HudManager import com.odtheking.odin.clickgui.settings.AlwaysActive import com.odtheking.odin.clickgui.settings.impl.* -import com.odtheking.odin.events.FloorEnterEvent -import com.odtheking.odin.events.MessageEvent -import com.odtheking.odin.events.RoomEnterEvent -import com.odtheking.odin.events.SecretsUpdateEvent +import com.odtheking.odin.events.* import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Category import com.odtheking.odin.features.Module @@ -54,6 +51,10 @@ object ClickGUIModule : Module( if (room == null) DungeonMap.syncSocket.shutdown() else DungeonMap.syncSocket.send(gson.toJson(room)) } + + on<LevelEvent.Load> { + DungeonMap.syncSocket.shutdown() + } } val devMessage by BooleanSetting("Developer Message", false, desc = "Sends development related messages to the chat.") @@ -49,7 +49,7 @@ class WebSocketConnection { if (last) { val message = messageBuilder.toString() messageBuilder.clear() - onMessageFunc(message) + if (_webSocket.get() === webSocket) onMessageFunc(message) } webSocket.request(1) return null @@ -57,7 +57,7 @@ class WebSocketConnection { override fun onBinary(webSocket: WebSocket, data: ByteBuffer, last: Boolean): CompletionStage<*>? { val text = StandardCharsets.UTF_8.decode(data).toString() - onMessageFunc(text) + if (_webSocket.get() === webSocket) onMessageFunc(text) webSocket.request(1) return null } |
Fixed Croesus HUD only appearing sometimes查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 136 +++++++++++++++++++++++++++++++++++++++++++++++------------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt | 4 +-- 2 files changed, 78 insertions(+), 62 deletions(-) @@ -8,7 +8,7 @@ import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.MessageEvent -import com.odtheking.odin.events.ScreenEvent +import com.odtheking.odin.events.ScreenCloseEvent import com.odtheking.odin.events.SetSlotEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive @@ -88,6 +88,10 @@ object Croesus : Module( private var currentChestProfit: Double? = null private var mostProfitableSlots = setOf<Int>() + private val chestSlotData = linkedMapOf<Int, ChestData>() + private val chestItemContributions = linkedMapOf<Int, ChestItem>() + private val chestCostContributions = linkedMapOf<Int, Double>() + init { scope.launch { cachedPrices = fetchJson<Map<String, Double>>("https://lb.odtheking.com/averages/7day").getOrElse { OdinMod.logger.error("Failed to fetch lowest bin prices for Croesus module.", it); emptyMap() } @@ -141,15 +145,18 @@ object Croesus : Module( val screenTitle = mc.screen?.title?.string ?: return@on when { - screenTitle.matches(chestNameRegex) -> handleChestContents(menu.items) - screenTitle.matches(chestPreviewScreenRegex) -> handleCroesusScreen(menu.items) + screenTitle.matches(chestNameRegex) -> handleChestContents(slotIndex, itemStack) + screenTitle.matches(chestPreviewScreenRegex) -> handleCroesusScreen(slotIndex, itemStack) } } - on<ScreenEvent.Open> { + on<ScreenCloseEvent> { mostProfitableSlots = emptySet() currentChestProfit = null chestData = emptyList() + chestSlotData.clear() + chestItemContributions.clear() + chestCostContributions.clear() } onReceive<ClientboundPlayerInfoUpdatePacket> { @@ -170,35 +177,36 @@ object Croesus : Module( } } - private fun handleCroesusScreen(items: List<ItemStack>) { - val chests = mutableListOf<ChestData>() - - items.forEachIndexed { index, stack -> - if (stack.isEmpty || index > 16 || stack.item != Items.PLAYER_HEAD) return@forEachIndexed + private fun handleCroesusScreen(index: Int, stack: ItemStack) { + if (index > 16) return + if (stack.isEmpty || stack.item != Items.PLAYER_HEAD) chestSlotData.remove(index) + else { val lore = stack.loreString val loreStartIndex = lore.indexOfFirst { it == "Contents" } + 1 - if (loreStartIndex == 0) return@forEachIndexed - val loreEndIndex = lore.indexOfFirst { it.isEmpty() } + if (loreStartIndex == 0) chestSlotData.remove(index) + else { + val loreEndIndex = lore.indexOfFirst { it.isEmpty() } - val chestCost = lore.getOrNull(loreEndIndex + 2)?.let { costLine -> - chestCostRegex.find(costLine)?.groupValues?.get(1)?.replace(",", "")?.toDoubleOrNull() - } ?: 0.0 + val chestCost = lore.getOrNull(loreEndIndex + 2)?.let { costLine -> + chestCostRegex.find(costLine)?.groupValues?.get(1)?.replace(",", "")?.toDoubleOrNull() + } ?: 0.0 - val items = mutableListOf<ChestItem>() - var totalValue = 0.0 + val items = mutableListOf<ChestItem>() + var totalValue = 0.0 - lore.subList(loreStartIndex, loreEndIndex).forEach { item -> - val price = parseItemValue(item) ?: 0.0 - totalValue += price - items.add(ChestItem(item, price)) - } + lore.subList(loreStartIndex, loreEndIndex).forEach { item -> + val price = parseItemValue(item) ?: 0.0 + totalValue += price + items.add(ChestItem(item, price)) + } - chests.add(ChestData(stack.hoverName, items, totalValue - chestCost, index)) + chestSlotData[index] = ChestData(stack.hoverName, items, totalValue - chestCost, index) + } } - chestData = chests.sortedByDescending { it.profit } + chestData = chestSlotData.values.sortedByDescending { it.profit } mostProfitableSlots = chestData.filter { it.profit > 0 }.take(2).map { it.slotIndex }.toSet() } @@ -222,60 +230,68 @@ object Croesus : Module( return cachedPrices[item.uppercase() .replace("'", "").replace(" -", "").replace(" ", "_")] } - private fun handleChestContents(items: List<ItemStack>) { - val chestItems = mutableListOf<ChestItem>() - var chestCost = 0.0 - var profit = 0.0 + private fun handleChestContents(index: Int, stack: ItemStack) { + if (index > 40) return - items.forEachIndexed { index, stack -> - if (stack.isEmpty || index > 40) return@forEachIndexed + when { + stack.isEmpty -> { + chestItemContributions.remove(index) + chestCostContributions.remove(index) + } - when (stack.item) { - Items.CHEST -> { - stack.loreString.forEach { loreLine -> - chestCostRegex.find(loreLine)?.groupValues?.get(1)?.let { cost -> - chestCost = cost.replace(",", "").toDouble() - } + stack.item == Items.CHEST -> { + chestItemContributions.remove(index) + chestCostContributions.remove(index) + stack.loreString.forEach { loreLine -> + chestCostRegex.find(loreLine)?.groupValues?.get(1)?.let { cost -> + chestCostContributions[index] = cost.replace(",", "").toDouble() } } + } - Items.ENCHANTED_BOOK -> { - chestEnchantsRegex.find(stack.customData.get("enchantments").toString())?.destructured?.let { (name, level) -> - cachedPrices["ENCHANTED_BOOK-${name.uppercase()}-$level"]?.let { - chestItems += ChestItem(stack.hoverName.string, it) - profit += it - } - } - } - else -> { - previewEssenceRegex.find(stack.hoverName.string)?.destructured?.let { (name, quantity) -> - if (!includeEssence) return@forEachIndexed - val price = cachedPrices["ESSENCE_${name.uppercase()}"] ?: return@forEachIndexed - chestItems += ChestItem(stack.hoverName.string, price * quantity.toDouble()) - profit += price * quantity.toDouble() - } ?: previewShardRegex.find(stack.hoverName.string)?.destructured?.let { (shardName) -> - cachedPrices["SHARD_${shardName.uppercase().replace(" ", "_").replace("'s", "")}"]?.let { - chestItems += ChestItem(stack.hoverName.string, it) - profit += it - } - } ?: cachedPrices[stack.itemId]?.let { - chestItems += ChestItem(stack.hoverName.string, it) - profit += it - } + stack.item == Items.ENCHANTED_BOOK -> { + chestCostContributions.remove(index) + val contribution = chestEnchantsRegex.find(stack.customData.get("enchantments").toString())?.destructured?.let { (name, level) -> + cachedPrices["ENCHANTED_BOOK-${name.uppercase()}-$level"]?.let { ChestItem(stack.hoverName.string, it) } } + if (contribution != null) chestItemContributions[index] = contribution else chestItemContributions.remove(index) + } + + else -> { + chestCostContributions.remove(index) + val contribution = resolveChestItemContribution(stack) + if (contribution != null) chestItemContributions[index] = contribution else chestItemContributions.remove(index) } } - currentChestProfit = profit - chestCost + + val profit = chestItemContributions.values.sumOf { it.price } + currentChestProfit = profit - chestCostContributions.values.sum() chestData = listOf( ChestData( Component.literal("§eChest"), - chestItems, + chestItemContributions.values.toList(), currentChestProfit ?: 0.0, -1 ) ) } + private fun resolveChestItemContribution(stack: ItemStack): ChestItem? { + previewEssenceRegex.find(stack.hoverName.string)?.destructured?.let { (name, quantity) -> + if (!includeEssence) return null + val price = cachedPrices["ESSENCE_${name.uppercase()}"] ?: return null + return ChestItem(stack.hoverName.string, price * quantity.toDouble()) + } + + previewShardRegex.find(stack.hoverName.string)?.destructured?.let { (shardName) -> + cachedPrices["SHARD_${shardName.uppercase().replace(" ", "_").replace("'s", "")}"]?.let { + return ChestItem(stack.hoverName.string, it) + } + } + + return cachedPrices[stack.itemId]?.let { ChestItem(stack.hoverName.string, it) } + } + private fun hasStrikeThrough(itemName: String, loreComponents: List<Component>): Boolean = loreComponents.any { line -> line.siblings.any { sibling -> @@ -3,7 +3,7 @@ package com.odtheking.odin.features.impl.nether import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.GuiEvent -import com.odtheking.odin.events.ScreenEvent +import com.odtheking.odin.events.ScreenCloseEvent import com.odtheking.odin.events.SetSlotEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -79,7 +79,7 @@ object Vesuvius : Module( if (slotIndex.equalsOneOf(13, 14) && itemStack.item == Items.PLAYER_HEAD) handleKuudraChest(itemStack) } - on<ScreenEvent.Open> { + on<ScreenCloseEvent> { currentChest = null } } |
Fixed TerminalSounds Fixed Mimic Message (??) Fixed TerminalTimes查看变更src/main/java/com/odtheking/mixin/mixins/ClientPacketListenerMixin.java | 8 ++++++++ src/main/java/com/odtheking/mixin/mixins/EntityMixin.java | 5 ----- src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSounds.kt | 18 +++--------------- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalTimes.kt | 14 ++++++++++++-- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/TerminalHandler.kt | 3 +-- 6 files changed, 25 insertions(+), 25 deletions(-) @@ -3,10 +3,12 @@ package com.odtheking.mixin.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.odtheking.odin.events.EntityEvent; import com.odtheking.odin.events.PacketEvent; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.network.PacketListener; import net.minecraft.network.protocol.Packet; +import net.minecraft.world.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -23,4 +25,10 @@ public class ClientPacketListenerMixin { if (new PacketEvent.Receive(packet).postAndCatch()) return; original.call(packet, listener); } + + @WrapOperation(method = "handleEntityEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;handleEntityEvent(B)V")) + private void onHandleEntityEvent(Entity instance, byte id, Operation<Void> original) { + new EntityEvent.Event(instance, id).postAndCatch(); + original.call(instance, id); + } } \ No newline at end of file @@ -40,9 +40,4 @@ public abstract class EntityMixin { Entity entity = (Entity)(Object)this; new EntityEvent.Move(entity, Vec3.ZERO, 0f, 0f, onGround).postAndCatch(); } - - @Inject(method = "handleEntityEvent", at = @At("TAIL")) - private void onHandleEntityEvent(byte id, CallbackInfo ci) { - new EntityEvent.Event((Entity)(Object)this, id).postAndCatch(); - } } \ No newline at end of file @@ -57,7 +57,7 @@ abstract class TerminalEvent(val terminal: TerminalHandler) : Event { class Open(terminal: TerminalHandler) : TerminalEvent(terminal) class Close(terminal: TerminalHandler) : TerminalEvent(terminal) class Solve(terminal: TerminalHandler) : TerminalEvent(terminal) - class Click(terminal: TerminalHandler, val slotIndex: Int, val button: Int) : TerminalEvent(terminal) + class Click(terminal: TerminalHandler, val slotIndex: Int, val button: Int, val solution: ArrayList<Int>) : TerminalEvent(terminal) } interface TickEvent : Event { @@ -1,10 +1,8 @@ package com.odtheking.odin.features.impl.boss -import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.events.MessageEvent import com.odtheking.odin.events.TerminalEvent -import com.odtheking.odin.events.core.EventPriority import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -25,7 +23,6 @@ object TerminalSounds : Module( private val clickSoundSettings = createSoundSettings("Click Sound", "entity.blaze.hurt") { clickSounds } private val completeSounds by BooleanSetting("Complete Sounds", false, desc = "Plays a sound when you complete a terminal.") private val completeSoundSettings = createSoundSettings("Completion Sound", "entity.experience_orb.pickup") { completeSounds } - private val cancelLastClick by BooleanSetting("Cancel Last Click", false, desc = "Cancels the last click sound instead of playing both click and completion sound.").withDependency { clickSounds && completeSounds } private val coreRegex = Regex("^The Core entrance is opening!$") private val gateRegex = Regex("^The gate has been destroyed!$") @@ -37,8 +34,9 @@ object TerminalSounds : Module( else if (shouldReplaceSounds && completeSounds && !clickSounds) playSoundSettings(completeSoundSettings()) } - on<TerminalEvent.Click> (EventPriority.HIGHEST) { - if (shouldReplaceSounds) playSoundForSlot(slotIndex, button) + on<TerminalEvent.Click> { + if ((solution.isEmpty() || (terminal.type == TerminalTypes.MELODY && slotIndex == 43)) && completeSounds) playSoundSettings(completeSoundSettings()) + else if (clickSounds) playTerminalSound() } onReceive<ClientboundSoundPacket> { @@ -55,16 +53,6 @@ object TerminalSounds : Module( } } - private fun playSoundForSlot(slot: Int, button: Int) { - with(TerminalUtils.currentTerm ?: return) { - if (!canClick(slot, button)) return - if ((solution.size == 1 || (type == TerminalTypes.MELODY && slot == 43)) && completeSounds) { - if (!cancelLastClick) playTerminalSound() - playSoundSettings(completeSoundSettings()) - } else playTerminalSound() - } - } - private fun playTerminalSound() { if (System.currentTimeMillis() - lastPlayed <= 2) return playSoundSettings(clickSoundSettings()) @@ -38,6 +38,8 @@ object TerminalTimes : Module( private var sectionTimer = 0L private var currentTick = 0L private var phaseTimer = 0L + private var lastSectionTime = 0f + private var lastPhaseTime = 0f init { on<TerminalEvent.Solve> { @@ -45,10 +47,11 @@ object TerminalTimes : Module( pbs.time(terminal.type.name, (System.currentTimeMillis() - terminal.timeOpened) / 1000f, "s§7!", "§a${terminal.type.termName}${if (mc.screen is TermSimGUI) " §7(termsim)" else ""} §7solved in §6", sendMessage = terminalTimes) } - on<MessageEvent.ModifyChat> { + on<MessageEvent.Chat> { if (!terminalSplits) return@on terminalCompleteRegex.find(message)?.destructured?.let { (name, activated, type, current, total) -> - component = Component.literal("§6$name §a$activated a $type! (§c${current}§a/${total}) §8(§7${sectionTimer.seconds}s §8| §7${phaseTimer.seconds}s§8)") + lastSectionTime = sectionTimer.seconds + lastPhaseTime = phaseTimer.seconds if ((current == total && gateBlown) || (current.toIntOrNull() ?: return@on) < completed.first) resetSection() else completed = Pair(current.toIntOrNull() ?: return@on, total.toIntOrNull() ?: return@on) @@ -67,6 +70,13 @@ object TerminalTimes : Module( } } + on<MessageEvent.ModifyChat> { + if (!terminalSplits) return@on + terminalCompleteRegex.find(message)?.destructured?.let { (name, activated, type, current, total) -> + component = Component.literal("§6$name §a$activated a $type! (§c${current}§a/${total}) §8(§7${lastSectionTime}s §8| §7${lastPhaseTime}s§8)") + } + } + on<TickEvent.Server> { if (terminalSplits && !useRealTime) currentTick += 50 } @@ -56,7 +56,7 @@ abstract class TerminalHandler(val type: TerminalTypes) { lastClickTime = System.currentTimeMillis() if (simulateClick) simulateClick(slotIndex, button) - + TerminalEvent.Click(this, slotIndex, button, solution).postAndCatch() mc.screen?.let { screen -> if (screen is TermSimGUI) { screen.clickIndex(slotIndex, button) @@ -64,7 +64,6 @@ abstract class TerminalHandler(val type: TerminalTypes) { } } mc.player?.clickSlot(slotIndex, button, if (button == InputConstants.MOUSE_BUTTON_MIDDLE) ContainerInput.CLONE else ContainerInput.PICKUP) - TerminalEvent.Click(this, slotIndex, button).postAndCatch() } open fun canClick(slotIndex: Int, button: Int): Boolean = slotIndex in solution |
Reverted inDungeon checks for event listeners in DungeonListener to avoid losing data查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 2 -- 1 file changed, 2 deletions(-) @@ -72,7 +72,6 @@ object DungeonListener { } onReceive<ClientboundPlayerInfoUpdatePacket> { - if (!DungeonUtils.inDungeons) return@onReceive val tabListEntries = entries().mapNotNull { it.displayName?.string }.ifEmpty { return@onReceive } updateDungeonTeammates(tabListEntries) updateDungeonStats(tabListEntries) @@ -81,7 +80,6 @@ object DungeonListener { } onReceive<ClientboundSetPlayerTeamPacket> { - if (!DungeonUtils.inDungeons) return@onReceive val text = parameters.getOrNull()?.let { it.playerPrefix.string.plus(it.playerSuffix.string).noControlCodes } ?: return@onReceive floorRegex.find(text)?.groupValues?.get(1)?.let { |
Version bump查看变更gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -10,7 +10,7 @@ loom_version=1.16-SNAPSHOT mod_id=odin mod_name=Odin mod_description=A Hypixel Skyblock QoL mod -mod_version=0.3.2 +mod_version=0.3.3 maven_group=com.odtheking archives_base_name=odin-fabric # Mod Dependencies |
Fixed SimonSays checking any block for powered state查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/SimonSays.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -83,7 +83,7 @@ object SimonSays : Module( 110 -> if (updated.block == Blocks.AIR) { if (grid.count { mc.level?.getBlockState(it)?.isAir == true } > 8) resetSolution() - } else if (old.block == Blocks.STONE_BUTTON && updated.getValue(BlockStateProperties.POWERED)) { + } else if (old.block == Blocks.STONE_BUTTON && updated.block == Blocks.STONE_BUTTON && updated.getValue(BlockStateProperties.POWERED)) { clickNeeded = clickInOrder.indexOf(pos.east()) + 1 if (clickNeeded >= clickInOrder.size) { resetSolution() |
Changed dungeon Time Elapsed from tab list to scoreboard查看变更src/main/kotlin/com/odtheking/odin/clickgui/settings/ModuleButton.kt | 2 +- src/main/kotlin/com/odtheking/odin/events/EventDispatcher.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt | 18 +++++------------- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 11 ++++++++--- 4 files changed, 15 insertions(+), 18 deletions(-) @@ -47,7 +47,7 @@ class ModuleButton(val module: Module, val panel: Panel) { if (hoverHandler.percent() >= 100 && y >= panel.panelSetting.y + Panel.HEIGHT) ClickGUI.setDescription(module.description, x + Panel.WIDTH + 10f, y, hoverHandler) - NVGRenderer.rect(x, y, Panel.WIDTH, Panel.HEIGHT, color.rgba) + NVGRenderer.rect(x, y, Panel.WIDTH, Panel.HEIGHT, color.rgba) NVGRenderer.text(module.name, x + Panel.WIDTH / 2 - nameWidth / 2, y + Panel.HEIGHT / 2 - 9f, 18f, Colors.WHITE.rgba, NVGRenderer.defaultFont) if (representableSettings.isEmpty()) return Panel.HEIGHT @@ -89,7 +89,7 @@ object EventDispatcher { val blockState = mc.level?.getBlockState(pos) ?: return@on if (blockState.block is SkullBlock) { val distance = mc.player?.eyePosition?.distanceToSqr(Vec3(pos)) ?: return@on - if (distance > 20.25) return@on + if (distance > 21) return@on } if (isSecret(blockState, pos)) SecretPickupEvent.Interact(pos, blockState).postAndCatch() @@ -43,9 +43,7 @@ object Vesuvius : Module( private val uselessLinesRegex = Regex("^Contents|Cost|Click to open!|FREE|Already opened!|Can't open another chest!|Paid Chest|") private val salvageItemsRegex = Regex("Boots|Leggings|Chestplate|Helmet|Cloak|Aurora Staff|Hollow Wand") - private val ultimateEnchants = setOf( - "Fatal Tempo", "Inferno" - ) + private val ultimateEnchants = setOf("Fatal Tempo", "Inferno") data class Key(val type: String, val coins: Int, val quantity: Int, val tier: Int) private data class ChestItem(val name: Component, val price: Double) @@ -70,9 +68,8 @@ object Vesuvius : Module( } on<GuiEvent.RenderSlot> { - if (screen.title.string.containsOneOf("Vesuvius", "Croesus") && slot.item.hoverName.string == "Kuudra's Hollow") { - if (hideClaimed && slot.item.loreString.any { it == "No more chests to open!"}) cancel() - } + if (hideClaimed && screen.title.string.containsOneOf("Vesuvius", "Croesus") && + slot.item.hoverName.string == "Kuudra's Hollow" && slot.item.loreString.any { it == "No more chests to open!"}) cancel() } on<SetSlotEvent> { @@ -106,9 +103,7 @@ object Vesuvius : Module( val item = component.string.replace("✪", "").trim() - if (item.contains("Molten") && useSalvagePrices) { - return (cachedPrices["ESSENCE_CRIMSON"] ?: 0.0) * 600.0 * essenceBonus - } + if (item.contains("Molten") && useSalvagePrices) return (cachedPrices["ESSENCE_CRIMSON"] ?: 0.0) * 600.0 * essenceBonus previewEnchantedBookRegex.find(item)?.destructured?.let { (name, level) -> val ult = if (name in ultimateEnchants) "ULTIMATE_" else "" @@ -138,7 +133,6 @@ object Vesuvius : Module( } if (useSalvagePrices && salvage) { - modMessage("$component}") val price = cachedPrices["ESSENCE_CRIMSON"] ?: 0.0 val quantity = starCountToEssence[starCount] ?: 0.0 return price * quantity @@ -164,9 +158,7 @@ object Vesuvius : Module( var profit = 0.0 var chestCost = 0.0 - val lore = item.lore - - lore.forEach { component -> + item.lore.forEach { component -> val string = component.string if (string.contains("Kuudra Key")) { @@ -72,6 +72,7 @@ object DungeonListener { } onReceive<ClientboundPlayerInfoUpdatePacket> { + if (!DungeonUtils.inDungeons) return@onReceive val tabListEntries = entries().mapNotNull { it.displayName?.string }.ifEmpty { return@onReceive } updateDungeonTeammates(tabListEntries) updateDungeonStats(tabListEntries) @@ -80,6 +81,7 @@ object DungeonListener { } onReceive<ClientboundSetPlayerTeamPacket> { + if (!DungeonUtils.inDungeons) return@onReceive val text = parameters.getOrNull()?.let { it.playerPrefix.string.plus(it.playerSuffix.string).noControlCodes } ?: return@onReceive floorRegex.find(text)?.groupValues?.get(1)?.let { @@ -96,15 +98,19 @@ object DungeonListener { dungeonStats.percentCleared = it DungeonUtils.updateScore() } + + dungeonStats.elapsedTime = timeRegex.find(text)?.groupValues?.get(1) ?: dungeonStats.elapsedTime } onReceive<ClientboundTabListPacket> { + if (!DungeonUtils.inDungeons) return@onReceive Blessing.entries.forEach { blessing -> blessing.regex.find(footer.string)?.let { blessing.current = romanToInt(it.groupValues[1]) } } } on<MessageEvent.Chat> { + if (!DungeonUtils.inDungeons) return@on if (expectingBloodRegex.matches(message)) expectingBloodUpdate = true doorOpenRegex.find(message)?.let { dungeonStats.doorOpener = it.groupValues[1] } deathRegex.find(message)?.let { match -> @@ -139,7 +145,7 @@ object DungeonListener { } on<EntityEvent.Add> { - if (entity.type == EntityType.PLAYER && entity.uuid.version() != 4) + if (entity.type == EntityType.PLAYER && entity.uuid.version() != 4 && DungeonUtils.inDungeons) DungeonUtils.dungeonTeammates.find { it.entity == null && it.name == entity.name.string }?.entity = entity as? Player } } @@ -170,7 +176,6 @@ object DungeonListener { puzzleCount = puzzleCountRegex.find(entry)?.groupValues?.get(1)?.toIntOrNull() ?: puzzleCount deaths = deathsRegex.find(entry)?.groupValues?.get(1)?.toIntOrNull() ?: deaths crypts = cryptRegex.find(entry)?.groupValues?.get(1)?.toIntOrNull() ?: crypts - elapsedTime = timeRegex.find(entry)?.groupValues?.get(1) ?: elapsedTime } } } @@ -194,7 +199,7 @@ object DungeonListener { private val doorOpenRegex = Regex("^(?:\\[\\w+] )?(\\w+) opened a (?:WITHER|Blood) door!") private val secretPercentRegex = Regex("^ Secrets Found: ([\\d.]+)%$") private val deathRegex = Regex("☠ (\\w{1,16}) .* and became a ghost\\.") - private val timeRegex = Regex("^ Time: ((?:\\d+h ?)?(?:\\d+m ?)?\\d+s)$") + private val timeRegex = Regex("^Time Elapsed: ((?:\\d+h ?)?(?:\\d+m ?)?\\d+s)$") private val completedRoomsRegex = Regex("^ Completed Rooms: (\\d+)$") private val clearedRegex = Regex("^Cleared: (\\d+)% \\(\\d+\\)$") private val secretCountRegex = Regex("^ Secrets Found: (\\d+)$") |
fix playersize translate (#156)查看变更src/main/kotlin/com/odtheking/odin/features/impl/render/PlayerSize.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -65,7 +65,7 @@ object PlayerSize : Module( if (!randoms.containsKey(gameProfile.id)) return if (!devSize && gameProfile.name == mc.player?.gameProfile?.name) return val random = randoms[gameProfile.id] ?: return - if (random.scale[1] < 0) matrix.translate(0f, random.scale[1] * 2, 1f) + if (random.scale[1] < 0) matrix.translate(0f, random.scale[1] * 2, 0f) matrix.scale(random.scale[0], random.scale[1], random.scale[2]) } |
| kuudra profit tracker (#149) * fix vesuvius not working at all
* vitality rework compat
* Add lava leech and kuudra pet bonus to profit calculation
* better detection
* should finally be done :sob:
* should finally be done :sob:
* profit tracker for kuudra
* maybe dont leave in the debug ig
* fixed stuff
* forgot some item colors
* Cleanup merge
---------
Co-authored-by: odtheking <78278562+odtheking@users.noreply.github.com>
Co-authored-by: odtheking <odtheking1@gmail.com> 查看变更src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt | 1 + src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 6 +- src/main/kotlin/com/odtheking/odin/features/impl/nether/KuudraTracker.kt | 272 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt | 22 +++---- 4 files changed, 286 insertions(+), 15 deletions(-) @@ -77,6 +77,7 @@ object ModuleManager { // nether SupplyHelper, BuildHelper, RemovePerks, NoPre, PearlWaypoints, FreshTools, KuudraInfo, Misc, Vesuvius, + KuudraTracker, RenderTest, ) @@ -22,7 +22,6 @@ import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import kotlinx.coroutines.launch import net.minecraft.client.gui.GuiGraphicsExtractor -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.network.chat.Component import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket import net.minecraft.world.item.ItemStack @@ -140,11 +139,10 @@ object Croesus : Module( on<SetSlotEvent> { val screenTitle = mc.screen?.title?.string ?: return@on - val items = (mc.screen as? AbstractContainerScreen<*>)?.menu?.items ?: return@on when { - screenTitle.matches(chestNameRegex) -> handleChestContents(items) - screenTitle.matches(chestPreviewScreenRegex) -> handleCroesusScreen(items) + screenTitle.matches(chestNameRegex) -> handleChestContents(menu.items) + screenTitle.matches(chestPreviewScreenRegex) -> handleCroesusScreen(menu.items) } } @@ -0,0 +1,272 @@ +package com.odtheking.odin.features.impl.nether + +import com.odtheking.odin.clickgui.settings.impl.ActionSetting +import com.odtheking.odin.clickgui.settings.impl.ListSetting +import com.odtheking.odin.clickgui.settings.impl.MapSetting +import com.odtheking.odin.clickgui.settings.impl.NumberSetting +import com.odtheking.odin.events.GuiEvent +import com.odtheking.odin.events.MessageEvent +import com.odtheking.odin.events.core.on +import com.odtheking.odin.events.core.onSend +import com.odtheking.odin.features.Module +import com.odtheking.odin.features.ModuleManager +import com.odtheking.odin.features.impl.nether.Vesuvius.getPriceOfKey +import com.odtheking.odin.features.impl.nether.Vesuvius.keys +import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.equalsOneOf +import com.odtheking.odin.utils.lore +import com.odtheking.odin.utils.render.text +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.GuiGraphicsExtractor +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.MutableComponent +import net.minecraft.network.protocol.game.ServerboundContainerClickPacket +import net.minecraft.world.item.Items + +object KuudraTracker : Module( + name = "Kuudra Tracker", + description = "Tracks your Kuudra runs." +) { + private val lineAmount by NumberSetting("Lines", 10, 0,40, 1, "The amount of items to display in the profit tracker.") + private val reset by ActionSetting("Reset Profit", "Resets the Profit Tracker") { + totalKeys = mutableListOf(0, 0, 0, 0, 0) + singleItems.clear() + multiItems.clear() + paid = 0 + free = 0 + last = LastAdded(mutableListOf(), mutableListOf(), 0) + + updateDisplay() + ModuleManager.saveConfigurations() + } + + private val chestRegex = Regex("(Free|Paid) Chest") + private val hudRegex = Regex("^((Free|Paid) Chest)|(Kuudra - .+)|(.+)?Vesuvius$") + private val uselessLinesRegex = Regex("^Contents|Cost|Click to open!|FREE|Already opened!|Can't open another chest!|Paid Chest$") + private val amountRegex = Regex("(.+)? x(\\d+)$") + + private val lightPurpleRegex = Regex("Fatal Tempo|Inferno|Crimson Essence") + private val whiteRegex = Regex("Book") + private val darkPurpleRegex = Regex("Kuudra Teeth|Fire Eel Shard|Kuudra Mandible|Barbarian Duke X Shard|XYZ Shard|Hellstorm Wand|Tentacle Dye") + private val blueRegex = Regex("Kada Knight Shard|Wheel of Fate|Ananke Feather|Burning Kuudra Core|Matcho Shard|Kuudra Tentacle|Lava Flame Shard|Wither Spectre Shard") + private val goldRegex = Regex("Boots|Leggings|Chestplate|Helmet|Cloak|Aurora Staff|Hollow Wand|Heavy Pearl|Kraken Shard|Hellwisp Shard|Ananke Shard|Moltenfish Shard|Tormentor") + + val profitHud by HUD("Kuudra Tracker HUD", "Displays the profit of all your Kudura chests.") { + if (!it) return@HUD 0 to 0 + drawOverlay(true) + } + + private var singleItems by MapSetting("Single Items",mutableMapOf<String, Int>()) + private var multiItems by MapSetting("Multi Items", mutableMapOf<String, Int>()) + + private var paid by NumberSetting("Paid Chests", 0, 0, Int.MAX_VALUE, 1, "Amount of Paid Chests opened.").hide() + private var free by NumberSetting("Free Chests", 0, 0, Int.MAX_VALUE, 1, "Amount of Paid Chests opened").hide() + + private var toDisplay = mutableListOf<Pair<MutableComponent, Double>>() + private var totalKeys by ListSetting("Total Keys", mutableListOf(0, 0, 0, 0, 0)).hide() //Tier 1-5 + + private var last = LastAdded(mutableListOf(), mutableListOf(), 0) + + init { + updateDisplay() + + on<GuiEvent.DrawTooltip> { + val title = screen.title.string + if (enabled && title.matches(hudRegex)) { + guiGraphics.pose().pushMatrix() + val sf = mc.window.guiScale + guiGraphics.pose().scale(1f / sf, 1f / sf) + guiGraphics.pose().translate(profitHud.x.toFloat(), profitHud.y.toFloat()) + guiGraphics.pose().scale(profitHud.scale) + + guiGraphics.drawOverlay(false) + + guiGraphics.pose().popMatrix() + } + } + + on<MessageEvent.Chat> { + val title = mc.screen?.title?.string ?: return@on + if (message.equalsOneOf("You cannot afford this!", "Whoa! Slow down there!") && title.matches(chestRegex)) { + for (single in last.single) { + singleItems[single] = (singleItems.getOrDefault(single, 0) - 1).coerceAtLeast(0) + } + for ((itemComponent, amount) in last.multi) { + multiItems[itemComponent] = (multiItems.getOrDefault(itemComponent, 0) - amount).coerceAtLeast(0) + } + totalKeys[last.key - 1]-- + paid-- + + updateDisplay() + ModuleManager.saveConfigurations() + } + } + + onSend<ServerboundContainerClickPacket> { + val title = mc.screen?.title?.string ?: return@onSend + if (!title.matches(chestRegex)) return@onSend + + val cursorStack = mc.player?.containerMenu?.carried ?: return@onSend + + if (!cursorStack.`is`(Items.CHEST)) return@onSend + + val loreLines = cursorStack.lore + + updateDisplay() + + if (loreLines.any { it.string.equalsOneOf("Already opened!", "Can't open another chest!")}) return@onSend + + last = LastAdded(mutableListOf(), mutableListOf(), 0) + + for (lore in loreLines) { + if (lore.string.matches(uselessLinesRegex)) continue + + if (lore.string.contains("Kuudra Key")) { + keys.find { it.type == lore.string }?.let { key -> + totalKeys[key.tier - 1]++ + last.key = key.tier + continue + } + } + + amountRegex.find(lore.string)?.destructured?.let { (name, amount) -> + multiItems[name] = multiItems.getOrDefault(name, 0) + (amount.toIntOrNull() ?: 0) + continue + } + + singleItems[lore.string] = singleItems.getOrDefault(lore.string, 0) + 1 + } + + if (title == "Paid Chest") paid++ + if (title == "Free Chest") free++ + + updateDisplay() + ModuleManager.saveConfigurations() + } + } + + private fun updateDisplay() { + toDisplay.clear() + + singleItems.forEach { (name, amount) -> + if (amount == 0) return@forEach + + val price = Vesuvius.parseItemValue(Component.literal(name))?.times(amount) ?: return@forEach + + toDisplay.add(Component.literal(name).withStyle(getColor(name)).append(Component.literal(" x$amount").withStyle(ChatFormatting.DARK_GRAY)) to price) + } + + multiItems.forEach { (name, amount) -> + if (amount == 0) return@forEach + + val built = Component.literal(name).append(Component.literal(" x$amount").withStyle(ChatFormatting.DARK_GRAY)) + + val price = Vesuvius.parseItemValue(built) ?: return@forEach + + toDisplay.add(built.withStyle(getColor(name)) to price) + } + } + + private fun getKeyPrices(): Double { + return getPriceOfKey("Kuudra Key") * totalKeys[0] + + getPriceOfKey("Hot Kuudra Key") * totalKeys[1] + + getPriceOfKey("Burning Kuudra Key") * totalKeys[2] + + getPriceOfKey("Fiery Kuudra Key") * totalKeys[3] + + getPriceOfKey("Infernal Kuudra Key") * totalKeys[4] + } + + private fun GuiGraphicsExtractor.drawOverlay(isEditing: Boolean): Pair<Int, Int> { + var yOffset = 0 + val maxWidth = 300 + + var profit = 0.0 + + val display = if (isEditing) { sampleProfitData } else toDisplay + + for ((index, pair) in display + .sortedByDescending { it.second } + .withIndex() + ) { + val price = "%,.0f".format(pair.second) + + profit += pair.second + + if (index >= lineAmount) continue + + text(mc.font, pair.first, 0, yOffset, -1) + text(mc.font, price, maxWidth - mc.font.width(price), yOffset, Colors.MINECRAFT_DARK_GRAY.rgba) + + yOffset += 9 + } + + yOffset += 6 + + val keyPrices = if (isEditing) getPriceOfKey("Infernal Kuudra Key").toString() else "%,.0f".format(getKeyPrices()) + + text(mc.font, "§cTotal Key Costs:", 0, yOffset, -1) + text(mc.font, keyPrices, maxWidth - mc.font.width(keyPrices), yOffset, Colors.MINECRAFT_RED.rgba) + + yOffset += 12 + + val profitStr = "%,.0f".format(profit - getKeyPrices()) + + text("§aProfit:", 0, yOffset) + text(profitStr, maxWidth - mc.font.width(profitStr), yOffset, Colors.MINECRAFT_GREEN) + + yOffset += 15 + + val freeChests = if (isEditing) 0 else free + val paidChests = if (isEditing) 1 else paid + + val avg = "%,.0f".format((profit - getKeyPrices()) / (freeChests + paidChests)) + + text(mc.font, "Total Chests: Free [$freeChests], Paid [$paidChests]", 0, yOffset, Colors.MINECRAFT_YELLOW.rgba) + text(mc.font, "Avg [$avg]", maxWidth - mc.font.width("Avg [$avg]"), yOffset, Colors.MINECRAFT_GOLD.rgba) + + yOffset += 9 + + return maxWidth to yOffset + } + + private fun getColor(name: String): ChatFormatting { + if (name.contains(whiteRegex)) return ChatFormatting.WHITE + if (name.contains(goldRegex)) return ChatFormatting.GOLD + if (name.contains(darkPurpleRegex)) return ChatFormatting.DARK_PURPLE + if (name.contains(lightPurpleRegex)) return ChatFormatting.LIGHT_PURPLE + if (name.contains(blueRegex)) return ChatFormatting.BLUE + + return ChatFormatting.WHITE + } + + data class LastAdded(var single: MutableList<String>, var multi: MutableList<Multi>, var key: Int) + data class Multi(val component: String, val amount: Int) + + private val sampleProfitData = listOf( + Pair( + Component.literal("Fervor Helmet") + .withStyle(ChatFormatting.GOLD), + 748000.0 + ), + Pair( + Component.literal("Enchanted Book (").withStyle(ChatFormatting.WHITE) + .append(Component.literal("Ferocious Mana V").withStyle(ChatFormatting.LIGHT_PURPLE)) + .append(Component.literal(")").withStyle(ChatFormatting.WHITE)), + 118500000.0 + ), + Pair( + Component.literal("Crimson Essence").withStyle(ChatFormatting.LIGHT_PURPLE) + .append(Component.literal(" x2000").withStyle(ChatFormatting.DARK_GRAY)), + 2420000.0 + ), + Pair( + Component.literal("Kuudra Teeth").withStyle(ChatFormatting.DARK_PURPLE) + .append(Component.literal(" x4").withStyle(ChatFormatting.DARK_GRAY)), + 35480.0 + ), + Pair( + Component.literal("Kraken Shard").withStyle(ChatFormatting.GOLD) + .append(Component.literal(" x2").withStyle(ChatFormatting.DARK_GRAY)), + 821226.0 + ) + ) +} @@ -41,13 +41,13 @@ object Vesuvius : Module( private val pearlRegex = Regex("^Heavy Pearl x(\\d+)$") private val chestRegex = Regex("^((Free|Paid) Chest)|(Kuudra - .+)$") private val uselessLinesRegex = Regex("^Contents|Cost|Click to open!|FREE|Already opened!|Can't open another chest!|Paid Chest|") - private val salvageItemsRegex = Regex("^Boots|Chestplate|Helmet|Cloak|Aurora Staff|Hollow Wand") + private val salvageItemsRegex = Regex("Boots|Leggings|Chestplate|Helmet|Cloak|Aurora Staff|Hollow Wand") private val ultimateEnchants = setOf( "Fatal Tempo", "Inferno" ) - private data class Key(val type: String, val coins: Int, val quantity: Int) + data class Key(val type: String, val coins: Int, val quantity: Int, val tier: Int) private data class ChestItem(val name: Component, val price: Double) private data class ChestData(val items: List<ChestItem>, val cost: Double, val profit: Double) @@ -87,8 +87,7 @@ object Vesuvius : Module( } } - private fun parseItemValue(component: Component): Double? { - + fun parseItemValue(component: Component): Double? { var starCount = 0 val salvage = salvageItemsRegex.containsMatchIn(component.string) @@ -139,6 +138,7 @@ object Vesuvius : Module( } if (useSalvagePrices && salvage) { + modMessage("$component}") val price = cachedPrices["ESSENCE_CRIMSON"] ?: 0.0 val quantity = starCountToEssence[starCount] ?: 0.0 return price * quantity @@ -149,7 +149,7 @@ object Vesuvius : Module( return cachedPrices[item.uppercase().replace(" ", "_")] } - private fun getPriceOfKey(key: String): Double { + fun getPriceOfKey(key: String): Double { keys.find { it.type == key }?.let { val material = minOf(cachedPrices["ENCHANTED_RED_SAND"] ?: 0.0, cachedPrices["ENCHANTED_MYCELIUM"] ?: 0.0) val star = (cachedPrices["CORRUPTED_NETHER_STAR"] ?: 0.0) @@ -211,12 +211,12 @@ object Vesuvius : Module( return maxWidth to yOffset } - private val keys = listOf<Key>( - Key("Kuudra Key", 155200, 2), - Key("Hot Kuudra Key", 310400, 4), - Key("Burning Kuudra Key", 582000, 16), - Key("Fiery Kuudra Key", 1164000, 40), - Key("Infernal Kuudra Key", 2328000, 80) + val keys = listOf<Key>( + Key("Kuudra Key", 155200, 2, 1), + Key("Hot Kuudra Key", 310400, 4, 2), + Key("Burning Kuudra Key", 582000, 16, 3), + Key("Fiery Kuudra Key", 1164000, 40, 4), + Key("Infernal Kuudra Key", 2328000, 80, 5) ) private val itemReplacements = mapOf( |
Rotate the dwp AABB to match the room's rotation (#155)查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypointEditor.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypointPacks.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 15 +++++++++++++++ src/main/kotlin/com/odtheking/odin/utils/VecUtils.kt | 24 ++++++++++++++++++++---- 4 files changed, 38 insertions(+), 7 deletions(-) @@ -56,7 +56,7 @@ internal fun DungeonWaypoints.handleEditorInput(event: InputEvent) { return } if (allowTextEdit && mc.player?.isCrouching == true) { - openWaypointTitlePrompt(room, blockPos, relativeAabbAt(pos), editableWaypoints) + openWaypointTitlePrompt(room, blockPos, room.getRelativeAABB(relativeAabbAt(pos)), editableWaypoints) return } if (editableWaypoints.removeIf { it.blockPos == blockPos }) { @@ -65,7 +65,7 @@ internal fun DungeonWaypoints.handleEditorInput(event: InputEvent) { OdinMod.scope.launch { saveWaypoints() } return } - editableWaypoints.add(createWaypoint(blockPos, relativeAabbAt(pos))) + editableWaypoints.add(createWaypoint(blockPos, room.getRelativeAABB(relativeAabbAt(pos)))) devMessage("Added waypoint at $blockPos") syncRoomToActive(room) OdinMod.scope.launch { saveWaypoints() } @@ -84,7 +84,7 @@ fun DungeonRoom.setWaypoints() { val name = data?.name ?: return waypoints = DungeonWaypoints.allActiveWaypoints[name] ?.mapTo(mutableSetOf()) { waypoint -> - waypoint.copy(blockPos = getRealCoords(waypoint.blockPos)) + waypoint.copy(blockPos = getRealCoords(waypoint.blockPos), aabb = getRealAABB(waypoint.aabb)) } ?: mutableSetOf() } @@ -5,6 +5,7 @@ import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoint import com.odtheking.odin.utils.* import net.minecraft.core.BlockPos import net.minecraft.world.level.block.Blocks +import net.minecraft.world.phys.AABB class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData? = null) { val tiles: ArrayList<IVec2> = ArrayList(4) @@ -181,4 +182,18 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData val rot = rotation ?: return BlockPos.ZERO return pos.rotateAroundNorth(rot).offset(clay.x, 0, clay.z) } + + fun getRelativeAABB(aabb: AABB): AABB { + val rot = rotation ?: return aabb + val minPos = aabb.minPosition.add(-0.5, -0.5, -0.5).rotateToNorth(rot).add(0.5, 0.5, 0.5) + val maxPos = aabb.maxPosition.add(-0.5, -0.5, -0.5).rotateToNorth(rot).add(0.5, 0.5, 0.5) + return AABB(minPos, maxPos) + } + + fun getRealAABB(aabb: AABB): AABB { + val rot = rotation ?: return aabb + val minPos = aabb.maxPosition.add(-0.5, -0.5, -0.5).rotateAroundNorth(rot).add(0.5, 0.5, 0.5) + val maxPos = aabb.minPosition.add(-0.5, -0.5, -0.5).rotateAroundNorth(rot).add(0.5, 0.5, 0.5) + return AABB(minPos, maxPos) + } } \ No newline at end of file @@ -26,9 +26,9 @@ fun Vec3.addVec(x: Number = 0.0, y: Number = 0.0, z: Number = 0.0): Vec3 = Vec3(this.x + x.toDouble(), this.y + y.toDouble(), this.z + z.toDouble()) /** - * Rotates a Vec3 around the given rotation. + * Rotates a BlockPos around the given rotation. * @param rotation The rotation to rotate around - * @return The rotated Vec3 + * @return The rotated BlockPos */ fun BlockPos.rotateAroundNorth(rotation: RoomRotation): BlockPos = when (rotation) { @@ -38,10 +38,18 @@ fun BlockPos.rotateAroundNorth(rotation: RoomRotation): BlockPos = RoomRotation.EAST -> BlockPos(this.z, this.y, -this.x) } +fun Vec3.rotateAroundNorth(rotation: RoomRotation): Vec3 = + when (rotation) { + RoomRotation.NORTH -> Vec3(-this.x, this.y, -this.z) + RoomRotation.WEST -> Vec3(-this.z, this.y, this.x) + RoomRotation.SOUTH -> Vec3(this.x, this.y, this.z) + RoomRotation.EAST -> Vec3(this.z, this.y, -this.x) + } + /** - * Rotates a Vec3 to the given rotation. + * Rotates a BlockPos to the given rotation. * @param rotation The rotation to rotate to - * @return The rotated Vec3 + * @return The rotated BlockPos */ fun BlockPos.rotateToNorth(rotation: RoomRotation): BlockPos = when (rotation) { @@ -51,6 +59,14 @@ fun BlockPos.rotateToNorth(rotation: RoomRotation): BlockPos = RoomRotation.EAST -> BlockPos(-this.z, this.y, this.x) } +fun Vec3.rotateToNorth(rotation: RoomRotation): Vec3 = + when (rotation) { + RoomRotation.NORTH -> Vec3(-this.x, this.y, -this.z) + RoomRotation.WEST -> Vec3(this.z, this.y, -this.x) + RoomRotation.SOUTH -> Vec3(this.x, this.y, this.z) + RoomRotation.EAST -> Vec3(-this.z, this.y, this.x) + } + fun isXZInterceptable(box: AABB, range: Double, pos: Vec3, yaw: Float, pitch: Float): Boolean { val start = pos.addVec(y = (mc.player?.eyeY ?: 0.0)) val goal = start.add(getLook(yaw, pitch).multiply(range, range, range)) |
Fixed PositionalMessages data class constructor never firing issue查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PositionalMessages.kt | 23 ++++++++++++++++++++--- src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 24 +++++++++++++----------- 2 files changed, 33 insertions(+), 14 deletions(-) @@ -31,9 +31,26 @@ object PositionalMessages : Module( private val messageSize by NumberSetting("Message Size", 1f, 0.1f, 4f, 0.1f, desc = "The size at which to display the message in the box.").withDependency { showPositions && displayMessage } data class PosMessage(val x: Double, val y: Double, val z: Double, val x2: Double?, val y2: Double?, val z2: Double?, val delay: Int, val distance: Double?, val color: Color, val message: String, val send: Boolean) { - val center: Vec3 get() = Vec3((x + (x2 ?: x)) / 2, (y + (y2 ?: y)) / 2, (z + (z2 ?: z)) / 2) - val box: AABB? get() = if (x2 != null && y2 != null && z2 != null) AABB(x, y, z, x2, y2, z2) else null - val radiusSquared: Double? get() = distance?.let { it * it } + @Transient + private var _center: Vec3? = null + val center: Vec3 + get() = _center ?: Vec3((x + (x2 ?: x)) / 2, (y + (y2 ?: y)) / 2, (z + (z2 ?: z)) / 2).also { _center = it } + + @Transient + private var _box: AABB? = null + val box: AABB? + get() { + if (_box == null && x2 != null && y2 != null && z2 != null) _box = AABB(x, y, z, x2, y2, z2) + return _box + } + + @Transient + private var _radiusSquared: Double? = null + val radiusSquared: Double? + get() { + if (_radiusSquared == null) distance?.let { _radiusSquared = it * it } + return _radiusSquared + } } val posMessageStrings by ListSetting("Pos Messages", mutableListOf<PosMessage>()) private val sentMessages = mutableSetOf<PosMessage>() @@ -128,31 +128,33 @@ object ClickGUIModule : Module( modMessage( Component.literal(getChatBreak()) - .append(Component.literal("§3Odin update available: §f$latestVersionNumber\n\n")) + .append("\n") + .append(Component.literal("§3Odin update available: §f$latestVersionNumber\n")) + .append(Component.literal("§7Click the links below to download the latest version.\n\n")) .append( - Component.literal("§bGitHub link").withStyle { - it.withClickEvent(ClickEvent.OpenUrl(URI(RELEASE_LINK))).withHoverEvent( - HoverEvent.ShowText(Component.literal(RELEASE_LINK)) + Component.literal(" §a§nModrinth Link").withStyle { + it.withClickEvent(ClickEvent.OpenUrl(URI(MODRINTH_LINK))).withHoverEvent( + HoverEvent.ShowText(Component.literal(MODRINTH_LINK)) ) } ) - .append(Component.literal("\n")) + .append(Component.literal(" ")) .append( - Component.literal("§bModrinth Link").withStyle { - it.withClickEvent(ClickEvent.OpenUrl(URI(MODRINTH_LINK))).withHoverEvent( - HoverEvent.ShowText(Component.literal(MODRINTH_LINK)) + Component.literal("§b§nGitHub link").withStyle { + it.withClickEvent(ClickEvent.OpenUrl(URI(RELEASE_LINK))).withHoverEvent( + HoverEvent.ShowText(Component.literal(RELEASE_LINK)) ) } ) - .append(Component.literal("\n")) + .append(Component.literal(" ")) .append( - Component.literal("§9Discord link").withStyle { + Component.literal("§9§nDiscord link").withStyle { it.withClickEvent(ClickEvent.OpenUrl(URI("https://discord.gg/2nCbC9hkxT"))).withHoverEvent( HoverEvent.ShowText(Component.literal("https://discord.gg/2nCbC9hkxT")) ) } ) - .append(Component.literal("\n\n${getChatBreak()}§r")), + .append(Component.literal("\n${getChatBreak()}§r")), "" ) |
Fixed BloodCamp estimate going wild due to event change查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 15 +++++++++------ src/main/kotlin/com/odtheking/odin/features/impl/boss/termGUI/CustomTermGui.kt | 7 +++---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BloodCamp.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/InvincibilityTimer.kt | 8 ++++---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PositionalMessages.kt | 6 +++--- src/main/kotlin/com/odtheking/odin/features/impl/render/GyroWand.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/Splits.kt | 33 ++++++++++++++++----------------- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/NumbersHandler.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/TerminalHandler.kt | 2 +- 9 files changed, 40 insertions(+), 39 deletions(-) @@ -26,19 +26,22 @@ object TerminalSolver : Module( val customTermSize by NumberSetting("Term Size", 2f, 1f, 3f, 0.1f, desc = "The size of the custom terminal GUI.").withDependency { renderType == 2 } val roundness by NumberSetting("Roundness", 5, 0f, 15f, 1f, desc = "The roundness of the custom terminal gui.").withDependency { renderType == 2 } val gap by NumberSetting("Slot gap", 2, 0, 8, 1, desc = "The gap between the slots in the custom terminal gui.").withDependency { renderType == 2 } + private val solverSettings by DropdownSetting("Solver Functionality") + val clickPrediction by BooleanSetting("Client Prediction", true, desc = "Visually predicts the server state before the gui update is sent to the client.").withDependency { solverSettings } + val terminalReloadThreshold by NumberSetting("Resolve timeout", 600, 300, 1200, 10, unit = "ms", desc = "The amount of time before the terminal reloads after a click wasn't registered while using click prediction.").withDependency { clickPrediction && solverSettings } private val cancelMelodySolver by BooleanSetting("Stop Melody Solver", false, desc = "Stops rendering the melody solver.").withDependency { solverSettings } val melodyTermSize by NumberSetting("Melody Size", 1.5f, 1f, 3f, 0.1f, desc = "The size of the melody terminal GUI.").withDependency { !cancelMelodySolver && solverSettings && renderType == 2 } - val showNumbers by BooleanSetting("Show Numbers", true, desc = "Shows numbers in the order terminal.").withDependency { solverSettings } - val rubixMode by SelectorSetting("Rubix Mode", "Left Clicks Only", arrayListOf("Fewest Clicks", "Left Clicks Only"), desc = "Whether the rubix solver should mix in right clicks for the fewest clicks overall, or stick to left clicks only.").withDependency { solverSettings } - val hideClicked by BooleanSetting("Client Prediction", true, desc = "Visually predicts the server state before the gui update is sent to the client.").withDependency { solverSettings } - val terminalReloadThreshold by NumberSetting("Resolve timeout", 600, 300, 1200, 10, unit = "ms", desc = "The amount of time before the terminal reloads after a click wasn't registered while using hide clicked.").withDependency { hideClicked && solverSettings } + val rubixMode by SelectorSetting("Rubix Mode", "Fewest Clicks", arrayListOf("Fewest Clicks", "Left Clicks Only"), desc = "Whether the rubix solver should mix in right clicks for the fewest clicks overall, or stick to left clicks only.").withDependency { solverSettings } + private val firstClickProtSettings by DropdownSetting("First Click Prot Dropdown") val firstClickProt by NumberSetting("First Click Prot", 500, 350, 800, 10, unit = "ms", desc = "The amount of time after opening a terminal where clicks are blocked to prevent bans (recommended value is 500 minus your ping).").withDependency { firstClickProtSettings } val shouldFirstClickProtWithTicks by BooleanSetting("Account For Server Lag", false, desc = "Prevents bans from clicking when the server lags after opening the terminal (disabled in singleplayer").withDependency { firstClickProtSettings } val firstClickProtTicks by NumberSetting("Lag Protection Ticks", 8, 7, 16, unit = "ticks", desc = "Each tick = 50ms (recommended value is 8)").withDependency { shouldFirstClickProtWithTicks && firstClickProtSettings } + private val showColors by DropdownSetting("Color Settings") val backgroundColor by ColorSetting("Background", Colors.gray26, true, desc = "Background color of the terminal solver.").withDependency { showColors } + val textColor by ColorSetting("Text", Colors.WHITE, true, desc = "Text color of the terminal solver.").withDependency { showColors } val panesColor by ColorSetting("Panes", Colors.MINECRAFT_GREEN, true, desc = "Color of the panes terminal solver.").withDependency { showColors } @@ -67,7 +70,7 @@ object TerminalSolver : Module( init { on<GuiEvent.SlotClick> { TerminalUtils.currentTerm?.let { - it.click(slotIndex, button, hideClicked) + it.click(slotIndex, button, clickPrediction) cancel() } } @@ -86,7 +89,7 @@ object TerminalSolver : Module( if (slot.index <= currentTerm.type.windowSize - 1) { currentTerm.getSlotRendering(slot.index)?.let { (color, text) -> guiGraphics.fill(slot.x, slot.y, slot.x + 16, slot.y + 16, color.rgba) - text?.let { guiGraphics.centeredText(screen.font, it, slot.x + 8, slot.y + 4, Colors.WHITE.rgba) } + text?.let { guiGraphics.centeredText(screen.font, it, slot.x + 8, slot.y + 4, textColor.rgba) } cancel() } if (renderType == 0) cancel() @@ -6,7 +6,6 @@ import com.odtheking.odin.events.ScreenEvent import com.odtheking.odin.features.impl.boss.TerminalSolver import com.odtheking.odin.features.impl.boss.TerminalSolver.renderDebug import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.render.roundedFill import com.odtheking.odin.utils.render.text import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils @@ -50,13 +49,13 @@ abstract class TermGui { grid?.let { g -> val (bx, by) = g.toBase(click.x(), click.y(), guiScale) hoveredSlotIndex = g.slots.firstOrNull { it.containsBase(bx, by) }?.slotIndex - hoveredSlotIndex?.let { TerminalUtils.currentTerm?.click(it, click.button(), TerminalSolver.hideClicked) } + hoveredSlotIndex?.let { TerminalUtils.currentTerm?.click(it, click.button(), TerminalSolver.clickPrediction) } }; return true }, key = fun ScreenEvent.KeyPress.(): Any { if (!isTerminalOverrideKey(input)) return false hoveredSlotIndex?.let { - TerminalUtils.currentTerm?.click(it, if (!input.hasControlDown()) InputConstants.MOUSE_BUTTON_LEFT else InputConstants.MOUSE_BUTTON_RIGHT, TerminalSolver.hideClicked) + TerminalUtils.currentTerm?.click(it, if (!input.hasControlDown()) InputConstants.MOUSE_BUTTON_LEFT else InputConstants.MOUSE_BUTTON_RIGHT, TerminalSolver.clickPrediction) }; return true }) ) @@ -127,7 +126,7 @@ fun simpleTermGui(rows: Int, cols: Int, startRow: Int, startCol: Int): TermGui = override fun buildTerminal(screen: AbstractContainerScreen<*>) = buildTerminalGrid(screen, rows, cols, startRow, startCol) { slotIndex -> SlotVisual({ TerminalUtils.currentTerm?.getSlotRendering(slotIndex) }) { x, y, w, h -> - TerminalUtils.currentTerm?.getSlotRendering(slotIndex)?.second?.let { renderSlotText(it, x, y, w, h, Colors.WHITE) } + TerminalUtils.currentTerm?.getSlotRendering(slotIndex)?.second?.let { renderSlotText(it, x, y, w, h, TerminalSolver.textColor) } } } } \ No newline at end of file @@ -63,7 +63,7 @@ object BloodCamp : Module( init { on<EntityEvent.Move> { - if (newPos == Vec3.ZERO || !DungeonUtils.inClear) return@on + if (newPos == Vec3.ZERO || newPos == entity.position() || !DungeonUtils.inClear) return@on val entity = entity as? ArmorStand ?: return@on if (currentWatcherEntity?.let { @@ -78,9 +78,9 @@ object InvincibilityTimer : Module( private val cooldownRegex = Regex("^Cooldown: (\\d+)s$") private val itemCat by DropdownSetting("Cooldown On Item") - private val showOnItem by BooleanSetting("Show On Item", true, "Renders the cooldown on the spirit mask and bonzo mask items").withDependency { itemCat } + private val showOnItem by BooleanSetting("Show On Item", false, "Renders the cooldown on the spirit mask and bonzo mask items").withDependency { itemCat } private val durability by BooleanSetting("Display As Durability", false, "True: durability, False: colored vertical slide").withDependency { itemCat } - private val cdColor by ColorSetting("Cooldown Color", Colors.gray38, true, "Color of the cooldown").withDependency { itemCat } + private val cdColor by ColorSetting("Cooldown Color", Colors.gray38, false, "Color of the cooldown").withDependency { itemCat } init { on<TickEvent.Server> { @@ -112,8 +112,8 @@ object InvincibilityTimer : Module( "SPIRIT_MASK","STARRED_SPIRIT_MASK" -> InvincibilityType.SPIRIT.currentCooldown.toDouble() / (InvincibilityType.SPIRIT.maxCooldownTime * 20) else -> return@on } - if(percent <= 0) return@on - if(durability) { + if (percent <= 0) return@on + if (durability) { guiGraphics.fakeItem(slot.item, slot.x, slot.y) guiGraphics.fill(slot.x + 2, slot.y + 13, slot.x + 14, slot.y + 15, Colors.BLACK.rgba) guiGraphics.fill(slot.x + 2, slot.y + 13, slot.x + 14 - ((1 - percent) * 12).toInt(), slot.y + 14,cdColor.rgba) @@ -31,9 +31,9 @@ object PositionalMessages : Module( private val messageSize by NumberSetting("Message Size", 1f, 0.1f, 4f, 0.1f, desc = "The size at which to display the message in the box.").withDependency { showPositions && displayMessage } data class PosMessage(val x: Double, val y: Double, val z: Double, val x2: Double?, val y2: Double?, val z2: Double?, val delay: Int, val distance: Double?, val color: Color, val message: String, val send: Boolean) { - val center: Vec3 by lazy { Vec3((x + (x2 ?: x)) / 2, (y + (y2 ?: y)) / 2, (z + (z2 ?: z)) / 2) } - val box: AABB? by lazy { if (x2 != null && y2 != null && z2 != null) AABB(x, y, z, x2, y2, z2) else null } - val radiusSquared by lazy { distance?.let { it * it } } + val center: Vec3 get() = Vec3((x + (x2 ?: x)) / 2, (y + (y2 ?: y)) / 2, (z + (z2 ?: z)) / 2) + val box: AABB? get() = if (x2 != null && y2 != null && z2 != null) AABB(x, y, z, x2, y2, z2) else null + val radiusSquared: Double? get() = distance?.let { it * it } } val posMessageStrings by ListSetting("Pos Messages", mutableListOf<PosMessage>()) private val sentMessages = mutableSetOf<PosMessage>() @@ -19,9 +19,9 @@ object GyroWand : Module( name = "Gyro Wand", description = "Shows area of effect and cooldown of the Gyrokinetic Wand." ) { - private val color by ColorSetting("Color", Colors.MINECRAFT_DARK_PURPLE.withAlpha(0.5f), allowAlpha = true, desc = "The color of the Gyrokinetic Wand range.") + private val color by ColorSetting("Color", Colors.MINECRAFT_DARK_PURPLE.withAlpha(0.5f), true, desc = "The color of the Gyrokinetic Wand range.") private val showCooldown by BooleanSetting("Show Cooldown", true, desc = "Shows the cooldown of the Gyrokinetic Wand.") - private val cooldownColor by ColorSetting("Cooldown Color", Colors.MINECRAFT_RED.withAlpha(0.5f), allowAlpha = true, desc = "The color of the cooldown of the Gyrokinetic Wand.").withDependency { showCooldown } + private val cooldownColor by ColorSetting("Cooldown Color", Colors.MINECRAFT_RED.withAlpha(0.5f), true, desc = "The color of the cooldown of the Gyrokinetic Wand.").withDependency { showCooldown } private val depthCheck by BooleanSetting("Depth Check", true, desc = "Whether or not the cylinder should have a depth check.") private val gravityStormRegex = Regex("(?s)(.*(-\\d+ Mana \\(Gravity Storm\\)).*)") @@ -8,6 +8,7 @@ import com.odtheking.odin.utils.PersonalBest import com.odtheking.odin.utils.formatTime import com.odtheking.odin.utils.render.getStringWidth import com.odtheking.odin.utils.render.text +import com.odtheking.odin.utils.skyblock.SplitRow import com.odtheking.odin.utils.skyblock.SplitsManager.currentRows import com.odtheking.odin.utils.skyblock.floor7SplitGroup import com.odtheking.odin.utils.toFixed @@ -40,34 +41,32 @@ object Splits : Module( val labelWidth = segments.maxOfOrNull { getStringWidth(it.name) } ?: 0 val totalWidth = labelWidth + 4 + timeWidth + 2 + val displayRows = mutableListOf<SplitRow>() segments.forEachIndexed { index, row -> - if (row.time == 0L && !show0Time) return@forEachIndexed - val time = formatTime(row.time) - text(row.name, 0, index * 9, Colors.WHITE) - - val displayText = if (showTickTime) "$time §8(§7${(row.tickTime / 20f).toFixed()}§8)" else time - - val timeX = if (fixedWidth) labelWidth + 4 + timeWidth - getStringWidth(displayText) - else labelWidth + 4 - - text(displayText, timeX, index * 9, Colors.WHITE) + displayRows.add(row) + if (bossEntrySplit && index == 2 && rows.size > 3) { + val bossTime = segments.take(3).sumOf { it.time } + val bossTickTime = segments.take(3).sumOf { it.tickTime } + displayRows.add(SplitRow("§9Boss Entry", bossTime, bossTickTime, isCurrent = false)) + } } - if (bossEntrySplit && rows.size > 3) { - val y = segments.size * 9 - text("§9Boss Entry", 0, y, Colors.WHITE) + var y = 0 + displayRows.forEach { row -> + if (row.time == 0L && !show0Time) return@forEach + val time = formatTime(row.time) + text(row.name, 0, y, Colors.WHITE) - val totalTime = formatTime(segments.take(3).sumOf { it.time }) - val displayText = if (showTickTime) "$totalTime §8(§7${(segments.take(3).sumOf { it.tickTime } / 20f).toFixed()}§8)" - else totalTime + val displayText = if (showTickTime) "$time §8(§7${(row.tickTime / 20f).toFixed()}§8)" else time val timeX = if (fixedWidth) labelWidth + 4 + timeWidth - getStringWidth(displayText) else labelWidth + 4 text(displayText, timeX, y, Colors.WHITE) + y += 9 } - totalWidth to 9 * (rows.size + (if (bossEntrySplit) 1 else 0)) + totalWidth to y } private val currentSplitHud by HUD("Current Split HUD", "Shows only the current split and its tick time.") { example -> @@ -28,6 +28,6 @@ class NumbersHandler: TerminalHandler(TerminalTypes.NUMBERS) { 1 -> TerminalSolver.orderColor2 2 -> TerminalSolver.orderColor3 else -> Colors.TRANSPARENT - } to if (TerminalSolver.showNumbers) (abs((solution.size - 14) - solutionIndex) + 1).toString() else null + } to (abs((solution.size - 14) - solutionIndex) + 1).toString() } } \ No newline at end of file @@ -34,7 +34,7 @@ abstract class TerminalHandler(val type: TerminalTypes) { solution.clear() solution.addAll(solve(event.slots.subList(0, type.windowSize - 9), event.slotIndex)) - if (TerminalSolver.hideClicked) clickedSlots.forEach { (index, button) -> simulateClick(index, button) } + if (TerminalSolver.clickPrediction) clickedSlots.forEach { (index, button) -> simulateClick(index, button) } } protected abstract fun renderSlot(slotIndex: Int): Pair<Color, String?>? |
| fixed invincibility timer on items (#151) * fixed invincibility timer on item and reorganized settings
* formatting
* formatting v2 查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/InvincibilityTimer.kt | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) @@ -3,6 +3,7 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting +import com.odtheking.odin.clickgui.settings.impl.DropdownSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.LevelEvent @@ -22,21 +23,16 @@ object InvincibilityTimer : Module( ) { private val invincibilityAlert by BooleanSetting("Invincibility Alert", true, desc = "Plays a sound when you get invincibility.") private val invincibilityAnnounce by BooleanSetting("Announce Invincibility", true, desc = "Announces when you get invincibility.") - private val showWhen by SelectorSetting("Show", "Always", listOf("Always", "Any", "When Active", "On Cooldown"), "Controls when invincibility items are shown.") - private val equippedMaskColor by ColorSetting("Equipped Mask", Colors.MINECRAFT_DARK_PURPLE, desc = "Color of the equipped mask in the HUD. (Bonzo/Spirit)") - - private val showSpirit by BooleanSetting("Show Spirit Mask", true, desc = "Shows the Spirit Mask in the HUD.") - private val showBonzo by BooleanSetting("Show Bonzo Mask", true, desc = "Shows the Bonzo Mask in the HUD.") - private val showPhoenix by BooleanSetting("Show Phoenix Pet", true, desc = "Shows the Phoenix Pet in the HUD.") - - private val onlyInDungeons by BooleanSetting("Only In Dungeons", true, "Only proc in dungeons") - private val showOnItem by BooleanSetting("Show On Item", true, "Renders the cooldown on the spirit mask and bonzo mask items") - private val showPhoenixOnTopLeft by BooleanSetting("Phoenix As Top Left", false, "Displays the cooldown for phoenix pet on the top-left most slot in your inventory") - private val durability by BooleanSetting("Display As Durability", false, "True: durability, False: colored vertical slide").withDependency { showOnItem } - private val cdColor by ColorSetting("Cooldown Color", Colors.gray38, true, "Color of the cooldown").withDependency { showOnItem } - + private val onlyInDungeons by BooleanSetting("Only In Dungeons", true, "Only proc invincibility timers while in a dungeon") + + private val maskCat by DropdownSetting("Displayed Items") + private val showSpirit by BooleanSetting("Show Spirit Mask", true, desc = "Shows the Spirit Mask in the HUD.").withDependency { maskCat } + private val showBonzo by BooleanSetting("Show Bonzo Mask", true, desc = "Shows the Bonzo Mask in the HUD.").withDependency { maskCat } + private val showPhoenix by BooleanSetting("Show Phoenix Pet", true, desc = "Shows the Phoenix Pet in the HUD.").withDependency { maskCat } + + private val hudCat by DropdownSetting("Invincibility Hud") private val hud by HUD(name, "Shows the invincibility time in the HUD.") { example -> - if ((!DungeonUtils.inDungeons && !example) || (showOnlyInBoss && !DungeonUtils.inBoss && !example)) return@HUD 0 to 0 + if(!example && (onlyInDungeons && !DungeonUtils.inDungeons) || (showOnlyInBoss && !DungeonUtils.inBoss)) return@HUD 0 to 0 val visibleTypes = InvincibilityType.entries.filter { type -> (when (type) { @@ -75,9 +71,16 @@ object InvincibilityTimer : Module( } width + 20 to visibleTypes.size * 14 - } - private val showOnlyInBoss by BooleanSetting("Show In Boss", false, desc = "Only shows invincibility timers during dungeon boss fights.") + }.withDependency { hudCat } + private val showOnlyInBoss by BooleanSetting("Show In Boss", false, desc = "Only shows invincibility timers during dungeon boss fights.").withDependency { hudCat } + private val showWhen by SelectorSetting("Show", "Always", listOf("Always", "Any", "When Active", "On Cooldown"), "Controls when invincibility items are shown.").withDependency { hudCat } + private val equippedMaskColor by ColorSetting("Equipped Mask", Colors.MINECRAFT_DARK_PURPLE, desc = "Color of the equipped mask in the HUD. (Bonzo/Spirit)").withDependency { hudCat } private val cooldownRegex = Regex("^Cooldown: (\\d+)s$") + + private val itemCat by DropdownSetting("Cooldown On Item") + private val showOnItem by BooleanSetting("Show On Item", true, "Renders the cooldown on the spirit mask and bonzo mask items").withDependency { itemCat } + private val durability by BooleanSetting("Display As Durability", false, "True: durability, False: colored vertical slide").withDependency { itemCat } + private val cdColor by ColorSetting("Cooldown Color", Colors.gray38, true, "Color of the cooldown").withDependency { itemCat } init { on<TickEvent.Server> { @@ -105,18 +108,15 @@ object InvincibilityTimer : Module( on<GuiEvent.RenderSlot> { if (!showOnItem) return@on val percent = when(slot.item.itemId) { - "BONZO_MASK", "STARRED_BONZO_MASK" -> InvincibilityType.BONZO.currentCooldown.toDouble() / InvincibilityType.BONZO.maxCooldownTime - "SPIRIT_MASK", "STARRED_SPIRIT_MASK" -> InvincibilityType.SPIRIT.currentCooldown.toDouble() / InvincibilityType.SPIRIT.maxCooldownTime - else -> { - if(slot.containerSlot != 9 || !showPhoenixOnTopLeft) return@on - InvincibilityType.PHOENIX.currentCooldown.toDouble() / InvincibilityType.PHOENIX.maxCooldownTime - } + "BONZO_MASK","STARRED_BONZO_MASK" -> InvincibilityType.BONZO.currentCooldown.toDouble() / (InvincibilityType.BONZO.maxCooldownTime * 20) + "SPIRIT_MASK","STARRED_SPIRIT_MASK" -> InvincibilityType.SPIRIT.currentCooldown.toDouble() / (InvincibilityType.SPIRIT.maxCooldownTime * 20) + else -> return@on } - if (percent !in 0.0..1.0) return@on - if (durability && percent > 0) { + if(percent <= 0) return@on + if(durability) { guiGraphics.fakeItem(slot.item, slot.x, slot.y) guiGraphics.fill(slot.x + 2, slot.y + 13, slot.x + 14, slot.y + 15, Colors.BLACK.rgba) - guiGraphics.fill(slot.x + 2, slot.y + 13, slot.x + 14 - ((1 - percent) * 12).toInt(), slot.y + 14, cdColor.rgba) + guiGraphics.fill(slot.x + 2, slot.y + 13, slot.x + 14 - ((1 - percent) * 12).toInt(), slot.y + 14,cdColor.rgba) cancel() } else { guiGraphics.fakeItem(slot.item, slot.x, slot.y) |
| Update mixin compatibility level to JAVA_25 (#154) Very small change, was forgotten during 26.1 port I guess. Minecraft 26.1 requires Java 25. 查看变更src/main/resources/odin.mixins.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "com.odtheking.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "JAVA_25", "injectors": { "defaultRequire": 1 }, @@ -34,4 +34,4 @@ "mixins.SynchedEntityDataMixin", "mixins.YggdrasilSignatureIgnoreMixin" ] -} \ No newline at end of file +} |
Added QuiverDisplay Cleaned up Positionalmessages查看变更src/main/kotlin/com/odtheking/odin/commands/PosMsgCommand.kt | 40 +++++++++++++++++++++------------------- src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BreakerDisplay.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DungeonQueue.kt | 3 ++- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PositionalMessages.kt | 74 ++++++++++++++++++++++++++------------------------------------------------ src/main/kotlin/com/odtheking/odin/features/impl/skyblock/ChatCommands.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/QuiverDisplay.kt | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/kotlin/com/odtheking/odin/utils/network/WebUtils.kt | 7 ++++++- src/main/kotlin/com/odtheking/odin/utils/render/DrawContextUtils.kt | 9 +++++++-- 9 files changed, 116 insertions(+), 75 deletions(-) @@ -12,6 +12,10 @@ import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.modMessage import kotlin.math.floor +private val colorSuggestions = listOf("darkblue", "darkgreen", "darkaqua", "darkred", "darkpurple", + "gold", "gray", "darkgray", "blue", "green", "aqua", "red", + "lightpurple", "yellow", "white", "black") + val posMsgCommand = Commodore("posmsg") { literal("add") { literal("at").executable { @@ -25,17 +29,16 @@ val posMsgCommand = Commodore("posmsg") { suggests { listOfNotNull(mc.player?.z?.let { floor(it).toString() }) } } param("color") { - suggests { - listOf("darkblue", "darkgreen", "darkaqua", "darkred", "darkpurple", - "gold", "gray", "darkgray", "blue", "green", "aqua", "red", - "lightpurple", "yellow", "white", "black") - } + suggests { colorSuggestions } + } + param("send") { + suggests { listOf("true", "false") } } - runs { x: Double, y: Double, z: Double, delay: Int, distance: Double, color: String, message: GreedyString? -> - if (posMessageStrings.any { it.message == message?.string }) return@runs + runs { x: Double, y: Double, z: Double, delay: Int, distance: Double, color: String, send: Boolean, message: GreedyString -> + if (posMessageStrings.any { it.message == message.string }) return@runs val color = getColorFromString(color) ?: return@runs modMessage("Unknown color $color") - posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, null, null, null, delay, distance, color, message?.string)) + posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, null, null, null, delay, distance, color, message.string, send)) modMessage("Message \"${message}\" added at $x, $y, $z, with ${delay}t delay, triggered up to $distance blocks away.") ModuleManager.saveConfigurations() } @@ -43,17 +46,16 @@ val posMsgCommand = Commodore("posmsg") { literal("in").executable { param("color") { - suggests { - listOf("darkblue", "darkgreen", "darkaqua", "darkred", "darkpurple", - "gold", "gray", "darkgray", "blue", "green", "aqua", "red", - "lightpurple", "yellow", "white", "black") - } + suggests { colorSuggestions } + } + param("send") { + suggests { listOf("true", "false") } } - runs { x: Double, y: Double, z: Double, x2: Double, y2: Double, z2: Double, delay: Int, color: String, message: GreedyString? -> - if (posMessageStrings.any { it.message == message?.string }) return@runs + runs { x: Double, y: Double, z: Double, x2: Double, y2: Double, z2: Double, delay: Int, color: String, send: Boolean, message: GreedyString -> + if (posMessageStrings.any { it.message == message.string }) return@runs val color = getColorFromString(color) ?: return@runs modMessage("Unknown color $color") - posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, x2, y2, z2, delay, null, color, message?.string)) + posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, x2, y2, z2, delay, null, color, message.string, send)) modMessage("Message \"${message}\" added in $x, $y, $z, $x2, $y2, $z2, with ${delay}t delay.") ModuleManager.saveConfigurations() } @@ -69,7 +71,7 @@ val posMsgCommand = Commodore("posmsg") { input } suggests { - posMessageStrings.mapNotNull { it.message }.distinct() + posMessageStrings.map { it.message }.distinct() } } @@ -88,8 +90,8 @@ val posMsgCommand = Commodore("posmsg") { } literal("list").runs { - val output = posMessageStrings.joinToString(separator = "\n") { - "${posMessageStrings.indexOf(it) + 1}: ${it.x}, ${it.y}, ${it.z}, ${it.x2}, ${it.y2}, ${it.z2}, ${it.delay}, ${it.distance}, ${it.color.hex()}, \"${it.message}\"" + val output = posMessageStrings.withIndex().joinToString(separator = "\n") { (index, it) -> + "${index + 1}: ${it.x}, ${it.y}, ${it.z}, ${it.x2}, ${it.y2}, ${it.z2}, ${it.delay}, ${it.distance}, ${it.color.hex()}, send=${it.send}, \"${it.message}\"" } modMessage(if (posMessageStrings.isEmpty()) "Positional Message list is empty!" else "Positonal Message list:\n$output") } @@ -73,7 +73,7 @@ object ModuleManager { //skyblock ChatCommands, NoCursorReset, Ragnarock, SpringBoots, WardrobeKeybinds, PetKeybinds, AutoSprint, - CommandKeybinds, SlotBinds, Splits, LoadoutKeybinds, + CommandKeybinds, SlotBinds, Splits, LoadoutKeybinds, QuiverDisplay, // nether SupplyHelper, BuildHelper, RemovePerks, NoPre, PearlWaypoints, FreshTools, KuudraInfo, Misc, Vesuvius, @@ -6,7 +6,7 @@ import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.itemId -import com.odtheking.odin.utils.loreString +import com.odtheking.odin.utils.lore import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils @@ -27,7 +27,7 @@ object BreakerDisplay : Module( init { on<SetSlotEvent> { if (!DungeonUtils.inDungeons || itemStack.itemId != "DUNGEONBREAKER") return@on - itemStack.loreString.firstNotNullOfOrNull { chargesRegex.find(it) }?.let { match -> + itemStack.lore.firstNotNullOfOrNull { chargesRegex.find(it.string) }?.let { match -> charges = match.groupValues[1].toIntOrNull() ?: 0 maxCharges = match.groupValues[2].toIntOrNull() ?: 0 } @@ -29,6 +29,7 @@ object DungeonQueue : Module( private val disablePartyLeave by BooleanSetting("Disable on leave/kick", true, desc = "Disables the requeue on party leave message.").withDependency { autoRequeue } private val enterRegex = Regex("^-*\\n\\[[^]]+] (\\w+) entered (?:MM )?\\w+ Catacombs, Floor (\\w+)!\\n-*$") + private val timeoutExeceptionRegex = Regex("^Exception Connecting:ReadTimeoutException : null$") private val kickedInstanceRegex = Regex("^You are no longer allowed to access this instance!$") private val kickedJoiningRegex = Regex("^You were kicked while joining that server!$") private val extraStatsRegex = Regex(" {29}> EXTRA STATS <") @@ -39,7 +40,7 @@ object DungeonQueue : Module( init { on<MessageEvent.Chat> { when { - announceKick && (message.matches(kickedJoiningRegex) || message.matches(kickedInstanceRegex)) -> sendCommand("pc I was kicked!") + announceKick && (message.matches(kickedJoiningRegex) || message.matches(kickedInstanceRegex) || message.matches(timeoutExeceptionRegex)) -> sendCommand("pc I was kicked!") message.matches(enterRegex) -> warpTimer = System.currentTimeMillis() + 30_000L autoRequeue && message.matches(extraStatsRegex) -> { if (disableRequeue.also { disableRequeue = false }) return@on @@ -25,80 +25,58 @@ object PositionalMessages : Module( description = "Sends a message when you're near a certain position. /posmsg" ) { private val onlyDungeons by BooleanSetting("Only in Dungeons", true, desc = "Only sends messages when you're in a dungeon.") - private val oncePerWorld by BooleanSetting("Once Per World", false, desc = "Whether or not to only send each message once per world.") private val showPositions by BooleanSetting("Show Positions", true, desc = "Draws boxes/lines around the positions.") - private val cylinderHeight by NumberSetting("Height", 0.2, 0.1, 5.0, 0.1, desc = "Height of the cylinder for in messages.").withDependency { showPositions } - private val depthCheck by BooleanSetting("Depth Check", true, desc = "Whether or not the boxes should be seen through walls. False = Through walls.").withDependency { showPositions } + private val cylinderHeight by NumberSetting("Height", 0.2f, 0.1, 5.0, 0.1, desc = "Height of the cylinder for in messages.").withDependency { showPositions } private val displayMessage by BooleanSetting("Show Message", true, desc = "Whether or not to display the message in the box.").withDependency { showPositions } - private val messageSize by NumberSetting("Message Size", 1f, 0.1f, 4f, 0.1f, desc = "Whether or not to display the message size in the box.").withDependency { showPositions && displayMessage } + private val messageSize by NumberSetting("Message Size", 1f, 0.1f, 4f, 0.1f, desc = "The size at which to display the message in the box.").withDependency { showPositions && displayMessage } - data class PosMessage(val x: Double, val y: Double, val z: Double, val x2: Double?, val y2: Double?, val z2: Double?, val delay: Int, val distance: Double?, val color: Color, val message: String?) + data class PosMessage(val x: Double, val y: Double, val z: Double, val x2: Double?, val y2: Double?, val z2: Double?, val delay: Int, val distance: Double?, val color: Color, val message: String, val send: Boolean) { + val center: Vec3 by lazy { Vec3((x + (x2 ?: x)) / 2, (y + (y2 ?: y)) / 2, (z + (z2 ?: z)) / 2) } + val box: AABB? by lazy { if (x2 != null && y2 != null && z2 != null) AABB(x, y, z, x2, y2, z2) else null } + val radiusSquared by lazy { distance?.let { it * it } } + } val posMessageStrings by ListSetting("Pos Messages", mutableListOf<PosMessage>()) - private val sentMessages = mutableMapOf<PosMessage, Boolean>() + private val sentMessages = mutableSetOf<PosMessage>() init { onSend<ServerboundMovePlayerPacket> { - posMessageSend() + if (onlyDungeons && !DungeonUtils.inBoss) return@onSend + posMessageStrings.forEach { posMessage -> + if (posMessage.send && posMessage !in sentMessages) posMessage.x2?.let { handleInString(posMessage) } ?: handleAtString(posMessage) + } } on<RenderEvent.Extract> { - if (!showPositions || (onlyDungeons && !DungeonUtils.inDungeons)) return@on - val player = mc.player ?: return@on + if (!showPositions || (onlyDungeons && !DungeonUtils.inBoss)) return@on posMessageStrings.forEach { posMessage -> - val distanceToMessage = if (posMessage.distance != null) - player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) - else { - val centerX = (posMessage.x + (posMessage.x2 ?: posMessage.x)) / 2 - val centerY = (posMessage.y + (posMessage.y2 ?: posMessage.y)) / 2 - val centerZ = (posMessage.z + (posMessage.z2 ?: posMessage.z)) / 2 - player.distanceToSqr(centerX, centerY, centerZ) - } - if (distanceToMessage > 1024) return@forEach - if (posMessage.distance != null) { - drawCylinder(Vec3(posMessage.x, posMessage.y, posMessage.z), posMessage.distance.toFloat(), cylinderHeight.toFloat(), color = posMessage.color, depth = depthCheck) - if (displayMessage) posMessage.message?.let { drawText(it, Vec3(posMessage.x, posMessage.y + 1, posMessage.z), messageSize, depthCheck) } + drawCylinder(posMessage.center, posMessage.distance.toFloat(), cylinderHeight, color = posMessage.color, depth = true) + if (displayMessage) drawText(posMessage.message, Vec3(posMessage.x, posMessage.y + 1, posMessage.z), messageSize, true) } else { - val box = AABB(posMessage.x, posMessage.y, posMessage.z, posMessage.x2 ?: return@forEach, posMessage.y2 ?: return@forEach,posMessage.z2 ?: return@forEach) - drawWireFrameBox(box, posMessage.color, depth = depthCheck) - if (!displayMessage) return@forEach - val center = Vec3((posMessage.x + posMessage.x2) / 2, (posMessage.y + posMessage.y2) / 2, (posMessage.z + posMessage.z2) / 2) - posMessage.message?.let { drawText(it, center.add(0.0, 1.0, 0.0), messageSize, depthCheck) } + drawWireFrameBox(posMessage.box ?: return@forEach, posMessage.color, depth = true) + if (displayMessage) drawText(posMessage.message, posMessage.center.add(0.0, 1.0, 0.0), messageSize, true) } } } on<LevelEvent.Load> { - if (oncePerWorld) sentMessages.forEach { (message) -> sentMessages[message] = false } - } - } - - private fun posMessageSend() { - if (onlyDungeons && !DungeonUtils.inDungeons) return - posMessageStrings.forEach { message -> - message.x2?.let { handleInString(message) } ?: handleAtString(message) + sentMessages.clear() } } private fun handleAtString(posMessage: PosMessage) { - val msgSent = sentMessages.getOrDefault(posMessage, false) val player = mc.player ?: return - val radius = posMessage.distance ?: return - if (player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= radius * radius) { - if (!msgSent && player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= radius * radius) - schedule(posMessage.delay) { sendCommand("pc ${posMessage.message}") } - sentMessages[posMessage] = true - } else if (!oncePerWorld) sentMessages[posMessage] = false + val radiusSquared = posMessage.radiusSquared ?: return + if (player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) > radiusSquared) return + sentMessages.add(posMessage) + schedule(posMessage.delay) { sendCommand("pc ${posMessage.message}") } } private fun handleInString(posMessage: PosMessage) { + val aabb = posMessage.box ?: return val position = mc.player?.position() ?: return - val msgSent = sentMessages.getOrDefault(posMessage, false) - val aabb = AABB(posMessage.x, posMessage.y, posMessage.z, posMessage.x2 ?: return, posMessage.y2 ?: return, posMessage.z2 ?: return) - if (aabb.contains(position)) { - if (!msgSent && aabb.contains(position)) - schedule(posMessage.delay) { sendCommand("pc ${posMessage.message}") } - sentMessages[posMessage] = true - } else if (!oncePerWorld) sentMessages[posMessage] = false + if (!aabb.contains(position)) return + sentMessages.add(posMessage) + schedule(posMessage.delay) { sendCommand("pc ${posMessage.message}") } } } \ No newline at end of file @@ -214,7 +214,7 @@ object ChatCommands : Module( } private fun findPartyMember(partialName: String): String = - PartyUtils.members.find { it.contains(partialName, true) } ?: partialName + PartyUtils.members.find { it.startsWith(partialName, true) } ?: partialName private fun channelMessage(message: Any, name: String, channel: ChatChannel) { when (channel) { @@ -0,0 +1,50 @@ +package com.odtheking.odin.features.impl.skyblock + +import com.odtheking.odin.clickgui.settings.impl.BooleanSetting +import com.odtheking.odin.clickgui.settings.impl.ColorSetting +import com.odtheking.odin.events.SetSlotEvent +import com.odtheking.odin.events.core.on +import com.odtheking.odin.features.Module +import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.lore +import com.odtheking.odin.utils.render.textDim +import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.Style +import net.minecraft.network.chat.TextColor +import net.minecraft.world.item.Items + +object QuiverDisplay : Module( + name = "Quiver Display", + description = "Displays the number of arrows in your quiver." +) { + private val showName by BooleanSetting("Show Name", true, desc = "Shows the arrow type's name before the count.") + private val countColor by ColorSetting("Count Color", Colors.MINECRAFT_GREEN, true, desc = "The color of the arrow count.") + + private val quiverHud by HUD("Quiver HUD", "Displays the number of arrows in your quiver.", false) { example -> + val text = when { + example -> buildText(Component.literal("Flint Arrow"), "100") + arrows != null -> arrows?.let { buildText(it.first, it.second) } + else -> null + } ?: return@HUD 0 to 0 + return@HUD textDim(text, 0, 0) + } + + private val arrowRegex = Regex("^Arrows Remaining: ([\\d,]+)$") + private var arrows: Pair<Component, String>? = null + + private fun buildText(name: Component, count: String) = + (if (showName) name.copy().append("§8x") else Component.literal("")) + .append(Component.literal(count).withStyle(Style.EMPTY.withColor(TextColor.fromRgb(countColor.rgba and 0xFFFFFF)))) + .visualOrderText + + init { + on<SetSlotEvent> { + if (slotIndex != (if (DungeonUtils.inClear) 9 else 44) || (itemStack.item != Items.FEATHER && itemStack.item != Items.ARROW)) return@on + + arrows = itemStack.hoverName to (itemStack.lore.find { arrowRegex.matches(it.string) }?.let { + arrowRegex.find(it.string)?.groupValues?.get(1) + } ?: return@on) + } + } +} \ No newline at end of file @@ -3,6 +3,7 @@ package com.odtheking.odin.utils.network import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonObject +import com.odtheking.odin.OdinMod import com.odtheking.odin.OdinMod.logger import kotlinx.coroutines.suspendCancellableCoroutine import java.io.InputStream @@ -55,6 +56,7 @@ object WebUtils { .uri(URI.create(url)) .header("Accept", "application/json") .header("User-Agent", USER_AGENT) + .header("X-Mod-Version", OdinMod.version.friendlyString) .GET() .timeout(Duration.ofSeconds(10)) .build() @@ -79,7 +81,10 @@ object WebUtils { if (!cont.isActive) return@whenComplete if (response.statusCode() in 200..299) cont.resume(Result.success(response)) - else cont.resume(Result.failure(InputStreamException(response.body(), request.uri().toString()))) + else { + cont.resume(Result.failure(InputStreamException(response.body(), request.uri().toString()))) + logger.error("Request to ${request.uri()} failed with status code ${response.statusCode()}: $response") + } } } } @@ -14,13 +14,18 @@ fun GuiGraphicsExtractor.text(text: String, x: Int, y: Int, color: Color = Color text(mc.font, text, x, y, color.rgba, shadow) } +fun GuiGraphicsExtractor.text(text: FormattedCharSequence, x: Int, y: Int, color: Color = Colors.WHITE, shadow: Boolean = true) { + text(mc.font, text, x, y, color.rgba, shadow) +} + fun GuiGraphicsExtractor.textDim(text: String, x: Int, y: Int, color: Color = Colors.WHITE, shadow: Boolean = true): Pair<Int, Int> { text(text, x, y, color, shadow) return mc.font.width(text) to mc.font.lineHeight } -fun GuiGraphicsExtractor.text(text: FormattedCharSequence, x: Int, y: Int, color: Color = Colors.WHITE, shadow: Boolean = true) { - text(mc.font, text, x, y, color.rgba, shadow) +fun GuiGraphicsExtractor.textDim(text: FormattedCharSequence, x: Int, y: Int, color: Color = Colors.WHITE, shadow: Boolean = true): Pair<Int, Int> { + text(text, x, y, color, shadow) + return mc.font.width(text) to mc.font.lineHeight } fun getStringWidth(text: String): Int = mc.font.width(text) |
Moved some events from network thread to main thread查看变更src/main/java/com/odtheking/mixin/mixins/ChatListenerMixin.java | 17 --------------
src/main/java/com/odtheking/mixin/mixins/EntityMixin.java | 28 ++++++++++++++++++++++++
src/main/java/com/odtheking/mixin/mixins/LivingEntityMixin.java | 20 +++++++++++++++++
src/main/java/com/odtheking/mixin/mixins/SynchedEntityDataMixin.java | 27 +++++++++++++++++++++++
src/main/kotlin/com/odtheking/odin/clickgui/HudManager.kt | 14 ++++++------
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/KeybindSetting.kt | 13 +++--------
src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/NumberSetting.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/commands/DevCommand.kt | 15 ++-----------
src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 27 ++++++++++++++++++-----
src/main/kotlin/com/odtheking/odin/events/EventDispatcher.kt | 47 +++++++++++++++++++++------------------
src/main/kotlin/com/odtheking/odin/features/Module.kt | 4 ++--
src/main/kotlin/com/odtheking/odin/features/impl/boss/ArrowAlign.kt | 4 ++--
src/main/kotlin/com/odtheking/odin/features/impl/boss/ArrowsDevice.kt | 17 +++++---------
src/main/kotlin/com/odtheking/odin/features/impl/boss/DragonCheck.kt | 26 +++++++++-------------
src/main/kotlin/com/odtheking/odin/features/impl/boss/ExtraStats.kt | 27 +++++++++++------------
src/main/kotlin/com/odtheking/odin/features/impl/boss/InactiveWaypoints.kt | 14 ++++++------
src/main/kotlin/com/odtheking/odin/features/impl/boss/KingRelics.kt | 31 ++++++++++----------------
src/main/kotlin/com/odtheking/odin/features/impl/boss/LividSolver.kt | 12 +++++-----
src/main/kotlin/com/odtheking/odin/features/impl/boss/MelodyMessage.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/boss/SimonSays.kt | 4 ++--
src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 4 ++--
src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSounds.kt | 8 +++----
src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalTimes.kt | 17 +++++++-------
src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 18 +++++++--------
src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragons.kt | 13 +++++------
src/main/kotlin/com/odtheking/odin/features/impl/boss/termGUI/CustomTermGui.kt | 4 ++--
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BetterPartyFinder.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BloodCamp.kt | 62 +++++++++++++++++++++-------------------------------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DoorHighlight.kt | 25 ++++++++++-----------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DungeonQueue.kt | 10 ++++-----
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Highlight.kt | 5 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/InvincibilityTimer.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/LeapMenu.kt | 29 ++++++++++++------------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Mimic.kt | 61 ++++++++++++++++++---------------------------------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/SecretClicked.kt | 12 +++++-----
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/SecretsCounter.kt | 41 ++++++++++++++--------------------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypointEditor.kt | 4 ++--
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypoints.kt | 7 +++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/TextPromtScreen.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/WorldScan.kt | 14 ++++++------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/RoomData.kt | 3 ++-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BeamsSolver.kt | 5 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BoulderSolver.kt | 9 ++++----
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/IceFillSolver.kt | 11 ++++------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 16 +++++---------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/QuizSolver.kt | 5 ++---
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/WaterSolver.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/nether/FreshTools.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/nether/Misc.kt | 8 +++----
src/main/kotlin/com/odtheking/odin/features/impl/nether/NoPre.kt | 12 +++++-----
src/main/kotlin/com/odtheking/odin/features/impl/nether/SupplyHelper.kt | 10 ++++-----
src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt | 5 ++---
src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 10 ++++-----
src/main/kotlin/com/odtheking/odin/features/impl/render/GyroWand.kt | 9 +++-----
src/main/kotlin/com/odtheking/odin/features/impl/render/HidePlayers.kt | 12 ++++++----
src/main/kotlin/com/odtheking/odin/features/impl/render/RenderOptimizer.kt | 42 +++++++++++++++--------------------
src/main/kotlin/com/odtheking/odin/features/impl/render/Waypoints.kt | 12 +++++-----
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/ChatCommands.kt | 8 +++----
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/CommandKeybinds.kt | 18 +++++++--------
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/LoadoutKeybinds.kt | 30 ++++++++++++-------------
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PetKeybinds.kt | 26 +++++++++++-----------
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PlayerDisplay.kt | 29 ++++++++++++------------
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/Ragnarock.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/SlotBinds.kt | 4 ++--
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/WardrobeKeybinds.kt | 26 +++++++++++-----------
src/main/kotlin/com/odtheking/odin/utils/JsonResourceLoader.kt | 13 +++++------
src/main/kotlin/com/odtheking/odin/utils/skyblock/ActionBarListener.kt | 21 +++++++-----------
src/main/kotlin/com/odtheking/odin/utils/skyblock/KuudraUtils.kt | 10 ++++-----
src/main/kotlin/com/odtheking/odin/utils/skyblock/LocationUtils.kt | 3 +--
src/main/kotlin/com/odtheking/odin/utils/skyblock/PartyUtils.kt | 40 ++++++++++++++++-----------------
src/main/kotlin/com/odtheking/odin/utils/skyblock/SplitsManager.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 25 +++++++++++----------
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonUtils.kt | 26 ++++++----------------
src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/TerminalUtils.kt | 4 ++--
.../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/TerminalHandler.kt | 6 ++---
src/main/kotlin/com/odtheking/odin/utils/ui/TextInputHandler.kt | 36 ++++++++++++++----------------
src/main/resources/assets/odin/puzzles/{boulderSolutions.json => boulder-solutions.json} | 0
src/main/resources/assets/odin/puzzles/{creeperBeamsSolutions.json => creeper-beams-solutions.json} | 0
src/main/resources/assets/odin/puzzles/{iceFillFloors.json => ice-fill-floors.json} | 0
src/main/resources/assets/odin/puzzles/{quizAnswers.json => quiz-answers.json} | 0
src/main/resources/assets/odin/puzzles/{waterSolutions.json => water-solutions.json} | 0
src/main/resources/odin.mixins.json | 3 ++-
84 files changed, 599 insertions(+), 645 deletions(-)@@ -1,17 +0,0 @@ -package com.odtheking.mixin.mixins; - -import com.odtheking.odin.features.impl.skyblock.PlayerDisplay; -import net.minecraft.client.multiplayer.chat.ChatListener; -import net.minecraft.network.chat.Component; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArg; - -@Mixin(ChatListener.class) -public class ChatListenerMixin { - - @ModifyArg(method = "handleOverlay", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;setOverlayMessage(Lnet/minecraft/network/chat/Component;Z)V"), index = 0) - private Component modifyOverlayMessage(Component component) { - return PlayerDisplay.modifyText(component); - } -} @@ -1,10 +1,16 @@ package com.odtheking.mixin.mixins; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.odtheking.odin.events.EntityEvent; import com.odtheking.odin.features.impl.dungeon.Highlight; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.InterpolationHandler; +import net.minecraft.world.phys.Vec3; 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; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(Entity.class) @@ -17,4 +23,26 @@ public abstract class EntityMixin { Integer color = Highlight.getTeammateColor(self); if (color != null) cir.setReturnValue(color); } + + @WrapOperation( + method = "moveOrInterpolateTo(Ljava/util/Optional;Ljava/util/Optional;Ljava/util/Optional;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/InterpolationHandler;interpolateTo(Lnet/minecraft/world/phys/Vec3;FF)V") + ) + private void onMoveOrInterpolateTo(InterpolationHandler instance, Vec3 position, float yRot, float xRot, Operation<Void> original) { + original.call(instance, position, yRot, xRot); + + Entity entity = (Entity)(Object)this; + new EntityEvent.Move(entity, position, yRot, xRot, entity.onGround()).postAndCatch(); + } + + @Inject(method = "setOnGround", at = @At("TAIL")) + private void onSetOnGround(boolean onGround, CallbackInfo ci) { + Entity entity = (Entity)(Object)this; + new EntityEvent.Move(entity, Vec3.ZERO, 0f, 0f, onGround).postAndCatch(); + } + + @Inject(method = "handleEntityEvent", at = @At("TAIL")) + private void onHandleEntityEvent(byte id, CallbackInfo ci) { + new EntityEvent.Event((Entity)(Object)this, id).postAndCatch(); + } } \ No newline at end of file @@ -0,0 +1,20 @@ +package com.odtheking.mixin.mixins; + +import com.odtheking.odin.events.EntityEvent; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +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(LivingEntity.class) +public abstract class LivingEntityMixin { + + @Inject(method = "setItemSlot", at = @At("TAIL")) + private void onSetItemSlot(EquipmentSlot slot, ItemStack itemStack, CallbackInfo ci) { + LivingEntity self = (LivingEntity)(Object)this; + if (self != null) new EntityEvent.SetItemSlot(self, slot, itemStack).postAndCatch(); + } +} \ No newline at end of file @@ -0,0 +1,27 @@ +package com.odtheking.mixin.mixins; + +import com.odtheking.odin.events.EntityEvent; +import net.minecraft.network.syncher.SyncedDataHolder; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Final; +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.callback.CallbackInfo; + +import java.util.List; + +@Mixin(SynchedEntityData.class) +public class SynchedEntityDataMixin { + @Final + @Shadow + private SyncedDataHolder entity; + + @Inject(method = "assignValues", at = @At("TAIL")) + private void onAssignValues(List<SynchedEntityData.DataValue<?>> items, CallbackInfo ci) { + Entity self = (Entity) this.entity; + if (self != null) new EntityEvent.SetData(self, items).postAndCatch(); + } +} \ No newline at end of file @@ -1,5 +1,6 @@ package com.odtheking.odin.clickgui +import com.mojang.blaze3d.platform.InputConstants import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.clickgui.settings.impl.HudElement import com.odtheking.odin.features.ModuleManager @@ -10,7 +11,6 @@ import net.minecraft.client.gui.screens.Screen import net.minecraft.client.input.KeyEvent import net.minecraft.client.input.MouseButtonEvent import net.minecraft.network.chat.Component -import org.lwjgl.glfw.GLFW import kotlin.math.sign import com.odtheking.odin.utils.ui.mouseX as odinMouseX import com.odtheking.odin.utils.ui.mouseY as odinMouseY @@ -92,12 +92,12 @@ object HudManager : Screen(Component.literal("HUD Manager")) { override fun keyPressed(keyEvent: KeyEvent): Boolean { hudSettingsCache.firstOrNull { it.isEnabled && it.value.isHovered() }?.let { hovered -> when (keyEvent.key) { - GLFW.GLFW_KEY_EQUAL -> hovered.value.scale = (hovered.value.scale + 0.1f).coerceIn(1f, 10f) - GLFW.GLFW_KEY_MINUS -> hovered.value.scale = (hovered.value.scale - 0.1f).coerceIn(1f, 10f) - GLFW.GLFW_KEY_RIGHT -> hovered.value.x += 10 - GLFW.GLFW_KEY_LEFT -> hovered.value.x -= 10 - GLFW.GLFW_KEY_UP -> hovered.value.y -= 10 - GLFW.GLFW_KEY_DOWN -> hovered.value.y += 10 + InputConstants.KEY_EQUALS -> hovered.value.scale = (hovered.value.scale + 0.1f).coerceIn(1f, 10f) + InputConstants.KEY_MINUS -> hovered.value.scale = (hovered.value.scale - 0.1f).coerceIn(1f, 10f) + InputConstants.KEY_RIGHT -> hovered.value.x += 10 + InputConstants.KEY_LEFT -> hovered.value.x -= 10 + InputConstants.KEY_UP -> hovered.value.y -= 10 + InputConstants.KEY_DOWN -> hovered.value.y += 10 } } @@ -14,7 +14,6 @@ import com.odtheking.odin.utils.ui.isAreaHovered import com.odtheking.odin.utils.ui.rendering.NVGRenderer import net.minecraft.client.input.KeyEvent import net.minecraft.client.input.MouseButtonEvent -import org.lwjgl.glfw.GLFW class KeybindSetting( name: String, @@ -71,8 +70,8 @@ class KeybindSetting( if (!listening) return false when (input.key) { - GLFW.GLFW_KEY_ESCAPE, GLFW.GLFW_KEY_BACKSPACE -> key = InputConstants.UNKNOWN - GLFW.GLFW_KEY_ENTER -> listening = false + InputConstants.KEY_ESCAPE, InputConstants.KEY_BACKSPACE -> key = InputConstants.UNKNOWN + InputConstants.KEY_RETURN -> listening = false else -> key = InputConstants.getKey(input) } @@ -85,8 +84,6 @@ class KeybindSetting( return this } - - override val isHovered: Boolean get() = isAreaHovered(lastX + width - 20 - keyNameWidth, lastY + getHeight() / 2f - 10f, keyNameWidth + 12f, 22f, true) @@ -102,10 +99,6 @@ class KeybindSetting( } companion object { - fun InputConstants.Key.isDown(): Boolean { - val window = mc.window - return if (value > 7) InputConstants.isKeyDown(window, value) - else GLFW.glfwGetMouseButton(window.handle(), value) == GLFW.GLFW_PRESS - } + fun InputConstants.Key.isDown(): Boolean = InputConstants.isKeyDown(mc.window, value) } } @@ -3,6 +3,7 @@ package com.odtheking.odin.clickgui.settings.impl import com.google.gson.Gson import com.google.gson.JsonElement import com.google.gson.JsonPrimitive +import com.mojang.blaze3d.platform.InputConstants import com.odtheking.odin.clickgui.ClickGUI.gray38 import com.odtheking.odin.clickgui.Panel import com.odtheking.odin.clickgui.settings.RenderableSetting @@ -15,7 +16,6 @@ import com.odtheking.odin.utils.ui.isAreaHovered import com.odtheking.odin.utils.ui.rendering.NVGRenderer import net.minecraft.client.input.KeyEvent import net.minecraft.client.input.MouseButtonEvent -import org.lwjgl.glfw.GLFW import kotlin.math.floor import kotlin.math.round import kotlin.math.roundToInt @@ -136,8 +136,8 @@ class NumberSetting<E>( if (!isHovered) return false val amount = when (input.key) { - GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_EQUAL -> incrementDouble - GLFW.GLFW_KEY_LEFT, GLFW.GLFW_KEY_MINUS -> -incrementDouble + InputConstants.KEY_RIGHT, InputConstants.KEY_EQUALS -> incrementDouble + InputConstants.KEY_LEFT, InputConstants.KEY_MINUS -> -incrementDouble else -> return false } @@ -4,7 +4,7 @@ import com.github.stivais.commodore.Commodore import com.github.stivais.commodore.utils.GreedyString import com.odtheking.odin.OdinMod import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.events.ChatMessageEvent +import com.odtheking.odin.events.MessageEvent import com.odtheking.odin.features.ModuleManager import com.odtheking.odin.features.impl.boss.MelodyMessage.melodyWebSocket import com.odtheking.odin.features.impl.boss.WitherDragonState @@ -16,10 +16,7 @@ import com.odtheking.odin.features.impl.dungeon.map.WorldScan import com.odtheking.odin.features.impl.nether.NoPre import com.odtheking.odin.features.impl.render.ClickGUIModule.webSocketUrl import com.odtheking.odin.features.impl.render.PlayerSize -import com.odtheking.odin.features.impl.render.PlayerSize.DEV_SERVER -import com.odtheking.odin.features.impl.render.PlayerSize.buildDevBody import com.odtheking.odin.utils.* -import com.odtheking.odin.utils.network.WebUtils.postData import com.odtheking.odin.utils.skyblock.KuudraUtils import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.skyblock.PartyUtils @@ -53,7 +50,7 @@ val devCommand = Commodore("oddev") { } literal("simulate").runs { greedyString: GreedyString -> - ChatMessageEvent(greedyString.string, Component.literal(greedyString.string)).postAndCatch() + MessageEvent.Chat(greedyString.string, Component.literal(greedyString.string)).postAndCatch() modMessage("§8Simulated message: ${greedyString.string}") } @@ -67,14 +64,6 @@ val devCommand = Commodore("oddev") { PlayerSize.clearCustomProperties() } - literal("adddev").runs { name: String, password: String, xSize: Float?, ySize: Float?, zSize: Float? -> - val devBody = buildDevBody(name, xSize ?: 0.6f, ySize ?: 0.6f, zSize ?: 0.6f, " ", password) - modMessage("Sending $devBody") - OdinMod.scope.launch { - modMessage(postData(DEV_SERVER, devBody).getOrNull()) - } - } - literal("generatefeaturelist").runs { fun generateFeatureList(): String { val featureList = StringBuilder() @@ -15,10 +15,12 @@ import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component import net.minecraft.network.protocol.Packet import net.minecraft.network.protocol.game.ClientboundSoundPacket +import net.minecraft.network.syncher.SynchedEntityData import net.minecraft.world.BossEvent import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionResult import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.EquipmentSlot import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.Slot @@ -35,9 +37,14 @@ class BlockInteractEvent(val pos: BlockPos) : CancellableEvent() class EntityInteractEvent(val pos: Vec3, val entity: Entity) : CancellableEvent() class UseItemOnPostEvent(val hand: InteractionHand, val hitResult: BlockHitResult, val interactionResult: InteractionResult) : Event -class ChatMessageEvent(val value: String, val component: Component) : CancellableEvent() -class MessageSentEvent(val message: String) : CancellableEvent() +open class MessageEvent(val message: String, open val component: Component) : CancellableEvent() { + class Chat(value: String, component: Component) : MessageEvent(value, component) + class Overlay(value: String, component: Component) : MessageEvent(value, component) + class ModifyChat(value: String, override var component: Component) : MessageEvent(value, component) + class ModifyOverlay(value: String, override var component: Component) : MessageEvent(value, component) +} +class MessageSentEvent(val message: String) : CancellableEvent() class RenderBossBarEvent(val bossBar: BossEvent) : CancellableEvent() interface SecretPickupEvent : Event { // all are currently packet based but can probably use mixins @@ -46,7 +53,7 @@ interface SecretPickupEvent : Event { // all are currently packet based but can class Bat(val packet: ClientboundSoundPacket) : SecretPickupEvent } -abstract class TerminalEvent(val terminal: TerminalHandler) : Event { // first 2 are packet based can use mixins +abstract class TerminalEvent(val terminal: TerminalHandler) : Event { class Open(terminal: TerminalHandler) : TerminalEvent(terminal) class Close(terminal: TerminalHandler) : TerminalEvent(terminal) class Solve(terminal: TerminalHandler) : TerminalEvent(terminal) @@ -54,8 +61,7 @@ abstract class TerminalEvent(val terminal: TerminalHandler) : Event { // first 2 } interface TickEvent : Event { - class Start(val world: ClientLevel) : TickEvent - class End(val world: ClientLevel) : TickEvent + class End(val level: ClientLevel) : TickEvent object Server : TickEvent } @@ -87,4 +93,13 @@ class SecretsUpdateEvent(val room: DungeonRoom, val foundSecrets: Int) : Event object LocationChangeEvent : Event object ScreenCloseEvent : Event -class SetSlotEvent(val slotIndex: Int, val itemStack: ItemStack, val slots: List<Slot>, val menu: AbstractContainerMenu) : Event \ No newline at end of file +class SetSlotEvent(val slotIndex: Int, val itemStack: ItemStack, val slots: List<Slot>, val menu: AbstractContainerMenu) : Event + +abstract class EntityEvent(val entity: Entity) : Event { + class Add(entity: Entity) : EntityEvent(entity) + class Remove(entity: Entity) : EntityEvent(entity) + class Move(entity: Entity, val newPos: Vec3, val yRot: Float, val xRot: Float, val onGround: Boolean) : EntityEvent(entity) + class SetItemSlot(entity: Entity, val slot: EquipmentSlot, val stack: ItemStack) : EntityEvent(entity) + class SetData(entity: Entity, val synchedDataValues: List<SynchedEntityData.DataValue<*>>) : EntityEvent(entity) + class Event(entity: Entity, val id: Byte) : EntityEvent(entity) +} \ No newline at end of file @@ -1,14 +1,15 @@ package com.odtheking.odin.events import com.odtheking.odin.OdinMod.mc +import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive -import com.odtheking.odin.events.core.onSend import com.odtheking.odin.utils.containsOneOf import com.odtheking.odin.utils.equalsOneOf import com.odtheking.odin.utils.noControlCodes import com.odtheking.odin.utils.render.RenderBatchManager import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils.isSecret +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents @@ -16,12 +17,9 @@ import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents -import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket import net.minecraft.network.protocol.game.ClientboundSoundPacket import net.minecraft.network.protocol.game.ClientboundTakeItemEntityPacket -import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket import net.minecraft.sounds.SoundEvents -import net.minecraft.world.InteractionHand import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.level.block.SkullBlock import net.minecraft.world.phys.Vec3 @@ -32,7 +30,6 @@ object EventDispatcher { ClientPlayConnectionEvents.JOIN.register { _, _, _ -> LevelEvent.Load.postAndCatch() } ClientPlayConnectionEvents.DISCONNECT.register { _, _ -> LevelEvent.Unload.postAndCatch() } - ClientTickEvents.START_LEVEL_TICK.register { world -> TickEvent.Start(world).postAndCatch() } ClientTickEvents.END_LEVEL_TICK.register { world -> TickEvent.End(world).postAndCatch() } LevelRenderEvents.AFTER_TRANSLUCENT_TERRAIN.register { @@ -52,25 +49,33 @@ object EventDispatcher { } } + ClientEntityEvents.ENTITY_LOAD.register { entity, _ -> EntityEvent.Add(entity).postAndCatch() } + ClientEntityEvents.ENTITY_UNLOAD.register { entity, _ -> EntityEvent.Remove(entity).postAndCatch() } + + ClientReceiveMessageEvents.MODIFY_GAME.register { message, overlay -> + if (overlay) MessageEvent.ModifyOverlay(message.string.noControlCodes, message).apply { postAndCatch() }.component + else MessageEvent.ModifyChat(message.string.noControlCodes, message).apply { postAndCatch() }.component + } + ClientReceiveMessageEvents.ALLOW_GAME.register { message, overlay -> - if (overlay) return@register true - !ChatMessageEvent(message.string.noControlCodes, message).postAndCatch() + if (overlay) !MessageEvent.Overlay(message.string.noControlCodes, message).postAndCatch() + else !MessageEvent.Chat(message.string.noControlCodes, message).postAndCatch() } onReceive<ClientboundTakeItemEntityPacket> { - if (mc.player == null || !DungeonUtils.inClear) return@onReceive + if (!DungeonUtils.inClear) return@onReceive val itemEntity = mc.level?.getEntity(itemId) as? ItemEntity ?: return@onReceive if (itemEntity.item.hoverName.string.containsOneOf(dungeonItemDrops, true) && itemEntity.distanceTo(mc.player ?: return@onReceive) <= 6) SecretPickupEvent.Item(itemEntity).postAndCatch() } - onReceive<ClientboundRemoveEntitiesPacket> { - if (mc.player == null || !DungeonUtils.inClear) return@onReceive - entityIds.forEach { id -> - val entity = mc.level?.getEntity(id) as? ItemEntity ?: return@forEach - if (entity.item.hoverName.string.containsOneOf(dungeonItemDrops, true) && entity.distanceTo(mc.player ?: return@onReceive) <= 6) - SecretPickupEvent.Item(entity).postAndCatch() - } + on<EntityEvent.Remove> { + if (!DungeonUtils.inClear) return@on + val entity = entity as? ItemEntity ?: return@on + if ( + entity.item.hoverName.string.containsOneOf(dungeonItemDrops, true) && + entity.distanceTo(mc.player ?: return@on) <= 6 + ) SecretPickupEvent.Item(entity).postAndCatch() } onReceive<ClientboundSoundPacket> { @@ -79,15 +84,15 @@ object EventDispatcher { SecretPickupEvent.Bat(this).postAndCatch() } - onSend<ServerboundUseItemOnPacket> { - if (!DungeonUtils.inDungeons || hand == InteractionHand.OFF_HAND) return@onSend - val blockState = mc.level?.getBlockState(hitResult.blockPos) ?: return@onSend + on<BlockInteractEvent> { + if (!DungeonUtils.inDungeons) return@on + val blockState = mc.level?.getBlockState(pos) ?: return@on if (blockState.block is SkullBlock) { - val distance = mc.player?.eyePosition?.distanceToSqr(Vec3(hitResult.blockPos)) ?: return@onSend - if (distance > 20.25) return@onSend + val distance = mc.player?.eyePosition?.distanceToSqr(Vec3(pos)) ?: return@on + if (distance > 20.25) return@on } - if (isSecret(blockState, hitResult.blockPos)) SecretPickupEvent.Interact(hitResult.blockPos, blockState).postAndCatch() + if (isSecret(blockState, pos)) SecretPickupEvent.Interact(pos, blockState).postAndCatch() } } @@ -1,5 +1,6 @@ package com.odtheking.odin.features +import com.mojang.blaze3d.platform.InputConstants import com.odtheking.odin.OdinMod import com.odtheking.odin.clickgui.settings.AlwaysActive import com.odtheking.odin.clickgui.settings.DevModule @@ -9,7 +10,6 @@ import com.odtheking.odin.events.core.EventBus import com.odtheking.odin.features.impl.render.ClickGUIModule import com.odtheking.odin.utils.modMessage import net.minecraft.client.gui.GuiGraphicsExtractor -import org.lwjgl.glfw.GLFW import kotlin.reflect.full.hasAnnotation /** @@ -18,7 +18,7 @@ import kotlin.reflect.full.hasAnnotation */ abstract class Module( val name: String, - val key: Int? = GLFW.GLFW_KEY_UNKNOWN, + val key: Int? = InputConstants.UNKNOWN.value, category: Category? = null, @Transient var description: String, toggled: Boolean = false, @@ -1,5 +1,6 @@ package com.odtheking.odin.features.impl.boss +import com.mojang.blaze3d.platform.InputConstants import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.KeybindSetting @@ -19,14 +20,13 @@ import com.odtheking.odin.utils.skyblock.dungeon.M7Phases import net.minecraft.core.BlockPos import net.minecraft.world.entity.decoration.ItemFrame import net.minecraft.world.item.Items -import org.lwjgl.glfw.GLFW object ArrowAlign : Module( name = "Arrow Align", description = "Shows the solution for the Arrow Align device." ) { private val blockWrong by BooleanSetting("Block Wrong Clicks", true, desc = "Blocks wrong clicks, shift will override this.") - private val preventKey by KeybindSetting("Prevent Blocking", GLFW.GLFW_KEY_LEFT_SHIFT, desc = "While holding this key, wrong clicks won't be blocked, even if you aren't sneaking.").withDependency { blockWrong } + private val preventKey by KeybindSetting("Prevent Blocking", InputConstants.KEY_LSHIFT, desc = "While holding this key, wrong clicks won't be blocked, even if you aren't sneaking.").withDependency { blockWrong } private val invertKey by BooleanSetting("Invert Key", false, desc = "Inverts the behavior of the prevent key. Wrong clicks will be blocked while holding the prevent key, and allowed otherwise.").withDependency { blockWrong } private val recentClickTimestamps = mutableMapOf<Int, Long>() @@ -4,12 +4,8 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.ActionSetting import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.events.BlockUpdateEvent -import com.odtheking.odin.events.ChatMessageEvent -import com.odtheking.odin.events.LevelEvent -import com.odtheking.odin.events.RenderEvent +import com.odtheking.odin.events.* import com.odtheking.odin.events.core.on -import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors @@ -19,7 +15,6 @@ import com.odtheking.odin.utils.render.drawFilledBox import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.skyblock.dungeon.M7Phases import net.minecraft.core.BlockPos -import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket import net.minecraft.world.level.block.Blocks import net.minecraft.world.phys.AABB import net.minecraft.world.phys.Vec3 @@ -102,14 +97,14 @@ object ArrowsDevice : Module( optimalAimPositions = emptyList() } - on<ChatMessageEvent> { + on<MessageEvent.Chat> { if (DungeonUtils.getF7Phase() != M7Phases.P3 || !isPlayerInRoom || isDeviceComplete) return@on - if (deviceCompleteRegex.find(value)?.groupValues?.get(1) == mc.player?.name?.string) onComplete("Chat") + if (deviceCompleteRegex.find(message)?.groupValues?.get(1) == mc.player?.name?.string) onComplete("Chat") } - onReceive<ClientboundSetEntityDataPacket> { - if (DungeonUtils.getF7Phase() != M7Phases.P3 || !isPlayerInRoom || isDeviceComplete) return@onReceive - if (mc.level?.getEntity(id)?.name?.string == "Active") onComplete("Entity") + on<EntityEvent.SetData> { + if (DungeonUtils.getF7Phase() != M7Phases.P3 || !isPlayerInRoom || isDeviceComplete) return@on + if (entity.name.string == "Active") onComplete("Entity") } } @@ -1,15 +1,12 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.OdinMod.mc +import com.odtheking.odin.events.EntityEvent import com.odtheking.odin.utils.modMessage -import net.minecraft.network.protocol.game.ClientboundAddEntityPacket -import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket -import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket import net.minecraft.world.entity.EntityType import net.minecraft.world.entity.boss.enderdragon.EnderDragon import net.minecraft.world.entity.decoration.ArmorStand import net.minecraft.world.item.Items -import net.minecraft.world.phys.Vec3 import java.util.* object DragonCheck { @@ -17,9 +14,9 @@ object DragonCheck { val dragonHealthMap = mutableMapOf<UUID, Pair<EnderDragon, Float>>() var lastDragonDeath: WitherDragonsEnum? = null - fun dragonUpdate(packet: ClientboundSetEntityDataPacket) { - val entity = mc.level?.getEntity(packet.id) as? EnderDragon ?: return - val dragonHealth = (packet.packedItems.find { it.id == 9 }?.value as? Float) ?: return + fun dragonUpdate(event: EntityEvent.SetData) { + val entity = event.entity as? EnderDragon ?: return + val dragonHealth = (event.synchedDataValues.find { it.id == 9 }?.value as? Float) ?: return dragonHealthMap[entity.uuid] = Pair(entity, dragonHealth) WitherDragonsEnum.entries.firstOrNull { it.entityUUID == entity.uuid }?.let { @@ -27,17 +24,14 @@ object DragonCheck { } } - fun dragonSpawn(packet: ClientboundAddEntityPacket) { - if (packet.type == EntityType.ENDER_DRAGON) - WitherDragonsEnum.entries.find { - it.aabbDimensions.contains(Vec3(packet.x, packet.y, packet.z)) - }?.setAlive(packet.uuid) + fun dragonSpawn(event: EntityEvent.Add) { + if (event.entity.type == EntityType.ENDER_DRAGON) + WitherDragonsEnum.entries.find { it.aabbDimensions.contains(event.entity.position()) }?.setAlive(event.entity.uuid) } - fun dragonSprayed(packet: ClientboundSetEquipmentPacket) { - if (packet.slots.none { it.second.item == Items.PACKED_ICE }) return - - val sprayedEntity = mc.level?.getEntity(packet.entity) as? ArmorStand ?: return + fun dragonSprayed(event: EntityEvent.SetItemSlot) { + if (event.stack.item == Items.PACKED_ICE ) return + val sprayedEntity = event.entity as? ArmorStand ?: return WitherDragonsEnum.entries.forEach { dragon -> val entity = mc.level?.getEntity(dragon.entityUUID ?: return@forEach) as? EnderDragon ?: return@forEach @@ -2,8 +2,8 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting -import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.MessageEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.* @@ -55,64 +55,63 @@ object ExtraStats : Module( ) init { - on<ChatMessageEvent> { + on<MessageEvent.Chat> { if (!DungeonUtils.inDungeons) return@on - if (cancelRegexes.any { it.matches(value) }) cancel() + if (extraStatsRegex.matches(message)) return@on sendCommand("showextrastats") + if (cancelRegexes.any { it.matches(message) }) cancel() - if (extraStatsRegex.matches(value)) return@on sendCommand("showextrastats") - - terminalCompleteRegex.find(value)?.let { + terminalCompleteRegex.find(message)?.let { extraStats.timePB = it.groupValues[3].isNotEmpty() extraStats.bossKilled = it.groupValues[1] return@on } - teamScoreRegex.find(value)?.let { + teamScoreRegex.find(message)?.let { extraStats.score = it.groupValues[1].toIntOrNull() ?: 0 extraStats.scorePB = it.groupValues[3].isNotEmpty() extraStats.scoreLetter = it.groupValues[2] return@on } - expRegex.find(value)?.let { + expRegex.find(message)?.let { extraStats.xp.add("§3${it.groupValues[1].replace("Experience", "EXP").replace("Catacombs", "Cata")}") return@on } - bitsRegex.find(value)?.let { + bitsRegex.find(message)?.let { extraStats.bits = it.groupValues[1] return@on } - damageRegex.find(value)?.let { + damageRegex.find(message)?.let { extraStats.damagePB = it.groupValues[3].isNotEmpty() extraStats.damage = formatNumber(it.groupValues[2]) + if (extraStats.damagePB) "(NEW PB!)" else "" extraStats.combatStats.add("§e${it.groupValues[1]}") return@on } - healRegex.find(value)?.let { + healRegex.find(message)?.let { extraStats.healPB = it.groupValues[3].isNotEmpty() extraStats.heal = formatNumber(it.groupValues[2]) + if (extraStats.healPB) "(NEW PB!)" else "" extraStats.combatStats.add("§a${it.groupValues[1]}") return@on } - enemyKillRegex.find(value)?.let { + enemyKillRegex.find(message)?.let { extraStats.enemyKillPB = it.groupValues[3].isNotEmpty() extraStats.enemyKill = (it.groupValues[2].toIntOrNull() ?: 0).toString() + if (extraStats.enemyKillPB) "(NEW PB!)" else "" extraStats.combatStats.add(1, "§b${it.groupValues[1]}") return@on } - deathsRegex.find(value)?.let { + deathsRegex.find(message)?.let { extraStats.deaths = it.groupValues[2].toIntOrNull() ?: 0 if (teamStats.equalsOneOf(1, 3)) extraStats.skillStats.add("§c${it.groupValues[1]}") return@on } - secretsRegex.find(value)?.let { + secretsRegex.find(message)?.let { extraStats.secretsFound = it.groupValues[2].toIntOrNull() ?: 0 if (teamStats.equalsOneOf(1, 3)) extraStats.skillStats.add(0, "§b${it.groupValues[1]}") if (teamStats == 3) extraStats.skillStats.add("") @@ -2,8 +2,8 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.MessageEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -67,12 +67,12 @@ object InactiveWaypoints : Module( }?.toSet().orEmpty() } - on<ChatMessageEvent> { + on<MessageEvent.Chat> { if (!DungeonUtils.inBoss) return@on when { - completedRegex.matches(value) -> { - val it = completedRegex.find(value) ?: return@on + completedRegex.matches(message) -> { + val it = completedRegex.find(message) ?: return@on val completed = (it.groupValues[4].toIntOrNull() ?: 0).apply { if (this == 1) firstInSection = true } if (completed == (it.groupValues[5].toIntOrNull() ?: 0)) { @@ -88,18 +88,18 @@ object InactiveWaypoints : Module( lastCompleted = completed } - gateRegex.matches(value) -> { + gateRegex.matches(message) -> { gate = true if (isComplete) newSection() } - goldorRegex.matches(value) -> { + goldorRegex.matches(message) -> { shouldRender = true resetState() section = 1 } - coreOpeningRegex.matches(value) -> { + coreOpeningRegex.matches(message) -> { shouldRender = false resetState() } @@ -2,13 +2,8 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting -import com.odtheking.odin.events.ChatMessageEvent -import com.odtheking.odin.events.LevelEvent -import com.odtheking.odin.events.RenderEvent -import com.odtheking.odin.events.TickEvent +import com.odtheking.odin.events.* import com.odtheking.odin.events.core.on -import com.odtheking.odin.events.core.onReceive -import com.odtheking.odin.events.core.onSend import com.odtheking.odin.features.Module import com.odtheking.odin.utils.* import com.odtheking.odin.utils.render.drawCustomBeacon @@ -16,9 +11,6 @@ import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.skyblock.dungeon.M7Phases import net.minecraft.core.BlockPos -import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket -import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket -import net.minecraft.world.InteractionHand import net.minecraft.world.item.Items import net.minecraft.world.level.block.Blocks @@ -47,18 +39,18 @@ object KingRelics : Module( private val relicPBs = PersonalBest(this, "Relics") init { - on<ChatMessageEvent> { - if (DungeonUtils.getF7Phase() != M7Phases.P5 || !relicPickupRegex.matches(value)) return@on + on<MessageEvent.Chat> { + if (DungeonUtils.getF7Phase() != M7Phases.P5 || !relicPickupRegex.matches(message)) return@on relicPlaceTick = serverTickCounter relicTicksToSpawn = relicSpawnTicks hasAnnouncedSpawn = false } - onSend<ServerboundUseItemOnPacket> { - if (DungeonUtils.getF7Phase() != M7Phases.P5 || relicPlaceTick == 0L || hand == InteractionHand.OFF_HAND) return@onSend + on<BlockInteractEvent> { + if (DungeonUtils.getF7Phase() != M7Phases.P5 || relicPlaceTick == 0L) return@on - val block = mc.level?.getBlockState(hitResult.blockPos)?.block - if (!block.equalsOneOf(Blocks.CAULDRON, Blocks.ANVIL)) return@onSend + val block = mc.level?.getBlockState(pos)?.block + if (!block.equalsOneOf(Blocks.CAULDRON, Blocks.ANVIL)) return@on Relic.entries.find { it.id == currentRelic?.id }?.let { relicPBs.time(it.name, (serverTickCounter - relicPlaceTick) / 20f, message = "§${it.colorCode}${it.name} relic §7placed in §6", sendMessage = relicAnnounceTime) @@ -66,12 +58,11 @@ object KingRelics : Module( } } - onReceive<ClientboundSetEquipmentPacket> { - if (DungeonUtils.getF7Phase() != M7Phases.P5 || currentRelic == null) return@onReceive + on<EntityEvent.SetItemSlot> { + if (DungeonUtils.getF7Phase() != M7Phases.P5 || currentRelic == null) return@on - val equipmentSlot = slots.find { it.second.item== Items.PLAYER_HEAD } ?: return@onReceive - - Relic.entries.find { it.id == equipmentSlot.second.itemId }?.let { relic -> + if (stack.item != Items.PLAYER_HEAD) return@on + Relic.entries.find { it.id == stack.itemId }?.let { relic -> if (relicPlaceTick > 0 && !hasAnnouncedSpawn) { modMessage("§${relic.colorCode}${relic.name} relic §7spawned in §6${(serverTickCounter - relicPlaceTick) / 20f}s") hasAnnouncedSpawn = true @@ -3,7 +3,6 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.ColorSetting import com.odtheking.odin.events.* import com.odtheking.odin.events.core.on -import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Colors @@ -13,7 +12,6 @@ import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.renderBoundingBox import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.core.BlockPos -import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket import net.minecraft.world.effect.MobEffects import net.minecraft.world.entity.player.Player import net.minecraft.world.level.block.Block @@ -41,9 +39,9 @@ object LividSolver : Module( private var invulnTime = 0 init { - on<ChatMessageEvent> { + on<MessageEvent.Chat> { if (!DungeonUtils.inDungeons || !DungeonUtils.isFloor(5)) return@on - if (value.matches(lividStartRegex)) invulnTime = 390 + if (message.matches(lividStartRegex)) invulnTime = 390 } on<BlockUpdateEvent> { @@ -52,9 +50,9 @@ object LividSolver : Module( modMessage("Found Livid: §${currentLivid.colorCode}${currentLivid.entityName}") } - onReceive<ClientboundSetEntityDataPacket> { - if (!DungeonUtils.inBoss || !DungeonUtils.isFloor(5)) return@onReceive - currentLivid.entity = (mc.level?.getEntity(id) as? Player)?.takeIf { it.name.string == "${currentLivid.entityName} Livid" } ?: return@onReceive + on<EntityEvent.SetData> { + if (!DungeonUtils.inBoss || !DungeonUtils.isFloor(5)) return@on + currentLivid.entity = (entity as? Player)?.takeIf { it.name.string == "${currentLivid.entityName} Livid" } ?: return@on } on<RenderEvent.Extract> { @@ -4,8 +4,8 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting import com.odtheking.odin.clickgui.settings.impl.StringSetting -import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.MessageEvent import com.odtheking.odin.events.SetSlotEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.core.on @@ -92,8 +92,8 @@ object MelodyMessage : Module( if (melodySendCoords) sendCommand("od sendcoords") } - on<ChatMessageEvent> { - if (broadcast || melodyProgress) onChatMessage(value) + on<MessageEvent.Chat> { + if (broadcast || melodyProgress) onChatMessage(message) } on<SetSlotEvent> { @@ -52,8 +52,8 @@ object SimonSays : Module( startClickCounter = 0 } - on<ChatMessageEvent> { - if (value == "[BOSS] Goldor: Who dares trespass into my domain?") startClickCounter = 0 + on<MessageEvent.Chat> { + if (message == "[BOSS] Goldor: Who dares trespass into my domain?") startClickCounter = 0 } on<BlockUpdateEvent> { @@ -99,11 +99,11 @@ object TerminalSolver : Module( } on<TerminalEvent.Open> { - if (renderType == 0 || renderType == 1) mc.execute { mc.resizeGui() } + if (renderType == 0 || renderType == 1) mc.resizeGui() } on<TerminalEvent.Close> { - if (renderType == 0 || renderType == 1) mc.execute { mc.resizeGui() } + if (renderType == 0 || renderType == 1) mc.resizeGui() } on<TickEvent.Server> { @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatMessageEvent +import com.odtheking.odin.events.MessageEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.core.EventPriority import com.odtheking.odin.events.core.on @@ -46,11 +46,11 @@ object TerminalSounds : Module( it.cancel() } - on<ChatMessageEvent> { + on<MessageEvent.Chat> { if (!DungeonUtils.inDungeons || !shouldReplaceSounds) return@on when { - value.matches(gateRegex) -> playSoundAtPlayer(SoundEvents.NOTE_BLOCK_PLING.value()) - value.matches(coreRegex) -> playSoundAtPlayer(SoundEvents.NOTE_BLOCK_PLING.value()) + message.matches(gateRegex) -> playSoundAtPlayer(SoundEvents.NOTE_BLOCK_PLING.value()) + message.matches(coreRegex) -> playSoundAtPlayer(SoundEvents.NOTE_BLOCK_PLING.value()) } } } @@ -2,8 +2,8 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.ActionSetting import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.MessageEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on @@ -11,6 +11,7 @@ import com.odtheking.odin.features.Module import com.odtheking.odin.features.impl.boss.termsim.TermSimGUI import com.odtheking.odin.utils.PersonalBest import com.odtheking.odin.utils.modMessage +import net.minecraft.network.chat.Component object TerminalTimes : Module( name = "Terminal Times", @@ -44,12 +45,10 @@ object TerminalTimes : Module( pbs.time(terminal.type.name, (System.currentTimeMillis() - terminal.timeOpened) / 1000f, "s§7!", "§a${terminal.type.termName}${if (mc.screen is TermSimGUI) " §7(termsim)" else ""} §7solved in §6", sendMessage = terminalTimes) } - on<ChatMessageEvent> { + on<MessageEvent.ModifyChat> { if (!terminalSplits) return@on - terminalCompleteRegex.find(value)?.destructured?.let { (name, activated, type, current, total) -> - cancel() - - modMessage("§6$name §a$activated a $type! (§c${current}§a/${total}) §8(§7${sectionTimer.seconds}s §8| §7${phaseTimer.seconds}s§8)", "") + terminalCompleteRegex.find(message)?.destructured?.let { (name, activated, type, current, total) -> + component = Component.literal("§6$name §a$activated a $type! (§c${current}§a/${total}) §8(§7${sectionTimer.seconds}s §8| §7${phaseTimer.seconds}s§8)") if ((current == total && gateBlown) || (current.toIntOrNull() ?: return@on) < completed.first) resetSection() else completed = Pair(current.toIntOrNull() ?: return@on, total.toIntOrNull() ?: return@on) @@ -57,11 +56,11 @@ object TerminalTimes : Module( } when { - gateDestroyedRegex.matches(value) -> if (completed.first == completed.second) resetSection() else gateBlown = true + gateDestroyedRegex.matches(message) -> if (completed.first == completed.second) resetSection() else gateBlown = true - goldorRegex.matches(value) -> resetSection(true) + goldorRegex.matches(message) -> resetSection(true) - coreOpeningRegex.matches(value) -> { + coreOpeningRegex.matches(message) -> { resetSection() modMessage("§bTimes: §a${times.joinToString(" §8| ") { "§a${it}s" }}§8, §bTotal: §a${phaseTimer.seconds}s") } @@ -3,8 +3,8 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.HudElement -import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.MessageEvent import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -101,26 +101,26 @@ object TickTimers : Module( } init { - on<ChatMessageEvent> { + on<MessageEvent.Chat> { when { - value.matches(MORT_REGEX) -> secretsCounter = 0 - value.matches(necronRegex) -> necronTime = 60 - value.matches(goldorRegex) -> goldorTickTime = 60 - value.matches(coreOpeningRegex) -> { + message.matches(MORT_REGEX) -> secretsCounter = 0 + message.matches(necronRegex) -> necronTime = 60 + message.matches(goldorRegex) -> goldorTickTime = 60 + message.matches(coreOpeningRegex) -> { goldorStartTime = -1 goldorTickTime = -1 } - value.matches(stormEndRegex) -> { + message.matches(stormEndRegex) -> { goldorStartTime = 104 padTickTime = -1 stormTick = -1 } - value.matches(stormStartRegex) -> { + message.matches(stormStartRegex) -> { padTickTime = 20 lightningTickTime = 560 stormTick = 0 } - !pyTriggered && value.matches(stormPyRegex) -> { + !pyTriggered && message.matches(stormPyRegex) -> { pyTriggered = true pyTickTime = 95 } …… diff 过长已截断 …… |
Fixed core logging causing CME查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/WorldScan.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) @@ -1,8 +1,8 @@ package com.odtheking.odin.features.impl.dungeon.map import com.odtheking.odin.OdinMod.mc +import com.odtheking.odin.events.FloorEnterEvent import com.odtheking.odin.events.LevelEvent -import com.odtheking.odin.events.LocationChangeEvent import com.odtheking.odin.events.RoomEnterEvent import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on @@ -70,19 +70,20 @@ object WorldScan { } ClientChunkEvents.CHUNK_LOAD.register { _, chunk -> - if (!DungeonUtils.inDungeons) chunksToScan.add(IVec2(chunk.pos.x, chunk.pos.z)) + if (DungeonUtils.floor == null) chunksToScan.add(IVec2(chunk.pos.x, chunk.pos.z)) else scanChunk(chunk) } + ClientChunkEvents.CHUNK_UNLOAD.register { _, chunk -> if (!DungeonUtils.inDungeons) chunksToScan.remove(IVec2(chunk.pos.x, chunk.pos.z)) } - on<LocationChangeEvent> { - if (DungeonUtils.inDungeons) { - val level = mc.level ?: return@on + on<FloorEnterEvent> { + mc.execute { + val level = mc.level ?: return@execute for (position in chunksToScan) scanChunk(level.getChunk(position.x, position.z)) + chunksToScan.clear() } - chunksToScan.clear() } } |
Allow chat commands to work in coop chat (#152)查看变更src/main/kotlin/com/odtheking/odin/features/impl/skyblock/ChatCommands.kt | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) @@ -28,6 +28,7 @@ object ChatCommands : Module( private val partyChatCommands by BooleanSetting("Party Commands", true, "Enables party chat commands.") private val guildChatCommands by BooleanSetting("Guild Commands", false, "Enables guild chat commands.") private val privateChatCommands by BooleanSetting("Private Commands", true, "Enables private chat commands.") + private val coopChatCommands by BooleanSetting("Co-op Commands", true, "Enables co-op chat commands.") private val showSettings by DropdownSetting("Show Settings", false) private val partyWarp by BooleanSetting("Warp", true, desc = "Executes the /party warp command.").withDependency { showSettings } @@ -57,7 +58,7 @@ object ChatCommands : Module( private val holding by BooleanSetting("Holding", true, desc = "Sends the item you are holding.").withDependency { showSettings } // https://regex101.com/r/joY7dm/1 - private val messageRegex = Regex("^(?:Party > (\\[[^]]*?])? ?(\\w{1,16})(?: [ቾ⚒])?: ?(.+)$|Guild > (\\[[^]]*?])? ?(\\w{1,16})(?: \\[([^]]*?)])?: ?(.+)$|From (\\[[^]]*?])? ?(\\w{1,16}): ?(.+)$)") + private val messageRegex = Regex("^(?:Party > (\\[[^]]*?])? ?(\\w{1,16})(?: [ቾ⚒])?: ?(.+)$|Guild > (\\[[^]]*?])? ?(\\w{1,16})(?: \\[([^]]*?)])?: ?(.+)$|From (\\[[^]]*?])? ?(\\w{1,16}): ?(.+)$|Co-op > (\\[[^]]*?])? ?(\\w{1,16})(?: [ቾ⚒])?: ?(.+)$)") private val endRunRegex = Regex(" {29}> EXTRA STATS <|^\\[NPC] Elle: Good job everyone. A hard fought battle come to an end. Let's get out of here before we run into any more trouble!$") private val dtReason = mutableListOf<Pair<String, String>>() @@ -74,15 +75,18 @@ object ChatCommands : Module( } val result = messageRegex.find(value) ?: return@on - val channel = when(result.value.split(" ")[0]) { + + val channelStr = result.value.split(" ")[0] + val channel = when(channelStr) { "From" -> if (!privateChatCommands) return@on else ChatChannel.PRIVATE "Party" -> if (!partyChatCommands) return@on else ChatChannel.PARTY "Guild" -> if (!guildChatCommands) return@on else ChatChannel.GUILD + "Co-op" -> if (!coopChatCommands) return@on else ChatChannel.COOP else -> return@on } - - val ign = result.groups[2]?.value ?: result.groups[5]?.value ?: result.groups[9]?.value ?: return@on - val msg = result.groups[3]?.value ?: result.groups[7]?.value ?: result.groups[10]?.value ?: return@on + + val ign = result.groups[2]?.value ?: result.groups[5]?.value ?: result.groups[9]?.value ?: result.groups[12]?.value ?: return@on + val msg = result.groups[3]?.value ?: result.groups[7]?.value ?: result.groups[10]?.value ?: result.groups[13]?.value ?: return@on if (!msg.startsWith("!")) return@on @@ -92,7 +96,7 @@ object ChatCommands : Module( } on<MessageSentEvent> { - if (!chatEmotes || (message.startsWith("/") && !listOf("/pc", "/ac", "/gc", "/msg", "/w", "/r").any { message.startsWith(it) })) return@on + if (!chatEmotes || (message.startsWith("/") && !listOf("/pc", "/ac", "/gc", "/cc", "/msg", "/w", "/r").any { message.startsWith(it) })) return@on var replaced = false val words = message.split(" ").toMutableList() @@ -120,6 +124,7 @@ object ChatCommands : Module( ) ChatChannel.GUILD -> mapOf("coords" to coords, "odin" to odin, "boop" to boop, "cf" to coinFlip, "8ball" to eightBall, "dice" to dice, "racism" to racism, "ping" to ping, "tps" to tps, "time" to time) ChatChannel.PRIVATE -> mapOf("coords" to coords, "odin" to odin, "boop" to boop, "cf" to coinFlip, "8ball" to eightBall, "dice" to dice, "racism" to racism, "ping" to ping, "tps" to tps, "invite" to invite, "time" to time) + ChatChannel.COOP -> mapOf("coords" to coords, "odin" to odin, "boop" to boop, "cf" to coinFlip, "8ball" to eightBall, "dice" to dice, "racism" to racism, "ping" to ping, "tps" to tps, "time" to time) } val words = message.drop(1).split(" ").map { it.lowercase() } @@ -216,6 +221,7 @@ object ChatCommands : Module( ChatChannel.GUILD -> sendCommand("gc $message") ChatChannel.PARTY -> sendCommand("pc $message") ChatChannel.PRIVATE -> sendCommand("msg $name $message") + ChatChannel.COOP -> sendCommand("cc $message") } } @@ -263,6 +269,6 @@ object ChatCommands : Module( ) private enum class ChatChannel { - PARTY, GUILD, PRIVATE + PARTY, GUILD, PRIVATE, COOP } } |
Fixed Phoenix pet's cooldown was incorrect when wearing Spirit/Bonzo mask (#153)查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/InvincibilityTimer.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) @@ -87,8 +87,10 @@ object InvincibilityTimer : Module( on<ChatMessageEvent> { if (onlyInDungeons && !DungeonUtils.inDungeons) return@on InvincibilityType.entries.firstOrNull { type -> value.matches(type.regex) }?.let { type -> - val seconds = mc.player?.getItemBySlot(EquipmentSlot.HEAD)?.loreString?.reversed() - ?.firstNotNullOfOrNull { cooldownRegex.matchEntire(it)?.groupValues?.get(1)?.toIntOrNull() } + val seconds = if (type == InvincibilityType.BONZO) { + mc.player?.getItemBySlot(EquipmentSlot.HEAD)?.loreString?.reversed() + ?.firstNotNullOfOrNull { cooldownRegex.matchEntire(it)?.groupValues?.get(1)?.toIntOrNull() } + } else null type.proc(seconds) val usedMasks = InvincibilityType.entries.count { it.currentCooldown > 0 } if (invincibilityAnnounce) sendCommand("pc ${type.name.lowercase().capitalizeFirst()} Procced! ($usedMasks/${InvincibilityType.entries.size})") |
Made DungeonMap websocket share entire room查看变更src/main/kotlin/com/odtheking/odin/clickgui/Panel.kt | 16 +++------------- src/main/kotlin/com/odtheking/odin/clickgui/settings/ModuleButton.kt | 9 ++------- src/main/kotlin/com/odtheking/odin/events/GuiEvent.kt | 2 -- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 5 ++++- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 52 ++++++++++++++++++---------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 1 + src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapScan.kt | 1 + src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 30 ++++++++++++++++++++++-------- 9 files changed, 52 insertions(+), 66 deletions(-) @@ -48,7 +48,7 @@ class Panel(private val category: Category) { panelSetting.x, panelSetting.y, WIDTH, - (previousHeight + if (ClickGUIModule.roundedPanelBottom) 10f else 0f).coerceAtLeast(HEIGHT), + previousHeight, 10f, 3f, 5f @@ -75,22 +75,12 @@ class Panel(private val category: Category) { if (panelSetting.extended) { for (button in moduleButtons) { if (!button.module.name.contains(SearchBar.currentSearch, true)) continue - startY += button.draw(panelSetting.x, startY + panelSetting.y, button == lastModuleButton) + startY += button.draw(panelSetting.x, startY + panelSetting.y) } } previousHeight = startY - if (ClickGUIModule.roundedPanelBottom) { - NVGRenderer.drawHalfRoundedRect( - panelSetting.x, - panelSetting.y + startY, - WIDTH, - 10f, - if (lastModuleButton?.module?.enabled == true) ClickGUIModule.clickGUIColor.rgba else gray26.rgba, - 5f, - false - ) - } + NVGRenderer.drawHalfRoundedRect(panelSetting.x, panelSetting.y + startY, WIDTH, 10f, if (lastModuleButton?.module?.enabled == true) ClickGUIModule.clickGUIColor.rgba else gray26.rgba, 5f, false) if (scrollOffset != 0f) NVGRenderer.popScissor() } @@ -40,19 +40,14 @@ class ModuleButton(val module: Module, val panel: Panel) { private val hover = HoverHandler(250) var extended = false - fun draw(x: Float, y: Float, lastModule: Boolean = false): Float { + fun draw(x: Float, y: Float): Float { hoverHandler.handle(x, y, Panel.WIDTH, Panel.HEIGHT - 1, true) hover.handle(x, y, Panel.WIDTH, Panel.HEIGHT - 1, true) if (hoverHandler.percent() >= 100 && y >= panel.panelSetting.y + Panel.HEIGHT) ClickGUI.setDescription(module.description, x + Panel.WIDTH + 10f, y, hoverHandler) - if (!ClickGUIModule.roundedPanelBottom && lastModule) { - NVGRenderer.rect(x, y, Panel.WIDTH, Panel.HEIGHT - 10f, color.rgba) - NVGRenderer.drawHalfRoundedRect(x, y + Panel.HEIGHT - 10f, Panel.WIDTH, 10f, color.rgba, 5f, false) - } else { - NVGRenderer.rect(x, y, Panel.WIDTH, Panel.HEIGHT, color.rgba) - } + NVGRenderer.rect(x, y, Panel.WIDTH, Panel.HEIGHT, color.rgba) NVGRenderer.text(module.name, x + Panel.WIDTH / 2 - nameWidth / 2, y + Panel.HEIGHT / 2 - 9f, 18f, Colors.WHITE.rgba, NVGRenderer.defaultFont) if (representableSettings.isEmpty()) return Panel.HEIGHT @@ -13,7 +13,5 @@ abstract class GuiEvent(val screen: Screen) : CancellableEvent() { class RenderSlot(screen: Screen, val guiGraphics: GuiGraphicsExtractor, val slot: Slot) : GuiEvent(screen) - class CustomTermGuiClick(screen: Screen, val slot: Int, val button: Int) : GuiEvent(screen) - class DrawTooltip(screen: Screen, val guiGraphics: GuiGraphicsExtractor, val mouseX: Int, val mouseY: Int) : GuiEvent(screen) } \ No newline at end of file @@ -176,7 +176,10 @@ object MapInfo : Module( if ((!DungeonUtils.inClear) && !it) return@HUD 0 to 0 val secrets = if (it) 0 to 2 else DungeonUtils.currentRoom?.foundSecrets?.let { found -> - DungeonUtils.currentRoom?.data?.maxSecrets?.let { max -> found to max } + DungeonUtils.currentRoom?.data?.maxSecrets?.let { max -> + if (max == 0) return@HUD 0 to 0 + found to max + } } ?: return@HUD 0 to 0 val color = when { secrets.first * 2 < secrets.second -> "§c" @@ -2,22 +2,15 @@ package com.odtheking.odin.features.impl.dungeon.map import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.* -import com.odtheking.odin.events.FloorEnterEvent -import com.odtheking.odin.events.RoomEnterEvent -import com.odtheking.odin.events.SecretsUpdateEvent -import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module -import com.odtheking.odin.features.impl.render.ClickGUIModule +import com.odtheking.odin.features.impl.dungeon.map.tile.DungeonRoom import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.IVec2 -import com.odtheking.odin.utils.devMessage import com.odtheking.odin.utils.network.WebUtils.gson import com.odtheking.odin.utils.network.webSocket import com.odtheking.odin.utils.render.hollowFill -import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils object DungeonMap : Module( @@ -34,7 +27,7 @@ object DungeonMap : Module( private val playerDropdown by DropdownSetting("Player Settings") val playerNamesScaling by NumberSetting("Player Names Scaling", 0.75f, 0.1f, 2f, 0.05f, desc = "Scale of player name labels.").withDependency { playerDropdown } val playerNameColor by ColorSetting("Player Name Color", Color(70, 70, 70), true, desc = "Color of player name labels.").withDependency { playerDropdown } - val playerHead by BooleanSetting("Own Player Head", false, desc = "Shows the player head on the map.").withDependency { playerDropdown } + val playerHead by BooleanSetting("Own Player Head", desc = "Shows the player head on the map.").withDependency { playerDropdown } private val roomDropdown by DropdownSetting("Room Settings") val normalRoomColor by ColorSetting("Normal Room", Color(107, 58, 17), true, desc = "Color of normal rooms.").withDependency { roomDropdown } @@ -47,15 +40,14 @@ object DungeonMap : Module( val unknownRoomColor by ColorSetting("Unknown Room", Color(40, 40, 40), true, desc = "Color of unknown rooms hinted by a door with no discovered room on the other side.").withDependency { roomDropdown } private val doorDropdown by DropdownSetting("Door Settings") - val normalDoorColor by ColorSetting("Normal Door", Color(107, 58, 17).darker(), desc = "Color of normal doors.").withDependency { doorDropdown } + val normalDoorColor by ColorSetting("Normal Door", Color(107, 58, 17).darker(), true, desc = "Color of normal doors.").withDependency { doorDropdown } val witherDoorColor by ColorSetting("Wither Door", Colors.BLACK, true, desc = "Color of wither doors.").withDependency { doorDropdown } val bloodDoorColor by ColorSetting("Blood Door", Color(255, 0, 0), true, desc = "Color of blood room doors.").withDependency { doorDropdown } val fairyDoorColor by ColorSetting("Fairy Door", Color(224, 0, 255).darker(), true, desc = "Color of fairy room doors.").withDependency { doorDropdown } val unknownDoorColor by ColorSetting("Unknown Door", Color(40, 40, 40).darker(), true, desc = "Color of doors with no discovered room on the other side.").withDependency { doorDropdown } val disablePred by BooleanSetting("Disable Prediction", false, desc = "Disables special-column room type prediction.") - - private val allowWebsocket by BooleanSetting("Websocket", true, desc = "Shares information in your room with the rest of your dungeon party.") + private val allowWebsocket by BooleanSetting("Parse map websocket", true, desc = "Parses the websocket data from your teammates update dungeon secrets in real time.") private val exampleRooms by lazy { buildExampleRooms() } private val exampleDoors by lazy { buildExampleDoors() } @@ -86,29 +78,21 @@ object DungeonMap : Module( val syncSocket = webSocket { onMessage { message -> - val (roomName, foundSecrets, position) = try { gson.fromJson(message, RoomSync::class.java) } catch (_: Exception) { return@onMessage } - val room = DungeonScan.rooms.find { it.name == roomName && it.topLeft == position } ?: return@onMessage - if ((room.foundSecrets ?: -1) < (foundSecrets ?: -1)) room.foundSecrets = foundSecrets + if (!allowWebsocket) return@onMessage + val synced = try { gson.fromJson(message, DungeonRoom::class.java) } catch (_: Exception) { return@onMessage } + if (synced.data == null) return@onMessage + + val room = DungeonScan.rooms.find { it.topLeft == synced.topLeft } ?: synced.also { new -> + DungeonScan.rooms.add(new) + for ((x, z) in new.tiles) { + if (x !in 0..5 || z !in 0..5) continue + DungeonScan.tiles[x + z * 6].room = new + } + } + + if (room.data == null) room.data = synced.data + if ((room.foundSecrets ?: -1) < (synced.foundSecrets ?: -1)) room.foundSecrets = synced.foundSecrets room.walkedInto = true } } - - init { - on<SecretsUpdateEvent> { - if (!allowWebsocket) return@on - room.name?.let { syncSocket.send(gson.toJson(RoomSync(it, foundSecrets, room.topLeft))) } - } - - on<FloorEnterEvent> { - if (!allowWebsocket) return@on - LocationUtils.lobbyId?.let { syncSocket.connect("${ClickGUIModule.webSocketUrl}$it") } ?: devMessage("Failed to connect to dungeon websocket, lobbyId is null.") - } - - on<RoomEnterEvent> { - if (room == null) syncSocket.shutdown() - else if (allowWebsocket) room.name?.let { syncSocket.send(gson.toJson(RoomSync(it, room.foundSecrets, room.topLeft))) } - } - } - - private data class RoomSync(val roomName: String, val foundSecrets: Int?, val position: IVec2) } \ No newline at end of file @@ -71,6 +71,7 @@ fun GuiGraphicsExtractor.renderDoors(doors: Collection<DungeonDoor>) { fun GuiGraphicsExtractor.renderPathHints(pathHints: Collection<DungeonTile>) { for ((position, room) in pathHints) { + if (room?.walkedInto == true) continue val (x, y) = position.x * MAP_ROOM_GAP to position.z * MAP_ROOM_GAP val colors = if (room?.type == RoomType.BLOOD) arrayOf(DungeonMap.bloodRoomColor.darker(0.5f)) @@ -208,6 +208,7 @@ object MapScan { if (newCheckmark != null && newCheckmark != room.checkmark) { room.checkmark = newCheckmark + if (newCheckmark == MapCheckmark.GREEN) room.foundSecrets = room.data?.maxSecrets ?: room.foundSecrets CheckmarkUpdateEvent(room, newCheckmark).postAndCatch() } } @@ -20,7 +20,7 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData var walkedInto: Boolean = false var clayPos: BlockPos? = null var highestBlock: Int? = null - var waypoints: MutableSet<DungeonWaypoints.DungeonWaypoint> = mutableSetOf() + @Transient var waypoints: MutableSet<DungeonWaypoints.DungeonWaypoint> = mutableSetOf() var checkmark: MapCheckmark = MapCheckmark.UNDISCOVERED @@ -7,16 +7,19 @@ import com.odtheking.odin.clickgui.HudManager import com.odtheking.odin.clickgui.settings.AlwaysActive import com.odtheking.odin.clickgui.settings.impl.* import com.odtheking.odin.events.ChatMessageEvent +import com.odtheking.odin.events.FloorEnterEvent +import com.odtheking.odin.events.RoomEnterEvent +import com.odtheking.odin.events.SecretsUpdateEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Category import com.odtheking.odin.features.Module import com.odtheking.odin.features.ModuleManager -import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.alert -import com.odtheking.odin.utils.getChatBreak -import com.odtheking.odin.utils.modMessage +import com.odtheking.odin.features.impl.dungeon.map.DungeonMap +import com.odtheking.odin.utils.* import com.odtheking.odin.utils.network.WebUtils.fetchJson +import com.odtheking.odin.utils.network.WebUtils.gson import com.odtheking.odin.utils.network.WebUtils.postData +import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.ui.rendering.NVGRenderer import kotlinx.coroutines.launch import net.minecraft.network.chat.ClickEvent @@ -35,13 +38,24 @@ object ClickGUIModule : Module( ) { val enableNotification by BooleanSetting("Chat notifications", true, desc = "Sends a message when you toggle a module with a keybind") val clickGUIColor by ColorSetting("Color", Color(50, 150, 220), desc = "The color of the Click GUI.") - - val roundedPanelBottom by BooleanSetting("Rounded Panel Bottoms", true, desc = "Whether to extend panels to make them rounded at the bottom.") + private val action by ActionSetting("Open HUD Editor", desc = "Opens the HUD editor when clicked.") { mc.setScreen(HudManager) } val hypixelApiUrl by StringSetting("API URL", "https://api.odtheking.com/hypixel/", 128, "The Hypixel API server to connect to.").hide() val webSocketUrl by StringSetting("Socket URL", "wss://ws.odtheking.com/", 128, "The Websocket server to connect to.").hide() - private val action by ActionSetting("Open HUD Editor", desc = "Opens the HUD editor when clicked.") { mc.setScreen(HudManager) } + init { + on<SecretsUpdateEvent> { DungeonMap.syncSocket.send(gson.toJson(room)) } + + on<FloorEnterEvent> { + LocationUtils.lobbyId?.let { DungeonMap.syncSocket.connect("${webSocketUrl}$it") } ?: devMessage("Failed to connect to dungeon websocket, lobbyId is null.") + } + + on<RoomEnterEvent> { + if (room == null) DungeonMap.syncSocket.shutdown() + else DungeonMap.syncSocket.send(gson.toJson(room)) + } + } + val devMessage by BooleanSetting("Developer Message", false, desc = "Sends development related messages to the chat.") val dungeonCoresLogging by BooleanSetting("Core loggings", false, desc = "") @@ -78,7 +92,7 @@ object ClickGUIModule : Module( init { OdinMod.scope.launch { latestVersionNumber = checkNewerVersion(OdinMod.version.toString()) - val name = OdinMod.mc.user.name.takeIf { !it.matches(Regex("Player\\d{2,3}")) } ?: return@launch + val name = mc.user.name.takeIf { !it.matches(Regex("Player\\d{2,3}")) } ?: return@launch postData("https://api.odtheking.com/tele/", """{"username": "$name", "version": "Fabric ${OdinMod.version}"}""") } |
Rubix stuff查看变更src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 1 + src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 31 ++++++++++--------------------- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSounds.kt | 7 +------ src/main/kotlin/com/odtheking/odin/features/impl/boss/termGUI/CustomTermGui.kt | 33 +++++++++------------------------ src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/RubixHandler.kt | 48 ++++++++++++++++++++---------------------------- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/TerminalHandler.kt | 23 +++++++++++++---------- 6 files changed, 54 insertions(+), 89 deletions(-) @@ -50,6 +50,7 @@ abstract class TerminalEvent(val terminal: TerminalHandler) : Event { // first 2 class Open(terminal: TerminalHandler) : TerminalEvent(terminal) class Close(terminal: TerminalHandler) : TerminalEvent(terminal) class Solve(terminal: TerminalHandler) : TerminalEvent(terminal) + class Click(terminal: TerminalHandler, val slotIndex: Int, val button: Int) : TerminalEvent(terminal) } interface TickEvent : Event { @@ -16,7 +16,6 @@ import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.network.chat.Component import net.minecraft.world.item.Items -import org.lwjgl.glfw.GLFW object TerminalSolver : Module( name = "Terminal Solver", @@ -28,17 +27,15 @@ object TerminalSolver : Module( val roundness by NumberSetting("Roundness", 5, 0f, 15f, 1f, desc = "The roundness of the custom terminal gui.").withDependency { renderType == 2 } val gap by NumberSetting("Slot gap", 2, 0, 8, 1, desc = "The gap between the slots in the custom terminal gui.").withDependency { renderType == 2 } private val solverSettings by DropdownSetting("Solver Functionality") - private val cancelToolTip by BooleanSetting("Stop Tooltips", true, desc = "Stops rendering tooltips in terminals.").withDependency { (renderType == 0 || renderType == 1) && solverSettings } - private val middleClickGUI by BooleanSetting("Middle Click GUI", true, desc = "Replaces right click with middle click in terminals.").withDependency { (renderType == 0 || renderType == 1) && solverSettings } - private val blockIncorrectClicks by BooleanSetting("Block Incorrect Clicks", true, desc = "Blocks incorrect clicks in terminals.").withDependency { (renderType == 0 || renderType == 1) && solverSettings } private val cancelMelodySolver by BooleanSetting("Stop Melody Solver", false, desc = "Stops rendering the melody solver.").withDependency { solverSettings } val melodyTermSize by NumberSetting("Melody Size", 1.5f, 1f, 3f, 0.1f, desc = "The size of the melody terminal GUI.").withDependency { !cancelMelodySolver && solverSettings && renderType == 2 } val showNumbers by BooleanSetting("Show Numbers", true, desc = "Shows numbers in the order terminal.").withDependency { solverSettings } + val rubixMode by SelectorSetting("Rubix Mode", "Left Clicks Only", arrayListOf("Fewest Clicks", "Left Clicks Only"), desc = "Whether the rubix solver should mix in right clicks for the fewest clicks overall, or stick to left clicks only.").withDependency { solverSettings } val hideClicked by BooleanSetting("Client Prediction", true, desc = "Visually predicts the server state before the gui update is sent to the client.").withDependency { solverSettings } val terminalReloadThreshold by NumberSetting("Resolve timeout", 600, 300, 1200, 10, unit = "ms", desc = "The amount of time before the terminal reloads after a click wasn't registered while using hide clicked.").withDependency { hideClicked && solverSettings } private val firstClickProtSettings by DropdownSetting("First Click Prot Dropdown") val firstClickProt by NumberSetting("First Click Prot", 500, 350, 800, 10, unit = "ms", desc = "The amount of time after opening a terminal where clicks are blocked to prevent bans (recommended value is 500 minus your ping).").withDependency { firstClickProtSettings } - val shouldFirstClickProtWithTicks by BooleanSetting("Account For Server Lag", false, desc = "Prevents bans from clicking when the server lags after opening the terminal (disabled in singleplayer").withDependency { firstClickProtSettings } + val shouldFirstClickProtWithTicks by BooleanSetting("Account For Server Lag", false, desc = "Prevents bans from clicking when the server lags after opening the terminal (disabled in singleplayer").withDependency { firstClickProtSettings } val firstClickProtTicks by NumberSetting("Lag Protection Ticks", 8, 7, 16, unit = "ticks", desc = "Each tick = 50ms (recommended value is 8)").withDependency { shouldFirstClickProtWithTicks && firstClickProtSettings } private val showColors by DropdownSetting("Color Settings") val backgroundColor by ColorSetting("Background", Colors.gray26, true, desc = "Background color of the terminal solver.").withDependency { showColors } @@ -68,23 +65,11 @@ object TerminalSolver : Module( private val renderMelody get() = !(cancelMelodySolver && TerminalUtils.currentTerm?.type == TerminalTypes.MELODY) init { - on<TickEvent.Server> { - TerminalUtils.currentTerm?.ticksOpened++ - } - on<GuiEvent.SlotClick> { - val term = TerminalUtils.currentTerm ?: return@on - - if (blockIncorrectClicks && !term.canClick(slotIndex, button)) return@on cancel() - - if (term.shouldProtect()) return@on cancel() - - if (middleClickGUI) { - term.click(slotIndex, if (button == 0) GLFW.GLFW_MOUSE_BUTTON_3 else button, hideClicked) - return@on cancel() + TerminalUtils.currentTerm?.let { + it.click(slotIndex, button, hideClicked) + cancel() } - - if (hideClicked) term.simulateClick(slotIndex, button) } on<GuiEvent.Render> { @@ -109,7 +94,7 @@ object TerminalSolver : Module( } on<GuiEvent.DrawTooltip> { - if (cancelToolTip && TerminalUtils.currentTerm != null) cancel() + if (TerminalUtils.currentTerm != null) cancel() this.guiGraphics.renderDebug() } @@ -120,6 +105,10 @@ object TerminalSolver : Module( on<TerminalEvent.Close> { if (renderType == 0 || renderType == 1) mc.execute { mc.resizeGui() } } + + on<TickEvent.Server> { + TerminalUtils.currentTerm?.ticksOpened++ + } } fun GuiGraphicsExtractor.renderDebug() { @@ -3,7 +3,6 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.events.ChatMessageEvent -import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.core.EventPriority import com.odtheking.odin.events.core.on @@ -38,14 +37,10 @@ object TerminalSounds : Module( else if (shouldReplaceSounds && completeSounds && !clickSounds) playSoundSettings(completeSoundSettings()) } - on<GuiEvent.SlotClick> (EventPriority.HIGHEST) { + on<TerminalEvent.Click> (EventPriority.HIGHEST) { if (shouldReplaceSounds) playSoundForSlot(slotIndex, button) } - on<GuiEvent.CustomTermGuiClick> (EventPriority.HIGHEST) { - if (shouldReplaceSounds) playSoundForSlot(slot, button) - } - onReceive<ClientboundSoundPacket> { if (sound.value() == SoundEvents.NOTE_BLOCK_PLING.value() && volume == 8f && pitch == 4.047619f && shouldReplaceSounds) it.cancel() @@ -1,10 +1,8 @@ package com.odtheking.odin.features.impl.boss.termGUI import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.ScreenEvent import com.odtheking.odin.features.impl.boss.TerminalSolver -import com.odtheking.odin.features.impl.boss.TerminalSolver.hideClicked import com.odtheking.odin.features.impl.boss.TerminalSolver.renderDebug import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Colors @@ -52,13 +50,13 @@ abstract class TermGui { grid?.let { g -> val (bx, by) = g.toBase(click.x(), click.y(), guiScale) hoveredSlotIndex = g.slots.firstOrNull { it.containsBase(bx, by) }?.slotIndex - hoveredSlotIndex?.let { customTerminalClick(it, click.button()) } + hoveredSlotIndex?.let { TerminalUtils.currentTerm?.click(it, click.button(), TerminalSolver.hideClicked) } }; return true }, key = fun ScreenEvent.KeyPress.(): Any { if (!isTerminalOverrideKey(input)) return false hoveredSlotIndex?.let { - customTerminalClick(it, if (!input.hasControlDown()) GLFW.GLFW_MOUSE_BUTTON_1 else GLFW.GLFW_MOUSE_BUTTON_2) + TerminalUtils.currentTerm?.click(it, if (!input.hasControlDown()) GLFW.GLFW_MOUSE_BUTTON_1 else GLFW.GLFW_MOUSE_BUTTON_2, TerminalSolver.hideClicked) }; return true }) ) @@ -66,9 +64,8 @@ abstract class TermGui { private fun currentTermScreen() = mc.screen as? AbstractContainerScreen<*> - private fun isActiveTermScreen(): Boolean = - !(!TerminalSolver.customGuiEnabled || TerminalUtils.currentTerm == null || currentTermScreen() == null) && - TerminalUtils.currentTerm?.type?.getGUI() === this + private fun isActiveTermScreen() = + TerminalSolver.customGuiEnabled && TerminalUtils.currentTerm?.type?.getGUI() === this && currentTermScreen() != null private fun isTerminalOverrideKey(event: KeyEvent) = mc.options.keyDrop.matches(event) || mc.options.keyHotbarSlots.any { it.matches(event) } @@ -97,22 +94,6 @@ abstract class TermGui { ) } - protected fun createSlotVisualFromRendering(slotIndex: Int) = SlotVisual( - resolve = { TerminalUtils.currentTerm?.getSlotRendering(slotIndex) } - ) { x, y, w, h -> - TerminalUtils.currentTerm?.getSlotRendering(slotIndex)?.second?.let { renderSlotText(it, x, y, w, h, Colors.WHITE) } - } - - fun customTerminalClick(slotIndex: Int, button: Int) { - TerminalUtils.currentTerm?.let { term -> - val screen = mc.screen ?: return@let - val btn = if (button == 0) GLFW.GLFW_MOUSE_BUTTON_3 else button - if (term.shouldProtect()) return@let - if (!GuiEvent.CustomTermGuiClick(screen, slotIndex, btn).postAndCatch() && term.canClick(slotIndex, btn)) - term.click(slotIndex, btn, hideClicked) - } - } - private fun render(guiGraphics: GuiGraphicsExtractor, mouseX: Int, mouseY: Int) { val g = grid ?: return val scale = guiScale @@ -144,5 +125,9 @@ abstract class TermGui { fun simpleTermGui(rows: Int, cols: Int, startRow: Int, startCol: Int): TermGui = object : TermGui() { override fun buildTerminal(screen: AbstractContainerScreen<*>) = - buildTerminalGrid(screen, rows, cols, startRow, startCol) { createSlotVisualFromRendering(it) } + buildTerminalGrid(screen, rows, cols, startRow, startCol) { slotIndex -> + SlotVisual({ TerminalUtils.currentTerm?.getSlotRendering(slotIndex) }) { x, y, w, h -> + TerminalUtils.currentTerm?.getSlotRendering(slotIndex)?.second?.let { renderSlotText(it, x, y, w, h, Colors.WHITE) } + } + } } \ No newline at end of file @@ -8,61 +8,53 @@ import net.minecraft.world.item.BlockItem import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack import net.minecraft.world.level.block.StainedGlassPaneBlock +import kotlin.math.abs class RubixHandler : TerminalHandler(TerminalTypes.RUBIX) { private var lockedColor: DyeColor? = null + private val rightClickSlots = HashSet<Int>() + override fun solve(slots: List<Slot>, updatedIndex: Int): List<Int> { val panes = slots.mapIndexedNotNull { index, slot -> slot.item.paneColor?.takeUnless { it == DyeColor.BLACK }?.let { index to it } } - return if (updatedIndex == LAST_PANE_SLOT && lockedColor == null) { - val (best, clicks) = rubixColorOrder - .map { it to clicksFor(it, panes) } - .minBy { (_, clicks) -> getRealSize(clicks) } + if (updatedIndex == LAST_PANE_SLOT && lockedColor == null) + lockedColor = rubixColorOrder.minBy { goal -> clicksFor(goal, panes).values.sumOf { abs(it) } } + + val clicks = lockedColor?.let { clicksFor(it, panes) }.orEmpty() - lockedColor = best - clicks - } else lockedColor?.let { clicksFor(it, panes) } ?: emptyList() + rightClickSlots.clear() + clicks.forEach { (slotIndex, count) -> if (count < 0) rightClickSlots.add(slotIndex) } + return clicks.flatMap { (slotIndex, count) -> List(abs(count)) { slotIndex } } } - private fun clicksFor(goal: DyeColor, panes: List<Pair<Int, DyeColor>>): List<Int> { + private fun clicksFor(goal: DyeColor, panes: List<Pair<Int, DyeColor>>): Map<Int, Int> { val goalIndex = rubixColorOrder.indexOf(goal) - return panes.flatMap { (slotIndex, color) -> - List(dist(rubixColorOrder.indexOf(color), goalIndex)) { slotIndex } - } + return panes.associate { (slotIndex, color) -> + val forward = dist(rubixColorOrder.indexOf(color), goalIndex) + slotIndex to if (forward > 2 && TerminalSolver.rubixMode != 1) forward - rubixColorOrder.size else forward + }.filterValues { it != 0 } } private val ItemStack.paneColor: DyeColor? get() = ((item as? BlockItem)?.block as? StainedGlassPaneBlock)?.color - override fun simulateClick(slotIndex: Int, clickType: Int) { - if (clickType == 1) solution.add(slotIndex) - else solution.remove(slotIndex) - if (solution.count { it == slotIndex } >= 5) solution.removeAll { it == slotIndex } - } - - override fun canClick(slotIndex: Int, button: Int): Boolean { - val needed = solution.count { it == slotIndex } - return (needed >= 3 && button == 1) || (needed in 1..<3 && button != 1) - } - - private fun getRealSize(clicks: List<Int>): Int = - clicks.groupingBy { it }.eachCount().values.sumOf { if (it >= 3) 5 - it else it } + override fun canClick(slotIndex: Int, button: Int): Boolean = + slotIndex in solution && (button == 1) == (slotIndex in rightClickSlots) private fun dist(pane: Int, most: Int): Int = if (pane > most) (most + rubixColorOrder.size) - pane else most - pane override fun renderSlot(slotIndex: Int): Pair<Color, String?>? { - val amount = solution.count { it == slotIndex } - val clicksRequired = if (amount < 3) amount else amount - 5 - if (clicksRequired == 0) return null + val remaining = solution.count { it == slotIndex }.takeIf { it > 0 } ?: return null + val clicksRequired = if (slotIndex in rightClickSlots) -remaining else remaining return when (clicksRequired) { 1 -> TerminalSolver.rubixColor1 2 -> TerminalSolver.rubixColor2 - -1 -> TerminalSolver.oppositeRubixColor1 + -1, 4 -> TerminalSolver.oppositeRubixColor1 else -> TerminalSolver.oppositeRubixColor2 } to clicksRequired.toString() } @@ -2,6 +2,7 @@ package com.odtheking.odin.utils.skyblock.dungeon.terminals.terminalhandler import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.SetSlotEvent +import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.features.impl.boss.TerminalSimulator import com.odtheking.odin.features.impl.boss.TerminalSolver import com.odtheking.odin.features.impl.boss.TerminalSolver.firstClickProt @@ -15,12 +16,10 @@ import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes import net.minecraft.world.inventory.ContainerInput import net.minecraft.world.inventory.Slot -import net.minecraft.world.item.Item import net.minecraft.world.item.Items import org.lwjgl.glfw.GLFW abstract class TerminalHandler(val type: TerminalTypes) { - private val lastSeenItems = HashMap<Int, Item>() val clickedSlots = ArrayList<Pair<Int, Int>>() val timeOpened = System.currentTimeMillis() val solution = ArrayList<Int>() @@ -30,10 +29,8 @@ abstract class TerminalHandler(val type: TerminalTypes) { open fun updateSlot(event: SetSlotEvent) { if (event.slots.isEmpty() || event.slotIndex !in 0 until type.windowSize - 9 || event.itemStack.item == Items.BLACK_STAINED_GLASS_PANE) return - if (lastSeenItems.put(event.slotIndex, event.itemStack.item) != event.itemStack.item) { - val index = clickedSlots.indexOfFirst { it.first == event.slotIndex } - if (index >= 0) clickedSlots.subList(0, index + 1).clear() - } + val index = clickedSlots.indexOfFirst { it.first == event.slotIndex } + if (index >= 0) clickedSlots.subList(0, index + 1).clear() solution.clear() solution.addAll(solve(event.slots.subList(0, type.windowSize - 9), event.slotIndex)) @@ -52,16 +49,22 @@ abstract class TerminalHandler(val type: TerminalTypes) { abstract fun solve(slots: List<Slot>, updatedIndex: Int): List<Int> open fun click(slotIndex: Int, button: Int, simulateClick: Boolean) { - val screen = mc.screen ?: return + if (!canClick(slotIndex, button) || shouldProtect()) return + + val button = if (button == 1 && type == TerminalTypes.RUBIX) GLFW.GLFW_MOUSE_BUTTON_2 else GLFW.GLFW_MOUSE_BUTTON_3 clickedSlots.add(slotIndex to button) lastClickTime = System.currentTimeMillis() + if (simulateClick) simulateClick(slotIndex, button) - if (screen is TermSimGUI) { - screen.clickIndex(slotIndex, button) - return + mc.screen?.let { screen -> + if (screen is TermSimGUI) { + screen.clickIndex(slotIndex, button) + return + } } mc.player?.clickSlot(slotIndex, button, if (button == GLFW.GLFW_MOUSE_BUTTON_3) ContainerInput.CLONE else ContainerInput.PICKUP) + TerminalEvent.Click(this, slotIndex, button).postAndCatch() } open fun canClick(slotIndex: Int, button: Int): Boolean = slotIndex in solution |
Made blaze accept any mob level查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BlazeSolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -19,7 +19,7 @@ object BlazeSolver { private var blazes = mutableListOf<ArmorStand>() private var roomType = 0 private var lastBlazeCount = 10 - private val blazeHealthRegex = Regex("^\\[Lv15] Blaze [\\d,]+/([\\d,]+)❤$") + private val blazeHealthRegex = Regex("^\\[Lv+\\d+] Blaze [\\d,]+/([\\d,]+)❤$") fun getBlaze() { if (!DungeonUtils.inDungeons || DungeonUtils.currentRoom?.data?.name?.equalsOneOf("Lower Blaze", "Higher Blaze") == false) return |
Improved visuals for storm tick timer and for Melody progress HUD查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/MelodyMessage.kt | 26 +++++++++++++++----------- src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BlazeSolver.kt | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) @@ -16,7 +16,7 @@ import com.odtheking.odin.utils.equalsOneOf import com.odtheking.odin.utils.network.WebUtils.gson import com.odtheking.odin.utils.network.webSocket import com.odtheking.odin.utils.render.getStringWidth -import com.odtheking.odin.utils.render.textDim +import com.odtheking.odin.utils.render.text import com.odtheking.odin.utils.sendCommand import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.skyblock.dungeon.DungeonClass @@ -45,7 +45,7 @@ object MelodyMessage : Module( fun track(data: MelodyData, index: Int, name: String) { rows = maxOf(rows, index + 1) val clay = data.clay ?: return - val label = melodyLabel(data, name) ?: return + val label = melodyLabel(data, name) ?: "" labelWidth = maxOf(labelWidth, getStringWidth("$clay $label")) } @@ -63,7 +63,7 @@ object MelodyMessage : Module( } } - (width * 5 + 2 + labelWidth) to (width * 2 * rows) + (width * 5 + 2 + labelWidth) to (width * rows) }.withDependency { broadcast } private val showPlayer by SelectorSetting("Show Player", "None", arrayListOf("None", "Class", "Name", "Class & Name"), desc = "How player details should be rendered in the Melody GUI.").withDependency { broadcast } @@ -172,22 +172,26 @@ object MelodyMessage : Module( private val width by lazy { getStringWidth("§d■") } private fun melodyLabel(data: MelodyData, playerName: String): String? = when (showPlayer) { - 1 -> data.dungeonClass.toString() - 2 -> playerName - 3 -> "$playerName (${data.dungeonClass})" + 1 -> "§${data.dungeonClass.colorCode}${data.dungeonClass.name.lowercase()}" + 2 -> "§6$playerName" + 3 -> "§6$playerName §8(§${data.dungeonClass.colorCode}${data.dungeonClass.name.lowercase()}§8)" else -> null } private fun GuiGraphicsExtractor.drawMelody(data: MelodyData, index: Int, playerName: String) { - val y = width * 2 * index + val y = width * index - 1 repeat(5) { - if (data.purple == it) textDim("§d■", width * it, y) - textDim("${if (data.pane == it) "§a" else "§f"}■", width * it, y + width) + val color = when (it) { + data.pane -> "§a" + data.purple -> "§d" + else -> "§f" + } + text("$color■", width * it, y) } - val label = melodyLabel(data, playerName) ?: return - data.clay?.let { textDim("$it $label", width * 5 + 2, y + width / 2) } + val label = melodyLabel(data, playerName) ?: "" + data.clay?.let { text("$it $label", width * 5 + 2, y) } } private data class UpdateMessage(val username: String, val type: Int, val slot: Int) @@ -79,7 +79,7 @@ object TickTimers : Module( private val stormTickHud by HUD("Storm Tick Hud", "Displays a timer for Storm's second phase, optionally counting down to the crush window.") { if (it) { val (time, max, prefix) = Triple(200, 620, "§bStorm:") - textDim(formatTimer(time, max, prefix), 0, 0, Colors.MINECRAFT_DARK_RED) + textDim(formatTimer(time, max, prefix, "§a"), 0, 0, Colors.MINECRAFT_DARK_RED) } else if (stormTick >= 0) textDim(formatTimer(stormTick, 620, "§bStorm:"), 0, 0, Colors.MINECRAFT_DARK_RED) else 0 to 0 } @@ -19,7 +19,7 @@ object BlazeSolver { private var blazes = mutableListOf<ArmorStand>() private var roomType = 0 private var lastBlazeCount = 10 - private val blazeHealthRegex = Regex("^\\[Lv11] Blaze [\\d,]+/([\\d,]+)❤$") + private val blazeHealthRegex = Regex("^\\[Lv15] Blaze [\\d,]+/([\\d,]+)❤$") fun getBlaze() { if (!DungeonUtils.inDungeons || DungeonUtils.currentRoom?.data?.name?.equalsOneOf("Lower Blaze", "Higher Blaze") == false) return |
Fixed Numbers and Melody CustomTermGui rendering查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/MelodyHandler.kt | 1 - src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/NumbersHandler.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) @@ -17,7 +17,6 @@ class MelodyHandler: TerminalHandler(TerminalTypes.MELODY) { return buildList { add(greenPane) add(magentaPane) - add(magentaPane + 36) if (greenPane % 9 == magentaPane % 9) add(greenClay) } @@ -28,6 +28,6 @@ class NumbersHandler: TerminalHandler(TerminalTypes.NUMBERS) { 1 -> TerminalSolver.orderColor2 2 -> TerminalSolver.orderColor3 else -> Colors.TRANSPARENT - } to if (TerminalSolver.showNumbers) (abs((solution.size - 10) - solutionIndex) + 1).toString() else null + } to if (TerminalSolver.showNumbers) (abs((solution.size - 14) - solutionIndex) + 1).toString() else null } } \ No newline at end of file |
Code cleanup查看变更src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/ExtraStats.kt | 3 +-- src/main/kotlin/com/odtheking/odin/features/impl/boss/MelodyMessage.kt | 10 +++++----- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalTimes.kt | 3 +-- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/NumbersSim.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/PanesSim.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/RubixSim.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/SelectAllSim.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/nether/SupplyHelper.kt | 3 +-- src/main/kotlin/com/odtheking/odin/utils/ChatManager.kt | 17 ----------------- 11 files changed, 14 insertions(+), 34 deletions(-) @@ -35,7 +35,7 @@ class BlockInteractEvent(val pos: BlockPos) : CancellableEvent() class EntityInteractEvent(val pos: Vec3, val entity: Entity) : CancellableEvent() class UseItemOnPostEvent(val hand: InteractionHand, val hitResult: BlockHitResult, val interactionResult: InteractionResult) : Event -class ChatMessageEvent(val value: String, val component: Component) : Event // mixin instead of packet (still needs to run before vanilla processing for hideMessage() +class ChatMessageEvent(val value: String, val component: Component) : CancellableEvent() class MessageSentEvent(val message: String) : CancellableEvent() class RenderBossBarEvent(val bossBar: BossEvent) : CancellableEvent() @@ -7,7 +7,6 @@ import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.* -import com.odtheking.odin.utils.ChatManager.hideMessage import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.network.chat.ClickEvent import net.minecraft.network.chat.Component @@ -59,7 +58,7 @@ object ExtraStats : Module( on<ChatMessageEvent> { if (!DungeonUtils.inDungeons) return@on - if (cancelRegexes.any { it.matches(value) }) hideMessage() + if (cancelRegexes.any { it.matches(value) }) cancel() if (extraStatsRegex.matches(value)) return@on sendCommand("showextrastats") @@ -97,7 +97,7 @@ object MelodyMessage : Module( } on<SetSlotEvent> { - if (broadcast || melodyProgress) onSlotUpdate(this) + if (broadcast || melodyProgress) onSlotUpdate() } on<LevelEvent.Load> { @@ -125,13 +125,13 @@ object MelodyMessage : Module( } } - private fun onSlotUpdate(event: SetSlotEvent) { + private fun SetSlotEvent.onSlotUpdate() { val term = TerminalUtils.currentTerm ?: return if (DungeonUtils.getF7Phase() != M7Phases.P3 || term.type != TerminalTypes.MELODY || mc.screen is TermSimGUI) return - val item = event.itemStack.item + val item = itemStack.item if (item == Items.LIME_TERRACOTTA) { - val position = event.slotIndex / 9 + val position = slotIndex / 9 if (lastSent.clay == position) return if (broadcast) melodyWebSocket.send(update(1, position)) if (melodyProgress) clayProgress[position]?.let { sendCommand("pc $it") } @@ -139,7 +139,7 @@ object MelodyMessage : Module( return } if (!broadcast || !item.equalsOneOf(Items.MAGENTA_STAINED_GLASS_PANE, Items.LIME_STAINED_GLASS_PANE)) return - val index = mapToRange(event.slotIndex) ?: return + val index = mapToRange(slotIndex) ?: return val meta = when (item) { Items.MAGENTA_STAINED_GLASS_PANE -> { if (lastSent.purple == index) return @@ -147,7 +147,7 @@ object TerminalSolver : Module( textWithWordWrap(mc.font, Component.literal(line), 5, 20 + (index * 10), 300, Colors.WHITE.rgba) } - menu.items.forEachIndexed { index, stack -> + items.forEachIndexed { index, stack -> item(stack, 5 + (index % 9) * 18, 250 + (index / 9) * 18) itemDecorations(mc.font, stack, 5 + (index % 9) * 18, 250 + (index / 9) * 18) } @@ -9,7 +9,6 @@ import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.features.impl.boss.termsim.TermSimGUI -import com.odtheking.odin.utils.ChatManager.hideMessage import com.odtheking.odin.utils.PersonalBest import com.odtheking.odin.utils.modMessage @@ -48,7 +47,7 @@ object TerminalTimes : Module( on<ChatMessageEvent> { if (!terminalSplits) return@on terminalCompleteRegex.find(value)?.destructured?.let { (name, activated, type, current, total) -> - hideMessage() + cancel() modMessage("§6$name §a$activated a $type! (§c${current}§a/${total}) §8(§7${sectionTimer.seconds}s §8| §7${phaseTimer.seconds}s§8)", "") @@ -14,7 +14,7 @@ object NumbersSim : TermSimGUI( ) { override fun create() { val used = (1..14).shuffled().toMutableList() - setSlots { + setSlots { if (floor(it.index / 9f) in 1f..2f && it.index % 9 in 1..7) ItemStack(Items.RED_STAINED_GLASS_PANE, used.first()).apply { set(DataComponents.CUSTOM_NAME, Component.literal("§a${used.removeFirst()}")) } else blackPane } @@ -16,7 +16,7 @@ object PanesSim : TermSimGUI( private val redPane get() = ItemStack(Items.RED_STAINED_GLASS_PANE).apply { set(DataComponents.CUSTOM_NAME, Component.literal("")) } override fun create() { - setSlots { + setSlots { if (floor(it.index / 9f) in 1f..3f && it.index % 9 in 2..6) if (Math.random() > 0.75) greenPane else redPane else blackPane } } @@ -20,7 +20,7 @@ object RubixSim : TermSimGUI( private val indices = listOf(12, 13, 14, 21, 22, 23, 30, 31, 32) override fun create() { - setSlots { + setSlots { if (floor(it.index / 9f) in 1f..3f && it.index % 9 in 3..5) getPane() else blackPane } @@ -22,7 +22,7 @@ class SelectAllSim( ) { override fun create() { val guaranteed = (10..16).plus(19..25).plus(28..34).plus(37..43).random() - setSlots { slot -> + setSlots { slot -> if (floor(slot.index / 9.0) in 1.0..4.0 && slot.index % 9 in 1..7) { val item = ItemStack(getPossibleItems(color).random()) @@ -7,7 +7,6 @@ import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module -import com.odtheking.odin.utils.ChatManager.hideMessage import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.equalsOneOf import com.odtheking.odin.utils.formatTime @@ -46,7 +45,7 @@ object SupplyHelper : Module( if (KuudraUtils.phase != 1) return@on val (name, current, total) = supplyPickUpRegex.find(value)?.destructured ?: return@on modMessage("§6$name §a§lrecovered a supply in ${formatTime(System.currentTimeMillis() - startRun)}! §r§8($current/$total)", "") - hideMessage() + cancel() } } } @@ -1,17 +0,0 @@ -package com.odtheking.odin.utils - -import com.odtheking.odin.events.ChatMessageEvent -import net.minecraft.network.chat.Component -import java.util.concurrent.ConcurrentLinkedQueue - -object ChatManager { - private val cancelQueue = ConcurrentLinkedQueue<Component>() - - fun ChatMessageEvent.hideMessage() { - cancelQueue.add(component) - } - - internal fun shouldCancelMessage(message: Component): Boolean { - return cancelQueue.remove(message) - } -} \ No newline at end of file |
Ported terminal gui changes查看变更src/main/java/com/odtheking/mixin/mixins/AbstractContainerMenuMixin.java | 26 ++++++++++++++++++ src/main/java/com/odtheking/mixin/mixins/AbstractContainerScreenMixin.java | 2 +- src/main/java/com/odtheking/mixin/mixins/ClientPacketListenerMixin.java | 13 --------- src/main/java/com/odtheking/mixin/mixins/MinecraftMixin.java | 9 +++++++ src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 7 ++++- src/main/kotlin/com/odtheking/odin/events/EventDispatcher.kt | 4 --- src/main/kotlin/com/odtheking/odin/events/GuiEvent.kt | 7 +---- src/main/kotlin/com/odtheking/odin/events/ScreenEvent.kt | 2 -- src/main/kotlin/com/odtheking/odin/features/impl/boss/MelodyMessage.kt | 49 +++++++++++++++++++++++----------- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 23 ++++++++-------- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSounds.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/termGUI/CustomTermGui.kt | 9 +++---- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/MelodySim.kt | 19 +++++++------ src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/NumbersSim.kt | 7 ++--- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/PanesSim.kt | 4 +-- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/RubixSim.kt | 9 ++----- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/SelectAllSim.kt | 8 +++--- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/StartGUI.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/StartsWithSim.kt | 19 +++++++++---- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/TermSimGUI.kt | 24 +++++------------ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BreakerDisplay.kt | 8 +++--- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 3 ++- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/LeapMenu.kt | 11 ++++---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BlazeSolver.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/nether/RemovePerks.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt | 30 ++++++++++----------- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/LoadoutKeybinds.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PetKeybinds.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/SlotBinds.kt | 5 ++-- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/WardrobeKeybinds.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/Utils.kt | 4 +-- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/TerminalTypes.kt | 10 +++---- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/TerminalUtils.kt | 62 +++++++++++++------------------------------ src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/MelodyHandler.kt | 26 +++++++++--------- .../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/NumbersHandler.kt | 13 +++++---- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/PanesHandler.kt | 9 +++---- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/RubixHandler.kt | 73 ++++++++++++++++++++------------------------------ .../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/SelectAllHandler.kt | 10 +++---- .../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/StartsWithHandler.kt | 41 +++++++++++----------------- .../kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/TerminalHandler.kt | 75 ++++++++++++++++++++-------------------------------- src/main/resources/odin.mixins.json | 1 + 41 files changed, 295 insertions(+), 341 deletions(-) @@ -0,0 +1,26 @@ +package com.odtheking.mixin.mixins; + +import com.odtheking.odin.events.SetSlotEvent; +import net.minecraft.core.NonNullList; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Final; +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.callback.CallbackInfo; + +@Mixin(AbstractContainerMenu.class) +public class AbstractContainerMenuMixin { + + @Shadow + @Final + public NonNullList<Slot> slots; + + @Inject(method = "setItem", at = @At("TAIL")) + private void postSetSlot(int slot, int stateId, ItemStack itemStack, CallbackInfo ci) { + new SetSlotEvent(slot, itemStack, slots, (AbstractContainerMenu) (Object) this).postAndCatch(); + } +} \ No newline at end of file @@ -26,7 +26,7 @@ public class AbstractContainerScreenMixin { @Inject(method = "slotClicked", at = @At("HEAD"), cancellable = true) private void onMouseClickedSlot(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci) { - if (new GuiEvent.SlotClick((Screen) (Object) this, slot, slotId, buttonNum, containerInput).postAndCatch()) ci.cancel(); + if (new GuiEvent.SlotClick((Screen) (Object) this, slotId, buttonNum).postAndCatch()) ci.cancel(); } @Inject(method = "extractTooltip", at = @At("HEAD"), cancellable = true) @@ -3,19 +3,12 @@ package com.odtheking.mixin.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import com.odtheking.odin.events.GuiEvent; import com.odtheking.odin.events.PacketEvent; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.network.PacketListener; import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; 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; - -import static com.odtheking.odin.OdinMod.getMc; @Mixin(ClientPacketListener.class) public class ClientPacketListenerMixin { @@ -25,12 +18,6 @@ public class ClientPacketListenerMixin { return true; } - @Inject(method = "handleContainerSetSlot", at = @At("TAIL")) - private void onSetSlot(ClientboundContainerSetSlotPacket packet, CallbackInfo ci) { - if (getMc().screen instanceof AbstractContainerScreen<?> container) - new GuiEvent.SlotUpdate(getMc().screen, packet, container.getMenu()).postAndCatch(); - } - @WrapOperation(method = "handleBundlePacket", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/Packet;handle(Lnet/minecraft/network/PacketListener;)V")) private void wrapPacketHandle(Packet<?> packet, PacketListener listener, Operation<Void> original) { if (new PacketEvent.Receive(packet).postAndCatch()) return; @@ -3,9 +3,12 @@ package com.odtheking.mixin.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.odtheking.odin.events.BlockInteractEvent; import com.odtheking.odin.events.EntityInteractEvent; +import com.odtheking.odin.events.ScreenCloseEvent; +import com.odtheking.odin.events.ScreenEvent; import com.odtheking.odin.features.impl.boss.TerminalSolver; import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; @@ -40,4 +43,10 @@ public abstract class MinecraftMixin { if (TerminalUtils.getCurrentTerm() != null && TerminalSolver.getTermSize() != (Integer) original) return TerminalSolver.getTermSize(); return original; } + + @Inject(method = "setScreen", at = @At(value = "HEAD")) + private void onSetScreen(Screen screen, CallbackInfo ci) { + if (screen != null) new ScreenEvent.Open(screen).postAndCatch(); + else ScreenCloseEvent.INSTANCE.postAndCatch(); + } } \ No newline at end of file @@ -20,6 +20,9 @@ import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionResult import net.minecraft.world.entity.Entity import net.minecraft.world.entity.item.ItemEntity +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.ItemStack import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.phys.BlockHitResult import net.minecraft.world.phys.Vec3 @@ -81,4 +84,6 @@ data object MapUpdateEvent : Event class CheckmarkUpdateEvent(val room: DungeonRoom, val checkmark: MapCheckmark) : Event class SecretsUpdateEvent(val room: DungeonRoom, val foundSecrets: Int) : Event -object LocationChangeEvent : Event \ No newline at end of file +object LocationChangeEvent : Event +object ScreenCloseEvent : Event +class SetSlotEvent(val slotIndex: Int, val itemStack: ItemStack, val slots: List<Slot>, val menu: AbstractContainerMenu) : Event \ No newline at end of file @@ -40,11 +40,7 @@ object EventDispatcher { RenderEvent.Last(context).postAndCatch() } - ScreenEvents.AFTER_INIT.register { _, screen, _, _ -> ScreenEvent.Open(screen).postAndCatch() } ScreenEvents.BEFORE_INIT.register { _, screen, _, _ -> - ScreenEvents.remove(screen).register { - ScreenEvent.Close(screen).postAndCatch() - } ScreenMouseEvents.allowMouseClick(screen).register { screen, event -> !ScreenEvent.MouseClick(screen, event).postAndCatch() } @@ -3,18 +3,13 @@ package com.odtheking.odin.events import com.odtheking.odin.events.core.CancellableEvent import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.screens.Screen -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket -import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.inventory.ContainerInput import net.minecraft.world.inventory.Slot abstract class GuiEvent(val screen: Screen) : CancellableEvent() { class Render(screen: Screen, val guiGraphics: GuiGraphicsExtractor, val mouseX: Int, val mouseY: Int) : GuiEvent(screen) - class SlotClick(screen: Screen, val slot: Slot?, val slotId: Int, val button: Int, val containerInput: ContainerInput) : GuiEvent(screen) - - class SlotUpdate(screen: Screen, val packet: ClientboundContainerSetSlotPacket, val menu: AbstractContainerMenu) : GuiEvent(screen) + class SlotClick(screen: Screen, val slotIndex: Int, val button: Int) : GuiEvent(screen) class RenderSlot(screen: Screen, val guiGraphics: GuiGraphicsExtractor, val slot: Slot) : GuiEvent(screen) @@ -10,8 +10,6 @@ abstract class ScreenEvent(val screen: Screen) : CancellableEvent() { class Open(screen: Screen) : ScreenEvent(screen) - class Close(screen: Screen) : ScreenEvent(screen) - class MouseClick(screen: Screen, val click: MouseButtonEvent) : ScreenEvent(screen) class MouseRelease(screen: Screen, val click: MouseButtonEvent) : ScreenEvent(screen) @@ -5,8 +5,8 @@ import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting import com.odtheking.odin.clickgui.settings.impl.StringSetting import com.odtheking.odin.events.ChatMessageEvent -import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.SetSlotEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -39,18 +39,34 @@ object MelodyMessage : Module( private val broadcast by BooleanSetting("Broadcast Progress", true, desc = "Broadcasts melody progress to all other odin users in the party.") private val melodyGui by HUD("Progress GUI", "Shows a gui with the progress of broadcasting odin users in melody.", true) { - if (it) drawMelody(MelodyData(3, 1, 2, DungeonClass.ARCHER), 0, mc.user.name) + var rows = 0 + var labelWidth = 0 + + fun track(data: MelodyData, index: Int, name: String) { + rows = maxOf(rows, index + 1) + val clay = data.clay ?: return + val label = melodyLabel(data, name) ?: return + labelWidth = maxOf(labelWidth, getStringWidth("$clay $label")) + } + + if (it) { + val example = MelodyData(3, 1, 2, DungeonClass.ARCHER) + drawMelody(example, 0, mc.user.name) + track(example, 0, mc.user.name) + } if (broadcast && melodyWebSocket.connected) { melodies.entries.forEachIndexed { i, (name, data) -> if (showPlayer == 0 && name == mc.user.name) return@forEachIndexed drawMelody(data, i, name) + track(data, i, name) } } - 40 to 15 + + (width * 5 + 2 + labelWidth) to (width * 2 * rows) }.withDependency { broadcast } - private val showPlayer by SelectorSetting("Show Player", "Name", arrayListOf("None", "Class", "Name", "Class & Name"), desc = "How player details should be rendered in the Melody GUI.").withDependency { broadcast } + private val showPlayer by SelectorSetting("Show Player", "None", arrayListOf("None", "Class", "Name", "Class & Name"), desc = "How player details should be rendered in the Melody GUI.").withDependency { broadcast } val melodyWebSocket = webSocket { onMessage { message -> @@ -80,7 +96,7 @@ object MelodyMessage : Module( if (broadcast || melodyProgress) onChatMessage(value) } - on<GuiEvent.SlotUpdate> { + on<SetSlotEvent> { if (broadcast || melodyProgress) onSlotUpdate(this) } @@ -109,13 +125,13 @@ object MelodyMessage : Module( } } - private fun onSlotUpdate(event: GuiEvent.SlotUpdate) { + private fun onSlotUpdate(event: SetSlotEvent) { val term = TerminalUtils.currentTerm ?: return if (DungeonUtils.getF7Phase() != M7Phases.P3 || term.type != TerminalTypes.MELODY || mc.screen is TermSimGUI) return - val item = event.packet.item.item + val item = event.itemStack.item if (item == Items.LIME_TERRACOTTA) { - val position = event.packet.slot / 9 + val position = event.slotIndex / 9 if (lastSent.clay == position) return if (broadcast) melodyWebSocket.send(update(1, position)) if (melodyProgress) clayProgress[position]?.let { sendCommand("pc $it") } @@ -123,7 +139,7 @@ object MelodyMessage : Module( return } if (!broadcast || !item.equalsOneOf(Items.MAGENTA_STAINED_GLASS_PANE, Items.LIME_STAINED_GLASS_PANE)) return - val index = mapToRange(event.packet.slot) ?: return + val index = mapToRange(event.slotIndex) ?: return val meta = when (item) { Items.MAGENTA_STAINED_GLASS_PANE -> { if (lastSent.purple == index) return @@ -155,6 +171,13 @@ object MelodyMessage : Module( private val width by lazy { getStringWidth("§d■") } + private fun melodyLabel(data: MelodyData, playerName: String): String? = when (showPlayer) { + 1 -> data.dungeonClass.toString() + 2 -> playerName + 3 -> "$playerName (${data.dungeonClass})" + else -> null + } + private fun GuiGraphicsExtractor.drawMelody(data: MelodyData, index: Int, playerName: String) { val y = width * 2 * index @@ -163,13 +186,7 @@ object MelodyMessage : Module( textDim("${if (data.pane == it) "§a" else "§f"}■", width * it, y + width) } - val label = when (showPlayer) { - 1 -> data.dungeonClass - 2 -> playerName - 3 -> "$playerName (${data.dungeonClass})" - else -> return - } - + val label = melodyLabel(data, playerName) ?: return data.clay?.let { textDim("$it $label", width * 5 + 2, y + width / 2) } } @@ -15,6 +15,7 @@ import net.minecraft.client.Options import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.network.chat.Component +import net.minecraft.world.item.Items import org.lwjgl.glfw.GLFW object TerminalSolver : Module( @@ -33,13 +34,12 @@ object TerminalSolver : Module( private val cancelMelodySolver by BooleanSetting("Stop Melody Solver", false, desc = "Stops rendering the melody solver.").withDependency { solverSettings } val melodyTermSize by NumberSetting("Melody Size", 1.5f, 1f, 3f, 0.1f, desc = "The size of the melody terminal GUI.").withDependency { !cancelMelodySolver && solverSettings && renderType == 2 } val showNumbers by BooleanSetting("Show Numbers", true, desc = "Shows numbers in the order terminal.").withDependency { solverSettings } - private val firstClickProtSettings by DropdownSetting("First Click Protect Dropdown") - val firstClickProt by NumberSetting("First Click Protection", 500, 350, 800, 10, unit = "ms", desc = "The amount of time after opening a terminal where clicks are blocked to prevent bans (recommended value is 500 minus your ping).").withDependency { firstClickProtSettings } + val hideClicked by BooleanSetting("Client Prediction", true, desc = "Visually predicts the server state before the gui update is sent to the client.").withDependency { solverSettings } + val terminalReloadThreshold by NumberSetting("Resolve timeout", 600, 300, 1200, 10, unit = "ms", desc = "The amount of time before the terminal reloads after a click wasn't registered while using hide clicked.").withDependency { hideClicked && solverSettings } + private val firstClickProtSettings by DropdownSetting("First Click Prot Dropdown") + val firstClickProt by NumberSetting("First Click Prot", 500, 350, 800, 10, unit = "ms", desc = "The amount of time after opening a terminal where clicks are blocked to prevent bans (recommended value is 500 minus your ping).").withDependency { firstClickProtSettings } val shouldFirstClickProtWithTicks by BooleanSetting("Account For Server Lag", false, desc = "Prevents bans from clicking when the server lags after opening the terminal (disabled in singleplayer").withDependency { firstClickProtSettings } val firstClickProtTicks by NumberSetting("Lag Protection Ticks", 8, 7, 16, unit = "ticks", desc = "Each tick = 50ms (recommended value is 8)").withDependency { shouldFirstClickProtWithTicks && firstClickProtSettings } - val hideClicked by BooleanSetting("Hide Clicked", false, desc = "Visually hides your first click before a gui updates instantly to improve perceived response time. Does not affect actual click time.").withDependency { solverSettings } - val terminalReloadThreshold by NumberSetting("Resolve timeout", 600, 300, 1000, 10, unit = "ms", desc = "The amount of time before the terminal reloads after a click wasn't registered while using hide clicked.").withDependency { hideClicked && solverSettings } - private val debug by BooleanSetting("Debug", false, desc = "Shows debug terminals.").withDependency { solverSettings } private val showColors by DropdownSetting("Color Settings") val backgroundColor by ColorSetting("Background", Colors.gray26, true, desc = "Background color of the terminal solver.").withDependency { showColors } @@ -61,6 +61,7 @@ object TerminalSolver : Module( val melodyColumColor by ColorSetting("Melody Column", Colors.MINECRAFT_DARK_PURPLE, true, desc = "Color of the column indicator for melody.").withDependency { showColors && !cancelMelodySolver } val melodyPointerColor by ColorSetting("Melody Pointer", Colors.MINECRAFT_GREEN, true, desc = "Color of the location for pressing for melody.").withDependency { showColors && !cancelMelodySolver } val melodyBackgroundColor by ColorSetting("Melody Background", Colors.gray38, true, desc = "Color of the background slot in melody.").withDependency { showColors && !cancelMelodySolver } + private val debug by BooleanSetting("Debug", false, desc = "Shows debug terminals.").withDependency { showColors } @JvmStatic val termSize get() = if (enabled && (renderType == 0 || renderType == 1) && TerminalUtils.currentTerm != null) if (normalTermSize == 6) Options.AUTO_GUI_SCALE else normalTermSize else 1 val customGuiEnabled get() = enabled && renderType == 2 && renderMelody @@ -74,16 +75,16 @@ object TerminalSolver : Module( on<GuiEvent.SlotClick> { val term = TerminalUtils.currentTerm ?: return@on - if (blockIncorrectClicks && !term.canClick(slotId, button)) return@on cancel() + if (blockIncorrectClicks && !term.canClick(slotIndex, button)) return@on cancel() if (term.shouldProtect()) return@on cancel() if (middleClickGUI) { - term.click(slotId, if (button == 0) GLFW.GLFW_MOUSE_BUTTON_3 else button, hideClicked && !term.isClicked) + term.click(slotIndex, if (button == 0) GLFW.GLFW_MOUSE_BUTTON_3 else button, hideClicked) return@on cancel() } - if (hideClicked && !term.isClicked) term.simulateClick(slotId, button) + if (hideClicked) term.simulateClick(slotIndex, button) } on<GuiEvent.Render> { @@ -130,9 +131,8 @@ object TerminalSolver : Module( "§7Container ID: §f${menu.containerId}", "§7Time Open: §f${System.currentTimeMillis() - term.timeOpened}ms", "§7Ticks Open: §f${term.ticksOpened}", - "§7Is Clicked: §f${term.isClicked}", - "§7Window Count: §f${term.windowCount}", "§7Solution: §f${term.solution.joinToString(", ")}", + "§7Clicked Slots: §f${term.clickedSlots}", ) pose().pushMatrix() @@ -140,7 +140,8 @@ object TerminalSolver : Module( pose().scale(1f / sf, 1f / sf) pose().scale(3f) - textWithWordWrap(mc.font, Component.literal(menu.items.filter { !it.isEmpty }.map { stack -> stack.hoverName.string }.toString()), 400, 0, 300, Colors.WHITE.rgba) + val items = menu.items.subList(0, term.type.windowSize) + textWithWordWrap(mc.font, Component.literal(items.filter { !it.isEmpty && it.item != Items.BLACK_STAINED_GLASS_PANE }.map { stack -> stack.hoverName.string }.toString()), 400, 0, 300, Colors.WHITE.rgba) debugInfo.forEachIndexed { index, line -> textWithWordWrap(mc.font, Component.literal(line), 5, 20 + (index * 10), 300, Colors.WHITE.rgba) @@ -39,7 +39,7 @@ object TerminalSounds : Module( } on<GuiEvent.SlotClick> (EventPriority.HIGHEST) { - if (shouldReplaceSounds) playSoundForSlot(slotId, button) + if (shouldReplaceSounds) playSoundForSlot(slotIndex, button) } on<GuiEvent.CustomTermGuiClick> (EventPriority.HIGHEST) { @@ -66,10 +66,9 @@ abstract class TermGui { private fun currentTermScreen() = mc.screen as? AbstractContainerScreen<*> - private fun isActiveTermScreen(): Boolean { - if (!TerminalSolver.customGuiEnabled || TerminalUtils.currentTerm == null || currentTermScreen() == null) return false - return TerminalUtils.currentTerm?.type?.getGUI() === this - } + private fun isActiveTermScreen(): Boolean = + !(!TerminalSolver.customGuiEnabled || TerminalUtils.currentTerm == null || currentTermScreen() == null) && + TerminalUtils.currentTerm?.type?.getGUI() === this private fun isTerminalOverrideKey(event: KeyEvent) = mc.options.keyDrop.matches(event) || mc.options.keyHotbarSlots.any { it.matches(event) } @@ -110,7 +109,7 @@ abstract class TermGui { val btn = if (button == 0) GLFW.GLFW_MOUSE_BUTTON_3 else button if (term.shouldProtect()) return@let if (!GuiEvent.CustomTermGuiClick(screen, slotIndex, btn).postAndCatch() && term.canClick(slotIndex, btn)) - term.click(slotIndex, btn, hideClicked && !term.isClicked) + term.click(slotIndex, btn, hideClicked) } } @@ -28,7 +28,7 @@ object MelodySim : TermSimGUI( magentaColumn = (1..5).random() limeColumn = 1 limeDirection = 1 - createNewGui { it.generateItemStack() } + setSlots { it.generateItemStack() } } private var counter = 0 @@ -37,25 +37,26 @@ object MelodySim : TermSimGUI( if (counter++ % 10 != 0) return limeColumn += limeDirection if (limeColumn == 1 || limeColumn == 5) limeDirection *= -1 - updateGui() + updateRow(currentRow) super.containerTick() } override fun slotClick(slot: Slot, button: Int) { if (slot.index % 9 != 7 || limeColumn != magentaColumn || slot.index / 9 != currentRow) return + val oldRow = currentRow magentaColumn = (1 until 5).random() currentRow++ - updateGui() + updateRow(0) + updateRow(oldRow) + if (currentRow <= 4) updateRow(currentRow) if (currentRow >= 5) TerminalUtils.lastTermOpened?.onComplete() super.slotClick(slot, button) } - private fun updateGui() { - guiInventorySlots.forEachIndexed { index, currentStack -> - currentStack?.setSlot(guiInventorySlots.map { it.generateItemStack() }.getOrNull(index)?.takeIf { it != currentStack.item } ?: return@forEachIndexed) - } + private fun updateRow(row: Int) { + for (col in 0..8) guiInventorySlots[row * 9 + col].let { it.setSlot(it.generateItemStack()) } } private fun Slot.generateItemStack(): ItemStack { @@ -69,6 +70,4 @@ object MelodySim : TermSimGUI( else -> blackPane } } -} - - +} \ No newline at end of file @@ -14,7 +14,7 @@ object NumbersSim : TermSimGUI( ) { override fun create() { val used = (1..14).shuffled().toMutableList() - createNewGui { + setSlots { if (floor(it.index / 9f) in 1f..2f && it.index % 9 in 1..7) ItemStack(Items.RED_STAINED_GLASS_PANE, used.first()).apply { set(DataComponents.CUSTOM_NAME, Component.literal("§a${used.removeFirst()}")) } else blackPane } @@ -22,10 +22,7 @@ object NumbersSim : TermSimGUI( override fun slotClick(slot: Slot, button: Int) { if (guiInventorySlots.minByOrNull { if (it.item.item == Items.RED_STAINED_GLASS_PANE) it.item.count else 1000 } != slot) return - createNewGui { - if (it == slot) ItemStack(Items.LIME_STAINED_GLASS_PANE, slot.item.count).apply { set(DataComponents.CUSTOM_NAME, Component.literal("")) } - else it.item - } + slot.setSlot(ItemStack(Items.LIME_STAINED_GLASS_PANE, slot.item.count).apply { set(DataComponents.CUSTOM_NAME, Component.literal("")) }) if (guiInventorySlots.none { it?.item?.item == Items.RED_STAINED_GLASS_PANE }) TerminalUtils.lastTermOpened?.onComplete() @@ -16,13 +16,13 @@ object PanesSim : TermSimGUI( private val redPane get() = ItemStack(Items.RED_STAINED_GLASS_PANE).apply { set(DataComponents.CUSTOM_NAME, Component.literal("")) } override fun create() { - createNewGui { + setSlots { if (floor(it.index / 9f) in 1f..3f && it.index % 9 in 2..6) if (Math.random() > 0.75) greenPane else redPane else blackPane } } override fun slotClick(slot: Slot, button: Int) { - createNewGui { if (it == slot) { if (slot.item.item == Items.RED_STAINED_GLASS_PANE) greenPane else redPane } else it.item } + slot.setSlot(if (slot.item.item == Items.RED_STAINED_GLASS_PANE) greenPane else redPane) if (guiInventorySlots.none { it?.item?.item == Items.RED_STAINED_GLASS_PANE }) TerminalUtils.lastTermOpened?.onComplete() @@ -20,7 +20,7 @@ object RubixSim : TermSimGUI( private val indices = listOf(12, 13, 14, 21, 22, 23, 30, 31, 32) override fun create() { - createNewGui { + setSlots { if (floor(it.index / 9f) in 1f..3f && it.index % 9 in 3..5) getPane() else blackPane } @@ -28,12 +28,7 @@ object RubixSim : TermSimGUI( override fun slotClick(slot: Slot, button: Int) { val current = order.find { it == ((slot.item.item as? BlockItem)?.block as? StainedGlassPaneBlock)?.color } ?: return - createNewGui { - if (it == slot) { - if (button == 1) genStack(order.indexOf(current) - 1) - else genStack((order.indexOf(current) + 1) % order.size) - } else it.item - } + slot.setSlot(if (button == 1) genStack(order.indexOf(current) - 1) else genStack((order.indexOf(current) + 1) % order.size)) if (indices.all { guiInventorySlots[it]?.item?.item == guiInventorySlots[12]?.item?.item }) TerminalUtils.lastTermOpened?.onComplete() @@ -22,7 +22,7 @@ class SelectAllSim( ) { override fun create() { val guaranteed = (10..16).plus(19..25).plus(28..34).plus(37..43).random() - createNewGui { slot -> + setSlots { slot -> if (floor(slot.index / 9.0) in 1.0..4.0 && slot.index % 9 in 1..7) { val item = ItemStack(getPossibleItems(color).random()) @@ -53,11 +53,9 @@ class SelectAllSim( override fun slotClick(slot: Slot, button: Int) { val possibleItems = getPossibleItems(color) if (!possibleItems.contains(slot.item.item)) return modMessage("§cThat item is not: ${color.name.uppercase()}!") + if (slot.item.hasGlint()) return modMessage("§cAlready selected!") - createNewGui { - if (it == slot) slot.item.apply { set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true) } - else it.item - } + slot.setSlot(slot.item.apply { set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true) }) if (guiInventorySlots.none { !it.item.hasGlint() && possibleItems.contains(it.item.item) }) TerminalUtils.lastTermOpened?.onComplete() @@ -41,7 +41,7 @@ object StartGUI : TermSimGUI( override fun create() { val termItems = buildTermItems() - createNewGui { + setSlots { when (it.index) { 4 -> resetButton 13 -> randomButton @@ -15,8 +15,10 @@ class StartsWithSim(private val letter: String = listOf("A", "B", "C", "G", "D", "What starts with: \'$letter\'?", TerminalTypes.STARTS_WITH.windowSize ) { + private val clickedOverrides = ArrayList<Int>() + override fun create() { - createNewGui { + setSlots { when { floor(it.index / 9f) !in 1f..3f || it.index % 9 !in 1..7 -> blackPane it.index == (10..16).random() -> getLetterItemStack() @@ -26,12 +28,14 @@ class StartsWithSim(private val letter: String = listOf("A", "B", "C", "G", "D", } } - override fun slotClick(slot: Slot, button: Int) = with(slot.item) { - if (!hoverName.string.startsWith(letter, true) || hasGlint()) return@with modMessage("§cThat item does not start with: \'$letter\'!") + override fun slotClick(slot: Slot, button: Int) { + if (!slot.item.hoverName.string.startsWith(letter, true)) return modMessage("§cThat item does not start with: \'$letter\'!") + if (slot.item.hasRealGlint() || slot.index in clickedOverrides) return modMessage("§cAlready selected!") - createNewGui { if (it == slot) apply { set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, false) } else it.item } + if (slot.item.item in enchantOverrides) clickedOverrides.add(slot.index) + slot.setSlot(slot.item.apply { set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true) }) - if (guiInventorySlots.none { it.item.hoverName.string.startsWith(letter, true) && !it.item.hasGlint() }) + if (guiInventorySlots.all { !it.item.hoverName.string.startsWith(letter, true) || it.item.hasRealGlint() || it.index in clickedOverrides }) TerminalUtils.lastTermOpened?.onComplete() super.slotClick(slot, button) @@ -46,4 +50,9 @@ class StartsWithSim(private val letter: String = listOf("A", "B", "C", "G", "D", return ItemStack(matchingItem) } + + private companion object { + fun ItemStack.hasRealGlint(): Boolean = hasGlint() && item !in enchantOverrides + val enchantOverrides = BuiltInRegistries.ITEM.filter { it.components().has(DataComponents.ENCHANTMENT_GLINT_OVERRIDE) } + Items.GOLDEN_APPLE + } } \ No newline at end of file @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.boss.termsim import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.GuiEvent -import com.odtheking.odin.events.PacketEvent +import com.odtheking.odin.events.ScreenCloseEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.features.impl.boss.TerminalSounds import com.odtheking.odin.utils.handlers.schedule @@ -11,9 +11,6 @@ import com.odtheking.odin.utils.skyblock.dungeon.terminals.terminalhandler.Termi import net.minecraft.client.gui.screens.inventory.ContainerScreen import net.minecraft.core.component.DataComponents import net.minecraft.network.chat.Component -import net.minecraft.network.protocol.game.ClientboundContainerClosePacket -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket -import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket import net.minecraft.sounds.SoundEvents import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory @@ -44,12 +41,10 @@ open class TermSimGUI( ) { val blackPane = ItemStack(Items.BLACK_STAINED_GLASS_PANE).apply { set(DataComponents.CUSTOM_NAME, Component.literal("")) } protected val guiInventorySlots get() = menu.slots.subList(0, size) - private var doesAcceptClick = true protected var ping = 0L - private var syncId = 0 open fun create() { - guiInventorySlots.forEach { it.setSlot(blackPane) } + setSlots { blackPane } } fun open(terminalPing: Long = 0L) { @@ -63,18 +58,17 @@ open class TermSimGUI( } internal fun TerminalHandler.onComplete() { - PacketEvent.Receive(ClientboundContainerClosePacket(-2)).postAndCatch() + ScreenCloseEvent.postAndCatch() TerminalEvent.Solve(this).postAndCatch() StartGUI.open(ping) } override fun onClose() { - doesAcceptClick = true super.onClose() } override fun slotClicked(slot: Slot, i: Int, j: Int, clickType: ContainerInput) { - if (GuiEvent.SlotClick(this, slot, i, j, clickType).postAndCatch()) return + if (GuiEvent.SlotClick(this, i, j).postAndCatch()) return slot?.let { delaySlotClick(it, j) } } @@ -85,23 +79,19 @@ open class TermSimGUI( private fun delaySlotClick(slot: Slot, button: Int) { if (mc.screen == StartGUI) return slotClick(slot, button) - if (!doesAcceptClick || slot.container != inv || slot.item.item == Items.BLACK_STAINED_GLASS_PANE) return + if (slot.container != inv || slot.item.item == Items.BLACK_STAINED_GLASS_PANE) return if (ping <= 0L) return slotClick(slot, button) - doesAcceptClick = false schedule((ping / 50).toInt().coerceAtLeast(0)) { - doesAcceptClick = true if (mc.screen == this) slotClick(slot, button) } } - protected fun createNewGui(block: (Slot) -> ItemStack) { - PacketEvent.Receive(ClientboundOpenScreenPacket(syncId++, MenuType.GENERIC_9x3, Component.literal(name))).postAndCatch() + protected fun setSlots(block: (Slot) -> ItemStack) { guiInventorySlots.forEach { it.setSlot(block(it)) } } protected fun Slot.setSlot(stack: ItemStack) { - GuiEvent.SlotUpdate(mc.screen ?: return, ClientboundContainerSetSlotPacket(-2, 0, index, stack), menu).postAndCatch() - set(stack) + menu.setItem(index, menu.incrementStateId(), stack) } protected fun playTermSimSound() { @@ -1,7 +1,7 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.GuiEvent +import com.odtheking.odin.events.SetSlotEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors @@ -25,9 +25,9 @@ object BreakerDisplay : Module( private val renderFull by BooleanSetting("Render full", false, desc = "Renders the HUD even when the Dungeon Breaker is full.") init { - on<GuiEvent.SlotUpdate> { - if (!DungeonUtils.inDungeons || packet.item.itemId != "DUNGEONBREAKER") return@on - packet.item.loreString.firstNotNullOfOrNull { chargesRegex.find(it) }?.let { match -> + on<SetSlotEvent> { + if (!DungeonUtils.inDungeons || itemStack.itemId != "DUNGEONBREAKER") return@on + itemStack.loreString.firstNotNullOfOrNull { chargesRegex.find(it) }?.let { match -> charges = match.groupValues[1].toIntOrNull() ?: 0 maxCharges = match.groupValues[2].toIntOrNull() ?: 0 } @@ -9,6 +9,7 @@ import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.ScreenEvent +import com.odtheking.odin.events.SetSlotEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -137,7 +138,7 @@ object Croesus : Module( } } - on<GuiEvent.SlotUpdate> { + on<SetSlotEvent> { val screenTitle = mc.screen?.title?.string ?: return@on val items = (mc.screen as? AbstractContainerScreen<*>)?.menu?.items ?: return@on @@ -6,6 +6,7 @@ import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.ScreenEvent +import com.odtheking.odin.events.SetSlotEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.* @@ -172,11 +173,11 @@ object LeapMenu : Module( leapedRegex.find(value)?.groupValues?.get(1)?.let { sendCommand("pc Leaped to ${it}!") } } - on<GuiEvent.SlotUpdate> { + on<SetSlotEvent> { currentLeapScreen()?.let { - if (packet.item.isEmpty || packet.item.item != Items.PLAYER_HEAD) return@on - val (name) = playerNameRegex.find(packet.item.hoverName.string)?.destructured ?: return@on - leapIndex[name] = packet.slot + if (itemStack.isEmpty || itemStack.item != Items.PLAYER_HEAD) return@on + val (name) = playerNameRegex.find(itemStack.hoverName.string)?.destructured ?: return@on + leapIndex[name] = slotIndex } } @@ -198,7 +199,7 @@ object LeapMenu : Module( val index = screenHandler.menu.slots.subList(11, 16).firstOrNull { it.item.hoverName.string.substringAfter(' ').equals(name.noControlCodes, true) }?.index ?: return - mc.player?.clickSlot(screenHandler.menu.containerId, index) + mc.player?.clickSlot(index) modMessage("Teleporting to $name.") } @@ -19,7 +19,7 @@ object BlazeSolver { private var blazes = mutableListOf<ArmorStand>() private var roomType = 0 private var lastBlazeCount = 10 - private val blazeHealthRegex = Regex("^\\[Lv15] Blaze [\\d,]+/([\\d,]+)❤$") + private val blazeHealthRegex = Regex("^\\[Lv11] Blaze [\\d,]+/([\\d,]+)❤$") fun getBlaze() { if (!DungeonUtils.inDungeons || DungeonUtils.currentRoom?.data?.name?.equalsOneOf("Lower Blaze", "Higher Blaze") == false) return @@ -21,7 +21,7 @@ object RemovePerks : Module( } on<GuiEvent.SlotClick> { - if (screen is AbstractContainerScreen<*> && screen.title.string == "Perk Menu" && slotCheck(screen.menu.getSlot(slotId).item.hoverName.string)) + if (screen is AbstractContainerScreen<*> && screen.title.string == "Perk Menu" && slotCheck(screen.menu.getSlot(slotIndex).item.hoverName.string)) cancel() } } @@ -3,8 +3,9 @@ package com.odtheking.odin.features.impl.nether import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.GuiEvent -import com.odtheking.odin.events.ScreenEvent +import com.odtheking.odin.events.SetSlotEvent import com.odtheking.odin.events.core.on +import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module import com.odtheking.odin.features.impl.dungeon.Croesus.cachedPrices import com.odtheking.odin.utils.* @@ -14,6 +15,7 @@ import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.network.chat.Component import net.minecraft.network.chat.Style import net.minecraft.network.chat.TextColor +import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import java.util.* @@ -74,16 +76,14 @@ object Vesuvius : Module( } } - on<GuiEvent.SlotUpdate> { - if (!screen.title.string.matches(chestRegex)) return@on + on<SetSlotEvent> { + if (mc.screen?.title?.string?.matches(chestRegex) != true) return@on - val item = packet.item - - if (packet.slot == 31 && item.item == Items.CHEST) handleKuudraChest(item) - if (packet.slot.equalsOneOf(13, 14) && item.item == Items.PLAYER_HEAD) handleKuudraChest(item) + if (slotIndex == 31 && itemStack.item == Items.CHEST) handleKuudraChest(itemStack) + if (slotIndex.equalsOneOf(13, 14) && itemStack.item == Items.PLAYER_HEAD) handleKuudraChest(itemStack) } - on<ScreenEvent.Open> { + onReceive<ClientboundOpenScreenPacket> { currentChest = null } } @@ -101,10 +101,10 @@ object Vesuvius : Module( when (style.color) { TextColor.fromLegacyFormat(ChatFormatting.GOLD) -> starCount += count TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE) -> starCount += count * 2 - } } + } Optional.empty<Unit>() - }, Style.EMPTY) + }, Style.EMPTY) val item = component.string.replace("✪", "").trim() @@ -178,9 +178,9 @@ object Vesuvius : Module( if (string.matches(uselessLinesRegex)) return@forEach val price = parseItemValue(component) ?: 0.0 - profit += price - chestItems.add(ChestItem(component, price)) - } + profit += price + chestItems.add(ChestItem(component, price)) + } currentChest = ChestData(chestItems, chestCost, (profit - chestCost)) } @@ -193,7 +193,7 @@ object Vesuvius : Module( val profit = "%,.0f".format(dataToDisplay?.profit) dataToDisplay?.items?.forEach { item -> - val price: String = "%,.0f".format(item.price) + val price = "%,.0f".format(item.price) text(mc.font,item.name, 0, yOffset, -1) text(price, maxWidth - mc.font.width(price), yOffset, Colors.MINECRAFT_GRAY) @@ -258,7 +258,7 @@ object Vesuvius : Module( ), ChestItem( Component.literal("Crimson Essence").withStyle(ChatFormatting.LIGHT_PURPLE) - .append(Component.literal(" x2000").withStyle(ChatFormatting.DARK_GRAY)), + .append(Component.literal(" x2000").withStyle(ChatFormatting.DARK_GRAY)), 2420000.0 ), ChestItem( @@ -68,7 +68,7 @@ object LoadoutKeybinds : Module( } } - mc.player?.clickSlot(screen.menu.containerId, index) + mc.player?.clickSlot(index) return true } } \ No newline at end of file @@ -74,7 +74,7 @@ object PetKeybinds : Module( else if (nounequip) return false } - mc.player?.clickSlot(screen.menu.containerId, index) + mc.player?.clickSlot(index) return true } } \ No newline at end of file @@ -2,6 +2,7 @@ package com.odtheking.odin.features.impl.skyblock import com.odtheking.odin.clickgui.settings.impl.* import com.odtheking.odin.events.GuiEvent +import com.odtheking.odin.events.ScreenCloseEvent import com.odtheking.odin.events.ScreenEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -48,7 +49,7 @@ object SlotBinds : Module( else -> return@on } - mc.player?.clickSlot(screen.menu.containerId, from, to % 36, ContainerInput.SWAP) + mc.player?.clickSlot(from, to % 36, ContainerInput.SWAP) cancel() } @@ -99,7 +100,7 @@ object SlotBinds : Module( guiGraphics.drawLine(startX.toFloat(), startY.toFloat(), endX.toFloat(), endY.toFloat(), lineColor, lineWidth) } - on<ScreenEvent.Close> { + on<ScreenCloseEvent> { previousSlot = null } } @@ -67,7 +67,7 @@ object WardrobeKeybinds : Module( } } if (disallowUnequippingEquipped && screen.menu.slots[index].item.isEmpty) return false - mc.player?.clickSlot(screen.menu.containerId, index) + mc.player?.clickSlot(index) return true } } @@ -171,8 +171,8 @@ fun BlockPos.getBlockBounds() = level.getBlockState(this).getShape(level, this).singleEncompassing().takeIf { !it.isEmpty }?.bounds() } -fun Player.clickSlot(containerId: Int, slotIndex: Int, button: Int = 0, containerInput: ContainerInput = ContainerInput.PICKUP) { - mc.gameMode?.handleContainerInput(containerId, slotIndex, button, containerInput, this) +fun Player.clickSlot(slotIndex: Int, button: Int = 0, containerInput: ContainerInput = ContainerInput.PICKUP) { + mc.gameMode?.handleContainerInput(containerMenu.containerId, slotIndex, button, containerInput, this) } fun formatNumber(numStr: String): String { @@ -46,25 +46,25 @@ enum class TerminalTypes( override fun getGUI() = gui }; - fun openHandler(guiName: String): TerminalHandler? { - return when (this) { + companion object { + fun openHandler(guiName: String): TerminalHandler? = when (entries.find { it.regex.matches(guiName) }) { PANES -> PanesHandler() RUBIX -> RubixHandler() NUMBERS -> NumbersHandler() - STARTS_WITH -> StartsWithHandler(regex.find(guiName)?.groupValues?.get(1) ?: run { + STARTS_WITH -> StartsWithHandler(STARTS_WITH.regex.find(guiName)?.groupValues?.get(1) ?: run { modMessage("Failed to find letter, please report this!") return null }) SELECT -> { SelectAllHandler(DyeColor.entries.find { - it.name.replace("_", " ") - .equals(regex.find(guiName)?.groupValues?.get(1)?.replace("SILVER", "LIGHT GRAY"), true) + it.name.replace("_", " ").equals(SELECT.regex.find(guiName)?.groupValues?.get(1)?.replace("SILVER", "LIGHT GRAY"), true) } ?: run { modMessage("Failed to find letter, please report this!") return null }) } MELODY -> MelodyHandler() + null -> return null } } } @@ -1,10 +1,7 @@ package com.odtheking.odin.utils.skyblock.dungeon.terminals import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.events.ChatMessageEvent -import com.odtheking.odin.events.GuiEvent -import com.odtheking.odin.events.TerminalEvent -import com.odtheking.odin.events.TickEvent +import com.odtheking.odin.events.* import com.odtheking.odin.events.core.EventPriority import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive @@ -14,14 +11,14 @@ import com.odtheking.odin.features.impl.boss.termsim.TermSimGUI import com.odtheking.odin.utils.devMessage import com.odtheking.odin.utils.skyblock.dungeon.terminals.terminalhandler.TerminalHandler import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen -import net.minecraft.network.protocol.game.* +import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket +import net.minecraft.network.protocol.game.ServerboundContainerClickPacket import net.minecraft.world.inventory.ContainerInput import net.minecraft.world.item.ItemStack object TerminalUtils { private val termSolverRegex = Regex("^(.{1,16}) activated a terminal! \\((\\d)/(\\d)\\)$") - private var lastClickTime = 0L @JvmStatic var currentTerm: TerminalHandler? = null private set @@ -29,42 +26,34 @@ object TerminalUtils { private set init { - onReceive<ClientboundOpenScreenPacket> (EventPriority.HIGHEST) { - val windowName = title.string - currentTerm?.let { if (!it.isClicked && it.windowCount <= 2) leftTerm() } - val newType = TerminalTypes.entries.find { it.regex.matches(windowName) } ?: return@onReceive - - if (newType != currentTerm?.type) newType.openHandler(windowName)?.let { + on<ScreenEvent.Open> (EventPriority.HIGHEST) { + TerminalTypes.openHandler(screen.title.string)?.let { devMessage("§aNew terminal: §6${it.type.name}") currentTerm = it TerminalEvent.Open(it).postAndCatch() lastTermOpened = it } - currentTerm?.openScreen() } - on<GuiEvent.SlotUpdate> { - currentTerm?.updateSlot(this) + on<ScreenCloseEvent> { + currentTerm?.let { + devMessage("§cLeft terminal: §6${it.type.name}") + currentTerm = null + TerminalEvent.Close(it).postAndCatch() + } } - onReceive<ClientboundContainerClosePacket> { leftTerm() } - onSend<ServerboundContainerClosePacket> { leftTerm() } - - onSend<ServerboundContainerClickPacket> { - lastClickTime = System.currentTimeMillis() - currentTerm?.isClicked = true + on<SetSlotEvent> { + if (menu !== (mc.screen as? AbstractContainerScreen<*>)?.menu) return@on + currentTerm?.updateSlot(this) } on<TickEvent.End> { - val term = currentTerm ?: return@on - if (System.currentTimeMillis() - lastClickTime >= TerminalSolver.terminalReloadThreshold && term.isClicked) { - val screen = (mc.screen as? AbstractContainerScreen<*>) ?: return@on - GuiEvent.SlotUpdate( - screen, - ClientboundContainerSetSlotPacket(screen.menu.containerId, 0, term.type.windowSize - 1, ItemStack.EMPTY), - screen.menu - ).postAndCatch() - term.isClicked = false + currentTerm?.let { term -> + if (term.clickedSlots.isNotEmpty() && System.currentTimeMillis() - term.lastClickTime >= TerminalSolver.terminalReloadThreshold) { + term.clickedSlots.clear() + (mc.screen as? AbstractContainerScreen<*>)?.menu?.let { SetSlotEvent(0, ItemStack.EMPTY, it.slots, it).postAndCatch() } + } } } @@ -76,11 +65,6 @@ object TerminalUtils { } } - on<GuiEvent.SlotClick> (EventPriority.HIGH) { - lastClickTime = System.currentTimeMillis() - currentTerm?.isClicked = true - } - onSend<ServerboundContainerClickPacket> (EventPriority.LOW) { val termSimScreen = mc.screen as? TermSimGUI ?: return@onSend if (containerInput != ContainerInput.PICKUP_ALL) termSimScreen.clickIndex(slotNum.toInt(), buttonNum.toInt()) @@ -94,12 +78,4 @@ object TerminalUtils { it.cancel() } } - - private fun left …… diff 过长已截断 …… |
Added dungeon core logging for future use查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonScan.kt | 15 +++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/WorldScan.kt | 6 ++++++ src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 1 + 3 files changed, 22 insertions(+) @@ -1,5 +1,7 @@ package com.odtheking.odin.features.impl.dungeon.map +import com.google.gson.reflect.TypeToken +import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.FloorEnterEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.MapUpdateEvent @@ -10,7 +12,9 @@ import com.odtheking.odin.features.impl.dungeon.map.tile.* import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.IVec2 +import com.odtheking.odin.utils.JsonResourceLoader.defaultGson import com.odtheking.odin.utils.skyblock.dungeon.Floor +import java.io.File import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList @@ -34,6 +38,17 @@ object DungeonScan { val directions = arrayOf(1 to 0, -1 to 0, 0 to 1, 0 to -1) + private val roomCoresFile = File(mc.gameDirectory, "config/odin/room-cores.json") + val roomCores: MutableMap<String, MutableList<Map<Int, List<String>>>> = runCatching { + defaultGson.fromJson<MutableMap<String, MutableList<Map<Int, List<String>>>>>(roomCoresFile.readText(), object : TypeToken<MutableMap<String, MutableList<Map<Int, List<String>>>>>() {}.type) + }.getOrNull() ?: mutableMapOf() + + fun recordRoomCore(name: String, core: Int, blocks: List<String>) { + roomCores.getOrPut(name) { mutableListOf() }.add(mapOf(core to blocks)) + roomCoresFile.parentFile.mkdirs() + roomCoresFile.writeText(defaultGson.toJson(roomCores)) + } + init { on<LevelEvent.Load> { reset() } on<FloorEnterEvent> { initClient(floor) } @@ -9,6 +9,7 @@ import com.odtheking.odin.events.core.on import com.odtheking.odin.features.impl.dungeon.map.tile.DungeonRoom import com.odtheking.odin.features.impl.dungeon.map.tile.RoomData import com.odtheking.odin.features.impl.dungeon.map.tile.RoomShape +import com.odtheking.odin.features.impl.render.ClickGUIModule import com.odtheking.odin.utils.IVec2 import com.odtheking.odin.utils.devMessage import com.odtheking.odin.utils.equalsOneOf @@ -106,6 +107,8 @@ object WorldScan { else return devMessage("Unknown room data for core: $core $chunkPosition") } + if (ClickGUIModule.dungeonCoresLogging) DungeonScan.recordRoomCore(data.name, core, getRoomCoreBlocks(chunk, (chunkPosition * 16) + 7)) + val tilePosition = (chunkPosition / 2) + 6 val tile = DungeonScan.tiles.getOrNull(tilePosition.x + (tilePosition.z * 6)) ?: return @@ -166,6 +169,9 @@ object WorldScan { return stringBuilder.toString().hashCode() to highestBlock } + fun getRoomCoreBlocks(chunk: LevelChunk, position: IVec2): List<String> = + (160 downTo 12).map { y -> chunk.getBlockState(position.x, y, position.z).block.descriptionId } + private fun LevelChunk.getBlockState(x: Int, y: Int, z: Int): BlockState { val sectionIndex = getSectionIndex(y) if (sectionIndex >= 0 && sectionIndex < sections.size) { @@ -43,6 +43,7 @@ object ClickGUIModule : Module( private val action by ActionSetting("Open HUD Editor", desc = "Opens the HUD editor when clicked.") { mc.setScreen(HudManager) } val devMessage by BooleanSetting("Developer Message", false, desc = "Sends development related messages to the chat.") + val dungeonCoresLogging by BooleanSetting("Core loggings", false, desc = "") private var firstJoin by BooleanSetting("First join", true, "").hide() |
Moved some events to main thread查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BreakerDisplay.kt | 10 +++++----- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 11 +++++------ src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt | 5 ++--- 3 files changed, 12 insertions(+), 14 deletions(-) @@ -1,14 +1,14 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.core.onReceive +import com.odtheking.odin.events.GuiEvent +import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.itemId import com.odtheking.odin.utils.loreString import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket object BreakerDisplay : Module( name = "Breaker Display", @@ -25,9 +25,9 @@ object BreakerDisplay : Module( private val renderFull by BooleanSetting("Render full", false, desc = "Renders the HUD even when the Dungeon Breaker is full.") init { - onReceive<ClientboundContainerSetSlotPacket> { - if (!DungeonUtils.inDungeons || item.itemId != "DUNGEONBREAKER") return@onReceive - item.loreString.firstNotNullOfOrNull { chargesRegex.find(it) }?.let { match -> + on<GuiEvent.SlotUpdate> { + if (!DungeonUtils.inDungeons || packet.item.itemId != "DUNGEONBREAKER") return@on + packet.item.loreString.firstNotNullOfOrNull { chargesRegex.find(it) }?.let { match -> charges = match.groupValues[1].toIntOrNull() ?: 0 maxCharges = match.groupValues[2].toIntOrNull() ?: 0 } @@ -8,6 +8,7 @@ import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent +import com.odtheking.odin.events.ScreenEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -22,8 +23,6 @@ import kotlinx.coroutines.launch import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.network.chat.Component -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket -import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items @@ -138,9 +137,9 @@ object Croesus : Module( } } - onReceive<ClientboundContainerSetSlotPacket> { - val screenTitle = mc.screen?.title?.string ?: return@onReceive - val items = (mc.screen as? AbstractContainerScreen<*>)?.menu?.items ?: return@onReceive + on<GuiEvent.SlotUpdate> { + val screenTitle = mc.screen?.title?.string ?: return@on + val items = (mc.screen as? AbstractContainerScreen<*>)?.menu?.items ?: return@on when { screenTitle.matches(chestNameRegex) -> handleChestContents(items) @@ -148,7 +147,7 @@ object Croesus : Module( } } - onReceive<ClientboundOpenScreenPacket> { + on<ScreenEvent.Open> { mostProfitableSlots = emptySet() currentChestProfit = null chestData = emptyList() @@ -3,8 +3,8 @@ package com.odtheking.odin.features.impl.nether import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.GuiEvent +import com.odtheking.odin.events.ScreenEvent import com.odtheking.odin.events.core.on -import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module import com.odtheking.odin.features.impl.dungeon.Croesus.cachedPrices import com.odtheking.odin.utils.* @@ -14,7 +14,6 @@ import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.network.chat.Component import net.minecraft.network.chat.Style import net.minecraft.network.chat.TextColor -import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import java.util.* @@ -84,7 +83,7 @@ object Vesuvius : Module( if (packet.slot.equalsOneOf(13, 14) && item.item == Items.PLAYER_HEAD) handleKuudraChest(item) } - onReceive<ClientboundOpenScreenPacket> { + on<ScreenEvent.Open> { currentChest = null } } |
Change Chat message event to fabric's dispatcher查看变更src/main/kotlin/com/odtheking/odin/commands/DevCommand.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 2 +- src/main/kotlin/com/odtheking/odin/events/EventDispatcher.kt | 14 ++++++-------- src/main/kotlin/com/odtheking/odin/features/impl/boss/ArrowsDevice.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/ExtraStats.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/InactiveWaypoints.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/KingRelics.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/LividSolver.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/MelodyMessage.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/SimonSays.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSounds.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalTimes.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragons.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BetterPartyFinder.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BloodCamp.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DoorHighlight.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DungeonQueue.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/InvincibilityTimer.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/LeapMenu.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Mimic.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/SecretClicked.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/SecretsCounter.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/nether/FreshTools.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/nether/Misc.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/nether/NoPre.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/nether/SupplyHelper.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/render/Waypoints.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/ChatCommands.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/Ragnarock.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/utils/ChatManager.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/utils/skyblock/KuudraUtils.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/utils/skyblock/PartyUtils.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/utils/skyblock/SplitsManager.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/TerminalUtils.kt | 4 ++-- 39 files changed, 75 insertions(+), 77 deletions(-) @@ -4,7 +4,7 @@ import com.github.stivais.commodore.Commodore import com.github.stivais.commodore.utils.GreedyString import com.odtheking.odin.OdinMod import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.features.ModuleManager import com.odtheking.odin.features.impl.boss.MelodyMessage.melodyWebSocket import com.odtheking.odin.features.impl.boss.WitherDragonState @@ -53,7 +53,7 @@ val devCommand = Commodore("oddev") { } literal("simulate").runs { greedyString: GreedyString -> - ChatPacketEvent(greedyString.string, Component.literal(greedyString.string)).postAndCatch() + ChatMessageEvent(greedyString.string, Component.literal(greedyString.string)).postAndCatch() modMessage("§8Simulated message: ${greedyString.string}") } @@ -32,7 +32,7 @@ class BlockInteractEvent(val pos: BlockPos) : CancellableEvent() class EntityInteractEvent(val pos: Vec3, val entity: Entity) : CancellableEvent() class UseItemOnPostEvent(val hand: InteractionHand, val hitResult: BlockHitResult, val interactionResult: InteractionResult) : Event -class ChatPacketEvent(val value: String, val component: Component) : Event // mixin instead of packet (still needs to run before vanilla processing for hideMessage() +class ChatMessageEvent(val value: String, val component: Component) : Event // mixin instead of packet (still needs to run before vanilla processing for hideMessage() class MessageSentEvent(val message: String) : CancellableEvent() class RenderBossBarEvent(val bossBar: BossEvent) : CancellableEvent() @@ -3,7 +3,6 @@ package com.odtheking.odin.events import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.events.core.onSend -import com.odtheking.odin.utils.ChatManager import com.odtheking.odin.utils.containsOneOf import com.odtheking.odin.utils.equalsOneOf import com.odtheking.odin.utils.noControlCodes @@ -17,7 +16,10 @@ import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents -import net.minecraft.network.protocol.game.* +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket +import net.minecraft.network.protocol.game.ClientboundSoundPacket +import net.minecraft.network.protocol.game.ClientboundTakeItemEntityPacket +import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket import net.minecraft.sounds.SoundEvents import net.minecraft.world.InteractionHand import net.minecraft.world.entity.item.ItemEntity @@ -54,9 +56,9 @@ object EventDispatcher { } } - ClientReceiveMessageEvents.ALLOW_GAME.register { text, overlay -> + ClientReceiveMessageEvents.ALLOW_GAME.register { message, overlay -> if (overlay) return@register true - !ChatManager.shouldCancelMessage(text) + !ChatMessageEvent(message.string.noControlCodes, message).postAndCatch() } onReceive<ClientboundTakeItemEntityPacket> { @@ -91,10 +93,6 @@ object EventDispatcher { if (isSecret(blockState, hitResult.blockPos)) SecretPickupEvent.Interact(hitResult.blockPos, blockState).postAndCatch() } - - onReceive<ClientboundSystemChatPacket> { - if (!overlay) ChatPacketEvent(content.string.noControlCodes, content).postAndCatch() - } } private val dungeonItemDrops = listOf( @@ -5,7 +5,7 @@ import com.odtheking.odin.clickgui.settings.impl.ActionSetting import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting import com.odtheking.odin.events.BlockUpdateEvent -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on @@ -102,7 +102,7 @@ object ArrowsDevice : Module( optimalAimPositions = emptyList() } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (DungeonUtils.getF7Phase() != M7Phases.P3 || !isPlayerInRoom || isDeviceComplete) return@on if (deviceCompleteRegex.find(value)?.groupValues?.get(1) == mc.player?.name?.string) onComplete("Chat") } @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -56,7 +56,7 @@ object ExtraStats : Module( ) init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!DungeonUtils.inDungeons) return@on if (cancelRegexes.any { it.matches(value) }) hideMessage() @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on @@ -67,7 +67,7 @@ object InactiveWaypoints : Module( }?.toSet().orEmpty() } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!DungeonUtils.inBoss) return@on when { @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.TickEvent @@ -47,7 +47,7 @@ object KingRelics : Module( private val relicPBs = PersonalBest(this, "Relics") init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (DungeonUtils.getF7Phase() != M7Phases.P5 || !relicPickupRegex.matches(value)) return@on relicPlaceTick = serverTickCounter relicTicksToSpawn = relicSpawnTicks @@ -41,7 +41,7 @@ object LividSolver : Module( private var invulnTime = 0 init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!DungeonUtils.inDungeons || !DungeonUtils.isFloor(5)) return@on if (value.matches(lividStartRegex)) invulnTime = 390 } @@ -4,7 +4,7 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting import com.odtheking.odin.clickgui.settings.impl.StringSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.TerminalEvent @@ -76,7 +76,7 @@ object MelodyMessage : Module( if (melodySendCoords) sendCommand("od sendcoords") } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (broadcast || melodyProgress) onChatMessage(value) } @@ -52,7 +52,7 @@ object SimonSays : Module( startClickCounter = 0 } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (value == "[BOSS] Goldor: Who dares trespass into my domain?") startClickCounter = 0 } @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.core.EventPriority @@ -51,7 +51,7 @@ object TerminalSounds : Module( it.cancel() } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!DungeonUtils.inDungeons || !shouldReplaceSounds) return@on when { value.matches(gateRegex) -> playSoundAtPlayer(SoundEvents.NOTE_BLOCK_PLING.value()) @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.ActionSetting import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.TickEvent @@ -45,7 +45,7 @@ object TerminalTimes : Module( pbs.time(terminal.type.name, (System.currentTimeMillis() - terminal.timeOpened) / 1000f, "s§7!", "§a${terminal.type.termName}${if (mc.screen is TermSimGUI) " §7(termsim)" else ""} §7solved in §6", sendMessage = terminalTimes) } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!terminalSplits) return@on terminalCompleteRegex.find(value)?.destructured?.let { (name, activated, type, current, total) -> hideMessage() @@ -3,7 +3,7 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.HudElement -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on @@ -101,7 +101,7 @@ object TickTimers : Module( } init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { when { value.matches(MORT_REGEX) -> secretsCounter = 0 value.matches(necronRegex) -> necronTime = 60 @@ -87,7 +87,7 @@ object WitherDragons : Module( WitherDragonsEnum.entries.find { it.statuePos == pos }?.setDead(false) } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (DungeonUtils.getF7Phase() != M7Phases.P5 || !witherKingRegex.matches(value)) return@on (DragonCheck.lastDragonDeath ?: WitherDragonsEnum.entries.find { it.state != WitherDragonState.DEAD }) ?.apply { @@ -7,7 +7,7 @@ import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting import com.odtheking.odin.commands.fetchAndDisplayCataStats -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.formatNumber @@ -47,7 +47,7 @@ object BetterPartyFinder : Module( private val kickedList = mutableSetOf<String>() init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!statsDisplay && !autoKickToggle) return@on val (name) = pfRegex.find(value)?.destructured ?: return@on if (name == mc.player?.name?.string) return@on @@ -100,7 +100,7 @@ object BloodCamp : Module( } } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!DungeonUtils.inClear) return@on if (BLOOD_START_REGEX.matches(value)) startTime = currentTickTime else if (BLOOD_MOVE_REGEX.matches(value)) { @@ -6,7 +6,7 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.ActionSetting import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive @@ -164,7 +164,7 @@ object Croesus : Module( } } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (DungeonUtils.inBoss && value.matches(extraStatsRegex)) { currentChestCount++ if (currentChestCount > chestWarning) alert("§cChest limit reached!") @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on @@ -45,7 +45,7 @@ object DoorHighlight : Module( private val bloodDoorOpenRegex = Regex("^The BLOOD DOOR has been opened!$") init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!DungeonUtils.inClear) return@on when { witherKeyObtainRegex.matches(value) || witherKeyPickedUpRegex.matches(value) -> witherKeys++ @@ -3,7 +3,7 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.PartyEvent import com.odtheking.odin.events.core.on @@ -37,7 +37,7 @@ object DungeonQueue : Module( var disableRequeue = false init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { when { announceKick && (value.matches(kickedJoiningRegex) || value.matches(kickedInstanceRegex)) -> sendCommand("pc I was kicked!") value.matches(enterRegex) -> warpTimer = System.currentTimeMillis() + 30_000L @@ -4,7 +4,7 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.TickEvent @@ -84,7 +84,7 @@ object InvincibilityTimer : Module( InvincibilityType.entries.forEach { it.tick() } } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (onlyInDungeons && !DungeonUtils.inDungeons) return@on InvincibilityType.entries.firstOrNull { type -> value.matches(type.regex) }?.let { type -> val seconds = mc.player?.getItemBySlot(EquipmentSlot.HEAD)?.loreString?.reversed() @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.* -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.ScreenEvent @@ -167,7 +167,7 @@ object LeapMenu : Module( }) ) - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (leapAnnounce && DungeonUtils.inDungeons) leapedRegex.find(value)?.groupValues?.get(1)?.let { sendCommand("pc Leaped to ${it}!") } } @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.impl.ActionSetting import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -40,7 +40,7 @@ object Mimic : Module( mimicKilled() } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (value.matches(princeRegex)) princeKilled() if (value.matches(batRegex)) batKilled() } @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.* -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.SecretPickupEvent @@ -59,7 +59,7 @@ object SecretClicked : Module( secretChime() } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (value == "That chest is locked!") clickedSecretsList.lastOrNull()?.locked = true } @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.OdinMod.scope import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -32,7 +32,7 @@ object SecretsCounter : Module( snapshotDone = false } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (dungeonEndRegex.containsMatchIn(value)) schedule(30) { fetchAndDisplay() } if (!dungeonStartRegex.containsMatchIn(value) || !secretsEnabled || snapshotDone) return@on @@ -150,7 +150,7 @@ object PuzzleSolvers : Module( if (tpMaze) TPMazeSolver.tpPacket(this) } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!DungeonUtils.inClear) return@on if (draftPrompt && isInPuzzle) failRegex.find(value)?.destructured?.let { modMessage("§7Click §ehere §7to fetch architect's draft", chatStyle = Style.EMPTY @@ -1,7 +1,7 @@ package com.odtheking.odin.features.impl.nether import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.render.text @@ -43,7 +43,7 @@ object FreshTools : Module( private val ownFreshRegex = Regex("^Your Fresh Tools Perk bonus doubles your building speed for the next 10 seconds!$") init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (notifyFresh && KuudraUtils.inKuudra && ownFreshRegex.matches(value)) sendCommand("pc FRESH") } @@ -3,7 +3,7 @@ package com.odtheking.odin.features.impl.nether import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.handlers.schedule @@ -22,7 +22,7 @@ object Misc : Module( private val endStoneRegex = Regex("^Used Extreme Focus! \\((\\d+) Mana\\)$") init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (manaDrain) endStoneRegex.find(value)?.groupValues?.getOrNull(1)?.let { mana -> val players = mc.level?.players()?.filter { it.distanceToSqr(mc.player ?: return@on) < 49 && it.uuid.version() == 4 } ?: return@on @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.nether import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -29,7 +29,7 @@ object NoPre : Module( var missing = Supply.None init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!KuudraUtils.inKuudra) return@on when { @@ -3,7 +3,7 @@ package com.odtheking.odin.features.impl.nether import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -36,7 +36,7 @@ object SupplyHelper : Module( private var startRun = 0L init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!KuudraUtils.inKuudra || !sendSupplyTime) return@on when { @@ -6,7 +6,7 @@ import com.odtheking.odin.clickgui.ClickGUI import com.odtheking.odin.clickgui.HudManager import com.odtheking.odin.clickgui.settings.AlwaysActive import com.odtheking.odin.clickgui.settings.impl.* -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Category import com.odtheking.odin.features.Module @@ -81,7 +81,7 @@ object ClickGUIModule : Module( postData("https://api.odtheking.com/tele/", """{"username": "$name", "version": "Fabric ${OdinMod.version}"}""") } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!profileRegex.matches(value)) return@on if (firstJoin) { @@ -5,7 +5,7 @@ import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.DropdownSetting import com.odtheking.odin.clickgui.settings.impl.KeybindSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on @@ -49,7 +49,7 @@ object Waypoints : Module( private val temporaryWaypoints = mutableListOf<Waypoint>() init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { val (name, x, y, z) = when { fromParty && partyRegex.matches(value) -> partyRegex.find(value)?.destructured fromAll && allRegex.matches(value) -> allRegex.find(value)?.destructured @@ -3,7 +3,7 @@ package com.odtheking.odin.features.impl.skyblock import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.DropdownSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.MessageSentEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -62,7 +62,7 @@ object ChatCommands : Module( private val dtReason = mutableListOf<Pair<String, String>>() init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (value.matches(endRunRegex)) { if (!dt || dtReason.isEmpty()) return@on schedule(30) { @@ -2,7 +2,7 @@ package com.odtheking.odin.features.impl.skyblock import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -22,7 +22,7 @@ object Ragnarock : Module( private val cancelRegex = Regex("Ragnarock was cancelled due to (?:being hit|taking damage)!") init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (cancelAlert && value.matches(cancelRegex)) alert("§cRagnarock Cancelled!") } @@ -1,13 +1,13 @@ package com.odtheking.odin.utils -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import net.minecraft.network.chat.Component import java.util.concurrent.ConcurrentLinkedQueue object ChatManager { private val cancelQueue = ConcurrentLinkedQueue<Component>() - fun ChatPacketEvent.hideMessage() { + fun ChatMessageEvent.hideMessage() { cancelQueue.add(component) } @@ -1,7 +1,7 @@ package com.odtheking.odin.utils.skyblock import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive @@ -84,7 +84,7 @@ object KuudraUtils { } } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (!inKuudra) return@on when (value) { @@ -1,7 +1,7 @@ package com.odtheking.odin.utils.skyblock import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.PartyEvent import com.odtheking.odin.events.core.on @@ -45,7 +45,7 @@ object PartyUtils { private set init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { joinedOther.find(value)?.let { return@on addMember(it.groupValues[2]) } joinedSelf.find(value)?.let { @@ -1,6 +1,6 @@ package com.odtheking.odin.utils.skyblock -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on @@ -21,7 +21,7 @@ object SplitsManager { private var tickCounter: Long = 0L init { - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (value == "Starting in 1 second.") startRun() else onSplitMessage(value) } @@ -102,7 +102,7 @@ object DungeonListener { } } - on<ChatPacketEvent> { + on<ChatMessageEvent> { if (expectingBloodRegex.matches(value)) expectingBloodUpdate = true doorOpenRegex.find(value)?.let { dungeonStats.doorOpener = it.groupValues[1] } deathRegex.find(value)?.let { match -> @@ -1,7 +1,7 @@ package com.odtheking.odin.utils.skyblock.dungeon.terminals import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.ChatMessageEvent import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.TickEvent @@ -68,7 +68,7 @@ object TerminalUtils { } } - on<ChatPacketEvent> { + on<ChatMessageEvent> { termSolverRegex.find(value)?.let { message -> if (message.groupValues[1] == mc.player?.name?.string) lastTermOpened?.let { TerminalEvent.Solve(it).postAndCatch() |
Fixed PuzzleTimer min time查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 13 +++++++++---- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonEnums.kt | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 15 deletions(-) @@ -13,6 +13,7 @@ import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.handlers.TickTask import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils +import com.odtheking.odin.utils.skyblock.dungeon.Puzzle import net.minecraft.core.BlockPos import net.minecraft.network.chat.ClickEvent import net.minecraft.network.chat.Component @@ -200,10 +201,14 @@ object PuzzleSolvers : Module( private val puzzlePBs = PersonalBest(this, "PuzzlePBs") fun onPuzzleComplete(puzzleName: String) { - puzzleTimersMap[puzzleName]?.let { - if (it.sentMessage || (System.currentTimeMillis() - it.timeEntered) / 1000f < 5) return - puzzlePBs.time(puzzleName, (System.currentTimeMillis() - it.timeEntered) / 1000f, "s§7!", "§a${puzzleName} §7solved in §6") - it.sentMessage = true + puzzleTimersMap[puzzleName]?.let { puzzleTimer -> + if (puzzleTimer.sentMessage) return + + val puzzleTime = (System.currentTimeMillis() - puzzleTimer.timeEntered) / 1000f + if (puzzleTime < (Puzzle.entries.find { it.displayName == puzzleName }?.timeToBeat ?: 0)) return@onPuzzleComplete + + puzzlePBs.time(puzzleName, puzzleTime, "s§7!", "§a${puzzleName} §7solved in §6") + puzzleTimer.sentMessage = true } } @@ -36,20 +36,21 @@ data class DungeonPlayer( */ enum class Puzzle( val displayName: String, + val timeToBeat: Int, var status: PuzzleStatus? = null, var player: String? = null ) { - UNKNOWN("???"), - BLAZE("Higher Or Lower"), - BEAMS("Creeper Beams"), - WEIRDOS("Three Weirdos"), - TTT("Tic Tac Toe"), - WATER_BOARD("Water Board"), - TP_MAZE("Teleport Maze"), - BOULDER("Boulder"), - ICE_FILL("Ice Fill"), - ICE_PATH("Ice Path"), - QUIZ("Quiz"); + UNKNOWN("???", 0), + BLAZE("Higher Or Lower", 6), + BEAMS("Creeper Beams", 6), + WEIRDOS("Three Weirdos", 2), + TTT("Tic Tac Toe", 0), + WATER_BOARD("Water Board", 10), + TP_MAZE("Teleport Maze", 6), + BOULDER("Boulder", 6), + ICE_FILL("Ice Fill", 10), + ICE_PATH("Ice Path", 0), + QUIZ("Quiz", 10); } enum class PuzzleStatus { |
Added Storm Tick Hud Bump version查看变更gradle.properties | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/ArrowAlign.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSimulator.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 56 ++++++++++++------------- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/Splits.kt | 41 ++++++++----------- src/main/kotlin/com/odtheking/odin/utils/skyblock/SplitsManager.kt | 172 +++++++++++++++++++++++++++++++++++++++++++---------------------------------- 6 files changed, 143 insertions(+), 132 deletions(-) @@ -10,7 +10,7 @@ loom_version=1.16-SNAPSHOT mod_id=odin mod_name=Odin mod_description=A Hypixel Skyblock QoL mod -mod_version=0.3.1 +mod_version=0.3.2 maven_group=com.odtheking archives_base_name=odin-fabric # Mod Dependencies @@ -25,7 +25,7 @@ object ArrowAlign : Module( name = "Arrow Align", description = "Shows the solution for the Arrow Align device." ) { - private val blockWrong by BooleanSetting("Block Wrong Clicks", false, desc = "Blocks wrong clicks, shift will override this.") + private val blockWrong by BooleanSetting("Block Wrong Clicks", true, desc = "Blocks wrong clicks, shift will override this.") private val preventKey by KeybindSetting("Prevent Blocking", GLFW.GLFW_KEY_LEFT_SHIFT, desc = "While holding this key, wrong clicks won't be blocked, even if you aren't sneaking.").withDependency { blockWrong } private val invertKey by BooleanSetting("Invert Key", false, desc = "Inverts the behavior of the prevent key. Wrong clicks will be blocked while holding the prevent key, and allowed otherwise.").withDependency { blockWrong } @@ -13,7 +13,7 @@ object TerminalSimulator : Module( description = "Simulates a floor 7 terminal from phase 3." ) { private val ping by NumberSetting("Ping", 0, 0, 500, 50, desc = "Ping of the terminal.") - val disableFirstClickProtection by BooleanSetting("First Click Protection", false, desc = "") + val disableFirstClickProtection by BooleanSetting("First Click Prot", false, desc = "Disables First click protection while solving termsim.") val termSimPBs = PersonalBest(this, "TermsimPBs") @@ -7,14 +7,12 @@ import com.odtheking.odin.events.ChatPacketEvent import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on -import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.MORT_REGEX import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.toFixed -import net.minecraft.network.protocol.game.ClientboundSetTimePacket object TickTimers : Module( name = "Tick Timers", @@ -27,17 +25,20 @@ object TickTimers : Module( private val necronRegex = Regex("^\\[BOSS] Necron: I'm afraid, your journey ends now\\.$") private val goldorRegex = Regex("^\\[BOSS] Goldor: Who dares trespass into my domain\\?$") private val coreOpeningRegex = Regex("^The Core entrance is opening!$") - private val stormStartRegex = Regex("^\\[BOSS] Storm: I should have known that I stood no chance\\.$") - private val stormPadRegex = Regex("^\\[BOSS] Storm: Pathetic Maxor, just like expected\\.$") + private val stormEndRegex = Regex("^\\[BOSS] Storm: I should have known that I stood no chance\\.$") + private val stormStartRegex = Regex("^\\[BOSS] Storm: Pathetic Maxor, just like expected\\.$") private val stormPyRegex = Regex("^\\[BOSS] Storm: (ENERGY HEED MY CALL|THUNDER LET ME BE YOUR CATALYST)!$") + private var necronTime = -1 + private val necronHud by HUD("Necron Hud", "Displays a timer for Necron's drop.") { if (it) textDim(formatTimer(35, 60, "§4Necron dropping in"), 0, 0, Colors.MINECRAFT_DARK_RED) else if (necronTime >= 0) textDim(formatTimer(necronTime, 60, "§4Necron dropping in"), 0, 0, Colors.MINECRAFT_DARK_RED) else 0 to 0 } - private var necronTime = -1 + private var goldorTickTime = -1 + private var goldorStartTime = -1 private val goldorHud: HudElement by HUD("Goldor Hud", "Displays a timer for Goldor's Core entrance opening.") { if (it) textDim(formatTimer(35, 60, "§7Tick:"), 0, 0, Colors.MINECRAFT_DARK_RED) @@ -48,8 +49,7 @@ object TickTimers : Module( } private val startTimer by BooleanSetting("Start timer", false, desc = "Displays a timer counting down until devices/terms are able to be activated/completed.").withDependency { goldorHud.enabled } - private var goldorTickTime = -1 - private var goldorStartTime = -1 + private var padTickTime = -1 private val stormHud by HUD("Storm Pad Hud", "Displays a timer for Storm's Pad.") { if (it) textDim(formatTimer(15, 20, "§bPad:"), 0, 0, Colors.MINECRAFT_DARK_RED) @@ -57,36 +57,38 @@ object TickTimers : Module( else 0 to 0 } + private var lightningTickTime = -1 + private val lightningHud by HUD("Storm Lightning Hud", "Displays a timer for Storm's Lightning.") { if (it) textDim(formatTimer(560, 560, "§bLightning:"), 0, 0, Colors.MINECRAFT_DARK_RED) else if (lightningTickTime >= 0) textDim(formatTimer(lightningTickTime, 560, "§bLightning:"), 0, 0, Colors.MINECRAFT_DARK_RED) else 0 to 0 } + private var pyTriggered = false + private var pyTickTime = -1 + private val pyHud by HUD("Storm PY Hud", "Displays a timer for when to crush storm under the purple pillar.") { if (it) textDim(formatTimer(95, 95, "§bPY:"), 0, 0, Colors.MINECRAFT_DARK_RED) else if (pyTickTime >= 0) textDim(formatTimer(pyTickTime, 95, "§bPY:"), 0, 0, Colors.MINECRAFT_DARK_RED) else 0 to 0 } - private var padTickTime = -1 - private var lightningTickTime = -1 - private var pyTriggered = false - private var pyTickTime = -1 + private var stormTick = -1 - private val outboundsHud by HUD("Outbounds Hud", "Displays a timer for out of bounds death ticks.") { - if (it) textDim(formatTimer(15, 20, "§8Outbounds:"), 0, 0, Colors.MINECRAFT_DARK_RED) - else if (outboundsTime >= 0) textDim(formatTimer(outboundsTime, 20, "§8Outbounds:"), 0, 0, Colors.MINECRAFT_DARK_RED) + private val stormTickHud by HUD("Storm Tick Hud", "Displays a timer for Storm's second phase, optionally counting down to the crush window.") { + if (it) { + val (time, max, prefix) = Triple(200, 620, "§bStorm:") + textDim(formatTimer(time, max, prefix), 0, 0, Colors.MINECRAFT_DARK_RED) + } else if (stormTick >= 0) textDim(formatTimer(stormTick, 620, "§bStorm:"), 0, 0, Colors.MINECRAFT_DARK_RED) else 0 to 0 } - private var outboundsTime = -1 private var secretsCounter = 0 private val secretsHud by HUD("Secrets Hud", "Displays a timer for secret spawn ticks.") { if (it) textDim(formatTimer(15, 20, "§7Secret:", overrideColor = "§c"), 0, 0, Colors.MINECRAFT_DARK_RED) else if (DungeonUtils.openRoomCount != 0 && !DungeonUtils.inBoss) { - // Color change at < 10 because that's when items are picked up. val time = 20 - secretsCounter % 20 val color = when { time < 5 -> "§a" @@ -108,13 +110,15 @@ object TickTimers : Module( goldorStartTime = -1 goldorTickTime = -1 } - value.matches(stormStartRegex) -> { - if (goldorHud.enabled) goldorStartTime = 104 - if (stormHud.enabled) padTickTime = -1 + value.matches(stormEndRegex) -> { + goldorStartTime = 104 + padTickTime = -1 + stormTick = -1 } - value.matches(stormPadRegex) -> { + value.matches(stormStartRegex) -> { padTickTime = 20 lightningTickTime = 560 + stormTick = 0 } !pyTriggered && value.matches(stormPyRegex) -> { pyTriggered = true @@ -125,8 +129,6 @@ object TickTimers : Module( on<TickEvent.Server> { if (!DungeonUtils.inDungeons) return@on - if (outboundsTime == 0) outboundsTime = 40 - if (outboundsTime >= 0) outboundsTime-- secretsCounter++ if (!DungeonUtils.inBoss) return@on if (goldorTickTime == 0 && goldorStartTime <= 0 && goldorHud.enabled) goldorTickTime = 60 @@ -137,13 +139,7 @@ object TickTimers : Module( if (lightningTickTime >= 0) lightningTickTime-- if (pyTickTime >= 0) pyTickTime-- if (necronTime >= 0) necronTime-- - } - - onReceive<ClientboundSetTimePacket> { - if (!DungeonUtils.inClear) return@onReceive - if (DungeonUtils.openRoomCount == 0) { - if (outboundsHud.enabled) outboundsTime = 40 - (gameTime % 40).toInt() - } else outboundsTime = -1 + if (stormTick >= 0) stormTick++ } on<LevelEvent.Load> { @@ -154,8 +150,8 @@ object TickTimers : Module( pyTickTime = -1 pyTriggered = false necronTime = -1 - outboundsTime = -1 secretsCounter = 0 + stormTick = -1 } } @@ -8,8 +8,7 @@ import com.odtheking.odin.utils.PersonalBest import com.odtheking.odin.utils.formatTime import com.odtheking.odin.utils.render.getStringWidth import com.odtheking.odin.utils.render.text -import com.odtheking.odin.utils.skyblock.SplitsManager.currentSplits -import com.odtheking.odin.utils.skyblock.SplitsManager.getAndUpdateSplitsTimes +import com.odtheking.odin.utils.skyblock.SplitsManager.currentRows import com.odtheking.odin.utils.skyblock.floor7SplitGroup import com.odtheking.odin.utils.toFixed @@ -34,19 +33,19 @@ object Splits : Module( return@HUD totalWidth to 9 * floor7SplitGroup.size } - val (times, tickTimes, current) = getAndUpdateSplitsTimes(currentSplits) - if (currentSplits.splits.isEmpty()) return@HUD 0 to 0 - val splits = currentSplits.splits.dropLast(1) + val rows = currentRows() + if (rows.isEmpty()) return@HUD 0 to 0 + val segments = rows.dropLast(1) - val labelWidth = splits.maxOfOrNull { getStringWidth(it.name) } ?: 0 + val labelWidth = segments.maxOfOrNull { getStringWidth(it.name) } ?: 0 val totalWidth = labelWidth + 4 + timeWidth + 2 - splits.forEachIndexed { index, split -> - val time = formatTime(if (index >= times.size) 0 else times[index]) - text(split.name, 0, index * 9, Colors.WHITE) + segments.forEachIndexed { index, row -> + if (row.time == 0L && !show0Time) return@forEachIndexed + val time = formatTime(row.time) + text(row.name, 0, index * 9, Colors.WHITE) - val displayText = if (showTickTime && index < tickTimes.size) "$time §8(§7${(tickTimes[index] / 20f).toFixed()}§8)" - else time + val displayText = if (showTickTime) "$time §8(§7${(row.tickTime / 20f).toFixed()}§8)" else time val timeX = if (fixedWidth) labelWidth + 4 + timeWidth - getStringWidth(displayText) else labelWidth + 4 @@ -54,12 +53,12 @@ object Splits : Module( text(displayText, timeX, index * 9, Colors.WHITE) } - if (bossEntrySplit && currentSplits.splits.size > 3) { - val y = splits.size * 9 + if (bossEntrySplit && rows.size > 3) { + val y = segments.size * 9 text("§9Boss Entry", 0, y, Colors.WHITE) - val totalTime = formatTime(times.take(3).sum()) - val displayText = if (showTickTime) "$totalTime §8(§7${(tickTimes.take(3).sum() / 20f).toFixed()}§8)" + val totalTime = formatTime(segments.take(3).sumOf { it.time }) + val displayText = if (showTickTime) "$totalTime §8(§7${(segments.take(3).sumOf { it.tickTime } / 20f).toFixed()}§8)" else totalTime val timeX = if (fixedWidth) labelWidth + 4 + timeWidth - getStringWidth(displayText) @@ -68,7 +67,7 @@ object Splits : Module( text(displayText, timeX, y, Colors.WHITE) } - totalWidth to 9 * (currentSplits.splits.size + (if (bossEntrySplit) 1 else 0)) + totalWidth to 9 * (rows.size + (if (bossEntrySplit) 1 else 0)) } private val currentSplitHud by HUD("Current Split HUD", "Shows only the current split and its tick time.") { example -> @@ -79,13 +78,9 @@ object Splits : Module( return@HUD w to 9 } - val splits = currentSplits.splits - if (splits.isEmpty()) return@HUD 0 to 0 + val current = currentRows().find { it.isCurrent } ?: return@HUD 0 to 0 - val (_, tickTimes, current) = getAndUpdateSplitsTimes(currentSplits) - if (current !in splits.indices || current >= tickTimes.size) return@HUD 0 to 0 - - val displayText = "§7${(tickTimes[current] / 20f).toFixed()}s" + val displayText = "§7${(current.tickTime / 20f).toFixed()}s" val w = getStringWidth(displayText) + 2 text(displayText, -w / 2, 0, Colors.WHITE) w to 9 @@ -93,7 +88,7 @@ object Splits : Module( private val fixedWidth by BooleanSetting("Fixed Width", true, desc = "Always use a fixed HUD width, right-aligning the times.") private val bossEntrySplit by BooleanSetting("Boss Entry Split", true, desc = "Split for boss entry.") - val sendSplits by BooleanSetting("Send Splits", true, desc = "Send splits to chat.") + private val show0Time by BooleanSetting("Show 0 splits", false, desc = "Shows splits which have their time at 0.") val showTickTime by BooleanSetting("Show Tick Time", true, desc = "Show tick-based time alongside real time.") val splitLocation by SelectorSetting("Split Location", "Both", listOf("Both", "Dungeons Only", "Kuudra Only"), desc = "Which areas to show splits in.") @@ -13,71 +13,16 @@ import com.odtheking.odin.utils.skyblock.dungeon.DungeonListener data class Split(val regex: Regex, val name: String, var time: Long = 0L, var ticks: Long = 0L) data class SplitsGroup(val splits: List<Split>, val personalBest: PersonalBest?) +data class SplitRow(val name: String, val time: Long, val tickTime: Long, val isCurrent: Boolean) object SplitsManager { - var currentSplits: SplitsGroup = SplitsGroup(emptyList(), null) + private var currentSplits: SplitsGroup = SplitsGroup(emptyList(), null) private var tickCounter: Long = 0L init { on<ChatPacketEvent> { - if (value == "Starting in 1 second.") { - tickCounter = 0L - currentSplits = when (LocationUtils.currentArea) { - Island.Dungeon -> { - if (Splits.splitLocation == 2) return@on - val floor = DungeonListener.floor ?: return@on - - with(dungeonSplits[floor.floorNumber].toMutableList()) { - addAll(0, listOf( - Split(MORT_REGEX, "§2Blood Open"), - Split(BLOOD_OPEN_REGEX, "§bBlood Clear"), - Split(Regex("\\[BOSS] The Watcher: You have proven yourself\\. You may pass\\."), "§dPortal Entry") - ) - ) - add(Split(Regex("^\\s*☠ Defeated (.+) in 0?([\\dhms ]+?)\\s*(\\(NEW RECORD!\\))?$"), "§1Total")) - SplitsGroup(map { it.copy(time = 0L, ticks = 0L) }, Splits.dungeonPBsList[floor.ordinal]) - } - } - - Island.Kuudra -> { - if (Splits.splitLocation == 1) return@on - when (KuudraUtils.kuudraTier) { - 5 -> SplitsGroup(kuudraT5SplitsGroup.map { it.copy(time = 0L, ticks = 0L) }, Splits.kuudraT5PBs) - 4 -> SplitsGroup(kuudraSplitsGroup.map { it.copy(time = 0L, ticks = 0L) }, Splits.kuudraT4PBs) - 3 -> SplitsGroup(kuudraSplitsGroup.map { it.copy(time = 0L, ticks = 0L) }, Splits.kuudraT3PBs) - 2 -> SplitsGroup(kuudraSplitsGroup.map { it.copy(time = 0L, ticks = 0L) }, Splits.kuudraT2PBs) - 1 -> SplitsGroup(kuudraSplitsGroup.map { it.copy(time = 0L, ticks = 0L) }, Splits.kuudraT1PBs) - else -> SplitsGroup(emptyList(), null) - } - } - - else -> SplitsGroup(emptyList(), null) - } - } else { - val currentSplit = currentSplits.splits.find { it.regex.matches(value) } ?: return@on - if (currentSplit.time != 0L) return@on - currentSplit.time = System.currentTimeMillis() - currentSplit.ticks = tickCounter - - val index = currentSplits.splits.indexOf(currentSplit).takeIf { it != 0 } ?: return@on - val currentSplitTime = (currentSplit.time - currentSplits.splits[index - 1].time) / 1000f - - if (index == currentSplits.splits.size - 1) { - val (times, _, _) = getAndUpdateSplitsTimes(currentSplits) - val capturedSplits = currentSplits.splits.toList() - val capturedPB = currentSplits.personalBest - schedule(10) { - if (capturedSplits.isEmpty()) return@schedule - capturedPB?.time(capturedSplits[index - 1].name, currentSplitTime, "s§7!", "§6${capturedSplits[index - 1].name} §7took §6", Splits.enabled) - capturedPB?.time(capturedSplits[index].name, times.last() / 1000f, "s§7!", "§6Total time §7took §6", Splits.enabled) - times.forEachIndexed { i, it -> - val name = if (i == capturedSplits.size - 1) "Total" else capturedSplits[i].name - if (Splits.sendSplits && Splits.enabled) modMessage("§6$name §7took §6${formatTime((it))}§7.") - } - } - } else currentSplits.personalBest?.time(currentSplits.splits[index - 1].name, currentSplitTime, "s§7!", "§6${currentSplits.splits[index - 1].name} §7took §6", Splits.enabled) - } + if (value == "Starting in 1 second.") startRun() else onSplitMessage(value) } on<TickEvent.Server> { @@ -85,34 +30,106 @@ object SplitsManager { } on<LevelEvent.Load> { - currentSplits = SplitsGroup(mutableListOf(), null) + currentSplits = SplitsGroup(emptyList(), null) tickCounter = 0L } } - fun getAndUpdateSplitsTimes(currentSplits: SplitsGroup): Triple<List<Long>, List<Long>, Int> { - if (currentSplits.splits.isEmpty() || currentSplits.splits[0].time == 0L) - return Triple(List(currentSplits.splits.size) { 0L }, List(currentSplits.splits.size) { 0L }, -1) + private fun startRun() { + tickCounter = 0L + currentSplits = when (LocationUtils.currentArea) { + Island.Dungeon -> buildDungeonSplits() ?: return + Island.Kuudra -> buildKuudraSplits() ?: return + else -> SplitsGroup(emptyList(), null) + } + } + + private fun buildDungeonSplits(): SplitsGroup? { + if (Splits.splitLocation == 2) return null + val floor = DungeonListener.floor ?: return null + + val splits = dungeonSplits[floor.floorNumber].toMutableList().apply { + addAll(0, listOf( + Split(MORT_REGEX, "§2Blood Open"), + Split(BLOOD_OPEN_REGEX, "§bBlood Clear"), + Split(PORTAL_ENTRY_REGEX, "§dPortal Entry"), + )) + add(Split(DUNGEON_CLEARED_REGEX, "§1Total")) + } + return SplitsGroup(splits.map { it.copy() }, Splits.dungeonPBsList[floor.ordinal]) + } + + private fun buildKuudraSplits(): SplitsGroup? { + if (Splits.splitLocation == 1) return null + return when (KuudraUtils.kuudraTier) { + 5 -> SplitsGroup(kuudraT5SplitsGroup.map { it.copy() }, Splits.kuudraT5PBs) + 4 -> SplitsGroup(kuudraSplitsGroup.map { it.copy() }, Splits.kuudraT4PBs) + 3 -> SplitsGroup(kuudraSplitsGroup.map { it.copy() }, Splits.kuudraT3PBs) + 2 -> SplitsGroup(kuudraSplitsGroup.map { it.copy() }, Splits.kuudraT2PBs) + 1 -> SplitsGroup(kuudraSplitsGroup.map { it.copy() }, Splits.kuudraT1PBs) + else -> SplitsGroup(emptyList(), null) + } + } + + private fun onSplitMessage(message: String) { + val splits = currentSplits.splits + val split = splits.find { it.regex.matches(message) } ?: return + if (split.time != 0L) return - val latestTime = if (currentSplits.splits.last().time == 0L) System.currentTimeMillis() else currentSplits.splits.last().time - val latestTick = if (currentSplits.splits.last().ticks == 0L) tickCounter else currentSplits.splits.last().ticks + split.time = System.currentTimeMillis() + split.ticks = tickCounter - val times = MutableList(currentSplits.splits.size) { 0L }.apply { this[size - 1] = latestTime - currentSplits.splits.first().time } - val tickTimes = MutableList(currentSplits.splits.size) { 0L }.apply { this[size - 1] = latestTick - currentSplits.splits.first().ticks } + val index = splits.indexOf(split) + if (index == 0) return - var current = currentSplits.splits.size - for (i in 0 until currentSplits.splits.size - 1) { - if (currentSplits.splits[i + 1].time != 0L) { - times[i] = currentSplits.splits[i + 1].time - currentSplits.splits[i].time - tickTimes[i] = currentSplits.splits[i + 1].ticks - currentSplits.splits[i].ticks + val previous = splits[index - 1] + val segmentTime = (split.time - previous.time) / 1000f + + if (index == splits.lastIndex) finishRun(index, segmentTime) + else currentSplits.personalBest?.time(previous.name, segmentTime, "s§7!", "§6${previous.name} §7took §6", Splits.enabled) + } + + private fun finishRun(totalIndex: Int, lastSegmentTime: Float) { + val rows = currentRows() + val splits = currentSplits.splits + val personalBest = currentSplits.personalBest + + schedule(10) { + if (rows.isEmpty()) return@schedule + personalBest?.time(splits[totalIndex - 1].name, lastSegmentTime, "s§7!", "§6${splits[totalIndex - 1].name} §7took §6", Splits.enabled) + personalBest?.time(splits[totalIndex].name, rows.last().time / 1000f, "s§7!", "§6Total time §7took §6", Splits.enabled) + rows.forEachIndexed { i, row -> + if (Splits.enabled) modMessage("§6${if (i == rows.lastIndex) "Total" else row.name} §7took §6${formatTime(row.time)}§7.") + } + } + } + + fun currentRows(): List<SplitRow> { + val splits = currentSplits.splits + if (splits.isEmpty()) return emptyList() + if (splits[0].time == 0L) return splits.map { SplitRow(it.name, 0L, 0L, isCurrent = false) } + + val last = splits.last() + val latestTime = if (last.time != 0L) last.time else System.currentTimeMillis() + val latestTicks = if (last.ticks != 0L) last.ticks else tickCounter + + val times = LongArray(splits.size).apply { this[splits.lastIndex] = latestTime - splits.first().time } + val ticks = LongArray(splits.size).apply { this[splits.lastIndex] = latestTicks - splits.first().ticks } + + var currentIndex = -1 + for (i in 0 until splits.lastIndex) { + val next = splits[i + 1] + if (next.time != 0L) { + times[i] = next.time - splits[i].time + ticks[i] = next.ticks - splits[i].ticks } else { - current = i - times[i] = latestTime - currentSplits.splits[i].time - tickTimes[i] = latestTick - currentSplits.splits[i].ticks + times[i] = latestTime - splits[i].time + ticks[i] = latestTicks - splits[i].ticks + currentIndex = i break } } - return Triple(times, tickTimes, current) + return splits.indices.map { i -> SplitRow(splits[i].name, times[i], ticks[i], isCurrent = i == currentIndex) } } } @@ -194,6 +211,9 @@ private val dungeonSplits = listOf( floor7SplitGroup, ) +private val PORTAL_ENTRY_REGEX = Regex("\\[BOSS] The Watcher: You have proven yourself\\. You may pass\\.") +private val DUNGEON_CLEARED_REGEX = Regex("^\\s*☠ Defeated (.+) in 0?([\\dhms ]+?)\\s*(\\(NEW RECORD!\\))?$") + // https://regex101.com/r/BXKhOI/1 private val BLOOD_OPEN_REGEX = Regex("^\\[BOSS] The Watcher: (Congratulations, you made it through the Entrance\\.|Ah, you've finally arrived\\.|Ah, we meet again\\.\\.\\.|So you made it this far\\.\\.\\. interesting\\.|You've managed to scratch and claw your way here, eh\\?|I'm starting to get tired of seeing you around here\\.\\.\\.|Oh\\.\\. hello\\?|Things feel a little more roomy now, eh\\?)$|^The BLOOD DOOR has been opened!$") -val MORT_REGEX = Regex("\\[NPC] Mort: Here, I found this map when I first entered the dungeon\\.|\\[NPC] Mort: Right-click the Orb for spells, and Left-click \\(or Drop\\) to use your Ultimate!") \ No newline at end of file +val MORT_REGEX = Regex("\\[NPC] Mort: Here, I found this map when I first entered the dungeon\\.|\\[NPC] Mort: Right-click the Orb for spells, and Left-click \\(or Drop\\) to use your Ultimate!") |
| option to show player name or class on melody progress (#147) * option to show player name or class on melody progress
* Made dungeon class not refetch every frame
---------
Co-authored-by: odtheking <odtheking1@gmail.com> 查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/MelodyMessage.kt | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) @@ -2,6 +2,7 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting +import com.odtheking.odin.clickgui.settings.impl.SelectorSetting import com.odtheking.odin.clickgui.settings.impl.StringSetting import com.odtheking.odin.events.ChatPacketEvent import com.odtheking.odin.events.GuiEvent @@ -18,6 +19,7 @@ import com.odtheking.odin.utils.render.getStringWidth import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.sendCommand import com.odtheking.odin.utils.skyblock.LocationUtils +import com.odtheking.odin.utils.skyblock.dungeon.DungeonClass import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.skyblock.dungeon.M7Phases import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes @@ -37,25 +39,26 @@ object MelodyMessage : Module( private val broadcast by BooleanSetting("Broadcast Progress", true, desc = "Broadcasts melody progress to all other odin users in the party.") private val melodyGui by HUD("Progress GUI", "Shows a gui with the progress of broadcasting odin users in melody.", true) { - if (it) drawMelody(MelodyData(3, 1, 2), 0) + if (it) drawMelody(MelodyData(3, 1, 2, DungeonClass.ARCHER), 0, mc.user.name) if (broadcast && melodyWebSocket.connected) { melodies.entries.forEachIndexed { i, (name, data) -> - if (!showOwn && name == mc.user.name) return@forEachIndexed - drawMelody(data, i) + if (showPlayer == 0 && name == mc.user.name) return@forEachIndexed + drawMelody(data, i, name) } } 40 to 15 }.withDependency { broadcast } - private val showOwn: Boolean by BooleanSetting("Show Own", false, desc = "Shows your own progress in the melody GUI.").withDependency { broadcast && melodyGui.enabled } + private val showPlayer by SelectorSetting("Show Player", "Name", arrayListOf("None", "Class", "Name", "Class & Name"), desc = "How player details should be rendered in the Melody GUI.").withDependency { broadcast } val melodyWebSocket = webSocket { - onMessage { - val (user, type, slot) = try { gson.fromJson(it, UpdateMessage::class.java) } catch (_: Exception) { return@onMessage } - val entry = melodies.getOrPut(user) { MelodyData(null, null, null) } + onMessage { message -> + val (username, type, slot) = try { gson.fromJson(message, UpdateMessage::class.java) } catch (_: Exception) { return@onMessage } + val playerClass = DungeonUtils.dungeonTeammates.find { it.name == username }?.clazz ?: return@onMessage + val entry = melodies.getOrPut(username) { MelodyData(null, null, null, playerClass) } when (type) { - 0 -> melodies.remove(user) + 0 -> melodies.remove(username) 1 -> entry.clay = slot 2 -> entry.purple = slot 5 -> entry.pane = slot @@ -64,7 +67,7 @@ object MelodyMessage : Module( } private val melodies = ConcurrentHashMap<String, MelodyData>() - private val lastSent = MelodyData(null, null, null) + private val lastSent = MelodyData(null, null, null, DungeonClass.EMPTY) init { on<TerminalEvent.Open> { @@ -152,16 +155,24 @@ object MelodyMessage : Module( private val width by lazy { getStringWidth("§d■") } - private fun GuiGraphicsExtractor.drawMelody(data: MelodyData, index: Int) { + private fun GuiGraphicsExtractor.drawMelody(data: MelodyData, index: Int, playerName: String) { val y = width * 2 * index repeat(5) { if (data.purple == it) textDim("§d■", width * it, y) textDim("${if (data.pane == it) "§a" else "§f"}■", width * it, y + width) } - data.clay?.let { textDim(it.toString(), width * 5 + 2, y + width / 2) } + + val label = when (showPlayer) { + 1 -> data.dungeonClass + 2 -> playerName + 3 -> "$playerName (${data.dungeonClass})" + else -> return + } + + data.clay?.let { textDim("$it $label", width * 5 + 2, y + width / 2) } } - private data class UpdateMessage(val user: String, val type: Int, val slot: Int) - private data class MelodyData(var purple: Int?, var pane: Int?, var clay: Int?) -} \ No newline at end of file + private data class UpdateMessage(val username: String, val type: Int, val slot: Int) + private data class MelodyData(var purple: Int?, var pane: Int?, var clay: Int?, val dungeonClass: DungeonClass) +} |
Refactored DungeonWaypointsPacks management for more straight forward code查看变更src/main/kotlin/com/odtheking/odin/commands/DungeonWaypointCommand.kt | 14 +++-- src/main/kotlin/com/odtheking/odin/config/DungeonWaypointConfig.kt | 6 +- src/main/kotlin/com/odtheking/odin/config/WaypointPackFileUtils.kt | 151 ++++++++++++++++----------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/LeapMenu.kt | 19 ++++++- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypointPacks.kt | 12 ++-- .../kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/WaypointPackSelectorScreen.kt | 14 ++--- 6 files changed, 87 insertions(+), 129 deletions(-) @@ -1,12 +1,14 @@ package com.odtheking.odin.commands import com.github.stivais.commodore.Commodore +import com.github.stivais.commodore.utils.GreedyString import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.OdinMod.scope import com.odtheking.odin.config.DungeonWaypointConfig import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.* import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.modMessage +import com.odtheking.odin.utils.network.WebUtils import com.odtheking.odin.utils.setClipboardContent import kotlinx.coroutines.launch @@ -67,11 +69,15 @@ val dungeonWaypointsCommand = Commodore("dwp", "dungeonwaypoints") { } } - literal("import").runs { _: String? -> + literal("import").runs { importString: GreedyString? -> scope.launch { - val clipboard = mc.keyboardHandler.clipboard.trim().trim { it == '\n' } - val waypoints = DungeonWaypointConfig.decodeWaypoints(clipboard, clipboard.startsWith("{")) - ?: return@launch modMessage("§cFailed to decode waypoints from clipboard. §fIs the data valid?") + val input = (importString?.let { + if (importString.string.startsWith("https://")) WebUtils.fetchString(importString.string).getOrNull() + else importString.string + } ?: mc.keyboardHandler.clipboard) + + val waypoints = DungeonWaypointConfig.decodeWaypoints(input.trim().trim { it == '\n' }) ?: return@launch modMessage("§cFailed to decode waypoints from clipboard. §fIs the data valid?") + DungeonWaypoints.importEditableWaypoints(waypoints) modMessage("Imported waypoints from clipboard!${if (!DungeonWaypoints.enabled) "§7(Make sure to enable the DungeonWayPoints module)" else ""}") } @@ -15,7 +15,7 @@ import java.util.zip.GZIPOutputStream import kotlin.io.encoding.Base64 object DungeonWaypointConfig { - private val gson = GsonBuilder() + val gson: Gson = GsonBuilder() .registerTypeAdapter(AABB::class.java, AABBSerializer()) .registerTypeAdapter(DungeonWaypoint::class.java, DungeonWaypointDeserializer()) .registerTypeAdapter(BlockPos::class.java, BlockPosSerializer()) @@ -39,10 +39,10 @@ object DungeonWaypointConfig { } } - fun decodeWaypoints(input: String, isJson: Boolean): MutableMap<String, MutableList<DungeonWaypoint>>? { + fun decodeWaypoints(input: String): MutableMap<String, MutableList<DungeonWaypoint>>? { return try { gson.fromJson( - if (isJson) input else decompress(Base64.decode(input)), + if (input.startsWith("{")) input else decompress(Base64.decode(input)), object : TypeToken<MutableMap<String, MutableList<DungeonWaypoint>>>() {}.type ) } catch (_: Exception) { @@ -1,150 +1,91 @@ package com.odtheking.odin.config -import com.google.gson.GsonBuilder import com.google.gson.reflect.TypeToken import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints.DungeonWaypoint import com.odtheking.odin.utils.modMessage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import net.minecraft.world.phys.AABB +import net.minecraft.util.FileUtil import java.io.File -object WaypointPackFileUtils { - - data class WaypointPack( - val name: String, - val file: File, - val waypoints: MutableMap<String, MutableList<DungeonWaypoint>> - ) - - private val gson = GsonBuilder() - .registerTypeAdapter(AABB::class.java, DungeonWaypointConfig.AABBSerializer()) - .registerTypeAdapter(DungeonWaypoint::class.java, DungeonWaypointConfig.DungeonWaypointDeserializer()) - .setPrettyPrinting() - .create() +private typealias PackMap = MutableMap<String, MutableList<DungeonWaypoint>> - private val packType = - object : TypeToken<MutableMap<String, MutableList<DungeonWaypoint>>>() {}.type +object WaypointPackFileUtils { private val legacyConfigFile = File(mc.gameDirectory, "config/odin/dungeon-waypoint-config.json") val packsFolder = File(mc.gameDirectory, "config/odin/dungeon-waypoints").apply { mkdirs() } + private val packType = object : TypeToken<PackMap>() {}.type init { - migrateLegacyConfigIfNeeded() - } - - private fun migrateLegacyConfigIfNeeded() { - if (!legacyConfigFile.exists() || packsFolder.listFiles()?.isNotEmpty() == true) return - runCatching { - val target = File(packsFolder, "default.json") - target.writeText(legacyConfigFile.readText()) + if (listPackNames().isEmpty()) runCatching { + val content = legacyConfigFile.takeIf(File::exists)?.readText() ?: DungeonWaypointConfig.gson.toJson(emptyPack()) + packFile("default").writeText(content) }.onFailure { it.printStackTrace() } } private fun packFile(name: String) = File(packsFolder, "$name.json") - private fun emptyPack() = mutableMapOf<String, MutableList<DungeonWaypoint>>() + private fun emptyPack(): PackMap = mutableMapOf() - suspend fun loadPack(packName: String) = withContext(Dispatchers.IO) { + fun listPackNames(): List<String> = + packsFolder.listFiles { f -> f.extension == "json" }?.map { it.nameWithoutExtension }?.sorted() ?: emptyList() + + suspend fun loadPack(packName: String): PackMap = withContext(Dispatchers.IO) { runCatching { - packFile(packName) - .takeIf(File::exists) - ?.readText() - ?.let { gson.fromJson(it, packType) } - ?: emptyPack() - }.getOrElse { - it.printStackTrace() - emptyPack() - } + packFile(packName).takeIf(File::exists)?.readText() + ?.let { DungeonWaypointConfig.gson.fromJson<PackMap>(it, packType) } ?: emptyPack() + }.getOrElse { it.printStackTrace(); emptyPack() } } - suspend fun savePack(packName: String, waypoints: MutableMap<String, MutableList<DungeonWaypoint>>) = - withContext(Dispatchers.IO) { - runCatching { - packFile(packName).writeText(gson.toJson(waypoints)) - }.onFailure { it.printStackTrace() } - } + suspend fun savePack(packName: String, waypoints: PackMap) = withContext(Dispatchers.IO) { + runCatching { packFile(packName).writeText(DungeonWaypointConfig.gson.toJson(waypoints)) }.onFailure { it.printStackTrace() } + } - suspend fun createPack(packName: String) = withContext(Dispatchers.IO) { + suspend fun createPack(packName: String): Boolean = withContext(Dispatchers.IO) { + val file = packFile(packName) when { - !isValidPackName(packName) -> { - modMessage("§cInvalid pack name! Use only letters, numbers, spaces, hyphens, and underscores.") - false - } - packFile(packName).exists() -> { - modMessage("§cPack '$packName' already exists!") - false - } - else -> runCatching { - packFile(packName).writeText(gson.toJson(emptyPack())) - modMessage("§aCreated new pack: $packName") - true - }.getOrElse { - it.printStackTrace() - modMessage("§cFailed to create pack!") - false + !isValidPackName(packName) -> fail("Invalid pack name!") + file.exists() -> fail("Pack '$packName' already exists!") + else -> attempt("Created new pack: $packName", "Failed to create pack!") { + file.writeText(DungeonWaypointConfig.gson.toJson(emptyPack())) } } } - suspend fun deletePack(packName: String) = withContext(Dispatchers.IO) { + suspend fun deletePack(packName: String): Boolean = withContext(Dispatchers.IO) { + val file = packFile(packName) when { - !packFile(packName).exists() -> { - modMessage("§cPack '$packName' does not exist!") - false - } - else -> runCatching { - if (!packFile(packName).delete()) error("Failed to delete ${packFile(packName).path}") - modMessage("§aDeleted pack: $packName") - true - }.getOrElse { - it.printStackTrace() - modMessage("§cFailed to delete pack!") - false + !file.exists() -> fail("Pack '$packName' does not exist!") + else -> attempt("Deleted pack: $packName", "Failed to delete pack!") { + check(file.delete()) { "Failed to delete ${file.path}" } } } } - suspend fun renamePack(oldName: String, newName: String) = withContext(Dispatchers.IO) { + suspend fun renamePack(oldName: String, newName: String): Boolean = withContext(Dispatchers.IO) { + val oldFile = packFile(oldName) + val newFile = packFile(newName) when { - !isValidPackName(newName) -> { - modMessage("§cInvalid pack name! Use only letters, numbers, spaces, hyphens, and underscores.") - false - } - !packFile(oldName).exists() -> { - modMessage("§cPack '$oldName' does not exist!") - false - } - packFile(newName).exists() -> { - modMessage("§cPack '$newName' already exists!") - false - } - else -> runCatching { - if (!packFile(oldName).renameTo(packFile(newName))) error("Failed to rename ${packFile(oldName).path}") - modMessage("§aRenamed pack from '$oldName' to '$newName'") - true - }.getOrElse { - it.printStackTrace() - modMessage("§cFailed to rename pack!") - false + !isValidPackName(newName) -> fail("Invalid pack name!") + !oldFile.exists() -> fail("Pack '$oldName' does not exist!") + newFile.exists() -> fail("Pack '$newName' already exists!") + else -> attempt("Renamed pack from '$oldName' to '$newName'", "Failed to rename pack!") { + check(oldFile.renameTo(newFile)) { "Failed to rename ${oldFile.path}" } } } } - suspend fun getAllPacks() = withContext(Dispatchers.IO) { - packsFolder.listFiles { f -> f.extension == "json" } - ?.mapNotNull { file -> - runCatching { - WaypointPack(file.nameWithoutExtension, file, gson.fromJson(file.readText(), packType)) - }.getOrElse { - println("Failed to load waypoint pack: ${file.name}") - it.printStackTrace() - null - } - } ?: emptyList() + private fun fail(message: String): Boolean { + modMessage("§c$message") + return false } + private fun attempt(successMessage: String, failureMessage: String, action: () -> Unit): Boolean = + runCatching(action).fold( + onSuccess = { modMessage("§a$successMessage"); true }, + onFailure = { it.printStackTrace(); fail(failureMessage) } + ) private fun isValidPackName(name: String) = - name.isNotBlank() && name.all { it.isLetterOrDigit() || it == '-' || it == '_' || it == ' ' } + name.isNotBlank() && FileUtil.sanitizeName(name) == name && FileUtil.isPathPartPortable(name) } @@ -3,6 +3,8 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.* import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.GuiEvent +import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.ScreenEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module @@ -18,6 +20,7 @@ import com.odtheking.odin.utils.ui.HoverHandler import com.odtheking.odin.utils.ui.widget.CustomGUIImpl import net.minecraft.client.gui.components.PlayerFaceExtractor import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.world.item.Items import org.lwjgl.glfw.GLFW object LeapMenu : Module( @@ -48,7 +51,9 @@ object LeapMenu : Module( private val hoverHandler = List(4) { HoverHandler(200L) } private val EMPTY = DungeonPlayer("Empty", DungeonClass.EMPTY, 0, null) - private val leapedRegex = Regex("You have teleported to (\\w{1,16})!") + private val leapedRegex = Regex("^You have teleported to (\\w{1,16})!$") + private val playerNameRegex = Regex("^(?:\\[.+?] )?(\\w{1,16})$") + private val leapIndex = HashMap<String, Int>() const val BOX_WIDTH = 200 const val BOX_HEIGHT = 75 @@ -166,6 +171,18 @@ object LeapMenu : Module( if (leapAnnounce && DungeonUtils.inDungeons) leapedRegex.find(value)?.groupValues?.get(1)?.let { sendCommand("pc Leaped to ${it}!") } } + + on<GuiEvent.SlotUpdate> { + currentLeapScreen()?.let { + if (packet.item.isEmpty || packet.item.item != Items.PLAYER_HEAD) return@on + val (name) = playerNameRegex.find(packet.item.hoverName.string)?.destructured ?: return@on + leapIndex[name] = packet.slot + } + } + + on<LevelEvent.Load> { + leapIndex.clear() + } } fun AbstractContainerScreen<*>.mouseTrigger(player: DungeonPlayer, quadrant: Int) { @@ -7,6 +7,8 @@ import com.odtheking.odin.features.ModuleManager import com.odtheking.odin.features.impl.dungeon.map.tile.DungeonRoom import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import net.minecraft.world.phys.AABB suspend fun DungeonWaypoints.loadWaypoints() { @@ -50,7 +52,7 @@ internal suspend fun DungeonWaypoints.createPack(packName: String): Boolean { } internal suspend fun DungeonWaypoints.deletePack(packName: String): Boolean { - if (WaypointPackFileUtils.getAllPacks().size <= 1) { + if (withContext(Dispatchers.IO) { WaypointPackFileUtils.listPackNames().size } <= 1) { modMessage("§cCannot delete the only pack!") return false } @@ -104,16 +106,12 @@ private suspend fun DungeonWaypoints.ensurePackState( requestedSelection: List<String> = selectedPackIds, requestedEditPackId: String = editPackId, ): WaypointPackState { - var availablePacks = WaypointPackFileUtils.getAllPacks() - if (availablePacks.isEmpty()) { - WaypointPackFileUtils.createPack("default") - availablePacks = WaypointPackFileUtils.getAllPacks() - } + val availablePackIds = withContext(Dispatchers.IO) { WaypointPackFileUtils.listPackNames() } val normalizedState = WaypointPackState( selectedPackIds = requestedSelection.ifEmpty { selectedPackIds }.toMutableList(), editPackId = requestedEditPackId.ifBlank { editPackId }, - ).normalized(availablePacks.map { it.name }) + ).normalized(availablePackIds) selectedPackIds = normalizedState.selectedPackIds.toMutableList() editPackId = normalizedState.editPackId @@ -7,7 +7,6 @@ import com.odtheking.odin.config.WaypointPackFileUtils import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.modMessage import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.components.Button import net.minecraft.client.gui.components.ScrollableLayout @@ -25,14 +24,10 @@ class WaypointPackSelectorScreen(private val parent: Screen?) : Screen(Component private var deleteConfirmTime = 0L private val deleteButtons = mutableMapOf<String, Button>() - private fun packNames() = WaypointPackFileUtils.packsFolder - .listFiles { f -> f.extension == "json" } - ?.map { it.nameWithoutExtension } - ?.sorted() ?: emptyList() + private fun packNames() = WaypointPackFileUtils.listPackNames() - private fun packWaypointCount(packName: String): Int = + private fun packWaypointCount(packName: String): Int? = DungeonWaypoints.loadedPacks[packName]?.values?.sumOf { it.size } - ?: runCatching { runBlocking { WaypointPackFileUtils.loadPack(packName).values.sumOf { it.size } } }.getOrDefault(0) override fun init() { super.init() @@ -89,7 +84,8 @@ class WaypointPackSelectorScreen(private val parent: Screen?) : Screen(Component .size(280, 24).tooltip(Tooltip.create(Component.literal(if (isEdit) "Currently editing" else "Click to edit"))).build()) val count = packWaypointCount(packName) - row.addChild(StringWidget(Component.literal("§a$count §7wp"), font).apply { setWidth(70) }) + val countText = if (count != null) "§a$count §7wp" else "§7? wp" + row.addChild(StringWidget(Component.literal(countText), font).apply { setWidth(70) }) row.addChild(Button.builder(Component.literal("✎")) { showRenameDialog(packName) } .size(30, 24).tooltip(Tooltip.create(Component.literal("Rename"))).build()) @@ -204,7 +200,7 @@ class WaypointPackSelectorScreen(private val parent: Screen?) : Screen(Component if (name.isNotBlank()) { loading = true OdinMod.scope.launch { - val ok = DungeonWaypointConfig.decodeWaypoints(clipboard, clipboard.startsWith("{")) + val ok = DungeonWaypointConfig.decodeWaypoints(clipboard) ?.takeIf { DungeonWaypoints.importPack(name, it) } ?.let { modMessage("§aImported waypoints as pack '$name'!"); true } ?: false |
Fixed bonzo mask cooldown not being adjusted on per catacombs level basis查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/InvincibilityTimer.kt | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) @@ -15,7 +15,6 @@ import com.odtheking.odin.utils.render.ItemStateRenderer.Companion.drawItemStack import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.world.entity.EquipmentSlot -import net.minecraft.world.item.ItemStack object InvincibilityTimer : Module( name = "Invincibility Timer", @@ -78,6 +77,7 @@ object InvincibilityTimer : Module( width + 20 to visibleTypes.size * 14 } private val showOnlyInBoss by BooleanSetting("Show In Boss", false, desc = "Only shows invincibility timers during dungeon boss fights.") + private val cooldownRegex = Regex("^Cooldown: (\\d+)s$") init { on<TickEvent.Server> { @@ -87,7 +87,9 @@ object InvincibilityTimer : Module( on<ChatPacketEvent> { if (onlyInDungeons && !DungeonUtils.inDungeons) return@on InvincibilityType.entries.firstOrNull { type -> value.matches(type.regex) }?.let { type -> - type.proc() + val seconds = mc.player?.getItemBySlot(EquipmentSlot.HEAD)?.loreString?.reversed() + ?.firstNotNullOfOrNull { cooldownRegex.matchEntire(it)?.groupValues?.get(1)?.toIntOrNull() } + type.proc(seconds) val usedMasks = InvincibilityType.entries.count { it.currentCooldown > 0 } if (invincibilityAnnounce) sendCommand("pc ${type.name.lowercase().capitalizeFirst()} Procced! ($usedMasks/${InvincibilityType.entries.size})") if (invincibilityAlert) alert(type.name.lowercase().capitalizeFirst()) @@ -126,32 +128,34 @@ object InvincibilityTimer : Module( val regex: Regex, private val maxInvincibilityTime: Int, val maxCooldownTime: Int, - val itemStack: ItemStack + private val textureHash: String ) { SPIRIT( Regex("^Second Wind Activated! Your Spirit Mask saved your life!$"), - 60, 600, - createSkullStack("9bbe721d7ad8ab965f08cbec0b834f779b5197f79da4aea3d13d253ece9dec2") + 60, 30, + "9bbe721d7ad8ab965f08cbec0b834f779b5197f79da4aea3d13d253ece9dec2" ), BONZO( Regex("^Your (?:. )?Bonzo's Mask saved your life!$"), - 60, 3600, - createSkullStack("12716ecbf5b8da00b05f316ec6af61e8bd02805b21eb8e440151468dc656549c") + 60, 180, + "12716ecbf5b8da00b05f316ec6af61e8bd02805b21eb8e440151468dc656549c" ), PHOENIX( Regex("^Your Phoenix Pet saved you from certain death!$"), - 80, 1200, - createSkullStack("66b1b59bc890c9c97527787dde20600c8b86f6b9912d51a6bfcdb0e4c2aa3c97") + 80, 60, + "66b1b59bc890c9c97527787dde20600c8b86f6b9912d51a6bfcdb0e4c2aa3c97" ); + val itemStack by lazy { createSkullStack(textureHash) } + var activeTime: Int = 0 private set var currentCooldown: Int = 0 private set - fun proc() { + fun proc(cooldownTime: Int?) { activeTime = maxInvincibilityTime - currentCooldown = maxCooldownTime + currentCooldown = (cooldownTime ?: maxCooldownTime) * 20 } fun tick() { |
Fixed DungeonMap while room text settings being set to only secrets rooms without secrets not having checkmark indicator查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -105,7 +105,7 @@ fun GuiGraphicsExtractor.renderIcon(pos: IVec2, identifier: Identifier) { fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { if (room.type.equalsOneOf(RoomType.UNDISCOVERED, RoomType.FAIRY, RoomType.ENTRANCE, RoomType.BLOOD)) return - if (!room.walkedInto) { + if (!room.walkedInto || (DungeonMap.roomText == 2 && (room.data?.maxSecrets ?: 0) == 0)) { when (room.checkmark) { MapCheckmark.GREEN -> green MapCheckmark.WHITE -> white |
Made Puzzle timers not count if time is under 5 seconds查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -201,7 +201,7 @@ object PuzzleSolvers : Module( fun onPuzzleComplete(puzzleName: String) { puzzleTimersMap[puzzleName]?.let { - if (it.sentMessage) return + if (it.sentMessage || (System.currentTimeMillis() - it.timeEntered) / 1000f < 5) return puzzlePBs.time(puzzleName, (System.currentTimeMillis() - it.timeEntered) / 1000f, "s§7!", "§a${puzzleName} §7solved in §6") it.sentMessage = true } |
| fix vesuvius not working at all (#143) * fix vesuvius not working at all
* vitality rework compat
* Add lava leech and kuudra pet bonus to profit calculation
* better detection
* should finally be done :sob:
* should finally be done :sob: 查看变更src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) @@ -1,6 +1,7 @@ package com.odtheking.odin.features.impl.nether import com.odtheking.odin.clickgui.settings.impl.BooleanSetting +import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive @@ -13,7 +14,6 @@ import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.network.chat.Component import net.minecraft.network.chat.Style import net.minecraft.network.chat.TextColor -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items @@ -26,6 +26,9 @@ object Vesuvius : Module( private val hideClaimed by BooleanSetting("Hide Claimed", true, desc = "Hides chests that have already been claimed.") private val useSalvagePrices by BooleanSetting("Use Salvaged", false, desc = "Uses the essence you would get by salvaging the piece instead.") + private val kuudraPetBonus by NumberSetting("Kuudra Pet Bonus", 0.0, 0.0, 20.0, 0.05, "The essence bonus from Kuudra pet.", unit = "%") + private val lavaLeechBonus by NumberSetting("Lava Leech Bonus", 0.0, 0.0, 13.0, 0.05, "The essence bonus from the Lava Leech Shard.", unit = "%") + private val vesuviusHud by HUD("Croesus Chest HUD", "Displays all chest contents with prices, sorted by profit.") { if (!it) return@HUD 0 to 0 drawOverlay(true) @@ -36,7 +39,7 @@ object Vesuvius : Module( private val shardRegex = Regex("^([A-Za-z' ]+) Shard(?: x(\\d+))?$") private val teethRegex = Regex("^Kuudra Teeth x(\\d+)$") private val pearlRegex = Regex("^Heavy Pearl x(\\d+)$") - private val chestRegex = Regex("^((Free|Paid) Chest Chest)|(Kuudra - .+)|Vesuvius$") + private val chestRegex = Regex("^((Free|Paid) Chest)|(Kuudra - .+)$") private val uselessLinesRegex = Regex("^Contents|Cost|Click to open!|FREE|Already opened!|Can't open another chest!|Paid Chest|") private val salvageItemsRegex = Regex("^Boots|Chestplate|Helmet|Cloak|Aurora Staff|Hollow Wand") @@ -53,7 +56,7 @@ object Vesuvius : Module( init { on<GuiEvent.DrawTooltip> { val title = screen.title.string - if (vesuviusHud.enabled && title.matches(chestRegex) && currentChest != null && title != "Vesuvius") { + if (vesuviusHud.enabled && title.matches(chestRegex) && currentChest != null) { guiGraphics.pose().pushMatrix() val sf = mc.window.guiScale guiGraphics.pose().scale(1f / sf, 1f / sf) @@ -67,16 +70,18 @@ object Vesuvius : Module( } on<GuiEvent.RenderSlot> { - if (screen.title.string.equalsOneOf("Vesuvius", "Croesus") && slot.item.hoverName.string == "Kuudra's Hollow") { + if (screen.title.string.containsOneOf("Vesuvius", "Croesus") && slot.item.hoverName.string == "Kuudra's Hollow") { if (hideClaimed && slot.item.loreString.any { it == "No more chests to open!"}) cancel() } } - onReceive<ClientboundContainerSetSlotPacket> { - val title = mc.screen?.title?.string ?:return@onReceive - if (!title.matches(chestRegex)) return@onReceive - if (slot == 31 && item.item == Items.CHEST) handleKuudraChest(item) - if (slot == 14 && item.item == Items.PLAYER_HEAD) handleKuudraChest(item) + on<GuiEvent.SlotUpdate> { + if (!screen.title.string.matches(chestRegex)) return@on + + val item = packet.item + + if (packet.slot == 31 && item.item == Items.CHEST) handleKuudraChest(item) + if (packet.slot.equalsOneOf(13, 14) && item.item == Items.PLAYER_HEAD) handleKuudraChest(item) } onReceive<ClientboundOpenScreenPacket> { @@ -89,6 +94,8 @@ object Vesuvius : Module( var starCount = 0 val salvage = salvageItemsRegex.containsMatchIn(component.string) + val essenceBonus = (1 + kuudraPetBonus / 100.0) * (1 + lavaLeechBonus / 100.0) + component.visit({ style, str -> val count = str.count { it == '✪' } if (count > 0) { @@ -103,17 +110,19 @@ object Vesuvius : Module( val item = component.string.replace("✪", "").trim() if (item.contains("Molten") && useSalvagePrices) { - return (cachedPrices["ESSENCE_CRIMSON"] ?: 0.0) * 600.0 + return (cachedPrices["ESSENCE_CRIMSON"] ?: 0.0) * 600.0 * essenceBonus } previewEnchantedBookRegex.find(item)?.destructured?.let { (name, level) -> val ult = if (name in ultimateEnchants) "ULTIMATE_" else "" + + enchantReplacements[name]?.let { itemId -> return cachedPrices["$itemId-${romanToInt(level)}"] } return cachedPrices["ENCHANTED_BOOK-$ult${name.uppercase().replace(" ", "_")}-${romanToInt(level)}"] } previewEssenceRegex.find(item)?.destructured?.let { (name, quantity) -> val price = cachedPrices["ESSENCE_${name.uppercase()}"] ?: return null - return price * quantity.toDouble() + return price * quantity.toDouble() * essenceBonus } shardRegex.find(item)?.let { shard -> @@ -217,6 +226,13 @@ object Vesuvius : Module( "Aurora Staff" to "RUNIC_STAFF", ) + private val enchantReplacements = mapOf( + "Vivacious Vitality" to "ENCHANTED_BOOK-FEROCIOUS_MANA", + "Hardened Vitality" to "ENCHANTED_BOOK-HARDENED_MANA", + "Strong Vitality" to "ENCHANTED_BOOK-STRONG_MANA", + "Vampiric Vitality" to "ENCHANTED_BOOK-MANA_VAMPIRE", + ) + private val starCountToEssence = mapOf( 0 to 120.0, 1 to 138.0, |
Added dev message to debug websocket in DungeonMap查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 5 +++-- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/SlotBinds.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/LocationUtils.kt | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) @@ -13,6 +13,7 @@ import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.IVec2 +import com.odtheking.odin.utils.devMessage import com.odtheking.odin.utils.network.WebUtils.gson import com.odtheking.odin.utils.network.webSocket import com.odtheking.odin.utils.render.hollowFill @@ -94,13 +95,13 @@ object DungeonMap : Module( init { on<SecretsUpdateEvent> { - if (!allowWebsocket || !syncSocket.connected) return@on + if (!allowWebsocket) return@on room.name?.let { syncSocket.send(gson.toJson(RoomSync(it, foundSecrets, room.topLeft))) } } on<FloorEnterEvent> { if (!allowWebsocket) return@on - LocationUtils.lobbyId?.let { syncSocket.connect("${ClickGUIModule.webSocketUrl}$it") } + LocationUtils.lobbyId?.let { syncSocket.connect("${ClickGUIModule.webSocketUrl}$it") } ?: devMessage("Failed to connect to dungeon websocket, lobbyId is null.") } on<RoomEnterEvent> { @@ -38,7 +38,7 @@ object SlotBinds : Module( init { on<GuiEvent.SlotClick> { - if (mc.hasShiftDown() || screen !is InventoryScreen) return@on + if (!mc.hasShiftDown() || screen !is InventoryScreen) return@on val clickedSlot = screen.hoveredSlot?.index?.takeIf { it in 5 until 45 } ?: return@on val boundSlot = slotBinds[clickedSlot] ?: return@on @@ -3,6 +3,7 @@ package com.odtheking.odin.utils.skyblock import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.LocationChangeEvent +import com.odtheking.odin.events.core.EventPriority import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.utils.equalsOneOf @@ -40,7 +41,7 @@ object LocationUtils { if (!isInSkyblock) isInSkyblock = objectiveName == "SBScoreboard" } - onReceive<ClientboundSetPlayerTeamPacket> { + onReceive<ClientboundSetPlayerTeamPacket> (EventPriority.LOW) { if (!isCurrentArea(Island.Unknown)) return@onReceive val text = parameters.getOrNull()?.let { it.playerPrefix.string.plus(it.playerSuffix.string).noControlCodes } ?: return@onReceive |
Fixed croesus not highlighting correctly查看变更src/main/kotlin/com/odtheking/odin/events/GuiEvent.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 26 ++++++++++++++++++++------ src/main/kotlin/com/odtheking/odin/features/impl/skyblock/SlotBinds.kt | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) @@ -12,7 +12,7 @@ abstract class GuiEvent(val screen: Screen) : CancellableEvent() { class Render(screen: Screen, val guiGraphics: GuiGraphicsExtractor, val mouseX: Int, val mouseY: Int) : GuiEvent(screen) - class SlotClick(screen: Screen, val slot: Slot, val slotId: Int, val button: Int, val containerInput: ContainerInput) : GuiEvent(screen) + class SlotClick(screen: Screen, val slot: Slot?, val slotId: Int, val button: Int, val containerInput: ContainerInput) : GuiEvent(screen) class SlotUpdate(screen: Screen, val packet: ClientboundContainerSetSlotPacket, val menu: AbstractContainerMenu) : GuiEvent(screen) @@ -14,6 +14,7 @@ import com.odtheking.odin.features.Module import com.odtheking.odin.utils.* import com.odtheking.odin.utils.network.WebUtils.fetchJson import com.odtheking.odin.utils.render.getStringWidth +import com.odtheking.odin.utils.render.roundedOutline import com.odtheking.odin.utils.render.text import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils @@ -72,6 +73,9 @@ object Croesus : Module( private val extraStatsRegex = Regex(" {29}> EXTRA STATS <") private val chestCostRegex = Regex("^([\\d,]+) Coins$") private val shardRegex = Regex("^([A-Za-z ]+) Shard$") + private val noMoreChestsRegex = Regex("^No more chests to open!$") + + private const val KISMET_FEATHER_LABEL = "Kismet Feather" private val ultimateEnchants = setOf( "Soul Eater", "Combo", "Legion", "One For All", "Rend", @@ -110,9 +114,19 @@ object Croesus : Module( val loreString = slot.item.loreString if (hideClaimed && loreString.any { it.matches(chestStatusRegex) } && (!includeKey || hasStrikeThrough("Dungeon Chest Key", slot.item.lore ))) cancel() - else if (highlightState) - guiGraphics.fill(slot.x, slot.y, slot.x + 16, slot.y + 16, - if (loreString.any { it.matches(chestOpenedRegex) }) Colors.MINECRAFT_GOLD.rgba else Colors.MINECRAFT_GREEN.rgba) + else { + if (highlightState) { + val color = when { + loreString.any { it.matches(noMoreChestsRegex) } -> Colors.MINECRAFT_RED.rgba + loreString.any { it.matches(chestOpenedRegex) } -> Colors.MINECRAFT_GOLD.rgba + else -> Colors.MINECRAFT_GREEN.rgba + } + guiGraphics.fill(slot.x, slot.y, slot.x + 16, slot.y + 16, color) + } + + if (hasStrikeThrough(KISMET_FEATHER_LABEL, slot.item.lore)) + guiGraphics.roundedOutline(slot.x, slot.y, slot.x + 16, slot.y + 16, Colors.MINECRAFT_AQUA.rgba, 1f) + } } else if (highlightProfitable && screen.title.string.matches(chestPreviewScreenRegex) && slot.index in mostProfitableSlots) { val color = when (mostProfitableSlots.indexOf(slot.index)) { @@ -126,11 +140,11 @@ object Croesus : Module( onReceive<ClientboundContainerSetSlotPacket> { val screenTitle = mc.screen?.title?.string ?: return@onReceive - val menu = (mc.screen as? AbstractContainerScreen<*>)?.menu ?: return@onReceive + val items = (mc.screen as? AbstractContainerScreen<*>)?.menu?.items ?: return@onReceive when { - screenTitle.matches(chestNameRegex) -> handleChestContents(menu.items) - screenTitle.matches(chestPreviewScreenRegex) -> handleCroesusScreen(menu.items) + screenTitle.matches(chestNameRegex) -> handleChestContents(items) + screenTitle.matches(chestPreviewScreenRegex) -> handleCroesusScreen(items) } } @@ -38,7 +38,7 @@ object SlotBinds : Module( init { on<GuiEvent.SlotClick> { - if (containerInput != ContainerInput.QUICK_MOVE || screen !is InventoryScreen) return@on + if (mc.hasShiftDown() || screen !is InventoryScreen) return@on val clickedSlot = screen.hoveredSlot?.index?.takeIf { it in 5 until 45 } ?: return@on val boundSlot = slotBinds[clickedSlot] ?: return@on |
Fixed ActionBarListener not registering room secrets when they are 0查看变更src/main/java/com/odtheking/mixin/mixins/AbstractContainerScreenMixin.java | 2 +- src/main/kotlin/com/odtheking/odin/events/GuiEvent.kt | 3 ++- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/TermSimGUI.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 24 ++++++++++++++---------- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/SlotBinds.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/ActionBarListener.kt | 2 +- 6 files changed, 20 insertions(+), 15 deletions(-) @@ -26,7 +26,7 @@ public class AbstractContainerScreenMixin { @Inject(method = "slotClicked", at = @At("HEAD"), cancellable = true) private void onMouseClickedSlot(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci) { - if (new GuiEvent.SlotClick((Screen) (Object) this, slotId, buttonNum).postAndCatch()) ci.cancel(); + if (new GuiEvent.SlotClick((Screen) (Object) this, slot, slotId, buttonNum, containerInput).postAndCatch()) ci.cancel(); } @Inject(method = "extractTooltip", at = @At("HEAD"), cancellable = true) @@ -5,13 +5,14 @@ import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.screens.Screen import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.inventory.ContainerInput import net.minecraft.world.inventory.Slot abstract class GuiEvent(val screen: Screen) : CancellableEvent() { class Render(screen: Screen, val guiGraphics: GuiGraphicsExtractor, val mouseX: Int, val mouseY: Int) : GuiEvent(screen) - class SlotClick(screen: Screen, val slotId: Int, val button: Int) : GuiEvent(screen) + class SlotClick(screen: Screen, val slot: Slot, val slotId: Int, val button: Int, val containerInput: ContainerInput) : GuiEvent(screen) class SlotUpdate(screen: Screen, val packet: ClientboundContainerSetSlotPacket, val menu: AbstractContainerMenu) : GuiEvent(screen) @@ -74,7 +74,7 @@ open class TermSimGUI( } override fun slotClicked(slot: Slot, i: Int, j: Int, clickType: ContainerInput) { - if (GuiEvent.SlotClick(this, i, j).postAndCatch()) return + if (GuiEvent.SlotClick(this, slot, i, j, clickType).postAndCatch()) return slot?.let { delaySlotClick(it, j) } } @@ -12,6 +12,7 @@ import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.IVec2 import com.odtheking.odin.utils.network.WebUtils.gson import com.odtheking.odin.utils.network.webSocket import com.odtheking.odin.utils.render.hollowFill @@ -53,7 +54,7 @@ object DungeonMap : Module( val disablePred by BooleanSetting("Disable Prediction", false, desc = "Disables special-column room type prediction.") - val shareSecrets by BooleanSetting("Share Secrets", true, desc = "Shares found secrets in your room with the rest of your dungeon party.") + private val allowWebsocket by BooleanSetting("Websocket", true, desc = "Shares information in your room with the rest of your dungeon party.") private val exampleRooms by lazy { buildExampleRooms() } private val exampleDoors by lazy { buildExampleDoors() } @@ -84,26 +85,29 @@ object DungeonMap : Module( val syncSocket = webSocket { onMessage { message -> - val sync = try { gson.fromJson(message, RoomSecretsSync::class.java) } catch (_: Exception) { return@onMessage } - val room = DungeonScan.rooms.find { it.name == sync.roomName } ?: return@onMessage - if ((room.foundSecrets ?: 0) >= sync.foundSecrets) return@onMessage - room.foundSecrets = sync.foundSecrets + val (roomName, foundSecrets, position) = try { gson.fromJson(message, RoomSync::class.java) } catch (_: Exception) { return@onMessage } + val room = DungeonScan.rooms.find { it.name == roomName && it.topLeft == position } ?: return@onMessage + if ((room.foundSecrets ?: -1) < (foundSecrets ?: -1)) room.foundSecrets = foundSecrets + room.walkedInto = true } } init { on<SecretsUpdateEvent> { - if (!shareSecrets || !syncSocket.connected) return@on - room.name?.let { syncSocket.send(gson.toJson(RoomSecretsSync(it, foundSecrets))) } + if (!allowWebsocket || !syncSocket.connected) return@on + room.name?.let { syncSocket.send(gson.toJson(RoomSync(it, foundSecrets, room.topLeft))) } } on<FloorEnterEvent> { - if (!shareSecrets) return@on + if (!allowWebsocket) return@on LocationUtils.lobbyId?.let { syncSocket.connect("${ClickGUIModule.webSocketUrl}$it") } } - on<RoomEnterEvent> { if (room == null) syncSocket.shutdown() } + on<RoomEnterEvent> { + if (room == null) syncSocket.shutdown() + else if (allowWebsocket) room.name?.let { syncSocket.send(gson.toJson(RoomSync(it, room.foundSecrets, room.topLeft))) } + } } - private data class RoomSecretsSync(val roomName: String, val foundSecrets: Int) + private data class RoomSync(val roomName: String, val foundSecrets: Int?, val position: IVec2) } \ No newline at end of file @@ -38,7 +38,7 @@ object SlotBinds : Module( init { on<GuiEvent.SlotClick> { - if (!mc.hasShiftDown() || screen !is InventoryScreen) return@on + if (containerInput != ContainerInput.QUICK_MOVE || screen !is InventoryScreen) return@on val clickedSlot = screen.hoveredSlot?.index?.takeIf { it in 5 until 45 } ?: return@on val boundSlot = slotBinds[clickedSlot] ?: return@on @@ -55,7 +55,7 @@ object ActionBarListener { SECRETS_REGEX.find(msg)?.destructured?.let { (found, max) -> DungeonUtils.currentRoom?.let { val updatedFoundSecrets = found.toIntOrNull() ?: return@let - if (it.data?.maxSecrets != max.toIntOrNull() || (it.foundSecrets ?: 0) >= updatedFoundSecrets) return@let + if (it.data?.maxSecrets != max.toIntOrNull() || (it.foundSecrets ?: -1) >= updatedFoundSecrets) return@let it.foundSecrets = updatedFoundSecrets SecretsUpdateEvent(it, updatedFoundSecrets).postAndCatch() } |
Added dungeon room found secrets support to dungeon map with websocket integration查看变更src/main/kotlin/com/odtheking/odin/commands/DevCommand.kt | 6 +- src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 1 + src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 17 +---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 44 +++++++++++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonScan.kt | 17 ----- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 91 ++++++++++++++++----------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 34 +++++----- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/RoomData.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/ActionBarListener.kt | 12 ++++ src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 2 +- src/main/resources/assets/odin/rooms.json | 244 +++++++++++++++++++++++++++++++++++------------------------------------ 11 files changed, 252 insertions(+), 218 deletions(-) @@ -10,6 +10,7 @@ import com.odtheking.odin.features.impl.boss.MelodyMessage.melodyWebSocket import com.odtheking.odin.features.impl.boss.WitherDragonState import com.odtheking.odin.features.impl.boss.WitherDragons import com.odtheking.odin.features.impl.boss.WitherDragonsEnum +import com.odtheking.odin.features.impl.dungeon.map.DungeonMap import com.odtheking.odin.features.impl.dungeon.map.DungeonScan import com.odtheking.odin.features.impl.dungeon.map.WorldScan import com.odtheking.odin.features.impl.nether.NoPre @@ -35,9 +36,12 @@ import net.minecraft.world.phys.BlockHitResult val devCommand = Commodore("oddev") { literal("ws") { - literal("connect").runs { lobby: String -> + literal("melody").runs { lobby: String -> melodyWebSocket.connect("${webSocketUrl}${lobby}") } + literal("secrets").runs { lobby: String -> + DungeonMap.syncSocket.connect("${webSocketUrl}${lobby}") + } } literal("getitem").runs { @@ -79,5 +79,6 @@ class FloorEnterEvent(val floor: Floor) : Event class ScoreUpdateEvent(val score: Int) : Event data object MapUpdateEvent : Event class CheckmarkUpdateEvent(val room: DungeonRoom, val checkmark: MapCheckmark) : Event +class SecretsUpdateEvent(val room: DungeonRoom, val foundSecrets: Int) : Event object LocationChangeEvent : Event \ No newline at end of file @@ -7,20 +7,17 @@ import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.RoomEnterEvent import com.odtheking.odin.events.ScoreUpdateEvent import com.odtheking.odin.events.core.on -import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.alert import com.odtheking.odin.utils.modMessage -import com.odtheking.odin.utils.noControlCodes import com.odtheking.odin.utils.render.drawFilledBox import com.odtheking.odin.utils.render.getStringWidth import com.odtheking.odin.utils.render.text import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.core.BlockPos -import net.minecraft.network.protocol.game.ClientboundSystemChatPacket import net.minecraft.world.phys.AABB object MapInfo : Module( @@ -178,7 +175,9 @@ object MapInfo : Module( private val roomSecrets by HUD("Room Secrets", "Displays the number of secrets in the current room.") { if ((!DungeonUtils.inClear) && !it) return@HUD 0 to 0 - val secrets = if (it) 0 to 2 else currentRoomSecrets ?: return@HUD 0 to 0 + val secrets = if (it) 0 to 2 else DungeonUtils.currentRoom?.foundSecrets?.let { found -> + DungeonUtils.currentRoom?.data?.maxSecrets?.let { max -> found to max } + } ?: return@HUD 0 to 0 val color = when { secrets.first * 2 < secrets.second -> "§c" secrets.first * 4 < secrets.second * 3 -> "§e" @@ -198,8 +197,6 @@ object MapInfo : Module( } private var portalAABB: AABB? = null - private var currentRoomSecrets: Pair<Int, Int>? = null - private val secretRegex = Regex("(\\d+)/(\\d+) Secrets") private var shownTitle = false init { @@ -210,15 +207,7 @@ object MapInfo : Module( if (printWhenScore) modMessage("§b${DungeonUtils.score} §ascore reached in §6${DungeonUtils.dungeonTime} §8|| §e${DungeonUtils.floor?.name}.") } - onReceive<ClientboundSystemChatPacket> { - if (!overlay) return@onReceive - secretRegex.find(content.string.noControlCodes )?.destructured?.let { (found, total) -> - currentRoomSecrets = Pair(found.toIntOrNull() ?: 0, total.toIntOrNull() ?: 0) - } - } - on<RoomEnterEvent> { - currentRoomSecrets = null if (room?.name == "Blood") portalAABB = AABB.encapsulatingFullBlocks(room.getRealCoords(BlockPos(16, 69, 29)), room.getRealCoords(BlockPos(14, 69, 29))).inflate(0.0, 4.0, 0.0) } @@ -1,16 +1,21 @@ package com.odtheking.odin.features.impl.dungeon.map import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency -import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.clickgui.settings.impl.DropdownSetting -import com.odtheking.odin.clickgui.settings.impl.NumberSetting +import com.odtheking.odin.clickgui.settings.impl.* +import com.odtheking.odin.events.FloorEnterEvent +import com.odtheking.odin.events.RoomEnterEvent +import com.odtheking.odin.events.SecretsUpdateEvent +import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module +import com.odtheking.odin.features.impl.render.ClickGUIModule import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.network.WebUtils.gson +import com.odtheking.odin.utils.network.webSocket import com.odtheking.odin.utils.render.hollowFill +import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils object DungeonMap : Module( @@ -21,6 +26,7 @@ object DungeonMap : Module( val backgroundOutline by ColorSetting("Background Outline", Colors.BLACK, true, desc = "The color of the background border.") val backgroundColor by ColorSetting("Background Color", Colors.BLACK.withAlpha(0.1f), true, desc = "Background color of the map.") + val roomText by SelectorSetting("Room Text", "Both", listOf("Both", "Room Name", "Room Secrets"), desc = "What to display on the rooms.") val textScaling by NumberSetting("Text Scaling", 0.45f, 0.1f, 1f, 0.05f, desc = "Scale of room name text.") private val playerDropdown by DropdownSetting("Player Settings") @@ -47,8 +53,11 @@ object DungeonMap : Module( val disablePred by BooleanSetting("Disable Prediction", false, desc = "Disables special-column room type prediction.") + val shareSecrets by BooleanSetting("Share Secrets", true, desc = "Shares found secrets in your room with the rest of your dungeon party.") + private val exampleRooms by lazy { buildExampleRooms() } private val exampleDoors by lazy { buildExampleDoors() } + private const val MAP_PX = 128 private val mapHud by HUD("Dungeon Map", "Displays the dungeon map.", false) { example -> if ((!DungeonUtils.inDungeons || (disableBoss && DungeonUtils.inBoss)) && !example) return@HUD 0 to 0 @@ -62,7 +71,7 @@ object DungeonMap : Module( } else { pose().translate(DungeonScan.startX.toFloat(), DungeonScan.startY.toFloat()) - pose().scale(DungeonScan.roomSize / DungeonScan.MAP_ROOM_SIZE.toFloat()) + pose().scale(DungeonScan.roomSize / 16f) renderMap(DungeonScan.rooms, DungeonScan.doors.values, DungeonScan.pathHints) @@ -73,5 +82,28 @@ object DungeonMap : Module( MAP_PX to MAP_PX } - private const val MAP_PX = 128 + val syncSocket = webSocket { + onMessage { message -> + val sync = try { gson.fromJson(message, RoomSecretsSync::class.java) } catch (_: Exception) { return@onMessage } + val room = DungeonScan.rooms.find { it.name == sync.roomName } ?: return@onMessage + if ((room.foundSecrets ?: 0) >= sync.foundSecrets) return@onMessage + room.foundSecrets = sync.foundSecrets + } + } + + init { + on<SecretsUpdateEvent> { + if (!shareSecrets || !syncSocket.connected) return@on + room.name?.let { syncSocket.send(gson.toJson(RoomSecretsSync(it, foundSecrets))) } + } + + on<FloorEnterEvent> { + if (!shareSecrets) return@on + LocationUtils.lobbyId?.let { syncSocket.connect("${ClickGUIModule.webSocketUrl}$it") } + } + + on<RoomEnterEvent> { if (room == null) syncSocket.shutdown() } + } + + private data class RoomSecretsSync(val roomName: String, val foundSecrets: Int) } \ No newline at end of file @@ -10,18 +10,13 @@ import com.odtheking.odin.features.impl.dungeon.map.tile.* import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.IVec2 -import com.odtheking.odin.utils.renderX -import com.odtheking.odin.utils.renderZ import com.odtheking.odin.utils.skyblock.dungeon.Floor -import net.minecraft.world.entity.player.Player import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList object DungeonScan { const val ROOM_SPACING = 4 - const val MAP_ROOM_SIZE = 16 - const val MAP_ROOM_GAP = MAP_ROOM_SIZE + ROOM_SPACING var roomSize = 16 val roomGap: Int get () = roomSize + ROOM_SPACING @@ -77,18 +72,6 @@ object DungeonScan { } } - fun playerRenderPosition(entity: Player?, mapPos: IVec2): Pair<Float, Float> { - entity?.let { - val mapX = (it.renderX.toFloat() + 200f) * MAP_ROOM_GAP / 32f - val mapZ = (it.renderZ.toFloat() + 200f) * MAP_ROOM_GAP / 32f - return mapX to mapZ - } - - val pixelX = (mapPos.x + 128) / 2f - startX - val pixelY = (mapPos.z + 128) / 2f - startY - return pixelX to pixelY - } - fun updateViewableDoors() { pathHints.clear() for ((_, door) in doors) { @@ -1,6 +1,9 @@ package com.odtheking.odin.features.impl.dungeon.map import com.odtheking.odin.OdinMod.mc +import com.odtheking.odin.features.impl.dungeon.map.DungeonScan.ROOM_SPACING +import com.odtheking.odin.features.impl.dungeon.map.DungeonScan.startX +import com.odtheking.odin.features.impl.dungeon.map.DungeonScan.startY import com.odtheking.odin.features.impl.dungeon.map.tile.* import com.odtheking.odin.utils.* import com.odtheking.odin.utils.Color.Companion.darker @@ -9,6 +12,7 @@ import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.components.PlayerFaceExtractor import net.minecraft.client.renderer.RenderPipelines import net.minecraft.resources.Identifier +import net.minecraft.world.entity.player.Player private val marker = Identifier.withDefaultNamespace("textures/map/decorations/frame.png") private val cross = Identifier.fromNamespaceAndPath("odin", "map/cross.png") @@ -16,49 +20,49 @@ private val green = Identifier.fromNamespaceAndPath("odin", "map/green_check.png private val white = Identifier.fromNamespaceAndPath("odin", "map/white_check.png") private val question = Identifier.fromNamespaceAndPath("odin", "map/question.png") +private const val MAP_ROOM_SIZE = 16 +private const val MAP_ROOM_GAP = MAP_ROOM_SIZE + ROOM_SPACING + fun GuiGraphicsExtractor.fillRoom(room: DungeonRoom, color: Int) { - val rs = DungeonScan.MAP_ROOM_SIZE - val rg = DungeonScan.MAP_ROOM_GAP - val ox = room.topLeft.x * rg - val oy = room.topLeft.z * rg + val ox = room.topLeft.x * MAP_ROOM_GAP + val oy = room.topLeft.z * MAP_ROOM_GAP when (val shape = room.shape) { - RoomShape.OneByOne -> fill(ox, oy, ox + rs, oy + rs, color) - RoomShape.TwoByTwo -> fill(ox, oy, ox + rg + rs, oy + rg + rs, color) + RoomShape.OneByOne -> fill(ox, oy, ox + MAP_ROOM_SIZE, oy + MAP_ROOM_SIZE, color) + RoomShape.TwoByTwo -> fill(ox, oy, ox + MAP_ROOM_GAP + MAP_ROOM_SIZE, oy + MAP_ROOM_GAP + MAP_ROOM_SIZE, color) RoomShape.L -> when (room.rotation) { RoomRotation.WEST -> { - fill(ox, oy, ox + rg + rs, oy + rs, color) - fill(ox, oy + rs, ox + rs, oy + rg + rs, color) + fill(ox, oy, ox + MAP_ROOM_GAP + MAP_ROOM_SIZE, oy + MAP_ROOM_SIZE, color) + fill(ox, oy + MAP_ROOM_SIZE, ox + MAP_ROOM_SIZE, oy + MAP_ROOM_GAP + MAP_ROOM_SIZE, color) } RoomRotation.NORTH -> { - fill(ox, oy, ox + rg + rs, oy + rs, color) - fill(ox + rg, oy + rs, ox + rg + rs, oy + rg + rs, color) + fill(ox, oy, ox + MAP_ROOM_GAP + MAP_ROOM_SIZE, oy + MAP_ROOM_SIZE, color) + fill(ox + MAP_ROOM_GAP, oy + MAP_ROOM_SIZE, ox + MAP_ROOM_GAP + MAP_ROOM_SIZE, oy + MAP_ROOM_GAP + MAP_ROOM_SIZE, color) } else -> { - fill(ox, oy, ox + rs, oy + rg + rs, color) - fill(ox + rs, oy + rg, ox + rg + rs, oy + rg + rs, color) + fill(ox, oy, ox + MAP_ROOM_SIZE, oy + MAP_ROOM_GAP + MAP_ROOM_SIZE, color) + fill(ox + MAP_ROOM_SIZE, oy + MAP_ROOM_GAP, ox + MAP_ROOM_GAP + MAP_ROOM_SIZE, oy + MAP_ROOM_GAP + MAP_ROOM_SIZE, color) } } else -> { - if (room.rotation == RoomRotation.SOUTH) fill(ox, oy, ox + (shape.tileAmount - 1) * rg + rs, oy + rs, color) - else fill(ox, oy, ox + rs, oy + (shape.tileAmount - 1) * rg + rs, color) + if (room.rotation == RoomRotation.SOUTH) fill(ox, oy, ox + (shape.tileAmount - 1) * MAP_ROOM_GAP + MAP_ROOM_SIZE, oy + MAP_ROOM_SIZE, color) + else fill(ox, oy, ox + MAP_ROOM_SIZE, oy + (shape.tileAmount - 1) * MAP_ROOM_GAP + MAP_ROOM_SIZE, color) } } } fun GuiGraphicsExtractor.renderDoors(doors: Collection<DungeonDoor>) { - val rs = DungeonScan.MAP_ROOM_SIZE - val rg = DungeonScan.MAP_ROOM_GAP - val half = (rs - 8) / 2f + val rg = MAP_ROOM_GAP + val half = (MAP_ROOM_SIZE - 8) / 2f for (door in doors) { val offset = door.rotation.offset pose().pushMatrix() pose().translate( - door.position.x * rg + offset.x * rs + offset.z * half, - door.position.z * rg + offset.z * rs + offset.x * half + door.position.x * rg + offset.x * MAP_ROOM_SIZE + offset.z * half, + door.position.z * rg + offset.z * MAP_ROOM_SIZE + offset.x * half ) fill(0, 0, 4 + 4 * offset.z, 4 + 4 * offset.x, door.color.rgba) pose().popMatrix() @@ -66,30 +70,27 @@ fun GuiGraphicsExtractor.renderDoors(doors: Collection<DungeonDoor>) { } fun GuiGraphicsExtractor.renderPathHints(pathHints: Collection<DungeonTile>) { - val rs = DungeonScan.MAP_ROOM_SIZE - val rg = DungeonScan.MAP_ROOM_GAP - for ((position, room) in pathHints) { - val (x, y) = position.x * rg to position.z * rg + val (x, y) = position.x * MAP_ROOM_GAP to position.z * MAP_ROOM_GAP val colors = if (room?.type == RoomType.BLOOD) arrayOf(DungeonMap.bloodRoomColor.darker(0.5f)) else if (!DungeonMap.disablePred && room?.isKnown1x1 == true) SpecialColumn.colorGuessForUnknown(position.x) else arrayOf(DungeonMap.unknownRoomColor) when (colors.size) { - 1 -> fill(x, y, x + rs, y + rs, colors[0].rgba) + 1 -> fill(x, y, x + MAP_ROOM_SIZE, y + MAP_ROOM_SIZE, colors[0].rgba) 2 -> { - val half = rs / 2 - fill(x, y, x + half, y + rs, colors[0].darker(0.5f).rgba) - fill(x + half, y, x + rs, y + rs, colors[1].darker(0.5f).rgba) + val half = MAP_ROOM_SIZE / 2 + fill(x, y, x + half, y + MAP_ROOM_SIZE, colors[0].darker(0.5f).rgba) + fill(x + half, y, x + MAP_ROOM_SIZE, y + MAP_ROOM_SIZE, colors[1].darker(0.5f).rgba) } 3 -> { - val third = rs / 3 - fill(x, y, x + third, y + rs, colors[0].darker(0.5f).rgba) - fill(x + third, y, x + third * 2, y + rs, colors[1].darker(0.5f).rgba) - fill(x + third * 2, y, x + rs, y + rs, colors[2].darker(0.5f).rgba) + val third = MAP_ROOM_SIZE / 3 + fill(x, y, x + third, y + MAP_ROOM_SIZE, colors[0].darker(0.5f).rgba) + fill(x + third, y, x + third * 2, y + MAP_ROOM_SIZE, colors[1].darker(0.5f).rgba) + fill(x + third * 2, y, x + MAP_ROOM_SIZE, y + MAP_ROOM_SIZE, colors[2].darker(0.5f).rgba) } } renderIcon(IVec2(x, y), question) @@ -97,7 +98,7 @@ fun GuiGraphicsExtractor.renderPathHints(pathHints: Collection<DungeonTile>) { } fun GuiGraphicsExtractor.renderIcon(pos: IVec2, identifier: Identifier) { - val size = DungeonScan.MAP_ROOM_SIZE - DungeonScan.ROOM_SPACING + val size = MAP_ROOM_SIZE - ROOM_SPACING blit(RenderPipelines.GUI_TEXTURED, identifier, pos.x + 2, pos.z + 2, size.toFloat(), size.toFloat(), size, size, size, size) } @@ -110,7 +111,7 @@ fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { MapCheckmark.WHITE -> white MapCheckmark.RED -> cross else -> null - }?.let { texture -> renderIcon(room.topLeft * DungeonScan.MAP_ROOM_GAP, texture) } + }?.let { texture -> renderIcon(room.topLeft * MAP_ROOM_GAP, texture) } return } @@ -124,7 +125,12 @@ fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { else -> Color(100, 100, 100) }.rgba - val lines = room.name?.split(" ") ?: return + val secretsLine = if ((room.data?.maxSecrets ?: 0) > 0) " ${room.foundSecrets ?: "?"}/${room.data?.maxSecrets}" else "" + val lines = when (DungeonMap.roomText) { + 0 -> "${room.name}$secretsLine" + 1 -> room.name + else -> secretsLine + }?.trim()?.split(" ") ?: return val totalH = (lines.size - 1) * fontH * DungeonMap.textScaling for ((i, line) in lines.withIndex()) { @@ -136,12 +142,24 @@ fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { } } +private fun playerRenderPosition(entity: Player?, mapPos: IVec2): Pair<Float, Float> { + entity?.let { + val mapX = (it.renderX.toFloat() + 200f) * MAP_ROOM_GAP / 32f + val mapZ = (it.renderZ.toFloat() + 200f) * MAP_ROOM_GAP / 32f + return mapX to mapZ + } + + val pixelX = (mapPos.x + 128) / 2f - startX + val pixelY = (mapPos.z + 128) / 2f - startY + return pixelX to pixelY +} + fun GuiGraphicsExtractor.renderPlayers() { val showNames = mc.player?.mainHandItem?.itemId?.equalsOneOf("INFINITE_SPIRIT_LEAP", "SPIRIT_LEAP") == true for (player in DungeonUtils.dungeonTeammatesNoSelf) { if (player.isDead) continue - val (px, pz) = DungeonScan.playerRenderPosition(player.entity, player.mapPos) + val (px, pz) = playerRenderPosition(player.entity, player.mapPos) pose().pushMatrix() pose().translate(px, pz) @@ -164,7 +182,7 @@ fun GuiGraphicsExtractor.renderPlayers() { } pose().pushMatrix() - val (selfX, selfZ) = DungeonScan.playerRenderPosition(mc.player, IVec2(0, 0)) + val (selfX, selfZ) = playerRenderPosition(mc.player, IVec2(0, 0)) pose().translate(selfX, selfZ) pose().rotate(Math.toRadians(180.0 + (mc.player?.yRot ?: 0f)).toFloat()) @@ -239,7 +257,6 @@ fun buildExampleDoors(): List<DungeonDoor> = listOf( DungeonDoor(IVec2(1, 5), DoorRotation.Horizontal, DoorType.Blood, DungeonMap.bloodDoorColor) ) - fun roomTypeColor(type: RoomType): Color = when (type) { RoomType.NORMAL -> DungeonMap.normalRoomColor RoomType.PUZZLE -> DungeonMap.puzzleRoomColor @@ -2,7 +2,6 @@ package com.odtheking.odin.features.impl.dungeon.map.tile import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints -import com.odtheking.odin.features.impl.dungeon.map.DungeonScan import com.odtheking.odin.utils.* import net.minecraft.core.BlockPos import net.minecraft.world.level.block.Blocks @@ -30,6 +29,8 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData var center: IVec2? = null private set + var foundSecrets: Int? = null + val isViewable: Boolean get() = walkedInto || checkmark != MapCheckmark.UNDISCOVERED val name: String? get() = data?.name @@ -43,26 +44,21 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData } private fun recalculateCenter() { - val gap = DungeonScan.MAP_ROOM_GAP - val half = DungeonScan.MAP_ROOM_SIZE / 2 - val rot = rotation - - if (rot == null) { - val anchor = tiles.minByOrNull { it.sortKey } ?: return - center = IVec2(anchor.x * gap + half, anchor.z * gap + half) - return - } - - val (offX, offZ) = when (shape) { - RoomShape.OneByOne -> 0 to 0 - RoomShape.TwoByTwo -> gap / 2 to gap / 2 - RoomShape.L -> if (rot == RoomRotation.NORTH || rot == RoomRotation.WEST) gap / 2 to 0 else gap / 2 to gap - else -> { - val span = (shape.tileAmount - 1) * gap / 2 - if (rot == RoomRotation.SOUTH) span to 0 else 0 to span + rotation?.let { rot -> + val (offX, offZ) = when (shape) { + RoomShape.OneByOne -> 0 to 0 + RoomShape.TwoByTwo -> 10 to 10 + RoomShape.L -> if (rot == RoomRotation.NORTH || rot == RoomRotation.WEST) 10 to 0 else 10 to 20 + else -> { + val span = (shape.tileAmount - 1) * 10 + if (rot == RoomRotation.SOUTH) span to 0 else 0 to span + } } + center = IVec2(topLeft.x * 20 + 8 + offX, topLeft.z * 20 + 8 + offZ) + } ?: run { + val anchor = tiles.minByOrNull { it.sortKey } ?: return + center = IVec2(anchor.x * 20 + 8, anchor.z * 20 + 8) } - center = IVec2(topLeft.x * gap + half + offX, topLeft.z * gap + half + offZ) } fun inferLayoutFromMap() { @@ -11,7 +11,7 @@ data class RoomData private constructor( val shape: RoomShape, val cores: List<Int>, val crypts: Int, - val secrets: Int, + val maxSecrets: Int, val trappedChests: Int, ) { companion object { @@ -1,10 +1,12 @@ package com.odtheking.odin.utils.skyblock import com.odtheking.odin.OdinMod.mc +import com.odtheking.odin.events.SecretsUpdateEvent import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.utils.noControlCodes +import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.network.protocol.game.ClientboundSystemChatPacket import net.minecraft.world.entity.ai.attributes.Attributes import kotlin.math.floor @@ -15,6 +17,7 @@ object ActionBarListener { private val OVERFLOW_MANA_REGEX = Regex("([\\d|,]+)\\uE017") private val DEFENSE_REGEX = Regex("([\\d|,]+)\\uE008( Defense)?") private val VITALITY_REGEX = Regex("([\\d.,]+)/([\\d.,]+)\\uE028") + private val SECRETS_REGEX = Regex("(\\d+)/(\\d+) Secrets") var currentHealth: Int = 0 private set @@ -49,6 +52,15 @@ object ActionBarListener { if (!overlay) return@onReceive val msg = content.string.noControlCodes + SECRETS_REGEX.find(msg)?.destructured?.let { (found, max) -> + DungeonUtils.currentRoom?.let { + val updatedFoundSecrets = found.toIntOrNull() ?: return@let + if (it.data?.maxSecrets != max.toIntOrNull() || (it.foundSecrets ?: 0) >= updatedFoundSecrets) return@let + it.foundSecrets = updatedFoundSecrets + SecretsUpdateEvent(it, updatedFoundSecrets).postAndCatch() + } + } + HEALTH_REGEX.find(msg)?.destructured?.let { (_, maxHp) -> maxHealth = maxHp.replace(",", "").toIntOrNull() ?: maxHealth } @@ -66,7 +66,7 @@ object DungeonListener { } on<RoomEnterEvent> { - dungeonStats.knownSecrets = DungeonScan.rooms.sumOf { if (it.walkedInto) it.data?.secrets ?: 0 else 0 } + dungeonStats.knownSecrets = DungeonScan.rooms.sumOf { if (it.walkedInto) it.data?.maxSecrets ?: 0 else 0 } } onReceive<ClientboundPlayerInfoUpdatePacket> { @@ -17,7 +17,7 @@ -1927723170 ], "crypts": 3, - "secrets": 6, + "maxSecrets": 6, "shape": "L" }, { @@ -26,7 +26,7 @@ "cores": [ -1701988142 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -37,7 +37,7 @@ 1440685825 ], "crypts": 4, - "secrets": 3, + "maxSecrets": 3, "shape": "1x2" }, { @@ -47,7 +47,7 @@ 313879976 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -60,7 +60,7 @@ -2080722918 ], "crypts": 5, - "secrets": 6, + "maxSecrets": 6, "shape": "2x2" }, { @@ -70,7 +70,7 @@ 1765345183, 2133731337 ], - "secrets": 4, + "maxSecrets": 4, "shape": "1x2" }, { @@ -80,7 +80,7 @@ -1092023380 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -89,7 +89,7 @@ "cores": [ 1491050733 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -99,7 +99,7 @@ -1764045332 ], "crypts": 2, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -109,7 +109,7 @@ -807913351 ], "crypts": 1, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -119,7 +119,7 @@ -1779299819 ], "crypts": 1, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -138,7 +138,7 @@ -1410316771 ], "crypts": 4, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -160,7 +160,7 @@ 454887921 ], "crypts": 6, - "secrets": 6, + "maxSecrets": 6, "shape": "1x2" }, { @@ -173,7 +173,7 @@ 112871973 ], "crypts": 21, - "secrets": 5, + "maxSecrets": 5, "trappedChests": 1, "shape": "2x2" }, @@ -183,7 +183,7 @@ "cores": [ -407233803 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -192,7 +192,7 @@ "cores": [ -1164795630 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -203,7 +203,7 @@ -76292695 ], "crypts": 3, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -216,7 +216,7 @@ -508170344 ], "crypts": 5, - "secrets": 8, + "maxSecrets": 8, "shape": "2x2" }, { @@ -228,7 +228,7 @@ 1179970013 ], "crypts": 5, - "secrets": 6, + "maxSecrets": 6, "shape": "1x3" }, { @@ -237,7 +237,7 @@ "cores": [ 1560740373 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -246,7 +246,7 @@ "cores": [ -448181487 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -258,7 +258,7 @@ -546080393 ], "crypts": 6, - "secrets": 5, + "maxSecrets": 5, "shape": "L" }, { @@ -268,7 +268,7 @@ -170648742 ], "crypts": 1, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -288,7 +288,7 @@ -1879625096 ], "crypts": 6, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -299,7 +299,7 @@ 1376295224 ], "crypts": 2, - "secrets": 5, + "maxSecrets": 5, "shape": "1x2" }, { @@ -311,7 +311,7 @@ -414187054 ], "crypts": 4, - "secrets": 6, + "maxSecrets": 6, "shape": "1x3" }, { @@ -332,7 +332,7 @@ -959823640 ], "crypts": 3, - "secrets": 4, + "maxSecrets": 4, "shape": "1x3" }, { @@ -344,7 +344,7 @@ 1735273877 ], "crypts": 4, - "secrets": 4, + "maxSecrets": 4, "shape": "L" }, { @@ -354,7 +354,7 @@ -1373895553 ], "crypts": 3, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -365,7 +365,7 @@ 291108944 ], "crypts": 2, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -376,7 +376,7 @@ 551939185 ], "crypts": 7, - "secrets": 5, + "maxSecrets": 5, "shape": "1x2" }, { @@ -385,7 +385,7 @@ "cores": [ 1857926981 ], - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -403,7 +403,7 @@ -1779237820 ], "crypts": 6, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -413,7 +413,7 @@ -697416341 ], "crypts": 6, - "secrets": 3, + "maxSecrets": 3, "trappedChests": 1, "shape": "1x1" }, @@ -423,7 +423,7 @@ "cores": [ -1999038851 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -433,7 +433,7 @@ -531914593 ], "crypts": 1, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -464,7 +464,7 @@ 908422074 ], "crypts": 8, - "secrets": 7, + "maxSecrets": 7, "shape": "2x2" }, { @@ -474,7 +474,7 @@ 992885012, 35550104 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x2" }, { @@ -484,7 +484,7 @@ 178277726 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -495,7 +495,7 @@ 648931278 ], "crypts": 2, - "secrets": 4, + "maxSecrets": 4, "shape": "1x2" }, { @@ -504,7 +504,7 @@ "cores": [ -41110951 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -517,7 +517,7 @@ -1847986179 ], "crypts": 4, - "secrets": 3, + "maxSecrets": 3, "shape": "1x2" }, { @@ -530,7 +530,7 @@ 244770880 ], "crypts": 2, - "secrets": 6, + "maxSecrets": 6, "shape": "1x3" }, { @@ -551,7 +551,7 @@ 1020848351 ], "crypts": 1, - "secrets": 3, + "maxSecrets": 3, "shape": "1x4" }, { @@ -561,7 +561,7 @@ -1752027278, -1477567187 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -588,7 +588,7 @@ "cores": [ 205151025 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -606,7 +606,7 @@ "cores": [ -1519890056 ], - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -615,7 +615,7 @@ "cores": [ -695235565 ], - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -627,7 +627,7 @@ -854539536 ], "crypts": 4, - "secrets": 6, + "maxSecrets": 6, "shape": "L" }, { @@ -639,7 +639,7 @@ 161195688, -91111782 ], - "secrets": 8, + "maxSecrets": 8, "shape": "L" }, { @@ -649,7 +649,7 @@ 643669164 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -659,7 +659,7 @@ 706748439 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -668,7 +668,7 @@ "cores": [ 1783642819 ], - "secrets": 4, + "maxSecrets": 4, "shape": "1x1" }, { @@ -678,7 +678,7 @@ 587195362 ], "crypts": 3, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -688,7 +688,7 @@ -1112550874 ], "crypts": 1, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -698,7 +698,7 @@ -441650545, 911747293 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -708,7 +708,7 @@ 1475692888, 925853313 ], - "secrets": 4, + "maxSecrets": 4, "shape": "1x2" }, { @@ -720,7 +720,7 @@ 300771790 ], "crypts": 4, - "secrets": 5, + "maxSecrets": 5, "shape": "L" }, { @@ -732,7 +732,7 @@ -1740446783 ], "crypts": 4, - "secrets": 7, + "maxSecrets": 7, "shape": "L" }, { @@ -745,7 +745,7 @@ -892257050 ], "crypts": 11, - "secrets": 10, + "maxSecrets": 10, "shape": "2x2" }, { @@ -755,7 +755,7 @@ 302128578 ], "crypts": 3, - "secrets": 4, + "maxSecrets": 4, "shape": "1x1" }, { @@ -764,7 +764,7 @@ "cores": [ 76347246 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -777,7 +777,7 @@ -408036566 ], "crypts": 2, - "secrets": 4, + "maxSecrets": 4, "shape": "1x4" }, { @@ -786,7 +786,7 @@ "cores": [ -543960045 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -795,7 +795,7 @@ "cores": [ -743021993 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -808,7 +808,7 @@ -95130049 ], "crypts": 4, - "secrets": 5, + "maxSecrets": 5, "shape": "2x2" }, { @@ -817,7 +817,7 @@ "cores": [ 1461632797 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -830,7 +830,7 @@ 1851993785 ], "crypts": 1, - "secrets": 3, + "maxSecrets": 3, "trappedChests": 1, "shape": "1x1" }, @@ -842,7 +842,7 @@ -85564220 ], "crypts": 2, - "secrets": 4, + "maxSecrets": 4, "shape": "1x1" }, { @@ -851,7 +851,7 @@ "cores": [ -726297434 ], - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -861,7 +861,7 @@ 95969553 ], "crypts": 1, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -870,7 +870,7 @@ "cores": [ -725718835 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -881,7 +881,7 @@ 1549240174 ], "crypts": 1, - "secrets": 5, + "maxSecrets": 5, "shape": "1x2" }, { @@ -891,7 +891,7 @@ -444976906 ], "crypts": 1, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -904,7 +904,7 @@ 1917735700 ], "crypts": 9, - "secrets": 7, + "maxSecrets": 7, "shape": "1x4" }, { @@ -916,7 +916,7 @@ 2061161472 ], "crypts": 2, - "secrets": 6, + "maxSecrets": 6, "shape": "L" }, { @@ -930,7 +930,7 @@ 634229450 ], "crypts": 4, - "secrets": 5, + "maxSecrets": 5, "shape": "1x4" }, { @@ -942,7 +942,7 @@ 2023286596 ], "crypts": 6, - "secrets": 6, + "maxSecrets": 6, "shape": "1x2" }, { @@ -952,7 +952,7 @@ 845343581 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -963,7 +963,7 @@ 664490773 ], "crypts": 7, - "secrets": 5, + "maxSecrets": 5, "shape": "1x2" }, { @@ -972,7 +972,7 @@ "cores": [ 1865354448 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -985,7 +985,7 @@ -215394360 ], "crypts": 9, - "secrets": 7, + "maxSecrets": 7, "shape": "1x4" }, { @@ -1004,7 +1004,7 @@ -1723880923 ], "crypts": 2, - "secrets": 4, + "maxSecrets": 4, "shape": "1x1" }, { @@ -1014,7 +1014,7 @@ -1362847550 ], "crypts": 3, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1027,7 +1027,7 @@ -1882412922 ], "crypts": 1, - "secrets": 9, + "maxSecrets": 9, "shape": "2x2" }, { @@ -1036,7 +1036,7 @@ "cores": [ 1613728006 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1045,7 +1045,7 @@ "cores": [ -1907291752 ], - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1054,7 +1054,7 @@ "cores": [ -609209015 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1067,7 +1067,7 @@ -608091458 ], "crypts": 1, - "secrets": 4, + "maxSecrets": 4, "shape": "1x3" }, { @@ -1077,7 +1077,7 @@ -1800029469 ], "crypts": 2, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1089,7 +1089,7 @@ -889320918, -1978791555 ], - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1100,7 +1100,7 @@ -1986945620 ], "crypts": 4, - "secrets": 3, + "maxSecrets": 3, "trappedChests": 1, "shape": "1x1" }, @@ -1112,7 +1112,7 @@ -380862700 ], "crypts": 4, - "secrets": 3, + "maxSecrets": 3, "shape": "1x2" }, { @@ -1122,7 +1122,7 @@ -610787478 ], "crypts": 1, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1132,7 +1132,7 @@ 1781758146 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1143,7 +1143,7 @@ -771559429 ], "crypts": 1, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1153,7 +1153,7 @@ -2146618657, -2091078305 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -1171,7 +1171,7 @@ "cores": [ -1899702429 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1181,7 +1181,7 @@ -1554372511, 80464378 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x2" }, { @@ -1191,7 +1191,7 @@ 568922940 ], "crypts": 2, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -1203,7 +1203,7 @@ 878407749 ], "crypts": 1, - "secrets": 5, + "maxSecrets": 5, "trappedChests": 1, "shape": "1x3" }, @@ -1214,7 +1214,7 @@ -566399217 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1224,7 +1224,7 @@ -998001944 ], "crypts": 1, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -1234,7 +1234,7 @@ -1188349447 ], "crypts": 5, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -1246,7 +1246,7 @@ -40178107 ], "crypts": 3, - "secrets": 9, + "maxSecrets": 9, "shape": "L" }, { @@ -1256,7 +1256,7 @@ 1484939648 ], "crypts": 2, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1266,7 +1266,7 @@ -151940807 ], "crypts": 2, - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1279,7 +1279,7 @@ -2033694003 ], "crypts": 1, - "secrets": 4, + "maxSecrets": 4, "shape": "2x2" }, { @@ -1290,7 +1290,7 @@ -340486787 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1300,7 +1300,7 @@ -1740718028 ], "crypts": 1, - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -1313,7 +1313,7 @@ -1867603567 ], "crypts": 6, - "secrets": 6, + "maxSecrets": 6, "shape": "2x2" }, { @@ -1330,7 +1330,7 @@ "cores": [ 1575914755 ], - "secrets": 3, + "maxSecrets": 3, "shape": "1x1" }, { @@ -1339,7 +1339,7 @@ "cores": [ -1444508608 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1357,7 +1357,7 @@ -738945309, 29612250 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1366,7 +1366,7 @@ "cores": [ 535698739 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -1383,7 +1383,7 @@ "cores": [ 872815555 ], - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" }, { @@ -1392,7 +1392,7 @@ "cores": [ -333637832 ], - "secrets": 2, + "maxSecrets": 2, "shape": "1x1" }, { @@ -1415,7 +1415,7 @@ 520419865 ], "crypts": 3, - "secrets": 8, + "maxSecrets": 8, "shape": "1x4" }, { @@ -1427,7 +1427,7 @@ 1276013917 ], "crypts": 5, - "secrets": 7, + "maxSecrets": 7, "shape": "L" }, { @@ -1439,7 +1439,7 @@ 1140917779 ], "crypts": 6, - "secrets": 4, + "maxSecrets": 4, "shape": "L" }, { @@ -1451,7 +1451,7 @@ 926568393 ], "crypts": 8, - "secrets": 4, + "maxSecrets": 4, "shape": "1x3" }, { @@ -1461,7 +1461,7 @@ -1403940419 ], "crypts": 1, - "secrets": 1, + "maxSecrets": 1, "shape": "1x1" } ] \ No newline at end of file |
Simplified DungeonRoom center math查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 58 +++++++++++++++++++++------------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapScan.kt | 3 ++- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonDoor.kt | 3 +-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 42 ++++++++++++++---------------------------- 5 files changed, 39 insertions(+), 69 deletions(-) @@ -48,7 +48,7 @@ object DungeonMap : Module( val disablePred by BooleanSetting("Disable Prediction", false, desc = "Disables special-column room type prediction.") private val exampleRooms by lazy { buildExampleRooms() } - private val exampleDoors by lazy { buildExampleDoors(exampleRooms) } + private val exampleDoors by lazy { buildExampleDoors() } private val mapHud by HUD("Dungeon Map", "Displays the dungeon map.", false) { example -> if ((!DungeonUtils.inDungeons || (disableBoss && DungeonUtils.inBoss)) && !example) return@HUD 0 to 0 @@ -217,44 +217,28 @@ fun buildExampleRooms(): List<DungeonRoom> { ) } -fun buildExampleDoors(rooms: List<DungeonRoom>): List<DungeonDoor> { - val roomByTile = rooms.flatMap { room -> room.occupiedTiles().map { it to room } }.toMap() - - fun door(position: IVec2, rotation: DoorRotation, type: DoorType): DungeonDoor { - val door = DungeonDoor(position, rotation, type) - door.color = when (type) { - DoorType.Wither -> DungeonMap.witherDoorColor - DoorType.Blood -> DungeonMap.bloodDoorColor - DoorType.Fairy -> DungeonMap.fairyDoorColor - DoorType.Normal -> listOfNotNull(roomByTile[position], roomByTile[position + rotation.offset]) - .firstOrNull { it.type != RoomType.NORMAL && it.type != RoomType.FAIRY } - ?.type?.let(::roomTypeColor) ?: DungeonMap.normalDoorColor - } - return door - } +fun buildExampleDoors(): List<DungeonDoor> = listOf( + DungeonDoor(IVec2(2, 0), DoorRotation.Vertical, DoorType.Normal, DungeonMap.entranceRoomColor), + DungeonDoor(IVec2(1, 0), DoorRotation.Vertical, DoorType.Normal, DungeonMap.puzzleRoomColor), + DungeonDoor(IVec2(0, 1), DoorRotation.Horizontal, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(2, 2), DoorRotation.Horizontal, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(1, 2), DoorRotation.Vertical, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(4, 0), DoorRotation.Vertical, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(3, 1), DoorRotation.Horizontal, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(3, 3), DoorRotation.Vertical, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(1, 3), DoorRotation.Horizontal, DoorType.Fairy, DungeonMap.fairyDoorColor), + DungeonDoor(IVec2(2, 3), DoorRotation.Vertical, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(4, 3), DoorRotation.Vertical, DoorType.Normal, DungeonMap.championRoomColor), + DungeonDoor(IVec2(5, 3), DoorRotation.Vertical, DoorType.Normal, DungeonMap.trapRoomColor), + DungeonDoor(IVec2(0, 4), DoorRotation.Horizontal, DoorType.Normal, DungeonMap.puzzleRoomColor), + DungeonDoor(IVec2(1, 4), DoorRotation.Horizontal, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(1, 4), DoorRotation.Vertical, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(3, 4), DoorRotation.Horizontal, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(3, 4), DoorRotation.Vertical, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(5, 4), DoorRotation.Vertical, DoorType.Normal, DungeonMap.normalDoorColor), + DungeonDoor(IVec2(1, 5), DoorRotation.Horizontal, DoorType.Blood, DungeonMap.bloodDoorColor) +) - return listOf( - door(IVec2(2, 0), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(1, 0), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(0, 1), DoorRotation.Horizontal, DoorType.Normal), - door(IVec2(2, 2), DoorRotation.Horizontal, DoorType.Normal), - door(IVec2(1, 2), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(4, 0), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(3, 1), DoorRotation.Horizontal, DoorType.Normal), - door(IVec2(3, 3), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(1, 3), DoorRotation.Horizontal, DoorType.Fairy), - door(IVec2(2, 3), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(4, 3), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(5, 3), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(0, 4), DoorRotation.Horizontal, DoorType.Normal), - door(IVec2(1, 4), DoorRotation.Horizontal, DoorType.Normal), - door(IVec2(1, 4), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(3, 4), DoorRotation.Horizontal, DoorType.Normal), - door(IVec2(3, 4), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(5, 4), DoorRotation.Vertical, DoorType.Normal), - door(IVec2(1, 5), DoorRotation.Horizontal, DoorType.Blood), - ) -} fun roomTypeColor(type: RoomType): Color = when (type) { RoomType.NORMAL -> DungeonMap.normalRoomColor @@ -6,6 +6,7 @@ import com.odtheking.odin.events.MapUpdateEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.impl.dungeon.map.tile.* +import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.IVec2 import com.odtheking.odin.utils.equalsOneOf import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils @@ -214,7 +215,7 @@ object MapScan { private fun addOrFixDoor(position: IVec2, rotation: DoorRotation, doorType: DoorType) { DungeonScan.doors.getOrPut(IVec2(-12 + 2 * position.x + rotation.offset.x, -12 + 2 * position.z + rotation.offset.z)) - { DungeonDoor(position, rotation, doorType) }.type = doorType + { DungeonDoor(position, rotation, doorType, Colors.WHITE) }.type = doorType } private fun getPx(colors: ByteArray, x: Int, z: Int): Byte = @@ -1,14 +1,13 @@ package com.odtheking.odin.features.impl.dungeon.map.tile import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.IVec2 class DungeonDoor( val position: IVec2, val rotation: DoorRotation, var type: DoorType, - var color: Color = Colors.WHITE, + var color: Color, ) { val originTileIndex = position.x + position.z * 6 val destinationTileIndex = position.x + rotation.offset.x + (position.z + rotation.offset.z) * 6 @@ -42,41 +42,27 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData } } - fun occupiedTiles(): List<IVec2> { - val rot = rotation ?: return emptyList() - return tileOffsets(rot).map { topLeft + it } - } - - private fun tileOffsets(rot: RoomRotation): List<IVec2> = when (shape) { - RoomShape.OneByOne -> listOf(IVec2(0, 0)) - RoomShape.TwoByTwo -> listOf(IVec2(0, 0), IVec2(1, 0), IVec2(0, 1), IVec2(1, 1)) - RoomShape.L -> when (rot) { - RoomRotation.WEST -> listOf(IVec2(0, 0), IVec2(1, 0), IVec2(0, 1)) - RoomRotation.NORTH -> listOf(IVec2(0, 0), IVec2(1, 0), IVec2(1, 1)) - else -> listOf(IVec2(0, 0), IVec2(0, 1), IVec2(1, 1)) - } - else -> if (rot == RoomRotation.SOUTH) (0 until shape.tileAmount).map { IVec2(it, 0) } - else (0 until shape.tileAmount).map { IVec2(0, it) } - } - private fun recalculateCenter() { - fun tileCenter(tile: IVec2) = IVec2(tile.x * DungeonScan.MAP_ROOM_GAP + DungeonScan.MAP_ROOM_SIZE / 2, tile.z * DungeonScan.MAP_ROOM_GAP + DungeonScan.MAP_ROOM_SIZE / 2) - + val gap = DungeonScan.MAP_ROOM_GAP + val half = DungeonScan.MAP_ROOM_SIZE / 2 val rot = rotation + if (rot == null) { - if (tiles.isEmpty()) return - center = tileCenter(tiles.minBy { it.sortKey }) + val anchor = tiles.minByOrNull { it.sortKey } ?: return + center = IVec2(anchor.x * gap + half, anchor.z * gap + half) return } - val corners = occupiedTiles().map(::tileCenter) - val z = when (shape) { - RoomShape.L -> - if (rot == RoomRotation.NORTH || rot == RoomRotation.WEST) corners.minOf { it.z } - else corners.maxOf { it.z } - else -> (corners.minOf { it.z } + corners.maxOf { it.z }) / 2 + val (offX, offZ) = when (shape) { + RoomShape.OneByOne -> 0 to 0 + RoomShape.TwoByTwo -> gap / 2 to gap / 2 + RoomShape.L -> if (rot == RoomRotation.NORTH || rot == RoomRotation.WEST) gap / 2 to 0 else gap / 2 to gap + else -> { + val span = (shape.tileAmount - 1) * gap / 2 + if (rot == RoomRotation.SOUTH) span to 0 else 0 to span + } } - center = IVec2((corners.minOf { it.x } + corners.maxOf { it.x }) / 2, z) + center = IVec2(topLeft.x * gap + half + offX, topLeft.z * gap + half + offZ) } fun inferLayoutFromMap() { |
Readded Dragon title查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragons.kt | 1 + src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragonsEnum.kt | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 16 deletions(-) @@ -44,6 +44,7 @@ object WitherDragons : Module( private val dragonAlerts by DropdownSetting("Dragon Alerts Dropdown") private val sendNotification by BooleanSetting("Send Dragon Confirmation", true, desc = "Sends a confirmation message when a dragon dies.").withDependency { dragonAlerts } + val dragonTitle by BooleanSetting("Dragon Title", true, desc = "Displays a title for spawning dragons.").withDependency { dragonAlerts } val sendTime by BooleanSetting("Send Dragon Time Alive", true, desc = "Sends a message when a dragon dies with the time it was alive.").withDependency { dragonAlerts } val sendSpawned by BooleanSetting("Send Dragon Spawned", true, desc = "Sends a message when a dragon has spawned.").withDependency { dragonAlerts } val sendSpray by BooleanSetting("Send Ice Sprayed", true, desc = "Sends a message when a dragon has been ice sprayed.").withDependency { dragonAlerts } @@ -9,6 +9,7 @@ import com.odtheking.odin.features.impl.boss.WitherDragons.sendSpawned import com.odtheking.odin.features.impl.boss.WitherDragons.sendTime import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.alert import com.odtheking.odin.utils.modMessage import net.minecraft.core.BlockPos import net.minecraft.core.particles.ParticleTypes @@ -29,14 +30,13 @@ enum class WitherDragonsEnum( var timesSpawned: Int = 0, var entityUUID: UUID? = null, var isSprayed: Boolean = false, - var spawnedTime: Long = 0, - val skipKillTime: Int = 0 + var spawnedTime: Long = 0 ) { - Red(BlockPos(27, 14, 59), BlockPos(32, 22, 59), AABB(14.5, 13.0, 45.5, 39.5, 28.0, 70.5), 'c', Colors.MINECRAFT_RED, 24.0..30.0, 56.0..62.0, skipKillTime = 50), - Orange(BlockPos(85, 14, 56), BlockPos(80, 23, 56), AABB(72.0, 8.0, 47.0, 102.0, 28.0, 77.0), '6', Colors.MINECRAFT_GOLD, 82.0..88.0, 53.0..59.0, skipKillTime = 62), - Green(BlockPos(27, 14, 94), BlockPos(32, 23, 94), AABB(7.0, 8.0, 80.0, 37.0, 28.0, 110.0), 'a', Colors.MINECRAFT_GREEN, 23.0..29.0, 91.0..97.0, skipKillTime = 52), - Blue(BlockPos(84, 14, 94), BlockPos(79, 23, 94), AABB(71.5, 13.0, 82.5, 96.5, 26.0, 107.5), 'b', Colors.MINECRAFT_AQUA, 82.0..88.0, 91.0..97.0, skipKillTime = 47), - Purple(BlockPos(56, 14, 125), BlockPos(56, 22, 120), AABB(45.5, 13.0, 113.5, 68.5, 23.0, 136.5), '5', Colors.MINECRAFT_DARK_PURPLE, 53.0..59.0, 122.0..128.0, skipKillTime = 38); + Red(BlockPos(27, 14, 59), BlockPos(32, 22, 59), AABB(14.5, 13.0, 45.5, 39.5, 28.0, 70.5), 'c', Colors.MINECRAFT_RED, 24.0..30.0, 56.0..62.0), + Orange(BlockPos(85, 14, 56), BlockPos(80, 23, 56), AABB(72.0, 8.0, 47.0, 102.0, 28.0, 77.0), '6', Colors.MINECRAFT_GOLD, 82.0..88.0, 53.0..59.0), + Green(BlockPos(27, 14, 94), BlockPos(32, 23, 94), AABB(7.0, 8.0, 80.0, 37.0, 28.0, 110.0), 'a', Colors.MINECRAFT_GREEN, 23.0..29.0, 91.0..97.0), + Blue(BlockPos(84, 14, 94), BlockPos(79, 23, 94), AABB(71.5, 13.0, 82.5, 96.5, 26.0, 107.5), 'b', Colors.MINECRAFT_AQUA, 82.0..88.0, 91.0..97.0), + Purple(BlockPos(56, 14, 125), BlockPos(56, 22, 120), AABB(45.5, 13.0, 113.5, 68.5, 23.0, 136.5), '5', Colors.MINECRAFT_DARK_PURPLE, 53.0..59.0, 122.0..128.0); fun setAlive(entityId: UUID?) { if (entityId != null) this.entityUUID = entityId @@ -48,15 +48,8 @@ enum class WitherDragonsEnum( spawnedTime = currentTick isSprayed = false - if (sendSpawned && WitherDragons.enabled) { - val numberSuffix = when (timesSpawned) { - 1 -> "st" - 2 -> "nd" - 3 -> "rd" - else -> "th" - } - modMessage("§${colorCode}${name} §fdragon spawned. This is the §${colorCode}${timesSpawned}${numberSuffix}§f time it has spawned.") - } + if (sendSpawned && WitherDragons.enabled) + modMessage("§${colorCode}${name} §fdragon spawned §8(§7${timesSpawned}§8)") } fun setDead(realTime: Boolean) { @@ -131,6 +124,7 @@ fun handleSpawnPacket(particle: ClientboundLevelParticlesPacket) { if (dragons.isNotEmpty() && (dragons.size == 2 || spawned >= 2) && priorityDragon == null) priorityDragon = findPriority(dragons).also { dragon -> + if (WitherDragons.dragonTitle && WitherDragons.enabled) alert("§${dragon.colorCode}${dragon.name} is spawning!", true) if (dragonPriorityToggle && WitherDragons.enabled && dragons.size > 1) modMessage("${dragons.joinToString(", ") { "§${it.colorCode}${it.name}" }}§r -> §${dragon.colorCode}${dragon.name} §7is your priority dragon!") } } |
Bump version Optimize imports查看变更gradle.properties | 2 +- src/main/kotlin/com/odtheking/odin/commands/DungeonWaypointCommand.kt | 1 - src/main/kotlin/com/odtheking/odin/commands/MainCommand.kt | 1 - src/main/kotlin/com/odtheking/odin/commands/TermsimCommand.kt | 1 - src/main/kotlin/com/odtheking/odin/features/Category.kt | 2 -- src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/boss/ArrowsDevice.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/InactiveWaypoints.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/KingRelics.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/SpiritBear.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 2 -- src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalTimes.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragonsEnum.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/boss/termGUI/CustomTermGui.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DungeonQueue.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Highlight.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/InvincibilityTimer.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MageBeam.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 2 -- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PositionalMessages.kt | 4 +--- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/SecretClicked.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/WorldScan.kt | 9 +++++++-- src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/render/Etherwarp.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/render/GyroWand.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/render/PlayerSize.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/render/RenderTest.kt | 6 +----- src/main/kotlin/com/odtheking/odin/features/impl/render/Shenanigans.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/render/Waypoints.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/LoadoutKeybinds.kt | 3 --- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PlayerDisplay.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/ServerUtils.kt | 1 - src/main/kotlin/com/odtheking/odin/utils/skyblock/LocationUtils.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/SplitsManager.kt | 2 +- 35 files changed, 28 insertions(+), 47 deletions(-) @@ -10,7 +10,7 @@ loom_version=1.16-SNAPSHOT mod_id=odin mod_name=Odin mod_description=A Hypixel Skyblock QoL mod -mod_version=0.3.0 +mod_version=0.3.1 maven_group=com.odtheking archives_base_name=odin-fabric # Mod Dependencies @@ -6,7 +6,6 @@ import com.odtheking.odin.OdinMod.scope import com.odtheking.odin.config.DungeonWaypointConfig import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.* import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.handlers.schedule import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.setClipboardContent import kotlinx.coroutines.launch @@ -10,7 +10,6 @@ import com.odtheking.odin.clickgui.HudManager import com.odtheking.odin.features.ModuleManager import com.odtheking.odin.features.impl.render.ClickGUIModule import com.odtheking.odin.utils.* -import com.odtheking.odin.utils.handlers.schedule import com.odtheking.odin.utils.skyblock.PartyUtils import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils @@ -3,7 +3,6 @@ package com.odtheking.odin.commands import com.github.stivais.commodore.Commodore import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.features.impl.boss.termsim.StartGUI -import com.odtheking.odin.utils.handlers.schedule import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes val termSimCommand = Commodore("termsim") { @@ -1,7 +1,5 @@ package com.odtheking.odin.features -import com.odtheking.odin.features.Category.Companion.categories - @ConsistentCopyVisibility data class Category private constructor(val name: String) { companion object { @@ -10,7 +10,6 @@ import com.odtheking.odin.clickgui.settings.impl.KeybindSetting import com.odtheking.odin.config.ModuleConfig import com.odtheking.odin.events.InputEvent import com.odtheking.odin.events.core.on -import com.odtheking.odin.features.ModuleManager.configs import com.odtheking.odin.features.impl.boss.* import com.odtheking.odin.features.impl.dungeon.* import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints @@ -6,8 +6,8 @@ import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting import com.odtheking.odin.events.BlockUpdateEvent import com.odtheking.odin.events.ChatPacketEvent -import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -3,8 +3,8 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting import com.odtheking.odin.events.ChatPacketEvent -import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color.Companion.withAlpha @@ -3,9 +3,9 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.TickEvent -import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.events.core.onSend @@ -1,8 +1,8 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.events.BlockUpdateEvent -import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors @@ -9,13 +9,11 @@ import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils import net.minecraft.client.Options import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen -import net.minecraft.client.gui.screens.options.VideoSettingsScreen import net.minecraft.network.chat.Component import org.lwjgl.glfw.GLFW @@ -3,9 +3,9 @@ package com.odtheking.odin.features.impl.boss import com.odtheking.odin.clickgui.settings.impl.ActionSetting import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.TerminalEvent import com.odtheking.odin.events.TickEvent -import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.features.impl.boss.termsim.TermSimGUI @@ -4,8 +4,8 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.HudElement import com.odtheking.odin.events.ChatPacketEvent -import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -9,7 +9,6 @@ import com.odtheking.odin.features.impl.boss.WitherDragons.sendSpawned import com.odtheking.odin.features.impl.boss.WitherDragons.sendTime import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.alert import com.odtheking.odin.utils.modMessage import net.minecraft.core.BlockPos import net.minecraft.core.particles.ParticleTypes @@ -8,7 +8,6 @@ import com.odtheking.odin.features.impl.boss.TerminalSolver.hideClicked import com.odtheking.odin.features.impl.boss.TerminalSolver.renderDebug import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.render.roundedFill import com.odtheking.odin.utils.render.text import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils @@ -4,8 +4,8 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.ChatPacketEvent -import com.odtheking.odin.events.PartyEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.PartyEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors @@ -3,9 +3,9 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting +import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.TickEvent -import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors @@ -6,8 +6,8 @@ import com.odtheking.odin.clickgui.settings.impl.ColorSetting import com.odtheking.odin.clickgui.settings.impl.SelectorSetting import com.odtheking.odin.events.ChatPacketEvent import com.odtheking.odin.events.GuiEvent -import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.* @@ -3,9 +3,9 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting +import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.TickEvent -import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -9,11 +9,9 @@ import com.odtheking.odin.events.ScoreUpdateEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module -import com.odtheking.odin.features.impl.dungeon.map.tile.RoomType import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.alert -import com.odtheking.odin.utils.handlers.TickTask import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.noControlCodes import com.odtheking.odin.utils.render.drawFilledBox @@ -4,8 +4,8 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ListSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting -import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onSend import com.odtheking.odin.features.Module @@ -19,8 +19,6 @@ import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket import net.minecraft.world.phys.AABB import net.minecraft.world.phys.Vec3 -import java.util.* -import kotlin.concurrent.schedule object PositionalMessages : Module( name = "Positional Messages", @@ -3,9 +3,9 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.* import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.SecretPickupEvent -import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color.Companion.withAlpha @@ -6,11 +6,16 @@ import com.odtheking.odin.events.LocationChangeEvent import com.odtheking.odin.events.RoomEnterEvent import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on -import com.odtheking.odin.features.impl.dungeon.map.tile.* -import com.odtheking.odin.utils.* +import com.odtheking.odin.features.impl.dungeon.map.tile.DungeonRoom +import com.odtheking.odin.features.impl.dungeon.map.tile.RoomData +import com.odtheking.odin.features.impl.dungeon.map.tile.RoomShape +import com.odtheking.odin.utils.IVec2 +import com.odtheking.odin.utils.devMessage +import com.odtheking.odin.utils.equalsOneOf import com.odtheking.odin.utils.skyblock.Island import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils +import com.odtheking.odin.utils.toIVec2 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.state.BlockState @@ -17,7 +17,6 @@ import com.odtheking.odin.utils.getChatBreak import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.network.WebUtils.fetchJson import com.odtheking.odin.utils.network.WebUtils.postData -import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.ui.rendering.NVGRenderer import kotlinx.coroutines.launch import net.minecraft.network.chat.ClickEvent @@ -31,7 +31,6 @@ import net.minecraft.world.level.block.piston.PistonHeadBlock import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.phys.AABB import net.minecraft.world.phys.Vec3 -import java.util.* import kotlin.math.abs import kotlin.math.ceil import kotlin.math.max @@ -3,8 +3,8 @@ package com.odtheking.odin.features.impl.render import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -14,7 +14,7 @@ import com.odtheking.odin.utils.network.WebUtils.postData import kotlinx.coroutines.launch import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey import net.minecraft.client.renderer.entity.state.AvatarRenderState -import java.util.UUID +import java.util.* object PlayerSize : Module( name = "Player Size", @@ -8,11 +8,7 @@ import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Category import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.render.drawCylinder -import com.odtheking.odin.utils.render.drawFilledBox -import com.odtheking.odin.utils.render.drawLine -import com.odtheking.odin.utils.render.drawStyledBox -import com.odtheking.odin.utils.render.drawWireFrameBox +import com.odtheking.odin.utils.render.* import net.minecraft.world.phys.AABB import kotlin.math.ceil import kotlin.math.sqrt @@ -1,7 +1,7 @@ package com.odtheking.odin.features.impl.render -import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.utils.render.drawTexturedQuad import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils @@ -6,8 +6,8 @@ import com.odtheking.odin.clickgui.settings.impl.DropdownSetting import com.odtheking.odin.clickgui.settings.impl.KeybindSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.events.ChatPacketEvent -import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color @@ -1,17 +1,14 @@ package com.odtheking.odin.features.impl.skyblock import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency -import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.DropdownSetting import com.odtheking.odin.clickgui.settings.impl.KeybindSetting import com.odtheking.odin.events.ScreenEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.clickSlot -import com.odtheking.odin.utils.modMessage import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import org.lwjgl.glfw.GLFW -import java.security.Key object LoadoutKeybinds : Module( name = "Loadout Keybinds", @@ -7,8 +7,8 @@ import com.odtheking.odin.clickgui.settings.impl.DropdownSetting import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.render.textDim -import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.skyblock.ActionBarListener +import com.odtheking.odin.utils.skyblock.LocationUtils import net.minecraft.network.chat.Component import kotlin.math.abs @@ -5,7 +5,6 @@ import com.odtheking.odin.events.core.onReceive import net.minecraft.network.protocol.game.ClientboundSetTimePacket import net.minecraft.network.protocol.ping.ClientboundPongResponsePacket import net.minecraft.util.Util -import java.util.ArrayDeque import kotlin.math.min object ServerUtils { @@ -1,8 +1,8 @@ package com.odtheking.odin.utils.skyblock import com.odtheking.odin.OdinMod.mc -import com.odtheking.odin.events.LocationChangeEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.LocationChangeEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.utils.equalsOneOf @@ -1,8 +1,8 @@ package com.odtheking.odin.utils.skyblock import com.odtheking.odin.events.ChatPacketEvent -import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.impl.skyblock.Splits import com.odtheking.odin.utils.PersonalBest |
Cleaned up DungeonMap implementation and added example preview查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 41 ++++++++++----------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonScan.kt | 10 +++--- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapScan.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 51 +++++++++++++++------------ 5 files changed, 157 insertions(+), 83 deletions(-) @@ -12,7 +12,6 @@ import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.render.hollowFill import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import net.minecraft.client.gui.GuiGraphicsExtractor object DungeonMap : Module( name = "Dungeon Map", @@ -21,7 +20,7 @@ object DungeonMap : Module( private val disableBoss by BooleanSetting("Disable in Boss", true, desc = "Disables the map during boss fights.") val backgroundOutline by ColorSetting("Background Outline", Colors.BLACK, true, desc = "The color of the background border.") - val backgroundColor by ColorSetting("Background Color", Colors.BLACK.withAlpha(0.2f), true, desc = "Background color of the map.") + val backgroundColor by ColorSetting("Background Color", Colors.BLACK.withAlpha(0.1f), true, desc = "Background color of the map.") val textScaling by NumberSetting("Text Scaling", 0.45f, 0.1f, 1f, 0.05f, desc = "Scale of room name text.") private val playerDropdown by DropdownSetting("Player Settings") @@ -48,29 +47,31 @@ object DungeonMap : Module( val disablePred by BooleanSetting("Disable Prediction", false, desc = "Disables special-column room type prediction.") - private val mapHud by HUD("Dungeon Map", "Displays the dungeon map.", false) { example -> - when { - (!DungeonUtils.inDungeons || (disableBoss && DungeonUtils.inBoss)) && !example -> 0 to 0 - example -> renderExampleMap() - else -> renderDungeonMap() - } - } + private val exampleRooms by lazy { buildExampleRooms() } + private val exampleDoors by lazy { buildExampleDoors(exampleRooms) } - private const val MAP_PX = 128 - - private fun GuiGraphicsExtractor.renderExampleMap(): Pair<Int, Int> { + private val mapHud by HUD("Dungeon Map", "Displays the dungeon map.", false) { example -> + if ((!DungeonUtils.inDungeons || (disableBoss && DungeonUtils.inBoss)) && !example) return@HUD 0 to 0 fill(0, 0, MAP_PX, MAP_PX, backgroundColor.rgba) hollowFill(0, 0, MAP_PX, MAP_PX, 1, backgroundOutline) - centeredText(mc.font, "MAP", MAP_PX / 2, MAP_PX / 2 - mc.font.lineHeight / 2, Colors.WHITE.rgba) - return MAP_PX to MAP_PX - } + pose().pushMatrix() - private fun GuiGraphicsExtractor.renderDungeonMap(): Pair<Int, Int> { - fill(0, 0, MAP_PX, MAP_PX, backgroundColor.rgba) - hollowFill(0, 0, MAP_PX, MAP_PX, 1, Colors.gray26) + if (example) { + pose().translate(5f, 5f) + renderMap(exampleRooms, exampleDoors, emptyList()) + } + else { + pose().translate(DungeonScan.startX.toFloat(), DungeonScan.startY.toFloat()) + pose().scale(DungeonScan.roomSize / DungeonScan.MAP_ROOM_SIZE.toFloat()) + + renderMap(DungeonScan.rooms, DungeonScan.doors.values, DungeonScan.pathHints) - renderMap() + if (!DungeonUtils.inBoss) renderPlayers() + } + pose().popMatrix() - return MAP_PX to MAP_PX + MAP_PX to MAP_PX } + + private const val MAP_PX = 128 } \ No newline at end of file @@ -20,9 +20,11 @@ import java.util.concurrent.CopyOnWriteArrayList object DungeonScan { const val ROOM_SPACING = 4 + const val MAP_ROOM_SIZE = 16 + const val MAP_ROOM_GAP = MAP_ROOM_SIZE + ROOM_SPACING var roomSize = 16 - var roomGap = 20 + val roomGap: Int get () = roomSize + ROOM_SPACING val connectionGap: Int get() = roomSize + ROOM_SPACING / 2 var startX = 5 @@ -54,14 +56,12 @@ object DungeonScan { viewableDoors.clear() pathHints.clear() roomSize = 16 - roomGap = 20 startX = 5 startY = 5 } fun initClient(floor: Floor) { roomSize = if (floor.floorNumber <= 3) 18 else 16 - roomGap = roomSize + ROOM_SPACING startX = when { floor.floorNumber <= 1 -> 22 @@ -79,8 +79,8 @@ object DungeonScan { fun playerRenderPosition(entity: Player?, mapPos: IVec2): Pair<Float, Float> { entity?.let { - val mapX = (it.renderX.toFloat() + 200f) * roomGap / 32f - val mapZ = (it.renderZ.toFloat() + 200f) * roomGap / 32f + val mapX = (it.renderX.toFloat() + 200f) * MAP_ROOM_GAP / 32f + val mapZ = (it.renderZ.toFloat() + 200f) * MAP_ROOM_GAP / 32f return mapX to mapZ } @@ -16,24 +16,9 @@ private val green = Identifier.fromNamespaceAndPath("odin", "map/green_check.png private val white = Identifier.fromNamespaceAndPath("odin", "map/white_check.png") private val question = Identifier.fromNamespaceAndPath("odin", "map/question.png") -internal fun GuiGraphicsExtractor.renderMap() { - pose().pushMatrix() - pose().translate(DungeonScan.startX.toFloat(), DungeonScan.startY.toFloat()) - - renderDoors() - - for (room in DungeonScan.rooms) { - if (room.isViewable) fillRoom(room, roomTypeColor(room.type).rgba, DungeonScan.roomSize, DungeonScan.roomGap) - } - - for (room in DungeonScan.rooms) renderRoomText(room) - - if (!DungeonUtils.inBoss) renderPlayers() - - pose().popMatrix() -} - -private fun GuiGraphicsExtractor.fillRoom(room: DungeonRoom, color: Int, rs: Int, rg: Int) { +fun GuiGraphicsExtractor.fillRoom(room: DungeonRoom, color: Int) { + val rs = DungeonScan.MAP_ROOM_SIZE + val rg = DungeonScan.MAP_ROOM_GAP val ox = room.topLeft.x * rg val oy = room.topLeft.z * rg when (val shape = room.shape) { @@ -62,12 +47,12 @@ private fun GuiGraphicsExtractor.fillRoom(room: DungeonRoom, color: Int, rs: Int } } -private fun GuiGraphicsExtractor.renderDoors() { - val rs = DungeonScan.roomSize - val rg = DungeonScan.roomGap +fun GuiGraphicsExtractor.renderDoors(doors: Collection<DungeonDoor>) { + val rs = DungeonScan.MAP_ROOM_SIZE + val rg = DungeonScan.MAP_ROOM_GAP val half = (rs - 8) / 2f - for ((_, door) in DungeonScan.doors) { + for (door in doors) { val offset = door.rotation.offset pose().pushMatrix() @@ -78,13 +63,17 @@ private fun GuiGraphicsExtractor.renderDoors() { fill(0, 0, 4 + 4 * offset.z, 4 + 4 * offset.x, door.color.rgba) pose().popMatrix() } +} + +fun GuiGraphicsExtractor.renderPathHints(pathHints: Collection<DungeonTile>) { + val rs = DungeonScan.MAP_ROOM_SIZE + val rg = DungeonScan.MAP_ROOM_GAP - for (unknownTile in DungeonScan.pathHints) { - val (x, y) = unknownTile.position.x * rg to unknownTile.position.z * rg - val rs = DungeonScan.roomSize + for ((position, room) in pathHints) { + val (x, y) = position.x * rg to position.z * rg - val colors = if (unknownTile.room?.type == RoomType.BLOOD) arrayOf(DungeonMap.bloodRoomColor.darker(0.5f)) - else if (!DungeonMap.disablePred && unknownTile.room?.isKnown1x1 == true) SpecialColumn.colorGuessForUnknown(unknownTile.position.x) + val colors = if (room?.type == RoomType.BLOOD) arrayOf(DungeonMap.bloodRoomColor.darker(0.5f)) + else if (!DungeonMap.disablePred && room?.isKnown1x1 == true) SpecialColumn.colorGuessForUnknown(position.x) else arrayOf(DungeonMap.unknownRoomColor) when (colors.size) { @@ -108,21 +97,20 @@ private fun GuiGraphicsExtractor.renderDoors() { } fun GuiGraphicsExtractor.renderIcon(pos: IVec2, identifier: Identifier) { - val rs = DungeonScan.roomSize - 4 - blit(RenderPipelines.GUI_TEXTURED, identifier, pos.x + 2, pos.z + 2, rs.toFloat(), rs.toFloat(), rs, rs, rs, rs) + val size = DungeonScan.MAP_ROOM_SIZE - DungeonScan.ROOM_SPACING + blit(RenderPipelines.GUI_TEXTURED, identifier, pos.x + 2, pos.z + 2, size.toFloat(), size.toFloat(), size, size, size, size) } -private fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { +fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { if (room.type.equalsOneOf(RoomType.UNDISCOVERED, RoomType.FAIRY, RoomType.ENTRANCE, RoomType.BLOOD)) return - val scannedRoom = DungeonScan.tiles.getOrNull(room.topLeft.x + room.topLeft.z * 6)?.room - if (scannedRoom?.walkedInto != true) { + if (!room.walkedInto) { when (room.checkmark) { MapCheckmark.GREEN -> green MapCheckmark.WHITE -> white MapCheckmark.RED -> cross else -> null - }?.let { texture -> renderIcon(room.topLeft * DungeonScan.roomGap, texture) } + }?.let { texture -> renderIcon(room.topLeft * DungeonScan.MAP_ROOM_GAP, texture) } return } @@ -136,7 +124,7 @@ private fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { else -> Color(100, 100, 100) }.rgba - val lines = scannedRoom.name?.split(" ") ?: return + val lines = room.name?.split(" ") ?: return val totalH = (lines.size - 1) * fontH * DungeonMap.textScaling for ((i, line) in lines.withIndex()) { @@ -148,7 +136,7 @@ private fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { } } -private fun GuiGraphicsExtractor.renderPlayers() { +fun GuiGraphicsExtractor.renderPlayers() { val showNames = mc.player?.mainHandItem?.itemId?.equalsOneOf("INFINITE_SPIRIT_LEAP", "SPIRIT_LEAP") == true for (player in DungeonUtils.dungeonTeammatesNoSelf) { @@ -189,6 +177,85 @@ private fun GuiGraphicsExtractor.renderPlayers() { pose().popMatrix() } +fun GuiGraphicsExtractor.renderMap(rooms: Collection<DungeonRoom>, doors: Collection<DungeonDoor>, pathHints: Collection<DungeonTile>) { + renderDoors(doors) + for (room in rooms) if (room.isViewable) fillRoom(room, roomTypeColor(room.type).rgba) + for (room in rooms) renderRoomText(room) + renderPathHints(pathHints) +} + +fun buildExampleRooms(): List<DungeonRoom> { + fun room(type: RoomType, topLeft: IVec2, rotation: RoomRotation?, checkmark: MapCheckmark, walkedInto: Boolean, data: RoomData? = null) = + DungeonRoom(type, topLeft, data).apply { + data?.let { shape = it.shape } + this.rotation = rotation + this.checkmark = checkmark + this.walkedInto = walkedInto + } + + return listOf( + room(RoomType.ENTRANCE, IVec2(2, 0), RoomRotation.NORTH, MapCheckmark.GREEN, true), + room(RoomType.NORMAL, IVec2(0, 0), RoomRotation.WEST, MapCheckmark.NONE, true, RoomData.getRoomData(1051405699)), // hallway + room(RoomType.PUZZLE, IVec2(1, 0), RoomRotation.NORTH, MapCheckmark.NONE, true, RoomData.getRoomData(379499781)), // water board + room(RoomType.NORMAL, IVec2(3, 0), RoomRotation.WEST, MapCheckmark.WHITE, true, RoomData.getRoomData(90256084)), // waterfall + room(RoomType.NORMAL, IVec2(4, 0), RoomRotation.SOUTH, MapCheckmark.NONE, true, RoomData.getRoomData(1024359556)), // bridges + room(RoomType.NORMAL, IVec2(1, 1), RoomRotation.SOUTH, MapCheckmark.WHITE, true, RoomData.getRoomData(1250712883)), // museum + room(RoomType.NORMAL, IVec2(4, 1), RoomRotation.SOUTH, MapCheckmark.NONE, true, RoomData.getRoomData(950592972)), // cathedral + room(RoomType.NORMAL, IVec2(1, 3), RoomRotation.NORTH, MapCheckmark.WHITE, true, RoomData.getRoomData(-333637832)), // water + room(RoomType.FAIRY, IVec2(2, 3), RoomRotation.SOUTH, MapCheckmark.GREEN, true), + room(RoomType.CHAMPION, IVec2(4, 3), RoomRotation.SOUTH, MapCheckmark.GREEN, false, RoomData.getRoomData(-1334473473)), // dragon + room(RoomType.TRAP, IVec2(5, 3), RoomRotation.NORTH, MapCheckmark.GREEN, false, RoomData.getRoomData(1590699551)), // old trap + room(RoomType.PUZZLE, IVec2(0, 4), RoomRotation.WEST, MapCheckmark.NONE, true, RoomData.getRoomData(799715466)), // tp maze + room(RoomType.NORMAL, IVec2(1, 4), RoomRotation.EAST, MapCheckmark.WHITE, true, RoomData.getRoomData(1484939648)), // spikes + room(RoomType.NORMAL, IVec2(2, 4), RoomRotation.WEST, MapCheckmark.WHITE, true, RoomData.getRoomData(-1764045332)), // beams + room(RoomType.NORMAL, IVec2(3, 4), RoomRotation.NORTH, MapCheckmark.NONE, false, RoomData.getRoomData(76347246)), // mirror + room(RoomType.NORMAL, IVec2(4, 4), RoomRotation.WEST, MapCheckmark.NONE, false, RoomData.getRoomData(-1899702429)), // silver sword + room(RoomType.NORMAL, IVec2(5, 4), RoomRotation.WEST, MapCheckmark.WHITE, false, RoomData.getRoomData(-151940807)), // staircase + room(RoomType.NORMAL, IVec2(0, 5), RoomRotation.SOUTH, MapCheckmark.WHITE, true, RoomData.getRoomData(259768244)), // archway + room(RoomType.BLOOD, IVec2(2, 5), RoomRotation.EAST, MapCheckmark.NONE, true), + room(RoomType.NORMAL, IVec2(3, 5), RoomRotation.SOUTH, MapCheckmark.NONE, false, RoomData.getRoomData(222124420)), // wizard + ) +} + +fun buildExampleDoors(rooms: List<DungeonRoom>): List<DungeonDoor> { + val roomByTile = rooms.flatMap { room -> room.occupiedTiles().map { it to room } }.toMap() + + fun door(position: IVec2, rotation: DoorRotation, type: DoorType): DungeonDoor { + val door = DungeonDoor(position, rotation, type) + door.color = when (type) { + DoorType.Wither -> DungeonMap.witherDoorColor + DoorType.Blood -> DungeonMap.bloodDoorColor + DoorType.Fairy -> DungeonMap.fairyDoorColor + DoorType.Normal -> listOfNotNull(roomByTile[position], roomByTile[position + rotation.offset]) + .firstOrNull { it.type != RoomType.NORMAL && it.type != RoomType.FAIRY } + ?.type?.let(::roomTypeColor) ?: DungeonMap.normalDoorColor + } + return door + } + + return listOf( + door(IVec2(2, 0), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(1, 0), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(0, 1), DoorRotation.Horizontal, DoorType.Normal), + door(IVec2(2, 2), DoorRotation.Horizontal, DoorType.Normal), + door(IVec2(1, 2), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(4, 0), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(3, 1), DoorRotation.Horizontal, DoorType.Normal), + door(IVec2(3, 3), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(1, 3), DoorRotation.Horizontal, DoorType.Fairy), + door(IVec2(2, 3), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(4, 3), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(5, 3), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(0, 4), DoorRotation.Horizontal, DoorType.Normal), + door(IVec2(1, 4), DoorRotation.Horizontal, DoorType.Normal), + door(IVec2(1, 4), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(3, 4), DoorRotation.Horizontal, DoorType.Normal), + door(IVec2(3, 4), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(5, 4), DoorRotation.Vertical, DoorType.Normal), + door(IVec2(1, 5), DoorRotation.Horizontal, DoorType.Blood), + ) +} + fun roomTypeColor(type: RoomType): Color = when (type) { RoomType.NORMAL -> DungeonMap.normalRoomColor RoomType.PUZZLE -> DungeonMap.puzzleRoomColor @@ -47,7 +47,6 @@ object MapScan { val length = end - index if (length == 16 || length == 18) { DungeonScan.roomSize = length - DungeonScan.roomGap = length + DungeonScan.ROOM_SPACING DungeonScan.startX = (index % MAP_SIZE) % DungeonScan.roomGap DungeonScan.startY = (index / MAP_SIZE) % DungeonScan.roomGap @@ -3,19 +3,18 @@ package com.odtheking.odin.features.impl.dungeon.map.tile import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints import com.odtheking.odin.features.impl.dungeon.map.DungeonScan -import com.odtheking.odin.utils.IVec2 -import com.odtheking.odin.utils.modMessage -import com.odtheking.odin.utils.rotateAroundNorth -import com.odtheking.odin.utils.rotateToNorth -import com.odtheking.odin.utils.sortKey +import com.odtheking.odin.utils.* import net.minecraft.core.BlockPos import net.minecraft.world.level.block.Blocks -import kotlin.math.roundToInt class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData? = null) { val tiles: ArrayList<IVec2> = ArrayList(4) var topLeft: IVec2 = initialPosition var rotation: RoomRotation? = null + set(value) { + field = value + recalculateCenter() + } var shape: RoomShape = RoomShape.OneByOne @@ -43,33 +42,41 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData } } - private fun recalculateCenter() { - if (tiles.isEmpty()) return - val rs = DungeonScan.roomSize.takeIf { it != -1 } ?: return + fun occupiedTiles(): List<IVec2> { + val rot = rotation ?: return emptyList() + return tileOffsets(rot).map { topLeft + it } + } - val rg = DungeonScan.roomGap - val half = rs / 2 + private fun tileOffsets(rot: RoomRotation): List<IVec2> = when (shape) { + RoomShape.OneByOne -> listOf(IVec2(0, 0)) + RoomShape.TwoByTwo -> listOf(IVec2(0, 0), IVec2(1, 0), IVec2(0, 1), IVec2(1, 1)) + RoomShape.L -> when (rot) { + RoomRotation.WEST -> listOf(IVec2(0, 0), IVec2(1, 0), IVec2(0, 1)) + RoomRotation.NORTH -> listOf(IVec2(0, 0), IVec2(1, 0), IVec2(1, 1)) + else -> listOf(IVec2(0, 0), IVec2(0, 1), IVec2(1, 1)) + } + else -> if (rot == RoomRotation.SOUTH) (0 until shape.tileAmount).map { IVec2(it, 0) } + else (0 until shape.tileAmount).map { IVec2(0, it) } + } - fun tileCenter(tile: IVec2) = tile.x * rg + half to tile.z * rg + half + private fun recalculateCenter() { + fun tileCenter(tile: IVec2) = IVec2(tile.x * DungeonScan.MAP_ROOM_GAP + DungeonScan.MAP_ROOM_SIZE / 2, tile.z * DungeonScan.MAP_ROOM_GAP + DungeonScan.MAP_ROOM_SIZE / 2) val rot = rotation if (rot == null) { - val tile = tiles.minBy { it.sortKey } - val (x, z) = tileCenter(tile) - center = IVec2(x, z) + if (tiles.isEmpty()) return + center = tileCenter(tiles.minBy { it.sortKey }) return } - val centers = tiles.map { tileCenter(it) } - val x = (centers.minOf { it.first } + centers.maxOf { it.first }) / 2f + val corners = occupiedTiles().map(::tileCenter) val z = when (shape) { RoomShape.L -> - if (rot == RoomRotation.NORTH || rot == RoomRotation.WEST) centers.minOf { it.second }.toFloat() - else centers.maxOf { it.second }.toFloat() - else -> - (centers.minOf { it.second } + centers.maxOf { it.second }) / 2f + if (rot == RoomRotation.NORTH || rot == RoomRotation.WEST) corners.minOf { it.z } + else corners.maxOf { it.z } + else -> (corners.minOf { it.z } + corners.maxOf { it.z }) / 2 } - center = IVec2(x.roundToInt(), z.roundToInt()) + center = IVec2((corners.minOf { it.x } + corners.maxOf { it.x }) / 2, z) } fun inferLayoutFromMap() { |
Fixed Splits fixed width not working Made Splits example show actual splits Fixed QuizTimer setting not having dependancies Fixed PuzzleHUD not rendering in example Fixed MapInfo sending 300 score more than once查看变更src/main/kotlin/com/odtheking/odin/commands/DungeonWaypointCommand.kt | 2 +- src/main/kotlin/com/odtheking/odin/commands/MainCommand.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/commands/TermsimCommand.kt | 5 +++-- src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragons.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 5 ++++- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PuzzleHud.kt | 7 +++---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/Splits.kt | 45 ++++++++++++++++++++++++++++----------------- src/main/kotlin/com/odtheking/odin/utils/render/ItemRenderer.kt | 11 ++--------- src/main/kotlin/com/odtheking/odin/utils/skyblock/SplitsManager.kt | 2 +- 10 files changed, 46 insertions(+), 39 deletions(-) @@ -13,7 +13,7 @@ import kotlinx.coroutines.launch val dungeonWaypointsCommand = Commodore("dwp", "dungeonwaypoints") { runs { - schedule(0) { mc.setScreen(WaypointPackSelectorScreen(mc.screen)) } + mc.schedule { mc.setScreen(WaypointPackSelectorScreen(mc.screen)) } } literal("fill").runs { @@ -16,11 +16,11 @@ import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils val mainCommand = Commodore("odin", "od") { runs { - schedule(0) { mc.setScreen(ClickGUI) } + mc.schedule { mc.setScreen(ClickGUI) } } literal("edithud").runs { - schedule(0) { mc.setScreen(HudManager) } + mc.schedule { mc.setScreen(HudManager) } } literal("tps").runs { @@ -1,16 +1,17 @@ package com.odtheking.odin.commands import com.github.stivais.commodore.Commodore +import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.features.impl.boss.termsim.StartGUI import com.odtheking.odin.utils.handlers.schedule import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes val termSimCommand = Commodore("termsim") { runs { ping: Long? -> - schedule(0) { StartGUI.open(ping ?: 0) } + mc.schedule { StartGUI.open(ping ?: 0) } } runs { terminal: TerminalTypes, ping: Long? -> - schedule(0) { terminal.getSimulator().open(ping ?: 0L) } + mc.schedule { terminal.getSimulator().open(ping ?: 0L) } } } \ No newline at end of file @@ -40,7 +40,7 @@ object WitherDragons : Module( }.withDependency { dragonTimerDropDown } private val dragonBoxes by BooleanSetting("Dragon Boxes", true, desc = "Displays boxes for where M7 dragons spawn.") - private val dragonTracers by BooleanSetting("Dragon Tracer", true, desc = "Draws a line to spawning dragons.") + private val dragonTracers by BooleanSetting("Target Tracer", true, desc = "Draws a line to priority spawning dragon.") private val dragonAlerts by DropdownSetting("Dragon Alerts Dropdown") private val sendNotification by BooleanSetting("Send Dragon Confirmation", true, desc = "Sends a confirmation message when a dragon dies.").withDependency { dragonAlerts } @@ -202,10 +202,12 @@ object MapInfo : Module( private var portalAABB: AABB? = null private var currentRoomSecrets: Pair<Int, Int>? = null private val secretRegex = Regex("(\\d+)/(\\d+) Secrets") + private var shownTitle = false init { on<ScoreUpdateEvent> { - if (!DungeonUtils.inDungeons || (!scoreTitle && !printWhenScore) || score < 300) return@on + if (shownTitle || !DungeonUtils.inDungeons || (!scoreTitle && !printWhenScore) || score < 300) return@on + shownTitle = true if (scoreTitle) alert("§c300 Score!") if (printWhenScore) modMessage("§b${DungeonUtils.score} §ascore reached in §6${DungeonUtils.dungeonTime} §8|| §e${DungeonUtils.floor?.name}.") } @@ -231,6 +233,7 @@ object MapInfo : Module( } on<LevelEvent.Load> { + shownTitle = false portalAABB = null } } @@ -23,9 +23,8 @@ object PuzzleHud : Module( PuzzleEntry(Puzzle.UNKNOWN.displayName, "✦", "§6", null) ) - private val hud by HUD("Puzzle HUD Position", "Displays dungeon puzzle statuses on the HUD.") { example -> - if (!DungeonUtils.inDungeons || DungeonUtils.inBoss && !example) return@HUD 0 to 0 - + private val hud by HUD("Puzzle HUD", "Displays dungeon puzzle statuses on the HUD.", false) { example -> + if (!DungeonUtils.inClear && !example) return@HUD 0 to 0 if (!example && showOnlyPre && DungeonUtils.openRoomCount != 0) return@HUD 0 to 0 val puzzleCount = if (example) 4 else DungeonUtils.puzzleCount @@ -54,7 +53,7 @@ object PuzzleHud : Module( width = textDim(headerText, 0, 0, Colors.WHITE).first entries.forEachIndexed { index, entry -> - val playerText = if (showPlayerNames && entry.player != null) " §7(${entry.player})" else "" + val playerText = if (showPlayerNames && entry.player != null) " §7(§6${entry.player}§7)" else "" val newWidth = textDim("§e${entry.name}§8: [${entry.statusColor}${entry.statusIcon}§8]$playerText", 0, (index + 1) * lineHeight, Colors.WHITE).first if (newWidth > width) width = newWidth } @@ -86,7 +86,7 @@ object PuzzleSolvers : Module( private val quizTimerHud by HUD("Quiz Timer Hud", "Displays a timer counting down until you must answer the quiz question.") { example -> val timer = if (example) Triple(220, 220, 1) else QuizSolver.timer if (quizSolver && timer.first > 0) textDim(formatQuizTimer(timer), 0, 0, Colors.WHITE) else 0 to 0 - } + }.withDependency { quizDropdown && quizSolver } private val quizReset by ActionSetting("Reset Quiz", desc = "Resets the solver.") { QuizSolver.reset() }.withDependency { quizDropdown && quizSolver } private val boulderDropDown by DropdownSetting("Boulder") @@ -10,6 +10,7 @@ import com.odtheking.odin.utils.render.getStringWidth import com.odtheking.odin.utils.render.text import com.odtheking.odin.utils.skyblock.SplitsManager.currentSplits import com.odtheking.odin.utils.skyblock.SplitsManager.getAndUpdateSplitsTimes +import com.odtheking.odin.utils.skyblock.floor7SplitGroup import com.odtheking.odin.utils.toFixed object Splits : Module( @@ -17,44 +18,54 @@ object Splits : Module( description = "Provides visual timers for Kuudra and Dungeons." ) { private val hud by HUD("Splits Display HUD", "Shows timers for each split.") { example -> - val totalWidth = getStringWidth("Split 0: 0h 00m 00s" + if (showTickTime) " (0h 00m 00s)" else "") + 2 + val timeWidth = 75 + if (showTickTime) 20 else -20 if (example) { - repeat(5) { i -> - val exampleTime = "0h 00m 00s" + if (showTickTime) " §8(§70s§8)" else "" + val labelWidth = floor7SplitGroup.maxOf { getStringWidth("${it.name}:") } + val timeExample = "59m 59s" + if (showTickTime) " §8(§759.9§8)" else "" + val totalWidth = labelWidth + 4 + timeWidth + 2 + + floor7SplitGroup.forEachIndexed { i, split -> if (fixedWidth) { - text("Split $i:", 0, i * 9, Colors.WHITE) - text(exampleTime, totalWidth - getStringWidth("0h 00m 00s" + if (showTickTime) " (0s)" else ""), i * 9, Colors.WHITE) - } else { - text("Split $i: $exampleTime", 0, i * 9, Colors.WHITE) - } + text("${split.name}:", 0, i * 9, Colors.WHITE) + text(timeExample, totalWidth - getStringWidth(timeExample) - 2, i * 9, Colors.WHITE) + } else text("${split.name}: $timeExample", 0, i * 9, Colors.WHITE) } - return@HUD totalWidth to 9 * 5 + return@HUD totalWidth to 9 * floor7SplitGroup.size } val (times, tickTimes, current) = getAndUpdateSplitsTimes(currentSplits) if (currentSplits.splits.isEmpty()) return@HUD 0 to 0 + val splits = currentSplits.splits.dropLast(1) - val maxWidth = currentSplits.splits.dropLast(1).maxOf { getStringWidth(it.name) } + val labelWidth = splits.maxOfOrNull { getStringWidth(it.name) } ?: 0 + val totalWidth = labelWidth + 4 + timeWidth + 2 - currentSplits.splits.dropLast(1).forEachIndexed { index, split -> + splits.forEachIndexed { index, split -> val time = formatTime(if (index >= times.size) 0 else times[index]) text(split.name, 0, index * 9, Colors.WHITE) - val displayText = if (showTickTime && index < tickTimes.size) "$time §8(§7${(tickTimes[index] / 20f).toFixed()}§8)" else time - val timeX = if (fixedWidth) totalWidth - getStringWidth(displayText) else maxWidth + 4 + val displayText = if (showTickTime && index < tickTimes.size) "$time §8(§7${(tickTimes[index] / 20f).toFixed()}§8)" + else time + + val timeX = if (fixedWidth) labelWidth + 4 + timeWidth - getStringWidth(displayText) + else labelWidth + 4 text(displayText, timeX, index * 9, Colors.WHITE) } if (bossEntrySplit && currentSplits.splits.size > 3) { - text("§9Boss Entry", 0, (currentSplits.splits.size - 1) * 9, Colors.WHITE) + val y = splits.size * 9 + text("§9Boss Entry", 0, y, Colors.WHITE) val totalTime = formatTime(times.take(3).sum()) - val displayText = if (showTickTime) "$totalTime §8(§7${(tickTimes.take(3).sum() / 20f).toFixed()}§8)" else totalTime - val timeX = if (fixedWidth) totalWidth - getStringWidth(displayText) else maxWidth + 4 + val displayText = if (showTickTime) "$totalTime §8(§7${(tickTimes.take(3).sum() / 20f).toFixed()}§8)" + else totalTime + + val timeX = if (fixedWidth) labelWidth + 4 + timeWidth - getStringWidth(displayText) + else labelWidth + 4 - text(displayText, timeX, (currentSplits.splits.size - 1) * 9, Colors.WHITE) + text(displayText, timeX, y, Colors.WHITE) } totalWidth to 9 * (currentSplits.splits.size + (if (bossEntrySplit) 1 else 0)) @@ -83,18 +83,11 @@ class ItemStateRenderer(vertexConsumers: MultiBufferSource.BufferSource) } companion object { - private val trackingCache = object : LinkedHashMap<ItemStack, TrackingItemStackRenderState>(16, 0.75f, true) { - override fun removeEldestEntry(eldest: MutableMap.MutableEntry<ItemStack, TrackingItemStackRenderState>) = size > 10 - } - fun GuiGraphicsExtractor.drawItemStack(item: ItemStack, x: Int, y: Int) { if (item.isEmpty) return - val tracking = trackingCache.getOrPut(item) { - TrackingItemStackRenderState().also { - mc.itemModelResolver.updateForTopItem(it, item, ItemDisplayContext.GUI, mc.level, mc.player, 0) - } - } + val tracking = TrackingItemStackRenderState() + mc.itemModelResolver.updateForTopItem(tracking, item, ItemDisplayContext.GUI, mc.level, mc.player, 0) val state = State( GuiItemRenderState( @@ -174,7 +174,7 @@ private val floor6SplitGroup = mutableListOf( Split(Regex("^\\[BOSS] Sadan: You did it\\. I understand now, you have earned my respect\\.$"), "§4Cleared"), ) -private val floor7SplitGroup = mutableListOf( +val floor7SplitGroup = mutableListOf( Split(entryRegexes[6], "§5Maxor"), Split(Regex("\\[BOSS] Storm: Pathetic Maxor, just like expected\\."), "§3Storm"), Split(Regex("\\[BOSS] Goldor: Who dares trespass into my domain\\?"), "§6Terminals"), |
Added Quiz timer查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 16 ++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/QuizSolver.kt | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) @@ -11,6 +11,7 @@ import com.odtheking.odin.features.impl.dungeon.map.tile.RoomType import com.odtheking.odin.utils.* import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.handlers.TickTask +import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.core.BlockPos import net.minecraft.network.chat.ClickEvent @@ -82,6 +83,10 @@ object PuzzleSolvers : Module( private val quizSolver by BooleanSetting("Quiz Solver", true, desc = "Solver for the trivia puzzle.").withDependency { quizDropdown } private val quizColor by ColorSetting("Quiz Color", Colors.MINECRAFT_GREEN.withAlpha(.75f), true, desc = "Color for the quiz solver.").withDependency { quizDropdown && quizSolver } private val quizDepth by BooleanSetting("Quiz Depth", false, desc = "Depth check for the trivia puzzle.").withDependency { quizDropdown && quizSolver } + private val quizTimerHud by HUD("Quiz Timer Hud", "Displays a timer counting down until you must answer the quiz question.") { example -> + val timer = if (example) Triple(220, 220, 1) else QuizSolver.timer + if (quizSolver && timer.first > 0) textDim(formatQuizTimer(timer), 0, 0, Colors.WHITE) else 0 to 0 + } private val quizReset by ActionSetting("Reset Quiz", desc = "Resets the solver.") { QuizSolver.reset() }.withDependency { quizDropdown && quizSolver } private val boulderDropDown by DropdownSetting("Boulder") @@ -109,6 +114,7 @@ object PuzzleSolvers : Module( on<TickEvent.Server> { if (!DungeonUtils.inClear) return@on if (waterSolver) WaterSolver.onServerTick() + QuizSolver.onServerTick() } on<LevelEvent.Load> { @@ -200,4 +206,14 @@ object PuzzleSolvers : Module( it.sentMessage = true } } + + private fun formatQuizTimer(timer: Triple<Int, Int, Int>): String { + val (tick, maxTick, stage) = timer + val color = when { + tick.toFloat() >= maxTick * 0.66 -> "§a" + tick.toFloat() >= maxTick * 0.33 -> "§6" + else -> "§c" + } + return "§5Quiz §8(§f$stage§8/§f3§8): $color${(tick / 20f).toFixed()}s" + } } \ No newline at end of file @@ -12,6 +12,9 @@ import net.minecraft.core.BlockPos import net.minecraft.world.phys.AABB object QuizSolver { + private const val START_DURATION = 220 + private const val QUESTION_DURATION = 100 + private var answers: MutableMap<String, List<String>> = JsonResourceLoader.loadJson( "/assets/odin/puzzles/quizAnswers.json", mutableMapOf() ) @@ -20,7 +23,22 @@ object QuizSolver { private var triviaOptions: MutableList<TriviaAnswer> = MutableList(3) { TriviaAnswer(null, false) } private data class TriviaAnswer(var blockPos: BlockPos?, var isCorrect: Boolean) + var timer: Triple<Int, Int, Int> = Triple(0, 0, 0) + private set + + fun onServerTick() { + val (tick, maxTick, stage) = timer + if (tick <= 0) return + timer = Triple(tick - 1, maxTick, stage) + } + fun onMessage(msg: String) { + when (msg) { + "[STATUE] Oruo the Omniscient: I am Oruo the Omniscient. I have lived many lives. I have learned all there is to know." -> timer = Triple(START_DURATION, START_DURATION, 1) + "[STATUE] Oruo the Omniscient: 2 questions left... Then you will have proven your worth to me!" -> timer = Triple(QUESTION_DURATION, QUESTION_DURATION, 2) + "[STATUE] Oruo the Omniscient: One more question!" -> timer = Triple(QUESTION_DURATION, QUESTION_DURATION, 3) + } + if (msg.startsWith("[STATUE] Oruo the Omniscient: ") && msg.endsWith("correctly!")) { if (msg.contains("answered the final question")) { onPuzzleComplete("Quiz") @@ -66,5 +84,6 @@ object QuizSolver { fun reset() { triviaOptions = MutableList(3) { TriviaAnswer(null, false) } triviaAnswers = null + timer = Triple(0, 0, 0) } } \ No newline at end of file |
cleaned up WitherDragons settings查看变更src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 1 + src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragons.kt | 6 +---- src/main/kotlin/com/odtheking/odin/features/impl/boss/WitherDragonsEnum.kt | 6 +---- src/main/kotlin/com/odtheking/odin/features/impl/boss/termsim/TermSimGUI.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 88 ++++++++++++++++++----------------------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonScan.kt | 6 +++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/render/ItemRenderer.kt | 11 +++++++-- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 12 ++++++++- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonUtils.kt | 43 ++++++++++++++++++-------------- 10 files changed, 78 insertions(+), 99 deletions(-) @@ -76,6 +76,7 @@ abstract class PacketEvent(val packet: Packet<*>) : CancellableEvent() { // idea class RoomEnterEvent(val room: DungeonRoom?) : Event class FloorEnterEvent(val floor: Floor) : Event +class ScoreUpdateEvent(val score: Int) : Event data object MapUpdateEvent : Event class CheckmarkUpdateEvent(val room: DungeonRoom, val checkmark: MapCheckmark) : Event @@ -40,15 +40,11 @@ object WitherDragons : Module( }.withDependency { dragonTimerDropDown } private val dragonBoxes by BooleanSetting("Dragon Boxes", true, desc = "Displays boxes for where M7 dragons spawn.") - - private val dragonTitleDropDown by DropdownSetting("Dragon Spawn Dropdown") - val dragonTitle by BooleanSetting("Dragon Title", true, desc = "Displays a title for spawning dragons.").withDependency { dragonTitleDropDown } - private val dragonTracers by BooleanSetting("Dragon Tracer", false, desc = "Draws a line to spawning dragons.").withDependency { dragonTitleDropDown } + private val dragonTracers by BooleanSetting("Dragon Tracer", true, desc = "Draws a line to spawning dragons.") private val dragonAlerts by DropdownSetting("Dragon Alerts Dropdown") private val sendNotification by BooleanSetting("Send Dragon Confirmation", true, desc = "Sends a confirmation message when a dragon dies.").withDependency { dragonAlerts } val sendTime by BooleanSetting("Send Dragon Time Alive", true, desc = "Sends a message when a dragon dies with the time it was alive.").withDependency { dragonAlerts } - val sendSpawning by BooleanSetting("Send Dragon Spawning", true, desc = "Sends a message when a dragon is spawning.").withDependency { dragonAlerts } val sendSpawned by BooleanSetting("Send Dragon Spawned", true, desc = "Sends a message when a dragon has spawned.").withDependency { dragonAlerts } val sendSpray by BooleanSetting("Send Ice Sprayed", true, desc = "Sends a message when a dragon has been ice sprayed.").withDependency { dragonAlerts } @@ -6,7 +6,6 @@ import com.odtheking.odin.features.impl.boss.WitherDragons.currentTick import com.odtheking.odin.features.impl.boss.WitherDragons.dragonPriorityToggle import com.odtheking.odin.features.impl.boss.WitherDragons.priorityDragon import com.odtheking.odin.features.impl.boss.WitherDragons.sendSpawned -import com.odtheking.odin.features.impl.boss.WitherDragons.sendSpawning import com.odtheking.odin.features.impl.boss.WitherDragons.sendTime import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Colors @@ -125,8 +124,6 @@ fun handleSpawnPacket(particle: ClientboundLevelParticlesPacket) { if (particle.x !in dragon.xRange || particle.z !in dragon.zRange) return@fold newSpawned to dragons - if (sendSpawning && WitherDragons.enabled) modMessage("§${dragon.colorCode}$dragon §fdragon is spawning.") - dragon.state = WitherDragonState.SPAWNING dragon.timeToSpawn = 100 dragons.add(dragon) @@ -135,8 +132,7 @@ fun handleSpawnPacket(particle: ClientboundLevelParticlesPacket) { if (dragons.isNotEmpty() && (dragons.size == 2 || spawned >= 2) && priorityDragon == null) priorityDragon = findPriority(dragons).also { dragon -> - if (WitherDragons.dragonTitle && WitherDragons.enabled) alert("§${dragon.colorCode}${dragon.name} is spawning!", true) - if (dragonPriorityToggle && WitherDragons.enabled) modMessage("${dragons.joinToString(", ") { "§${it.colorCode}${it.name}" }}§r -> §${dragon.colorCode}${dragon.name} §7is your priority dragon!") + if (dragonPriorityToggle && WitherDragons.enabled && dragons.size > 1) modMessage("${dragons.joinToString(", ") { "§${it.colorCode}${it.name}" }}§r -> §${dragon.colorCode}${dragon.name} §7is your priority dragon!") } } @@ -75,7 +75,7 @@ open class TermSimGUI( override fun slotClicked(slot: Slot, i: Int, j: Int, clickType: ContainerInput) { if (GuiEvent.SlotClick(this, i, j).postAndCatch()) return - slot.let { delaySlotClick(it, i) } + slot?.let { delaySlotClick(it, j) } } fun clickIndex(index: Int, button: Int) { @@ -5,6 +5,7 @@ import com.odtheking.odin.clickgui.settings.impl.* import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.RoomEnterEvent +import com.odtheking.odin.events.ScoreUpdateEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module @@ -34,71 +35,51 @@ object MapInfo : Module( private val printWhenScore by BooleanSetting("Print Score Time", true, desc = "Sends elapsed time in chat when 300 score is reached.") val togglePaul by SelectorSetting("Paul Settings", "Automatic", arrayListOf("Automatic", "Force Disable", "Force Enable"), desc = "Toggle Paul's settings.") - private var cachedScore = 0 - private var cachedSecretCount = 0 - private var cachedNeededSecrets = 0 - private var cachedTotalSecrets = 0 - private var cachedKnownSecrets = 0 - private var cachedMimicKilled = false - private var cachedPrinceKilled = false - private var cachedBatKilled = false - private var cachedCryptCount = 0 - private var cachedDeathCount = 0 - private val fullHud: HudElement by HUD("Full Hud", "Displays a full hud with score, secrets, crypts, and mimic info.") { if ((!DungeonUtils.inDungeons || (disableInBoss && DungeonUtils.inBoss)) && !it) return@HUD 0 to 0 - val score = cachedScore - val secretCount = cachedSecretCount - val neededSecrets = cachedNeededSecrets - val totalSecrets = cachedTotalSecrets - val mimicKilled = cachedMimicKilled - val princeKilled = cachedPrinceKilled - val batKilled = cachedBatKilled - val cryptCount = cachedCryptCount - val showRemaining = fullAddRemaining && alternate val useNeededSecrets = fullRemaining != 0 || showRemaining val scoreText = buildString { append("§7Score: ") - append(colorizeScore(score)) + append(colorizeScore(DungeonUtils.score)) } val secretText = buildString { append("§7Secrets: §b") - append(secretCount) + append(DungeonUtils.secretCount) if (showRemaining) { append("§7-§d") - append((neededSecrets - secretCount).coerceAtLeast(0)) + append((DungeonUtils.neededSecretsAmount - DungeonUtils.secretCount).coerceAtLeast(0)) } append("§7-§e") - append(if (useNeededSecrets) neededSecrets else (neededSecrets - secretCount).coerceAtLeast(0)) + append(if (useNeededSecrets) DungeonUtils.neededSecretsAmount else (DungeonUtils.neededSecretsAmount - DungeonUtils.secretCount).coerceAtLeast(0)) append("§7-§c") - append(totalSecrets) + append(DungeonUtils.totalSecrets) } val unknownSecretsText = if (unknown == 0) { buildString { append("§7Deaths: §c") - append(colorizeDeaths(cachedDeathCount)) + append(colorizeDeaths(DungeonUtils.deathCount)) } } else { buildString { append("§7Unfound: §e") - append((totalSecrets - cachedKnownSecrets).coerceAtLeast(0)) + append((DungeonUtils.totalSecrets - DungeonUtils.knownSecrets).coerceAtLeast(0)) } } val mimicText = buildString { - append("${if (mimicKilled) "§a" else "§c"}\uD83D\uDCE6") - append(" §8| ${if (princeKilled) "§a" else "§c"}\uD83E\uDD34") - append(" §8| ${if (batKilled) "§a" else "§c"}\uD83E\uDD87") + append("${if (DungeonUtils.mimicKilled) "§a" else "§c"}\uD83D\uDCE6") + append(" §8| ${if (DungeonUtils.princeKilled) "§a" else "§c"}\uD83E\uDD34") + append(" §8| ${if (DungeonUtils.batKilled) "§a" else "§c"}\uD83E\uDD87") } val cryptText = buildString { append("§7Crypts: ") - append(colorizeCrypts(cryptCount)) + append(colorizeCrypts(DungeonUtils.cryptCount.coerceAtMost(5))) } val trText = if (alternate) cryptText else scoreText @@ -135,21 +116,17 @@ object MapInfo : Module( private val compactSecrets: HudElement by HUD("Compact Secrets", "Displays a compact secrets hud with score and secrets.") { if ((!DungeonUtils.inDungeons || (disableInBoss && DungeonUtils.inBoss)) && !it) return@HUD 0 to 0 - val secretCount = cachedSecretCount - val neededSecrets = cachedNeededSecrets - val totalSecrets = cachedTotalSecrets - val secretText = buildString { append("§7Secrets: §b") - append(secretCount) + append(DungeonUtils.secretCount) if (compactAddRemaining) { append("§7-§d") - append((neededSecrets - secretCount).coerceAtLeast(0)) + append((DungeonUtils.neededSecretsAmount - DungeonUtils.secretCount).coerceAtLeast(0)) } append("§7-§e") - append(if (compactRemaining == 0 || fullAddRemaining) neededSecrets else (neededSecrets - secretCount).coerceAtLeast(0)) + append(if (compactRemaining == 0 || fullAddRemaining) DungeonUtils.neededSecretsAmount else (DungeonUtils.neededSecretsAmount - DungeonUtils.secretCount).coerceAtLeast(0)) append("§7-§c") - append(totalSecrets) + append(DungeonUtils.totalSecrets) } val width = getStringWidth(secretText) @@ -170,10 +147,10 @@ object MapInfo : Module( private val compactScore: HudElement by HUD("Compact Score", "Displays a compact score hud with score info.") { if ((!DungeonUtils.inDungeons || (disableInBoss && DungeonUtils.inBoss)) && !it) return@HUD 0 to 0 - val score = cachedScore - val mimicKilled = cachedMimicKilled - val princeKilled = cachedPrinceKilled - val batKilled = cachedBatKilled + val score = DungeonUtils.score + val mimicKilled = DungeonUtils.mimicKilled + val princeKilled = DungeonUtils.princeKilled + val batKilled = DungeonUtils.batKilled val missing = (if (mimicKilled) 0 else 2) + (if (princeKilled) 0 else 1) + (if (batKilled) 0 else 1) @@ -225,28 +202,12 @@ object MapInfo : Module( private var portalAABB: AABB? = null private var currentRoomSecrets: Pair<Int, Int>? = null private val secretRegex = Regex("(\\d+)/(\\d+) Secrets") - var shownTitle = false init { - TickTask(1) { - if (!enabled || !DungeonUtils.inDungeons) return@TickTask - cachedScore = DungeonUtils.score - cachedSecretCount = DungeonUtils.secretCount - cachedNeededSecrets = DungeonUtils.neededSecretsAmount - cachedTotalSecrets = DungeonUtils.totalSecrets - cachedKnownSecrets = DungeonUtils.knownSecrets - cachedMimicKilled = DungeonUtils.mimicKilled - cachedPrinceKilled = DungeonUtils.princeKilled - cachedBatKilled = DungeonUtils.batKilled - cachedCryptCount = DungeonUtils.cryptCount.coerceAtMost(5) - cachedDeathCount = DungeonUtils.deathCount - } - - TickTask(10) { - if (!enabled || !DungeonUtils.inDungeons || shownTitle || (!scoreTitle && !printWhenScore) || DungeonUtils.score < 300) return@TickTask + on<ScoreUpdateEvent> { + if (!DungeonUtils.inDungeons || (!scoreTitle && !printWhenScore) || score < 300) return@on if (scoreTitle) alert("§c300 Score!") - if (printWhenScore) modMessage("§b${DungeonUtils.score} §ascore reached in §6${DungeonUtils.dungeonTime} || ${DungeonUtils.floor?.name}.") - shownTitle = true + if (printWhenScore) modMessage("§b${DungeonUtils.score} §ascore reached in §6${DungeonUtils.dungeonTime} §8|| §e${DungeonUtils.floor?.name}.") } onReceive<ClientboundSystemChatPacket> { @@ -263,14 +224,13 @@ object MapInfo : Module( } on<RenderEvent.Extract> { - if (!highlightPortal || !DungeonUtils.inClear || cachedScore < 300) return@on + if (!highlightPortal || !DungeonUtils.inClear || DungeonUtils.score < 300) return@on portalAABB?.let { pos -> drawFilledBox(pos, Colors.MINECRAFT_GREEN.withAlpha(0.5f), depth = true) } } on<LevelEvent.Load> { - shownTitle = false portalAABB = null } } @@ -10,6 +10,8 @@ import com.odtheking.odin.features.impl.dungeon.map.tile.* import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.IVec2 +import com.odtheking.odin.utils.renderX +import com.odtheking.odin.utils.renderZ import com.odtheking.odin.utils.skyblock.dungeon.Floor import net.minecraft.world.entity.player.Player import java.util.concurrent.ConcurrentHashMap @@ -77,8 +79,8 @@ object DungeonScan { fun playerRenderPosition(entity: Player?, mapPos: IVec2): Pair<Float, Float> { entity?.let { - val mapX = (it.x.toFloat() + 200f) * roomGap / 32f - val mapZ = (it.z.toFloat() + 200f) * roomGap / 32f + val mapX = (it.renderX.toFloat() + 200f) * roomGap / 32f + val mapZ = (it.renderZ.toFloat() + 200f) * roomGap / 32f return mapX to mapZ } @@ -56,7 +56,7 @@ object PuzzleSolvers : Module( private val blazeLineNext by BooleanSetting("Blaze Solver Next Line", true, desc = "Shows the next line to click.").withDependency { blazeSolver && blazeDropDown } private val blazeLineAmount by NumberSetting("Blaze Solver Lines", 1, 1, 10, 1, desc = "Amount of lines to show.").withDependency { blazeSolver && blazeDropDown } private val blazeLineWidth by NumberSetting("Blaze Solver Lines Width", 2f, 0.5, 5, 0.1, desc = "Width for blaze lines.").withDependency { blazeSolver && blazeDropDown } - private val blazeStyle by SelectorSetting("Blaze Style", "Outline", arrayListOf("Filled", "Outline", "Filled Outline"), desc = "Whether or not the box should be filled.").withDependency { blazeSolver && blazeDropDown } + private val blazeStyle by SelectorSetting("Blaze Style", "Filled Outline", arrayListOf("Filled", "Outline", "Filled Outline"), desc = "Whether or not the box should be filled.").withDependency { blazeSolver && blazeDropDown } private val blazeFirstColor by ColorSetting("First Color", Colors.MINECRAFT_GREEN.withAlpha(.75f), true, desc = "Color for the first blaze.").withDependency { blazeSolver && blazeDropDown } private val blazeSecondColor by ColorSetting("Second Color", Colors.MINECRAFT_GOLD.withAlpha(.75f), true, desc = "Color for the second blaze.").withDependency { blazeSolver && blazeDropDown } private val blazeThirdColor by ColorSetting("Third Color", Colors.MINECRAFT_RED.withAlpha(.75f), true, desc = "Color for the third blaze.").withDependency { blazeSolver && blazeDropDown } @@ -83,11 +83,18 @@ class ItemStateRenderer(vertexConsumers: MultiBufferSource.BufferSource) } companion object { + private val trackingCache = object : LinkedHashMap<ItemStack, TrackingItemStackRenderState>(16, 0.75f, true) { + override fun removeEldestEntry(eldest: MutableMap.MutableEntry<ItemStack, TrackingItemStackRenderState>) = size > 10 + } + fun GuiGraphicsExtractor.drawItemStack(item: ItemStack, x: Int, y: Int) { if (item.isEmpty) return - val tracking = TrackingItemStackRenderState() - mc.itemModelResolver.updateForTopItem(tracking, item, ItemDisplayContext.GUI, mc.level, mc.player, 0) + val tracking = trackingCache.getOrPut(item) { + TrackingItemStackRenderState().also { + mc.itemModelResolver.updateForTopItem(it, item, ItemDisplayContext.GUI, mc.level, mc.player, 0) + } + } val state = State( GuiItemRenderState( @@ -44,7 +44,11 @@ object DungeonListener { init { on<TickEvent.End> { - if (DungeonUtils.inDungeons) inBoss = getBoss() + if (!DungeonUtils.inDungeons) return@on + val newInBoss = getBoss() + if (newInBoss == inBoss) return@on + inBoss = newInBoss + DungeonUtils.updateScore() } on<LevelEvent.Load> { @@ -58,6 +62,7 @@ object DungeonListener { inBoss = false floor = null paul = false + DungeonUtils.updateScore() } on<RoomEnterEvent> { @@ -69,6 +74,7 @@ object DungeonListener { updateDungeonTeammates(tabListEntries) updateDungeonStats(tabListEntries) getDungeonPuzzles(tabListEntries) + DungeonUtils.updateScore() } onReceive<ClientboundSetPlayerTeamPacket> { @@ -86,6 +92,7 @@ object DungeonListener { clearedRegex.find(text)?.groupValues?.get(1)?.toIntOrNull()?.let { if (dungeonStats.percentCleared != it && expectingBloodUpdate) dungeonStats.bloodDone = true dungeonStats.percentCleared = it + DungeonUtils.updateScore() } } @@ -116,7 +123,10 @@ object DungeonListener { "blaze done!", "blaze done", "blaze puzzle solved!" -> puzzles.find { it == Puzzle.BLAZE }.let { it?.status = PuzzleStatus.Completed } + + else -> return@on } + DungeonUtils.updateScore() } onReceive<ClientboundRemoveEntitiesPacket> { @@ -1,6 +1,7 @@ package com.odtheking.odin.utils.skyblock.dungeon import com.odtheking.odin.OdinMod.mc +import com.odtheking.odin.events.ScoreUpdateEvent import com.odtheking.odin.features.impl.dungeon.MapInfo.togglePaul import com.odtheking.odin.features.impl.dungeon.map.WorldScan import com.odtheking.odin.features.impl.dungeon.map.tile.DungeonRoom @@ -117,28 +118,34 @@ object DungeonUtils { inline val bloodDone: Boolean get() = DungeonListener.dungeonStats.bloodDone - inline val score: Int - get() { - val completed = completedRoomCount + (if (!bloodDone) 1 else 0) + (if (!inBoss) 1 else 0) - val total = if (totalRooms != 0) totalRooms else 36 + var score: Int = 0 + private set - val exploration = floor?.let { - val secretScore = if (totalSecrets > 0) { - floor(secretCount.toDouble() / (totalSecrets.toDouble() * it.requiredPercentage) * 40.0) - .toInt().coerceIn(0, 40) - } else 0 + fun updateScore() { + val completed = completedRoomCount + (if (!bloodDone) 1 else 0) + (if (!inBoss) 1 else 0) + val total = if (totalRooms != 0) totalRooms else 36 - secretScore + floor(completed.toFloat() / total * 60f).coerceIn(0f, 60f).toInt() - } ?: 0 + val exploration = floor?.let { + val secretScore = if (totalSecrets > 0) { + floor(secretCount.toDouble() / (totalSecrets.toDouble() * it.requiredPercentage) * 40.0) + .toInt().coerceIn(0, 40) + } else 0 - val skillRooms = floor(completed.toFloat() / total * 80f).coerceIn(0f, 80f).toInt() - val puzzlePenalty = (puzzleCount - puzzles.count { it.status == PuzzleStatus.Completed }) * 10 + secretScore + floor(completed.toFloat() / total * 60f).coerceIn(0f, 60f).toInt() + } ?: 0 - return exploration + (20 + skillRooms - puzzlePenalty - (deathCount * 2 - 1).coerceAtLeast(0)).coerceIn( - 20, - 100 - ) + getBonusScore + 100 - } + val skillRooms = floor(completed.toFloat() / total * 80f).coerceIn(0f, 80f).toInt() + val puzzlePenalty = (puzzleCount - puzzles.count { it.status == PuzzleStatus.Completed }) * 10 + + val newScore = exploration + (20 + skillRooms - puzzlePenalty - (deathCount * 2 - 1).coerceAtLeast(0)).coerceIn( + 20, + 100 + ) + getBonusScore + 100 + + if (newScore == score) return + score = newScore + ScoreUpdateEvent(newScore).postAndCatch() + } inline val neededSecretsAmount: Int get() = |
Add new islands Torrhus Canyon and Safari, rename Galatea to Moonglade Marsh (#137)查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/Island.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) @@ -22,7 +22,9 @@ enum class Island(val displayName: String) { Mineshaft("Mineshaft"), Rift("The Rift"), BackwaterBayou("Backwater Bayou"), - Galatea("Galatea"), + MoongladeMarsh("Moonglade Marsh"), LotusAtoll("Lotus Atoll"), + TorrhusCanyon("Torrhus Canyon"), + Safari("Safari"), Unknown("(Unknown)"); -} \ No newline at end of file +} |
Update JDK version from openjdk21 to openjdk25 (#136)查看变更jitpack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -1,2 +1,2 @@ jdk: - - openjdk21 \ No newline at end of file + - openjdk25 |
Fixed golden apple not being handled correctly in StartsWithHandler查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/StartsWithHandler.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) @@ -9,6 +9,7 @@ import net.minecraft.client.gui.screens.inventory.ContainerScreen import net.minecraft.core.component.DataComponents import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items class StartsWithHandler(private val letter: String): TerminalHandler(TerminalTypes.STARTS_WITH) { @@ -44,6 +45,6 @@ class StartsWithHandler(private val letter: String): TerminalHandler(TerminalTyp override fun renderSlot(slotIndex: Int): Pair<Color, String?> = TerminalSolver.startsWithColor to null companion object { - private val enchantOverrides = BuiltInRegistries.ITEM.filter { it.components().has(DataComponents.ENCHANTMENT_GLINT_OVERRIDE) } + private val enchantOverrides = BuiltInRegistries.ITEM.filter { it.components().has(DataComponents.ENCHANTMENT_GLINT_OVERRIDE) } + Items.GOLDEN_APPLE } } |
| Add torrhus canyon fairy souls to quiz answers (#135) * Update fairy soul amount
* Add intermediate color to secret spawn timer 查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 6 +++++- src/main/resources/assets/odin/puzzles/quizAnswers.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) @@ -88,7 +88,11 @@ object TickTimers : Module( else if (DungeonUtils.openRoomCount != 0 && !DungeonUtils.inBoss) { // Color change at < 10 because that's when items are picked up. val time = 20 - secretsCounter % 20 - val color = if (time < 10) "§a" else "§c" + val color = when { + time < 5 -> "§a" + time < 10 -> "§6" + else -> "§c" + } textDim(formatTimer(time, 20, "§7Secret:", overrideColor = color), 0, 0, Colors.MINECRAFT_DARK_RED) } else 0 to 0 @@ -10,7 +10,7 @@ "What is the status of Scarf?": ["Apprentice Necromancer"], "What is the name of the lady of the Nether?": ["Elle"], "What is the status of Bonzo?": ["New Necromancer"], - "How many total Fairy Souls are there?": ["273 Fairy Souls"], + "How many total Fairy Souls are there?": ["289 Fairy Souls"], "How many Fairy Souls are there in Backwater Bayou?": ["5 Fairy Souls"], "What is the status of Thorn?": ["Shaman Necromancer"], "How many Fairy Souls are there in Spider's Den?": ["19 Fairy Souls"], |
mixin cleanup查看变更src/main/java/com/odtheking/mixin/mixins/AvatarRendererMixin.java | 8 ++++---- src/main/java/com/odtheking/mixin/mixins/ChatScreenMixin.java | 4 ++-- src/main/java/com/odtheking/mixin/mixins/ClientPacketListenerMixin.java | 4 ++-- src/main/java/com/odtheking/mixin/mixins/ConnectionMixin.java | 4 ++-- src/main/java/com/odtheking/mixin/mixins/EntityRendererMixin.java | 2 +- src/main/java/com/odtheking/mixin/mixins/FontMixin.java | 16 ++++++++-------- src/main/java/com/odtheking/mixin/mixins/MultiPlayerGameModeMixin.java | 4 ++-- src/main/java/com/odtheking/mixin/mixins/ScreenEffectRendererMixin.java | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/ActionBarListener.kt | 2 +- 9 files changed, 23 insertions(+), 23 deletions(-) @@ -15,16 +15,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public class AvatarRendererMixin { @Inject(method = "scale(Lnet/minecraft/client/renderer/entity/state/AvatarRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;)V", at = @At("HEAD")) - private void scale(AvatarRenderState avatarRenderState, PoseStack poseStack, CallbackInfo ci) { - PlayerSize.preRenderCallbackScaleHook(avatarRenderState, poseStack); + private void scale(AvatarRenderState state, PoseStack poseStack, CallbackInfo ci) { + PlayerSize.preRenderCallbackScaleHook(state, poseStack); } @Inject( method = "extractRenderState(Lnet/minecraft/world/entity/Avatar;Lnet/minecraft/client/renderer/entity/state/AvatarRenderState;F)V", at = @At("HEAD") ) - private void extractRenderState(Avatar avatar, AvatarRenderState avatarRenderState, float f, CallbackInfo ci) { - if (!(avatar instanceof AbstractClientPlayer clientAvatarEntity)) return; + private void extractRenderState(Avatar entity, AvatarRenderState avatarRenderState, float f, CallbackInfo ci) { + if (!(entity instanceof AbstractClientPlayer clientAvatarEntity)) return; avatarRenderState.setData(PlayerSize.getGAME_PROFILE_KEY(), clientAvatarEntity.getGameProfile()); } } \ No newline at end of file @@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public class ChatScreenMixin { @Inject(method = "handleChatInput", at = @At("HEAD"), cancellable = true) - private void onSendMessage(String message, boolean addToHistory, CallbackInfo ci) { - if (new MessageSentEvent(message).postAndCatch()) ci.cancel(); + private void onSendMessage(String msg, boolean addToRecent, CallbackInfo ci) { + if (new MessageSentEvent(msg).postAndCatch()) ci.cancel(); } } @@ -26,9 +26,9 @@ public class ClientPacketListenerMixin { } @Inject(method = "handleContainerSetSlot", at = @At("TAIL")) - private void onSetSlot(ClientboundContainerSetSlotPacket clientboundContainerSetSlotPacket, CallbackInfo ci) { + private void onSetSlot(ClientboundContainerSetSlotPacket packet, CallbackInfo ci) { if (getMc().screen instanceof AbstractContainerScreen<?> container) - new GuiEvent.SlotUpdate(getMc().screen, clientboundContainerSetSlotPacket, container.getMenu()).postAndCatch(); + new GuiEvent.SlotUpdate(getMc().screen, packet, container.getMenu()).postAndCatch(); } @WrapOperation(method = "handleBundlePacket", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/Packet;handle(Lnet/minecraft/network/PacketListener;)V")) @@ -16,13 +16,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public abstract class ConnectionMixin { @Inject(method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/protocol/Packet;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/Connection;genericsFtw(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;)V"), cancellable = true) - private void channelRead0(ChannelHandlerContext channelHandlerContext, Packet<?> packet, CallbackInfo ci) { + private void channelRead0(ChannelHandlerContext ctx, Packet<?> packet, CallbackInfo ci) { if (packet instanceof ClientboundPingPacket pingPacket && pingPacket.getId() != 0) TickEvent.Server.INSTANCE.postAndCatch(); if (new PacketEvent.Receive(packet).postAndCatch()) ci.cancel(); } @Inject(method = "sendPacket", at = @At("HEAD"), cancellable = true) - private void sendImmediately(Packet<?> packet, ChannelFutureListener channelFutureListener, boolean flush, CallbackInfo ci) { + private void sendImmediately(Packet<?> packet, ChannelFutureListener listener, boolean flush, CallbackInfo ci) { if (new PacketEvent.Send(packet).postAndCatch()) ci.cancel(); } } \ No newline at end of file @@ -16,7 +16,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; public abstract class EntityRendererMixin<T extends Entity> { @Inject(method = "shouldRender", at = @At("HEAD"), cancellable = true) - private void onRender(T entity, Frustum frustum, double d, double e, double f, CallbackInfoReturnable<Boolean> cir) { + private void onRender(T entity, Frustum culler, double camX, double camY, double camZ, CallbackInfoReturnable<Boolean> cir) { if (!HidePlayers.shouldRenderPlayer(entity)) cir.setReturnValue(false); if (RenderOptimizer.hideEntityDeathAnimation() && entity instanceof LivingEntity livingEntity && livingEntity.isDeadOrDying()) @@ -12,25 +12,25 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable; @Mixin(Font.class) public class FontMixin { - @ModifyVariable(method = "prepareText(Ljava/lang/String;FFIZI)Lnet/minecraft/client/gui/Font$PreparedText;", at = @At("HEAD"), argsOnly = true) + @ModifyVariable(method = "prepareText(Ljava/lang/String;FFIZI)Lnet/minecraft/client/gui/Font$PreparedText;", at = @At("HEAD"), argsOnly = true, name = "text") private String onPrepareTextString(String text) { if (!CustomNameReplacer.isEnabled()) return text; return CustomNameReplacer.replaceStringIfNeeded(text); } - @ModifyVariable(method = "prepareText(Lnet/minecraft/util/FormattedCharSequence;FFIZZI)Lnet/minecraft/client/gui/Font$PreparedText;", at = @At("HEAD"), argsOnly = true) + @ModifyVariable(method = "prepareText(Lnet/minecraft/util/FormattedCharSequence;FFIZZI)Lnet/minecraft/client/gui/Font$PreparedText;", at = @At("HEAD"), argsOnly = true, name = "text") private FormattedCharSequence onPrepareTextSequence(FormattedCharSequence text) { if (!CustomNameReplacer.isEnabled()) return text; return CustomNameReplacer.replaceSequenceIfNeeded(text); } - @ModifyVariable(method = "width(Ljava/lang/String;)I", at = @At("HEAD"), argsOnly = true) - private String onWidthString(String text) { - if (!CustomNameReplacer.isEnabled()) return text; - return CustomNameReplacer.replaceStringIfNeeded(text); + @ModifyVariable(method = "width(Ljava/lang/String;)I", at = @At("HEAD"), argsOnly = true, name = "str") + private String onWidthString(String str) { + if (!CustomNameReplacer.isEnabled()) return str; + return CustomNameReplacer.replaceStringIfNeeded(str); } - @ModifyVariable(method = "width(Lnet/minecraft/network/chat/FormattedText;)I", at = @At("HEAD"), argsOnly = true) + @ModifyVariable(method = "width(Lnet/minecraft/network/chat/FormattedText;)I", at = @At("HEAD"), argsOnly = true, name = "text") private FormattedText onWidthFormattedText(FormattedText text) { if (!CustomNameReplacer.isEnabled()) return text; if (text instanceof Component component) { @@ -40,7 +40,7 @@ public class FontMixin { return text; } - @ModifyVariable(method = "width(Lnet/minecraft/util/FormattedCharSequence;)I", at = @At("HEAD"), argsOnly = true) + @ModifyVariable(method = "width(Lnet/minecraft/util/FormattedCharSequence;)I", at = @At("HEAD"), argsOnly = true, name = "text") private FormattedCharSequence onWidthSequence(FormattedCharSequence text) { if (!CustomNameReplacer.isEnabled()) return text; return CustomNameReplacer.replaceSequenceIfNeeded(text); @@ -13,9 +13,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(MultiPlayerGameMode.class) public class MultiPlayerGameModeMixin { + @Inject(method = "useItemOn(Lnet/minecraft/client/player/LocalPlayer;Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/phys/BlockHitResult;)Lnet/minecraft/world/InteractionResult;", at = @At("RETURN")) void useItemOnReturn(LocalPlayer player, InteractionHand hand, BlockHitResult blockHit, CallbackInfoReturnable<InteractionResult> cir) { - var returnValue = cir.getReturnValue(); - new UseItemOnPostEvent(hand, blockHit, returnValue).postAndCatch(); + new UseItemOnPostEvent(hand, blockHit, cir.getReturnValue()).postAndCatch(); } } @@ -14,7 +14,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public class ScreenEffectRendererMixin { @Inject(method = "renderFire", at = @At("HEAD"), cancellable = true) - private static void onRenderFireOverlay(PoseStack poseStack, MultiBufferSource multiBufferSource, TextureAtlasSprite textureAtlasSprite, CallbackInfo ci) { + private static void onRenderFireOverlay(PoseStack poseStack, MultiBufferSource bufferSource, TextureAtlasSprite sprite, CallbackInfo ci) { if (RenderOptimizer.shouldDisableFireOverlay()) ci.cancel(); } } @@ -68,7 +68,7 @@ object ActionBarListener { val vitalityMatch = VITALITY_REGEX.find(msg) isVitalityShown = vitalityMatch != null - vitalityMatch?.destructured?.let { (cVitality, mVitality, _) -> + vitalityMatch?.destructured?.let { (cVitality, mVitality) -> currentVitality = cVitality.replace(",", "").toDoubleOrNull()?.toInt() ?: currentVitality maxVitality = mVitality.replace(",", "").toDoubleOrNull()?.toInt() ?: maxVitality } |
Fixed vitality action bar schitzo查看变更src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PlayerDisplay.kt | 5 ++++- src/main/kotlin/com/odtheking/odin/utils/skyblock/ActionBarListener.kt | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) @@ -33,6 +33,7 @@ object PlayerDisplay : Module( private val hideMana by BooleanSetting("Hide Mana", true, desc = "Hides the mana bar.").withDependency { hideActionBar } private val hideOverflow by BooleanSetting("Hide Overflow Mana", true, desc = "Hides the overflow mana bar.").withDependency { hideActionBar } private val hideDefense by BooleanSetting("Hide Defense", true, desc = "Hides the defense bar.").withDependency { hideActionBar } + private val hideVitality by BooleanSetting("Hide Vitality", true, desc = "Hides the vitality bar.").withDependency { hideActionBar } private val overflow by DropdownSetting("Overflow Mana") private val separateOverflow by BooleanSetting("Separate Overflow Mana", true, desc = "Separates the overflow mana from the mana bar.").withDependency { overflow } private val hideZeroSF by BooleanSetting("Hide 0 Overflow", true, desc = "Hides the overflow mana when it's 0.").withDependency { overflow && separateOverflow } @@ -125,7 +126,8 @@ object PlayerDisplay : Module( private val HEALTH_REGEX = Regex("[\\d|,]+/[\\d|,]+\\uE010") private val MANA_REGEX = Regex("[\\d|,]+/[\\d|,]+\\uE003( Mana)?") private val OVERFLOW_MANA_REGEX = Regex("§?[\\d|,]+\\uE017") - private val DEFENSE_REGEX = Regex("[\\d|,]+§a\\uE008 Defense") + private val DEFENSE_REGEX = Regex("[\\d|,]+(§.)?\\uE008( Defense)?") + private val VITALITY_REGEX = Regex("[\\d.,]+/[\\d.,]+(§.)?\\uE028") @JvmStatic fun modifyText(text: Component): Component { @@ -135,6 +137,7 @@ object PlayerDisplay : Module( toReturn = if (hideMana) toReturn.replace(MANA_REGEX, "") else toReturn toReturn = if (hideOverflow) toReturn.replace(OVERFLOW_MANA_REGEX, "") else toReturn toReturn = if (hideDefense) toReturn.replace(DEFENSE_REGEX, "") else toReturn + toReturn = if (hideVitality) toReturn.replace(VITALITY_REGEX, "") else toReturn return Component.literal(toReturn.trim()) } @@ -13,8 +13,8 @@ object ActionBarListener { private val HEALTH_REGEX = Regex("([\\d|,]+)/([\\d|,]+)\\uE010") private val MANA_REGEX = Regex("([\\d|,]+)/([\\d|,]+)\\uE003") private val OVERFLOW_MANA_REGEX = Regex("([\\d|,]+)\\uE017") - private val DEFENSE_REGEX = Regex("([\\d|,]+)\\uE008 Defense") - private val VITALITY_REGEX = Regex("([\\d.,]+)/([\\d.,]+)♨( Vitality)?") + private val DEFENSE_REGEX = Regex("([\\d|,]+)\\uE008( Defense)?") + private val VITALITY_REGEX = Regex("([\\d.,]+)/([\\d.,]+)\\uE028") var currentHealth: Int = 0 private set |
Bump version查看变更gradle.properties | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) @@ -10,7 +10,7 @@ loom_version=1.16-SNAPSHOT mod_id=odin mod_name=Odin mod_description=A Hypixel Skyblock QoL mod -mod_version=0.2.3 +mod_version=0.3.0 maven_group=com.odtheking archives_base_name=odin-fabric # Mod Dependencies @@ -179,7 +179,7 @@ private fun GuiGraphicsExtractor.renderPlayers() { val (selfX, selfZ) = DungeonScan.playerRenderPosition(mc.player, IVec2(0, 0)) pose().translate(selfX, selfZ) pose().rotate(Math.toRadians(180.0 + (mc.player?.yRot ?: 0f)).toFloat()) - DungeonUtils.currentDungeonPlayer + if (DungeonMap.playerHead) { mc.player?.skin?.let { skin -> fill(-5, -5, 5, 5, DungeonUtils.currentDungeonPlayer.clazz.color.rgba) |
fix clickgui chevron orientation (#134)查看变更src/main/kotlin/com/odtheking/odin/clickgui/settings/impl/DropdownSetting.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) @@ -38,8 +38,8 @@ class DropdownSetting( val offset = (imageSize - 24f) / 2f NVGRenderer.push() - NVGRenderer.translate(x + width - 18f, y + height / 2f - 4f) - NVGRenderer.rotate(toggleAnimation.get(0f, Math.PI.toFloat() / 2f, enabled)) + NVGRenderer.translate(x + width - 22f, y + height / 2f) + NVGRenderer.rotate(toggleAnimation.get(Math.PI.toFloat() / 2f, 0f, enabled)) NVGRenderer.translate(-(12f + offset), -(12f + offset)) NVGRenderer.image(ClickGUI.chevronImage, 0f, 0f, imageSize, imageSize) NVGRenderer.pop() |
| Added Account for Server Lag and Ignore Melody to First Click Protection (now all in a dropdown together) (#112) * Added Account for Server Lag and Ignore Melody to First Click Protection (now all in a dropdown together)
* shorten name + slight fix
old one was too long
* slight cleanup
* maybe git will actually respect me for once
i hate git
* slight cleanup part 2 (im gonna go insane at some point)
* default to off
* rebase onto main I hope + add toggle for notifying when firstclickprot blocks a click + fix notify not working on custom term gui
* disable servertickprot in singleplayer
* small fix to servertickprot
* option to disable fc for termsim + add ticks opened to term debug
* Removed pointless settings
---------
Co-authored-by: Snowpls1 <snowpls1@github.com>
Co-authored-by: odtheking <odtheking1@gmail.com> 查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSimulator.kt | 2 ++ src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 20 ++++++++++++-------- src/main/kotlin/com/odtheking/odin/features/impl/boss/termGUI/CustomTermGui.kt | 8 ++++---- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/TerminalHandler.kt | 11 ++++++++++- 4 files changed, 28 insertions(+), 13 deletions(-) @@ -1,5 +1,6 @@ package com.odtheking.odin.features.impl.boss +import com.odtheking.odin.clickgui.settings.impl.BooleanSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.features.Module import com.odtheking.odin.features.impl.boss.termsim.* @@ -12,6 +13,7 @@ object TerminalSimulator : Module( description = "Simulates a floor 7 terminal from phase 3." ) { private val ping by NumberSetting("Ping", 0, 0, 500, 50, desc = "Ping of the terminal.") + val disableFirstClickProtection by BooleanSetting("First Click Protection", false, desc = "") val termSimPBs = PersonalBest(this, "TermsimPBs") @@ -4,6 +4,7 @@ import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.* import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.TerminalEvent +import com.odtheking.odin.events.TickEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color.Companion.darker @@ -27,7 +28,6 @@ object TerminalSolver : Module( val customTermSize by NumberSetting("Term Size", 2f, 1f, 3f, 0.1f, desc = "The size of the custom terminal GUI.").withDependency { renderType == 2 } val roundness by NumberSetting("Roundness", 5, 0f, 15f, 1f, desc = "The roundness of the custom terminal gui.").withDependency { renderType == 2 } val gap by NumberSetting("Slot gap", 2, 0, 8, 1, desc = "The gap between the slots in the custom terminal gui.").withDependency { renderType == 2 } - private val solverSettings by DropdownSetting("Solver Functionality") private val cancelToolTip by BooleanSetting("Stop Tooltips", true, desc = "Stops rendering tooltips in terminals.").withDependency { (renderType == 0 || renderType == 1) && solverSettings } private val middleClickGUI by BooleanSetting("Middle Click GUI", true, desc = "Replaces right click with middle click in terminals.").withDependency { (renderType == 0 || renderType == 1) && solverSettings } @@ -35,11 +35,13 @@ object TerminalSolver : Module( private val cancelMelodySolver by BooleanSetting("Stop Melody Solver", false, desc = "Stops rendering the melody solver.").withDependency { solverSettings } val melodyTermSize by NumberSetting("Melody Size", 1.5f, 1f, 3f, 0.1f, desc = "The size of the melody terminal GUI.").withDependency { !cancelMelodySolver && solverSettings && renderType == 2 } val showNumbers by BooleanSetting("Show Numbers", true, desc = "Shows numbers in the order terminal.").withDependency { solverSettings } - val firstClickProt by NumberSetting("First Click Protection", 500, 350, 800, 10, unit = "ms", desc = "The amount of time after opening a terminal where clicks are blocked to prevent bans (recommended value is 500 minus your ping).").withDependency { solverSettings } + private val firstClickProtSettings by DropdownSetting("First Click Protect Dropdown") + val firstClickProt by NumberSetting("First Click Protection", 500, 350, 800, 10, unit = "ms", desc = "The amount of time after opening a terminal where clicks are blocked to prevent bans (recommended value is 500 minus your ping).").withDependency { firstClickProtSettings } + val shouldFirstClickProtWithTicks by BooleanSetting("Account For Server Lag", false, desc = "Prevents bans from clicking when the server lags after opening the terminal (disabled in singleplayer").withDependency { firstClickProtSettings } + val firstClickProtTicks by NumberSetting("Lag Protection Ticks", 8, 7, 16, unit = "ticks", desc = "Each tick = 50ms (recommended value is 8)").withDependency { shouldFirstClickProtWithTicks && firstClickProtSettings } val hideClicked by BooleanSetting("Hide Clicked", false, desc = "Visually hides your first click before a gui updates instantly to improve perceived response time. Does not affect actual click time.").withDependency { solverSettings } val terminalReloadThreshold by NumberSetting("Resolve timeout", 600, 300, 1000, 10, unit = "ms", desc = "The amount of time before the terminal reloads after a click wasn't registered while using hide clicked.").withDependency { hideClicked && solverSettings } private val debug by BooleanSetting("Debug", false, desc = "Shows debug terminals.").withDependency { solverSettings } - private val showColors by DropdownSetting("Color Settings") val backgroundColor by ColorSetting("Background", Colors.gray26, true, desc = "Background color of the terminal solver.").withDependency { showColors } @@ -67,15 +69,16 @@ object TerminalSolver : Module( private val renderMelody get() = !(cancelMelodySolver && TerminalUtils.currentTerm?.type == TerminalTypes.MELODY) init { + on<TickEvent.Server> { + TerminalUtils.currentTerm?.ticksOpened++ + } + on<GuiEvent.SlotClick> { val term = TerminalUtils.currentTerm ?: return@on if (blockIncorrectClicks && !term.canClick(slotId, button)) return@on cancel() - if (term.shouldProtect()) { - modMessage("§cBlocked first click ${System.currentTimeMillis() - term.timeOpened}ms after opening.") - return@on cancel() - } + if (term.shouldProtect()) return@on cancel() if (middleClickGUI) { term.click(slotId, if (button == 0) GLFW.GLFW_MOUSE_BUTTON_3 else button, hideClicked && !term.isClicked) @@ -128,6 +131,7 @@ object TerminalSolver : Module( "§7Window Name: §f${mc.screen?.title?.string}", "§7Container ID: §f${menu.containerId}", "§7Time Open: §f${System.currentTimeMillis() - term.timeOpened}ms", + "§7Ticks Open: §f${term.ticksOpened}", "§7Is Clicked: §f${term.isClicked}", "§7Window Count: §f${term.windowCount}", "§7Solution: §f${term.solution.joinToString(", ")}", @@ -151,4 +155,4 @@ object TerminalSolver : Module( pose().popMatrix() } } -} \ No newline at end of file +} @@ -8,6 +8,7 @@ import com.odtheking.odin.features.impl.boss.TerminalSolver.hideClicked import com.odtheking.odin.features.impl.boss.TerminalSolver.renderDebug import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.render.roundedFill import com.odtheking.odin.utils.render.text import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils @@ -108,10 +109,9 @@ abstract class TermGui { TerminalUtils.currentTerm?.let { term -> val screen = mc.screen ?: return@let val btn = if (button == 0) GLFW.GLFW_MOUSE_BUTTON_3 else button - if (System.currentTimeMillis() - term.timeOpened >= TerminalSolver.firstClickProt && - !GuiEvent.CustomTermGuiClick(screen, slotIndex, btn).postAndCatch() && - term.canClick(slotIndex, btn) - ) term.click(slotIndex, btn, hideClicked && !term.isClicked) + if (term.shouldProtect()) return@let + if (!GuiEvent.CustomTermGuiClick(screen, slotIndex, btn).postAndCatch() && term.canClick(slotIndex, btn)) + term.click(slotIndex, btn, hideClicked && !term.isClicked) } } @@ -5,10 +5,15 @@ import com.google.common.primitives.SignedBytes import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.PacketEvent +import com.odtheking.odin.features.impl.boss.TerminalSimulator import com.odtheking.odin.features.impl.boss.TerminalSolver.firstClickProt +import com.odtheking.odin.features.impl.boss.TerminalSolver.firstClickProtTicks +import com.odtheking.odin.features.impl.boss.TerminalSolver.shouldFirstClickProtWithTicks import com.odtheking.odin.features.impl.boss.termsim.TermSimGUI import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.clickSlot +import com.odtheking.odin.utils.skyblock.Island +import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import net.minecraft.client.gui.screens.inventory.ContainerScreen @@ -25,6 +30,7 @@ abstract class TerminalHandler(val type: TerminalTypes) { val timeOpened = System.currentTimeMillis() var isClicked = false var windowCount = 0 + var ticksOpened = -1 open fun updateSlot(event: GuiEvent.SlotUpdate) { if (event.packet.slot !in 0 until type.windowSize) return @@ -77,5 +83,8 @@ abstract class TerminalHandler(val type: TerminalTypes) { open fun canClick(slotIndex: Int, button: Int): Boolean = slotIndex in solution - fun shouldProtect(): Boolean = System.currentTimeMillis() - timeOpened < firstClickProt + fun shouldProtect(): Boolean = + !(TerminalSimulator.disableFirstClickProtection && mc.screen is TermSimGUI) + && (System.currentTimeMillis() - timeOpened < firstClickProt || + (!LocationUtils.isCurrentArea(Island.SinglePlayer) && shouldFirstClickProtWithTicks && ticksOpened < firstClickProtTicks)) } \ No newline at end of file |
| Detailed TPS statistics (#124) * Detailed TPS statistics
* Slightly cleaner
---------
Co-authored-by: odtheking <odtheking1@gmail.com> 查看变更src/main/kotlin/com/odtheking/odin/commands/MainCommand.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/ChatCommands.kt | 4 ++-- src/main/kotlin/com/odtheking/odin/utils/ServerUtils.kt | 25 +++++++++++++++++++++++-- 3 files changed, 27 insertions(+), 6 deletions(-) @@ -24,7 +24,7 @@ val mainCommand = Commodore("odin", "od") { } literal("tps").runs { - modMessage("§aTPS: §f${ServerUtils.averageTps}") + modMessage(ServerUtils.getTpsString()) } literal("ping").runs { @@ -119,4 +119,4 @@ private enum class KuudraTier(private val test: String) { T1("normal"), T2("hot"), T3("burning"), T4("fiery"), T5("infernal"); fun instance() = "kuudra_${test}" -} \ No newline at end of file +} @@ -135,7 +135,7 @@ object ChatCommands : Module( "dice" -> if (dice) channelMessage((1..6).random(), name, channel) "racism" -> if (racism) channelMessage("$name is ${Random.nextInt(1, 101)}% racist. Racism is not allowed!", name, channel) "ping" -> if (ping) channelMessage("Current Ping: ${ServerUtils.currentPing}ms", name, channel) - "tps" -> if (tps) channelMessage("Current TPS: ${ServerUtils.averageTps.toFixed(1)}", name, channel) + "tps" -> if (tps) channelMessage(ServerUtils.getTpsString(), name, channel) "fps" -> if (fps) channelMessage("Current FPS: ${mc.fps}", name, channel) "time" -> if (time) channelMessage("Current Time: ${ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z"))}", name, channel) "location" -> if (location) channelMessage("Current Location: ${LocationUtils.currentArea.displayName}", name, channel) @@ -265,4 +265,4 @@ object ChatCommands : Module( private enum class ChatChannel { PARTY, GUILD, PRIVATE } -} \ No newline at end of file +} @@ -5,6 +5,7 @@ import com.odtheking.odin.events.core.onReceive import net.minecraft.network.protocol.game.ClientboundSetTimePacket import net.minecraft.network.protocol.ping.ClientboundPongResponsePacket import net.minecraft.util.Util +import java.util.ArrayDeque import kotlin.math.min object ServerUtils { @@ -18,10 +19,30 @@ object ServerUtils { var averagePing: Int = 0 private set + private const val TPS_HISTORY = 20 + + private val tpsLog = FloatArray(TPS_HISTORY) + private var tpsIndex = 0 + private var tpsCount = 0 + + private fun addTpsSample(tps: Float) { + tpsLog[tpsIndex] = tps + tpsIndex = (tpsIndex + 1) % TPS_HISTORY + if (tpsCount < TPS_HISTORY) tpsCount++ + } + + fun getTpsString(): String { + if (tpsLog.isEmpty()) return "Current: ${averageTps.toFixed(1)}" + + return "Current: ${averageTps.toFixed(1)} (max/min/avg) ${tpsLog.max().toFixed(1)}/${tpsLog.min().toFixed(1)}/${tpsLog.average().toFloat().toFixed(1)}" + } + init { onReceive<ClientboundSetTimePacket> { - if (prevTime != 0L) + if (prevTime != 0L) { averageTps = (20000f / (System.currentTimeMillis() - prevTime + 1)).coerceIn(0f, 20f) + addTpsSample(averageTps) + } prevTime = System.currentTimeMillis() } @@ -46,4 +67,4 @@ object ServerUtils { averagePing = (total / sampleSize).toInt() } } -} \ No newline at end of file +} |
| fixed waterboard solver breaking when shift clicking a lever (#131) * fixed waterboard solver breaking when shift clicking a lever
* fixed secret spawn timer showing in boss & changed color to signify pickup
---------
Co-authored-by: CheaterV <kurdokoleno@proton.me> 查看变更src/main/java/com/odtheking/mixin/mixins/MultiPlayerGameModeMixin.java | 21 +++++++++++++++++++++ src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 4 ++++ src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 14 +++++++++----- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 6 +++++- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/WaterSolver.kt | 10 +++++++--- src/main/resources/odin.mixins.json | 1 + 6 files changed, 47 insertions(+), 9 deletions(-) @@ -0,0 +1,21 @@ +package com.odtheking.mixin.mixins; + +import com.odtheking.odin.events.UseItemOnPostEvent; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.phys.BlockHitResult; +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.CallbackInfoReturnable; + +@Mixin(MultiPlayerGameMode.class) +public class MultiPlayerGameModeMixin { + @Inject(method = "useItemOn(Lnet/minecraft/client/player/LocalPlayer;Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/phys/BlockHitResult;)Lnet/minecraft/world/InteractionResult;", at = @At("RETURN")) + void useItemOnReturn(LocalPlayer player, InteractionHand hand, BlockHitResult blockHit, CallbackInfoReturnable<InteractionResult> cir) { + var returnValue = cir.getReturnValue(); + new UseItemOnPostEvent(hand, blockHit, returnValue).postAndCatch(); + } +} @@ -16,9 +16,12 @@ import net.minecraft.network.chat.Component import net.minecraft.network.protocol.Packet import net.minecraft.network.protocol.game.ClientboundSoundPacket import net.minecraft.world.BossEvent +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResult import net.minecraft.world.entity.Entity import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.BlockHitResult import net.minecraft.world.phys.Vec3 class InputEvent(val key: InputConstants.Key) : CancellableEvent() // better mixin is prob ideal no need for cancellable @@ -27,6 +30,7 @@ class BlockUpdateEvent(val pos: BlockPos, val old: BlockState, val updated: Bloc class BlockInteractEvent(val pos: BlockPos) : CancellableEvent() class EntityInteractEvent(val pos: Vec3, val entity: Entity) : CancellableEvent() +class UseItemOnPostEvent(val hand: InteractionHand, val hitResult: BlockHitResult, val interactionResult: InteractionResult) : Event class ChatPacketEvent(val value: String, val component: Component) : Event // mixin instead of packet (still needs to run before vanilla processing for hideMessage() class MessageSentEvent(val message: String) : CancellableEvent() @@ -12,7 +12,6 @@ import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.MORT_REGEX -import com.odtheking.odin.utils.skyblock.SplitsManager import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.toFixed import net.minecraft.network.protocol.game.ClientboundSetTimePacket @@ -85,8 +84,13 @@ object TickTimers : Module( private var secretsCounter = 0 private val secretsHud by HUD("Secrets Hud", "Displays a timer for secret spawn ticks.") { - if (it) textDim(formatTimer(15, 20, "§7Secret:"), 0, 0, Colors.MINECRAFT_DARK_RED) - else if (DungeonUtils.openRoomCount != 0) textDim(formatTimer(20 - secretsCounter % 20, 20, "§7Secret:"), 0, 0, Colors.MINECRAFT_DARK_RED) + if (it) textDim(formatTimer(15, 20, "§7Secret:", overrideColor = "§c"), 0, 0, Colors.MINECRAFT_DARK_RED) + else if (DungeonUtils.openRoomCount != 0 && !DungeonUtils.inBoss) { + // Color change at < 10 because that's when items are picked up. + val time = 20 - secretsCounter % 20 + val color = if (time < 10) "§a" else "§c" + textDim(formatTimer(time, 20, "§7Secret:", overrideColor = color), 0, 0, Colors.MINECRAFT_DARK_RED) + } else 0 to 0 } @@ -151,8 +155,8 @@ object TickTimers : Module( } } - private fun formatTimer(time: Int, max: Int, prefix: String): String { - val color = when { + private fun formatTimer(time: Int, max: Int, prefix: String, overrideColor: String? = null): String { + val color = overrideColor ?: when { time.toFloat() >= max * 0.66 -> "§a" time.toFloat() >= max * 0.33 -> "§6" else -> "§c" @@ -170,10 +170,14 @@ object PuzzleSolvers : Module( onSend<ServerboundUseItemOnPacket> { if (!DungeonUtils.inClear || this.hand == InteractionHand.OFF_HAND) return@onSend - if (waterSolver) WaterSolver.waterInteract(this) if (boulderSolver) BoulderSolver.playerInteract(this) } + on<UseItemOnPostEvent> { + if (!DungeonUtils.inClear || this.hand == InteractionHand.OFF_HAND) return@on + if (waterSolver) WaterSolver.waterInteract(this) + } + on<RenderEvent.Extract> { if (!DungeonUtils.inClear) return@on if (iceFillSolver) IceFillSolver.onRenderWorld(this, iceFillColor) @@ -2,6 +2,7 @@ package com.odtheking.odin.features.impl.dungeon.puzzlesolvers import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.RenderEvent +import com.odtheking.odin.events.UseItemOnPostEvent import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.JsonResourceLoader import com.odtheking.odin.utils.modMessage @@ -11,7 +12,7 @@ import com.odtheking.odin.utils.render.drawTracer import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.toFixed import net.minecraft.core.BlockPos -import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket +import net.minecraft.world.InteractionResult import net.minecraft.world.level.block.Blocks import net.minecraft.world.phys.Vec3 @@ -97,8 +98,11 @@ object WaterSolver { } } - fun waterInteract(event: ServerboundUseItemOnPacket) { - if (solutions.isEmpty()) return + fun waterInteract(event: UseItemOnPostEvent) { + // Interaction result different from SUCCESS either doesn't send the click packet to the server or + // doesn't consume the action. Not consuming the action leads to UseItemOn being called with the OFF_HAND + // which Hypixel treats as a regular click, causing a double lever flick i.e. the gate doesn't open. + if (solutions.isEmpty() || event.interactionResult != InteractionResult.SUCCESS) return LeverBlock.entries.find { it.leverPos == event.hitResult.blockPos }?.let { if (it == LeverBlock.WATER && openedWaterTicks == -1) openedWaterTicks = tickCounter it.i++ @@ -22,6 +22,7 @@ "mixins.LocalPlayerMixin", "mixins.MinecraftMixin", "mixins.MouseHandlerMixin", + "mixins.MultiPlayerGameModeMixin", "mixins.ScreenEffectRendererMixin", "mixins.ScreenMixin" ], |
Added vitality support to PlayerDisplay查看变更src/main/kotlin/com/odtheking/odin/OdinMod.kt | 4 +---
src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PlayerDisplay.kt | 33 ++++++++++++++++++++++-----------
src/main/kotlin/com/odtheking/odin/utils/skyblock/{SkyblockPlayer.kt => ActionBarListener.kt} | 35 ++++++++++++++++++++++-------------
3 files changed, 45 insertions(+), 27 deletions(-)@@ -9,7 +9,6 @@ import com.odtheking.odin.features.impl.render.Shenanigans import com.odtheking.odin.utils.IrisCompatability import com.odtheking.odin.utils.ServerUtils import com.odtheking.odin.utils.handlers.TickTasks -import com.odtheking.odin.utils.network.WebUtils.postData import com.odtheking.odin.utils.render.ItemStateRenderer import com.odtheking.odin.utils.render.RenderBatchManager import com.odtheking.odin.utils.render.RoundRectPIPRenderer @@ -21,7 +20,6 @@ import com.odtheking.odin.utils.ui.rendering.NVGPIPRenderer import com.odtheking.odin.utils.ui.widget.CustomGUIImpl import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.launch import net.fabricmc.api.ClientModInitializer import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback import net.fabricmc.fabric.api.client.rendering.v1.PictureInPictureRendererRegistry @@ -71,7 +69,7 @@ object OdinMod : ClientModInitializer { listOf( this, LocationUtils, TickTasks, KuudraUtils, - SkyblockPlayer, ServerUtils, EventDispatcher, + ActionBarListener, ServerUtils, EventDispatcher, DungeonListener, PartyUtils, TerminalUtils, DungeonUtils, SplitsManager, DungeonScan, IrisCompatability, RenderBatchManager, @@ -8,7 +8,7 @@ import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.LocationUtils -import com.odtheking.odin.utils.skyblock.SkyblockPlayer +import com.odtheking.odin.utils.skyblock.ActionBarListener import net.minecraft.network.chat.Component import kotlin.math.abs @@ -43,7 +43,7 @@ object PlayerDisplay : Module( val text = when { example -> 3000 to 4000 !LocationUtils.isInSkyblock -> return@HUD 0 to 0 - SkyblockPlayer.currentHealth != 0 && SkyblockPlayer.maxHealth != 0 -> SkyblockPlayer.currentHealth to SkyblockPlayer.maxHealth + ActionBarListener.currentHealth != 0 && ActionBarListener.maxHealth != 0 -> ActionBarListener.currentHealth to ActionBarListener.maxHealth else -> return@HUD 0 to 0 } return@HUD textDim(generateText(text.first, text.second, "❤"), 0, 0, healthColor) @@ -52,13 +52,13 @@ object PlayerDisplay : Module( private val manaHud by HUD("Mana HUD", "Displays the player's mana.") { example -> val text = when { - example -> generateText(2000, 20000, "✎") + (if (!separateOverflow) " ${generateText(SkyblockPlayer.overflowMana, "ʬ", hideZeroSF)}" else "") + example -> generateText(2000, 20000, "✎") + (if (!separateOverflow) " ${generateText(ActionBarListener.overflowMana, "ʬ", hideZeroSF)}" else "") !LocationUtils.isInSkyblock -> return@HUD 0 to 0 - SkyblockPlayer.maxMana != 0 -> when { - SkyblockPlayer.currentMana == 0 && separateOverflow -> return@HUD 0 to 0 - else -> generateText(SkyblockPlayer.currentMana, SkyblockPlayer.maxMana, "✎") + - (if (!separateOverflow && overflowManaHud.enabled) " ${generateText(SkyblockPlayer.overflowMana, "ʬ", hideZeroSF)}" else "") + ActionBarListener.maxMana != 0 -> when { + ActionBarListener.currentMana == 0 && separateOverflow -> return@HUD 0 to 0 + else -> generateText(ActionBarListener.currentMana, ActionBarListener.maxMana, "✎") + + (if (!separateOverflow && overflowManaHud.enabled) " ${generateText(ActionBarListener.overflowMana, "ʬ", hideZeroSF)}" else "") } else -> return@HUD 0 to 0 @@ -71,7 +71,7 @@ object PlayerDisplay : Module( val text = when { example -> 333 !LocationUtils.isInSkyblock -> return@HUD 0 to 0 - separateOverflow -> SkyblockPlayer.overflowMana + separateOverflow -> ActionBarListener.overflowMana else -> return@HUD 0 to 0 } return@HUD textDim(generateText(text, "ʬ", hideZeroSF), 0, 0, overflowManaColor) @@ -82,18 +82,29 @@ object PlayerDisplay : Module( val text = when { example -> 1000 !LocationUtils.isInSkyblock -> return@HUD 0 to 0 - SkyblockPlayer.currentDefense != 0 -> SkyblockPlayer.currentDefense + ActionBarListener.currentDefense != 0 -> ActionBarListener.currentDefense else -> return@HUD 0 to 0 } return@HUD textDim(generateText(text, "❈", true), 0, 0, defenseColor) } private val defenseColor by ColorSetting("Defense Color", Colors.MINECRAFT_GREEN, true, desc = "The color of the defense text.") + private val vitalityHud by HUD("Vitality HUD", "Displays the player's vitality.") { example -> + val text = when { + example -> 100 to 100 + !LocationUtils.isInSkyblock -> return@HUD 0 to 0 + ActionBarListener.isVitalityShown && ActionBarListener.maxVitality != 0 -> ActionBarListener.currentVitality to ActionBarListener.maxVitality + else -> return@HUD 0 to 0 + } + return@HUD textDim(generateText(text.first, text.second, "♨"), 0, 0, vitalityColor) + } + private val vitalityColor by ColorSetting("Vitality Color", Colors.MINECRAFT_DARK_RED, true, "The color of the vitality text.") + private val ehpHud by HUD("EHP HUD", "Displays the player's effective health (EHP).") { example -> val text = when { example -> 1000000 !LocationUtils.isInSkyblock -> return@HUD 0 to 0 - SkyblockPlayer.effectiveHP != 0 -> SkyblockPlayer.effectiveHP + ActionBarListener.effectiveHP != 0 -> ActionBarListener.effectiveHP else -> return@HUD 0 to 0 } return@HUD textDim(generateText(text, "", true), 0, 0, ehpColor) @@ -104,7 +115,7 @@ object PlayerDisplay : Module( val text = when { example -> 100 !LocationUtils.isInSkyblock -> return@HUD 0 to 0 - SkyblockPlayer.currentSpeed != 0 -> SkyblockPlayer.currentSpeed + ActionBarListener.currentSpeed != 0 -> ActionBarListener.currentSpeed else -> return@HUD 0 to 0 } return@HUD textDim(generateText(text, "✦", true), 0, 0, speedColor) @@ -9,33 +9,35 @@ import net.minecraft.network.protocol.game.ClientboundSystemChatPacket import net.minecraft.world.entity.ai.attributes.Attributes import kotlin.math.floor -object SkyblockPlayer { - /* - in module there should be: - health display current/Max - health bar - defense display - mana display current/Max - mana bar - current speed - current ehp - current overflow mana - */ - +object ActionBarListener { private val HEALTH_REGEX = Regex("([\\d|,]+)/([\\d|,]+)\\uE010") private val MANA_REGEX = Regex("([\\d|,]+)/([\\d|,]+)\\uE003") private val OVERFLOW_MANA_REGEX = Regex("([\\d|,]+)\\uE017") private val DEFENSE_REGEX = Regex("([\\d|,]+)\\uE008 Defense") + private val VITALITY_REGEX = Regex("([\\d.,]+)/([\\d.,]+)♨( Vitality)?") var currentHealth: Int = 0 private set var maxHealth: Int = 0 + private set var currentMana: Int = 0 + private set var maxMana: Int = 0 + private set var currentSpeed: Int = 0 + private set var currentDefense: Int = 0 + private set var overflowMana: Int = 0 + private set var effectiveHP: Int = 0 + private set + var currentVitality: Int = 0 + private set + var maxVitality: Int = 0 + private set + var isVitalityShown: Boolean = false + private set init { on<TickEvent.End> { @@ -64,6 +66,13 @@ object SkyblockPlayer { currentDefense = it.replace(",", "").toIntOrNull() ?: currentDefense } + val vitalityMatch = VITALITY_REGEX.find(msg) + isVitalityShown = vitalityMatch != null + vitalityMatch?.destructured?.let { (cVitality, mVitality, _) -> + currentVitality = cVitality.replace(",", "").toDoubleOrNull()?.toInt() ?: currentVitality + maxVitality = mVitality.replace(",", "").toDoubleOrNull()?.toInt() ?: maxVitality + } + effectiveHP = (currentHealth * (1 + currentDefense / 100)) } } |
Cleaned up PositionalMessages查看变更src/main/kotlin/com/odtheking/odin/commands/PosMsgCommand.kt | 48 +++++++++++++++--------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PositionalMessages.kt | 44 +++++++++++++++++++++----------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 2 +- 3 files changed, 37 insertions(+), 57 deletions(-) @@ -32,11 +32,11 @@ val posMsgCommand = Commodore("posmsg") { } } - runs { x: Double, y: Double, z: Double, delay: Long, distance: Double, color: String, message: GreedyString -> + runs { x: Double, y: Double, z: Double, delay: Int, distance: Double, color: String, message: GreedyString? -> + if (posMessageStrings.any { it.message == message?.string }) return@runs val color = getColorFromString(color) ?: return@runs modMessage("Unknown color $color") - posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, null, null, null, delay, distance, color, message.string - ).takeUnless { it in posMessageStrings } ?: return@runs modMessage("This message already exists!")) - modMessage("Message \"${message}\" added at $x, $y, $z, with ${delay}ms delay, triggered up to $distance blocks away.") + posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, null, null, null, delay, distance, color, message?.string)) + modMessage("Message \"${message}\" added at $x, $y, $z, with ${delay}t delay, triggered up to $distance blocks away.") ModuleManager.saveConfigurations() } } @@ -50,11 +50,11 @@ val posMsgCommand = Commodore("posmsg") { } } - runs { x: Double, y: Double, z: Double, x2: Double, y2: Double, z2: Double, delay: Long, color: String, message: GreedyString -> + runs { x: Double, y: Double, z: Double, x2: Double, y2: Double, z2: Double, delay: Int, color: String, message: GreedyString? -> + if (posMessageStrings.any { it.message == message?.string }) return@runs val color = getColorFromString(color) ?: return@runs modMessage("Unknown color $color") - posMessageStrings.add( - PositionalMessages.PosMessage(x, y, z, x2, y2, z2, delay, null, color, message.string).takeUnless { it in posMessageStrings } ?: return@runs modMessage("This message already exists!")) - modMessage("Message \"${message}\" added in $x, $y, $z, $x2, $y2, $z2, with ${delay}ms delay.") + posMessageStrings.add(PositionalMessages.PosMessage(x, y, z, x2, y2, z2, delay, null, color, message?.string)) + modMessage("Message \"${message}\" added in $x, $y, $z, $x2, $y2, $z2, with ${delay}t delay.") ModuleManager.saveConfigurations() } } @@ -64,37 +64,19 @@ val posMsgCommand = Commodore("posmsg") { param("message") { parser { greedy: GreedyString -> val input = greedy.string.trim() - - if (input.startsWith("#")) { - val withoutHash = input.substring(1) - val dashIndex = withoutHash.indexOf("-") - val indexStr = if (dashIndex > 0) withoutHash.substring(0, dashIndex) - else withoutHash - val index = indexStr.toIntOrNull() - if (index != null && index in 1..posMessageStrings.size) return@parser index.toString() - } - - val plainIndex = input.toIntOrNull() - if (plainIndex != null && plainIndex in 1..posMessageStrings.size) { - return@parser plainIndex.toString() - } - - val found = posMessageStrings.find { it.message.equals(input, true) } - if (found != null) return@parser (posMessageStrings.indexOf(found) + 1).toString() - - throw SyntaxException("Message not found. Available messages: ${List(posMessageStrings.size) { i -> "#${i+1}" }.joinToString()}") + val found = posMessageStrings.any { it.message.equals(input, true) } + if (!found) throw SyntaxException("Message not found. Available messages: ${posMessageStrings.joinToString { "\"${it.message}\"" }}") + input } suggests { - posMessageStrings.mapIndexed { index, msg -> "#${index + 1}-${msg.message}" } + posMessageStrings.mapNotNull { it.message }.distinct() } } runs { message: String -> - val index = message.toInt() - if (index < 1 || index > posMessageStrings.size) return@runs modMessage("Invalid Positional Message index #$index") - val removed = posMessageStrings[index - 1] - modMessage("Removed Positional Message #$index: \"${removed.message}\"") - posMessageStrings.removeAt(index - 1) + val removed = posMessageStrings.filter { it.message.equals(message, true) } + posMessageStrings.removeAll { it.message.equals(message, true) } + modMessage("Removed ${removed.size} Positional Message(s): \"$message\"") ModuleManager.saveConfigurations() } } @@ -10,6 +10,7 @@ import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onSend import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color +import com.odtheking.odin.utils.handlers.schedule import com.odtheking.odin.utils.render.drawCylinder import com.odtheking.odin.utils.render.drawText import com.odtheking.odin.utils.render.drawWireFrameBox @@ -33,7 +34,7 @@ object PositionalMessages : Module( private val displayMessage by BooleanSetting("Show Message", true, desc = "Whether or not to display the message in the box.").withDependency { showPositions } private val messageSize by NumberSetting("Message Size", 1f, 0.1f, 4f, 0.1f, desc = "Whether or not to display the message size in the box.").withDependency { showPositions && displayMessage } - data class PosMessage(val x: Double, val y: Double, val z: Double, val x2: Double?, val y2: Double?, val z2: Double?, val delay: Long, val distance: Double?, val color: Color, val message: String) + data class PosMessage(val x: Double, val y: Double, val z: Double, val x2: Double?, val y2: Double?, val z2: Double?, val delay: Int, val distance: Double?, val color: Color, val message: String?) val posMessageStrings by ListSetting("Pos Messages", mutableListOf<PosMessage>()) private val sentMessages = mutableMapOf<PosMessage, Boolean>() @@ -45,26 +46,26 @@ object PositionalMessages : Module( on<RenderEvent.Extract> { if (!showPositions || (onlyDungeons && !DungeonUtils.inDungeons)) return@on val player = mc.player ?: return@on - posMessageStrings.forEach { message -> - val distanceToMessage = if (message.distance != null) - player.distanceToSqr(message.x, message.y, message.z) + posMessageStrings.forEach { posMessage -> + val distanceToMessage = if (posMessage.distance != null) + player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) else { - val centerX = (message.x + (message.x2 ?: message.x)) / 2 - val centerY = (message.y + (message.y2 ?: message.y)) / 2 - val centerZ = (message.z + (message.z2 ?: message.z)) / 2 + val centerX = (posMessage.x + (posMessage.x2 ?: posMessage.x)) / 2 + val centerY = (posMessage.y + (posMessage.y2 ?: posMessage.y)) / 2 + val centerZ = (posMessage.z + (posMessage.z2 ?: posMessage.z)) / 2 player.distanceToSqr(centerX, centerY, centerZ) } if (distanceToMessage > 1024) return@forEach - if (message.distance != null) { - drawCylinder(Vec3(message.x, message.y, message.z), message.distance.toFloat(), cylinderHeight.toFloat(), color = message.color, depth = depthCheck) - if (displayMessage) drawText(message.message, Vec3(message.x, message.y + 1, message.z), messageSize, depthCheck) + if (posMessage.distance != null) { + drawCylinder(Vec3(posMessage.x, posMessage.y, posMessage.z), posMessage.distance.toFloat(), cylinderHeight.toFloat(), color = posMessage.color, depth = depthCheck) + if (displayMessage) posMessage.message?.let { drawText(it, Vec3(posMessage.x, posMessage.y + 1, posMessage.z), messageSize, depthCheck) } } else { - val box = AABB(message.x, message.y, message.z, message.x2 ?: return@forEach, message.y2 ?: return@forEach,message.z2 ?: return@forEach) - drawWireFrameBox(box, message.color, depth = depthCheck) + val box = AABB(posMessage.x, posMessage.y, posMessage.z, posMessage.x2 ?: return@forEach, posMessage.y2 ?: return@forEach,posMessage.z2 ?: return@forEach) + drawWireFrameBox(box, posMessage.color, depth = depthCheck) if (!displayMessage) return@forEach - val center = Vec3((message.x + message.x2) / 2, (message.y + message.y2) / 2, (message.z + message.z2) / 2) - drawText(message.message, center.add(0.0, 1.0, 0.0), messageSize, depthCheck) + val center = Vec3((posMessage.x + posMessage.x2) / 2, (posMessage.y + posMessage.y2) / 2, (posMessage.z + posMessage.z2) / 2) + posMessage.message?.let { drawText(it, center.add(0.0, 1.0, 0.0), messageSize, depthCheck) } } } } @@ -86,10 +87,8 @@ object PositionalMessages : Module( val player = mc.player ?: return val radius = posMessage.distance ?: return if (player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= radius * radius) { - if (!msgSent) Timer().schedule(posMessage.delay) { - if (player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= radius * radius) - sendCommand("pc ${posMessage.message}") - } + if (!msgSent && player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= radius * radius) + schedule(posMessage.delay) { sendCommand("pc ${posMessage.message}") } sentMessages[posMessage] = true } else if (!oncePerWorld) sentMessages[posMessage] = false } @@ -97,11 +96,10 @@ object PositionalMessages : Module( private fun handleInString(posMessage: PosMessage) { val position = mc.player?.position() ?: return val msgSent = sentMessages.getOrDefault(posMessage, false) - if (mc.player != null && AABB(posMessage.x, posMessage.y, posMessage.z, posMessage.x2 ?: return, posMessage.y2 ?: return, posMessage.z2 ?: return).contains(position)) { - if (!msgSent) Timer().schedule(posMessage.delay) { - if (AABB(posMessage.x, posMessage.y, posMessage.z, posMessage.x2, posMessage.y2, posMessage.z2).contains(position)) - sendCommand("pc ${posMessage.message}") - } + val aabb = AABB(posMessage.x, posMessage.y, posMessage.z, posMessage.x2 ?: return, posMessage.y2 ?: return, posMessage.z2 ?: return) + if (aabb.contains(position)) { + if (!msgSent && aabb.contains(position)) + schedule(posMessage.delay) { sendCommand("pc ${posMessage.message}") } sentMessages[posMessage] = true } else if (!oncePerWorld) sentMessages[posMessage] = false } @@ -100,7 +100,7 @@ object PuzzleSolvers : Module( private inline val isInPuzzle get() = DungeonUtils.currentRoom?.data?.type == RoomType.PUZZLE init { - TickTask(10) { + TickTask(5) { if (!enabled || !isInPuzzle) return@TickTask if (blazeSolver) BlazeSolver.getBlaze() if (waterSolver) WaterSolver.scan(optimizedSolutions) |
Added "Auto" GUI scale support to TerminalSolver查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) @@ -11,8 +11,10 @@ import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils +import net.minecraft.client.Options import net.minecraft.client.gui.GuiGraphicsExtractor import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.client.gui.screens.options.VideoSettingsScreen import net.minecraft.network.chat.Component import org.lwjgl.glfw.GLFW @@ -21,7 +23,7 @@ object TerminalSolver : Module( description = "Renders solution for terminals in floor 7." ) { private val renderType by SelectorSetting("Render type", "Odin", arrayListOf("Odin", "Normal", "Custom GUI"), desc = "How the terminal solver should render.") - private val normalTermSize by NumberSetting("Normal Term Size", 3, 1, 5, 1, desc = "The GUI scale increase for normal terminal GUI.").withDependency { renderType == 0 || renderType == 1 } + private val normalTermSize by NumberSetting("Normal Term Size", 3, 1, 6, 1, desc = "The GUI scale increase for normal terminal GUI.").withDependency { renderType == 0 || renderType == 1 } val customTermSize by NumberSetting("Term Size", 2f, 1f, 3f, 0.1f, desc = "The size of the custom terminal GUI.").withDependency { renderType == 2 } val roundness by NumberSetting("Roundness", 5, 0f, 15f, 1f, desc = "The roundness of the custom terminal gui.").withDependency { renderType == 2 } val gap by NumberSetting("Slot gap", 2, 0, 8, 1, desc = "The gap between the slots in the custom terminal gui.").withDependency { renderType == 2 } @@ -56,11 +58,11 @@ object TerminalSolver : Module( val selectColor by ColorSetting("Select", Colors.MINECRAFT_GREEN, true, desc = "Color of the select terminal solver.").withDependency { showColors } - val melodyColumColor by ColorSetting("Melody Column", Colors.MINECRAFT_DARK_PURPLE, true, desc = "Color of the colum indicator for melody.").withDependency { showColors && !cancelMelodySolver } + val melodyColumColor by ColorSetting("Melody Column", Colors.MINECRAFT_DARK_PURPLE, true, desc = "Color of the column indicator for melody.").withDependency { showColors && !cancelMelodySolver } val melodyPointerColor by ColorSetting("Melody Pointer", Colors.MINECRAFT_GREEN, true, desc = "Color of the location for pressing for melody.").withDependency { showColors && !cancelMelodySolver } val melodyBackgroundColor by ColorSetting("Melody Background", Colors.gray38, true, desc = "Color of the background slot in melody.").withDependency { showColors && !cancelMelodySolver } - @JvmStatic val termSize get() = if (enabled && (renderType == 0 || renderType == 1) && TerminalUtils.currentTerm != null) normalTermSize else 1 + @JvmStatic val termSize get() = if (enabled && (renderType == 0 || renderType == 1) && TerminalUtils.currentTerm != null) if (normalTermSize == 6) Options.AUTO_GUI_SCALE else normalTermSize else 1 val customGuiEnabled get() = enabled && renderType == 2 && renderMelody private val renderMelody get() = !(cancelMelodySolver && TerminalUtils.currentTerm?.type == TerminalTypes.MELODY) |
Fixed StartsWithHandler not properly working with "written_book"查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/StartsWithHandler.kt | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) @@ -6,8 +6,9 @@ import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.hasGlint import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes import net.minecraft.client.gui.screens.inventory.ContainerScreen +import net.minecraft.core.component.DataComponents +import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items class StartsWithHandler(private val letter: String): TerminalHandler(TerminalTypes.STARTS_WITH) { @@ -15,31 +16,34 @@ class StartsWithHandler(private val letter: String): TerminalHandler(TerminalTyp private var clickedSlot: Pair<Int, Int>? = null - override fun solve(items: List<ItemStack>): List<Int> { - clickedSlot?.let { - val screenHandler = (mc.screen as? ContainerScreen)?.menu - if (it.first != screenHandler?.containerId) { - val item = items[it.second].item - if (item == Items.NETHER_STAR || item == Items.EXPERIENCE_BOTTLE) clickedSlots.add(it.second) - clickedSlot = null - } + override fun solve(items: List<ItemStack>): List<Int> { + clickedSlot?.let { + val screenHandler = (mc.screen as? ContainerScreen)?.menu + if (it.first != screenHandler?.containerId) { + val item = items[it.second].item + if (item in enchantOverrides) clickedSlots.add(it.second) + clickedSlot = null } + } - return items.mapIndexedNotNull { index, item -> - if (item.hoverName.string.startsWith(letter, true) && - index !in clickedSlots && - (!item.hasGlint() || item.item == Items.NETHER_STAR || item.item == Items.EXPERIENCE_BOTTLE) - ) index else null - } + return items.mapIndexedNotNull { index, item -> + if (item.hoverName.string.startsWith(letter, true) && + index !in clickedSlots && + (!item.hasGlint() || item.item in enchantOverrides)) index else null } + } - override fun click(slotIndex: Int, button: Int, simulateClick: Boolean) { - val screenHandler = (mc.screen as? ContainerScreen)?.menu ?: return - if (canClick(slotIndex, button) && clickedSlot == null) - clickedSlot = screenHandler.containerId to slotIndex + override fun click(slotIndex: Int, button: Int, simulateClick: Boolean) { + val screenHandler = (mc.screen as? ContainerScreen)?.menu ?: return + if (canClick(slotIndex, button) && clickedSlot == null) + clickedSlot = screenHandler.containerId to slotIndex - super.click(slotIndex, button, simulateClick) - } + super.click(slotIndex, button, simulateClick) + } + + override fun renderSlot(slotIndex: Int): Pair<Color, String?> = TerminalSolver.startsWithColor to null - override fun renderSlot(slotIndex: Int): Pair<Color, String?> = TerminalSolver.startsWithColor to null + companion object { + private val enchantOverrides = BuiltInRegistries.ITEM.filter { it.components().has(DataComponents.ENCHANTMENT_GLINT_OVERRIDE) } + } } |
| Fix chat commands allowing long names (#132) * Fix chat command kick 查看变更src/main/kotlin/com/odtheking/odin/features/impl/skyblock/ChatCommands.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) @@ -123,13 +123,13 @@ object ChatCommands : Module( } val words = message.drop(1).split(" ").map { it.lowercase() } - + val arg = words.getOrNull(1)?.takeIf { it.length <= 16 } when (words[0]) { "help", "h" -> channelMessage("Commands: ${commandsMap.filterValues { it }.keys.joinToString(", ")}", name, channel) "odin", "od" -> if (odin) channelMessage("Odin! https://discord.gg/2nCbC9hkxT", name, channel) "coords", "co" -> if (coords) channelMessage(getPositionString(), name, channel) - "boop" if (boop) -> words.getOrNull(1)?.let { sendCommand("boop $it") } + "boop" if (boop) -> arg?.let { sendCommand("boop $it") } "cf" -> if (coinFlip) channelMessage(if (Math.random() < 0.5) "heads" else "tails", name, channel) "8ball" -> if (eightBall) channelMessage(responses.random(), name, channel) "dice" -> if (dice) channelMessage((1..6).random(), name, channel) @@ -150,16 +150,16 @@ object ChatCommands : Module( if (channel == ChatChannel.PARTY && partyAllInvite && PartyUtils.isLeader()) sendCommand("party settings allinvite") "pt", "ptme", "transfer" -> - if (channel == ChatChannel.PARTY && partyTransfer && PartyUtils.isLeader()) sendCommand("party transfer ${words.getOrNull(1)?.let { findPartyMember(it) } ?: name}") + if (channel == ChatChannel.PARTY && partyTransfer && PartyUtils.isLeader()) sendCommand("party transfer ${arg?.let { findPartyMember(it) } ?: name}") "promote" -> - if (channel == ChatChannel.PARTY && partyPromote && PartyUtils.isLeader()) sendCommand("party promote ${words.getOrNull(1)?.let { findPartyMember(it) } ?: name}") + if (channel == ChatChannel.PARTY && partyPromote && PartyUtils.isLeader()) sendCommand("party promote ${arg?.let { findPartyMember(it) } ?: name}") "demote" -> - if (channel == ChatChannel.PARTY && partyDemote && PartyUtils.isLeader()) sendCommand("party demote ${words.getOrNull(1)?.let { findPartyMember(it) } ?: name}") + if (channel == ChatChannel.PARTY && partyDemote && PartyUtils.isLeader()) sendCommand("party demote ${arg?.let { findPartyMember(it) } ?: name}") "kick", "k" -> - if (channel == ChatChannel.PARTY && kick && PartyUtils.isLeader()) sendCommand("p kick ${words.getOrNull(1)?.let { findPartyMember(it) } ?: name}") + if (channel == ChatChannel.PARTY && kick && PartyUtils.isLeader()) sendCommand("p kick ${arg?.let { findPartyMember(it) } ?: name}") "kickoffline", "ko" -> if (channel == ChatChannel.PARTY && kickOffline && PartyUtils.isLeader()) sendCommand("p kickoffline") |
| fixed wither essence to new ID hypixel uses (#133) Co-authored-by: CheaterV <kurdokoleno@proton.me> 查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -219,7 +219,7 @@ object DungeonUtils { return previousTeammates } - private const val WITHER_ESSENCE_ID = "e0f3e929-869e-3dca-9504-54c666ee6f23" + private const val WITHER_ESSENCE_ID = "2865274b-3097-394e-8149-ec629c72d850" private const val REDSTONE_KEY = "fed95410-aba1-39df-9b95-1d4f361eb66e" /** |
Adjust Croesus menu detection for the new update (#126)查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) @@ -60,6 +60,7 @@ object Croesus : Module( private var currentChestCount = 0 private val chestNameRegex = Regex("^(Wood|Iron|Gold|Diamond|Emerald|Obsidian|Bedrock)(?: Chest)?$") + private val croesusScreenRegex = Regex("^(?:\\(\\d+/\\d+\\) )?Croesus$") private val previewEnchantedBookRegex = Regex("^Enchanted Book \\(?([\\w ]+) (\\w+)\\)$") private val chestPreviewScreenRegex = Regex("^(?:Master )?Catacombs - ([FloorVI\\d ]*)$") private val chestStatusRegex = Regex("^Opened Chest: (.+)$|^No more chests to open!$") @@ -105,7 +106,7 @@ object Croesus : Module( } on<GuiEvent.RenderSlot> { - if (screen.title.string == "Croesus" && slot.item.hoverName.string.equalsOneOf("The Catacombs", "Master Mode The Catacombs")) { + if (screen.title.string.matches(croesusScreenRegex) && slot.item.hoverName.string.equalsOneOf("The Catacombs", "Master Mode The Catacombs")) { val loreString = slot.item.loreString if (hideClaimed && loreString.any { it.matches(chestStatusRegex) } && (!includeKey || hasStrikeThrough("Dungeon Chest Key", slot.item.lore ))) cancel() @@ -351,4 +352,4 @@ object Croesus : Module( 2 ) ) -} \ No newline at end of file +} |
Added message indication for first click查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TerminalSolver.kt | 11 +++++++---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/WaterSolver.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/TerminalHandler.kt | 3 +++ 3 files changed, 11 insertions(+), 5 deletions(-) @@ -8,6 +8,7 @@ import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalTypes import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils import net.minecraft.client.gui.GuiGraphicsExtractor @@ -67,10 +68,12 @@ object TerminalSolver : Module( on<GuiEvent.SlotClick> { val term = TerminalUtils.currentTerm ?: return@on - if ( - System.currentTimeMillis() - term.timeOpened < firstClickProt || - (blockIncorrectClicks && !term.canClick(slotId, button)) - ) return@on cancel() + if (blockIncorrectClicks && !term.canClick(slotId, button)) return@on cancel() + + if (term.shouldProtect()) { + modMessage("§cBlocked first click ${System.currentTimeMillis() - term.timeOpened}ms after opening.") + return@on cancel() + } if (middleClickGUI) { term.click(slotId, if (button == 0) GLFW.GLFW_MOUSE_BUTTON_3 else button, hideClicked && !term.isClicked) @@ -98,7 +98,7 @@ object WaterSolver { } fun waterInteract(event: ServerboundUseItemOnPacket) { - if (solutions.isEmpty() || mc.hasShiftDown()) return + if (solutions.isEmpty()) return LeverBlock.entries.find { it.leverPos == event.hitResult.blockPos }?.let { if (it == LeverBlock.WATER && openedWaterTicks == -1) openedWaterTicks = tickCounter it.i++ @@ -5,6 +5,7 @@ import com.google.common.primitives.SignedBytes import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.events.GuiEvent import com.odtheking.odin.events.PacketEvent +import com.odtheking.odin.features.impl.boss.TerminalSolver.firstClickProt import com.odtheking.odin.features.impl.boss.termsim.TermSimGUI import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.clickSlot @@ -75,4 +76,6 @@ abstract class TerminalHandler(val type: TerminalTypes) { } open fun canClick(slotIndex: Int, button: Int): Boolean = slotIndex in solution + + fun shouldProtect(): Boolean = System.currentTimeMillis() - timeOpened < firstClickProt } \ No newline at end of file |
Updated DevPlayers to use UUID Removed all DevPlayers fields Added first join message查看变更src/main/kotlin/com/odtheking/odin/OdinMod.kt | 5 ----- src/main/kotlin/com/odtheking/odin/commands/DevCommand.kt | 12 +++++------ src/main/kotlin/com/odtheking/odin/features/impl/render/ClickGUIModule.kt | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------- src/main/kotlin/com/odtheking/odin/features/impl/render/PlayerSize.kt | 37 ++++++++++++--------------------- src/main/kotlin/com/odtheking/odin/utils/ChatUtils.kt | 4 ++-- 5 files changed, 87 insertions(+), 61 deletions(-) @@ -89,10 +89,5 @@ object OdinMod : ClientModInitializer { PictureInPictureRendererRegistry.register { context -> ItemStateRenderer(context.bufferSource()) } - - val name = mc.user.name.takeIf { !it.matches(Regex("Player\\d{2,3}")) } ?: return - scope.launch { - postData("https://api.odtheking.com/tele/", """{"username": "$name", "version": "Fabric $version"}""") - } } } \ No newline at end of file @@ -64,12 +64,10 @@ val devCommand = Commodore("oddev") { } literal("adddev").runs { name: String, password: String, xSize: Float?, ySize: Float?, zSize: Float? -> - val x = xSize ?: 0.6f - val y = ySize ?: 0.6f - val z = zSize ?: 0.6f - modMessage("Sending data... name: $name, x: $x, y: $y, z: $z") + val devBody = buildDevBody(name, xSize ?: 0.6f, ySize ?: 0.6f, zSize ?: 0.6f, " ", password) + modMessage("Sending $devBody") OdinMod.scope.launch { - modMessage(postData(DEV_SERVER, buildDevBody(name, Colors.WHITE, x, y, z, false, " ", password)).getOrNull()) + modMessage(postData(DEV_SERVER, devBody).getOrNull()) } } @@ -176,8 +174,8 @@ val devCommand = Commodore("oddev") { literal("roomdata").runs { x: Int?, z: Int? -> val player = mc.player ?: return@runs - val tileX = ((x ?: player.blockX) + 201) shr 5 - val tileZ = ((z ?: player.blockZ) + 201) shr 5 + val tileX = x ?: ((player.blockX + 201) shr 5) + val tileZ = z ?: ((player.blockZ + 201) shr 5) val roomPos = IVec2(tileX * 32 - 185, tileZ * 32 - 185) val chunk = mc.level?.getChunk(roomPos.x shr 4, roomPos.z shr 4) ?: return@runs @@ -6,15 +6,18 @@ import com.odtheking.odin.clickgui.ClickGUI import com.odtheking.odin.clickgui.HudManager import com.odtheking.odin.clickgui.settings.AlwaysActive import com.odtheking.odin.clickgui.settings.impl.* -import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.ChatPacketEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.features.Category import com.odtheking.odin.features.Module +import com.odtheking.odin.features.ModuleManager import com.odtheking.odin.utils.Color import com.odtheking.odin.utils.alert import com.odtheking.odin.utils.getChatBreak import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.network.WebUtils.fetchJson +import com.odtheking.odin.utils.network.WebUtils.postData +import com.odtheking.odin.utils.skyblock.LocationUtils import com.odtheking.odin.utils.ui.rendering.NVGRenderer import kotlinx.coroutines.launch import net.minecraft.network.chat.ClickEvent @@ -42,6 +45,8 @@ object ClickGUIModule : Module( private val action by ActionSetting("Open HUD Editor", desc = "Opens the HUD editor when clicked.") { mc.setScreen(HudManager) } val devMessage by BooleanSetting("Developer Message", false, desc = "Sends development related messages to the chat.") + private var firstJoin by BooleanSetting("First join", true, "").hide() + override fun onKeybind() { toggle() } @@ -66,38 +71,77 @@ object ClickGUIModule : Module( private const val RELEASE_LINK = "https://github.com/odtheking/OdinFabric/releases/latest" private const val MODRINTH_LINK = "https://modrinth.com/mod/odin/versions" + private val profileRegex = Regex("Profile ID:\\s*(.{36})") private var latestVersionNumber: String? = null private var hasSentUpdateMessage = false init { OdinMod.scope.launch { latestVersionNumber = checkNewerVersion(OdinMod.version.toString()) + val name = OdinMod.mc.user.name.takeIf { !it.matches(Regex("Player\\d{2,3}")) } ?: return@launch + postData("https://api.odtheking.com/tele/", """{"username": "$name", "version": "Fabric ${OdinMod.version}"}""") } - on<LevelEvent.Load> { - if (hasSentUpdateMessage || latestVersionNumber == null) return@on - hasSentUpdateMessage = true + on<ChatPacketEvent> { + if (!profileRegex.matches(value)) return@on - modMessage(""" - ${getChatBreak()} + if (firstJoin) { + firstJoin = false + ModuleManager.saveConfigurations() + modMessage( + Component.literal(getChatBreak()).append(Component.literal(""" + §7Thanks for installing §3Odin ${OdinMod.version}§7! - §3Odin update available: §f$latestVersionNumber - """.trimIndent(), "") - - modMessage(Component.literal("§b$RELEASE_LINK").withStyle { - it.withClickEvent(ClickEvent.OpenUrl(URI(RELEASE_LINK))) - .withHoverEvent(HoverEvent.ShowText(Component.literal(RELEASE_LINK))) - }, "") - modMessage(Component.literal("§b$MODRINTH_LINK").withStyle { - it.withClickEvent(ClickEvent.OpenUrl(URI(MODRINTH_LINK))) - .withHoverEvent(HoverEvent.ShowText(Component.literal(MODRINTH_LINK))) - }, "") - - modMessage(""" - - ${getChatBreak()}§r - - """.trimIndent(), "") + §7Use §d§l/od §r§7to access GUI settings. + + §7Join the discord for support and suggestions. + """.trimIndent())) + .append(Component.literal("\n")) + .append( + Component.literal("§9https://discord.gg/2nCbC9hkxT").withStyle { + it.withClickEvent(ClickEvent.OpenUrl(URI("https://discord.gg/2nCbC9hkxT"))).withHoverEvent( + HoverEvent.ShowText(Component.literal("https://discord.gg/2nCbC9hkxT")) + ) + } + ) + .append(Component.literal("\n")) + .append(Component.literal(getChatBreak())), + "") + } + + if (hasSentUpdateMessage || latestVersionNumber == null) return@on + hasSentUpdateMessage = true + + modMessage( + Component.literal(getChatBreak()) + .append(Component.literal("§3Odin update available: §f$latestVersionNumber\n\n")) + .append( + Component.literal("§bGitHub link").withStyle { + it.withClickEvent(ClickEvent.OpenUrl(URI(RELEASE_LINK))).withHoverEvent( + HoverEvent.ShowText(Component.literal(RELEASE_LINK)) + ) + } + ) + .append(Component.literal("\n")) + .append( + Component.literal("§bModrinth Link").withStyle { + it.withClickEvent(ClickEvent.OpenUrl(URI(MODRINTH_LINK))).withHoverEvent( + HoverEvent.ShowText(Component.literal(MODRINTH_LINK)) + ) + } + ) + .append(Component.literal("\n")) + .append( + Component.literal("§9Discord link").withStyle { + it.withClickEvent(ClickEvent.OpenUrl(URI("https://discord.gg/2nCbC9hkxT"))).withHoverEvent( + HoverEvent.ShowText(Component.literal("https://discord.gg/2nCbC9hkxT")) + ) + } + ) + .append(Component.literal("\n\n${getChatBreak()}§r")), + "" + ) + alert("Odin Update Available") } } @@ -8,14 +8,13 @@ import com.odtheking.odin.OdinMod import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.* import com.odtheking.odin.features.Module -import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.network.WebUtils.fetchJson import com.odtheking.odin.utils.network.WebUtils.postData import kotlinx.coroutines.launch import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey import net.minecraft.client.renderer.entity.state.AvatarRenderState +import java.util.UUID object PlayerSize : Module( name = "Player Size", @@ -25,12 +24,10 @@ object PlayerSize : Module( private val devSizeX by NumberSetting("Size X", 1f, -1, 3f, 0.1, desc = "X scale of the dev size.") private val devSizeY by NumberSetting("Size Y", 1f, -1, 3f, 0.1, desc = "Y scale of the dev size.") private val devSizeZ by NumberSetting("Size Z", 1f, -1, 3f, 0.1, desc = "Z scale of the dev size.") - private val devWings by BooleanSetting("Wings", false, desc = "Toggles dragon wings.").withDependency { isRandom } - private val devWingsColor by ColorSetting("Wings Color", Colors.WHITE, desc = "Color of the dev wings.").withDependency { devWings && isRandom } private var showHidden by DropdownSetting("Show Hidden").withDependency { isRandom } private val passcode by StringSetting("Passcode", "odin", desc = "Passcode for dev features.").withDependency { showHidden && isRandom } - const val DEV_SERVER = "https://api.odtheking.com/devs/" + const val DEV_SERVER = "https://devs.odtheking.com" private val sendDevData by ActionSetting("Send Dev Data", desc = "Sends dev data to the server.") { showHidden = false @@ -40,11 +37,7 @@ object PlayerSize : Module( return@ActionSetting } OdinMod.scope.launch { - val body = buildDevBody( - mc.user.name, - devWingsColor, devSizeX, devSizeY, - devSizeZ, devWings, " ", passcode - ) + val body = buildDevBody(mc.user.name, devSizeX, devSizeY, devSizeZ, " ", passcode) modMessage(postData(DEV_SERVER, body).getOrNull()) updateCustomProperties() @@ -52,37 +45,35 @@ object PlayerSize : Module( }.withDependency { isRandom } - var randoms: HashMap<String, RandomPlayer> = HashMap() - val isRandom get() = randoms.containsKey(mc.user.name) + var randoms: HashMap<UUID, RandomPlayer> = HashMap() + val isRandom get() = randoms.containsKey(mc.user.profileId) data class RandomPlayer( @SerializedName("CustomName") val customName: String?, @SerializedName("DevName") val name: String, - @SerializedName("IsDev") val isDev: Boolean?, - @SerializedName("WingsColor") val wingsColor: List<Int>, - @SerializedName("Size") val scale: List<Float>, - @SerializedName("Wings") val wings: Boolean + @SerializedName("Uuid") val uuid: UUID, + @SerializedName("Size") val scale: List<Float> ) @JvmStatic fun preRenderCallbackScaleHook(entityRenderer: AvatarRenderState, matrix: PoseStack) { val gameProfile = entityRenderer.getData(GAME_PROFILE_KEY) ?: return - if (enabled && gameProfile.name == mc.player?.gameProfile?.name && !randoms.containsKey(gameProfile.name)) { + if (enabled && gameProfile.name == mc.player?.gameProfile?.name && !randoms.containsKey(gameProfile.id)) { if (devSizeY < 0) matrix.translate(0f, devSizeY * 2, 0f) matrix.scale(devSizeX, devSizeY, devSizeZ) } - if (!randoms.containsKey(gameProfile.name)) return + if (!randoms.containsKey(gameProfile.id)) return if (!devSize && gameProfile.name == mc.player?.gameProfile?.name) return - val random = randoms[gameProfile.name] ?: return + val random = randoms[gameProfile.id] ?: return if (random.scale[1] < 0) matrix.translate(0f, random.scale[1] * 2, 1f) matrix.scale(random.scale[0], random.scale[1], random.scale[2]) } suspend fun updateCustomProperties(): String { - val response = fetchJson<Array<RandomPlayer>>("https://api.odtheking.com/devs/").getOrNull() ?: return "Failed to fetch custom properties!" + val response = fetchJson<Array<RandomPlayer>>(DEV_SERVER).getOrNull() ?: return "Failed to fetch custom properties!" randoms.clear() - randoms.putAll(response.associateBy { it.name }) + randoms.putAll(response.associateBy { it.uuid }) CustomNameReplacer.rebuild(randoms.values) return response.joinToString("\n") } @@ -99,12 +90,10 @@ object PlayerSize : Module( } } - fun buildDevBody(devName: String, wingsColor: Color, sizeX: Float, sizeY: Float, sizeZ: Float, wings: Boolean, customName: String, password: String): String { + fun buildDevBody(devName: String, sizeX: Float, sizeY: Float, sizeZ: Float, customName: String, password: String): String { return Gson().toJson(mapOf( "devName" to devName, - "wingsColor" to listOf(wingsColor.red, wingsColor.green, wingsColor.blue), "size" to listOf(sizeX, sizeY, sizeZ), - "wings" to wings, "customName" to customName, "password" to password )) @@ -17,13 +17,13 @@ fun sendCommand(command: String) { fun modMessage(message: Any?, prefix: String = "§3Odin §8»§r ", chatStyle: Style? = null) { val text = Component.literal("$prefix$message") chatStyle?.let { text.setStyle(chatStyle) } - mc.execute { mc.gui.chat.addClientSystemMessage(text) } + mc.schedule { mc.gui.chat.addClientSystemMessage(text) } } fun modMessage(message: Component, prefix: String = "§3Odin §8»§r ", chatStyle: Style? = null) { val text = Component.literal(prefix).append(message) chatStyle?.let { text.setStyle(chatStyle) } - mc.execute { mc.gui.chat.addClientSystemMessage(text) } + mc.schedule { mc.gui.chat.addClientSystemMessage(text) } } fun devMessage(message: Any?) { |
Fixed WaterSolver registering clicks while crouched查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/WaterSolver.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/LocationUtils.kt | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) @@ -98,7 +98,7 @@ object WaterSolver { } fun waterInteract(event: ServerboundUseItemOnPacket) { - if (solutions.isEmpty()) return + if (solutions.isEmpty() || mc.hasShiftDown()) return LeverBlock.entries.find { it.leverPos == event.hitResult.blockPos }?.let { if (it == LeverBlock.WATER && openedWaterTicks == -1) openedWaterTicks = tickCounter it.i++ @@ -21,9 +21,7 @@ object LocationUtils { var currentArea: Island = Island.Unknown private set(value) { field = value - if (value !== Island.Unknown) { - LocationChangeEvent.postAndCatch() - } + if (value !== Island.Unknown) LocationChangeEvent.postAndCatch() } var lobbyId: String? = null |
Removed door world scanning Added option for own player head Fixed unnecessary dev messages查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DoorHighlight.kt | 6 ++---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 3 ++- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonScan.kt | 10 ++++++---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 43 ++++++++++++------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapScan.kt | 17 +++++++++++------ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/WorldScan.kt | 24 ++++-------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonDoor.kt | 12 +++++++++--- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 45 +++++++++++++++++++++++++++++++++++++++------ src/main/kotlin/com/odtheking/odin/utils/IVec2.kt | 4 +++- 9 files changed, 88 insertions(+), 76 deletions(-) @@ -68,13 +68,11 @@ object DoorHighlight : Module( on<RenderEvent.Extract> { if (!DungeonUtils.inClear) return@on - DungeonScan.viewableDoors.forEach { (door, _) -> + DungeonScan.doors.forEach { (_, door) -> if (!door.type.equalsOneOf(DoorType.Wither, DoorType.Blood)) return@forEach if (door.type == DoorType.Blood && bloodOpened) return@forEach - val worldX = (door.position.x - 6) * 32 + 7 + door.rotation.offset.x * 16 - val worldZ = (door.position.z - 6) * 32 + 7 + door.rotation.offset.z * 16 - val box = AABB(worldX - 1.0, 69.0, worldZ - 1.0, worldX + 2.0, 73.0, worldZ + 2.0) + val box = AABB(door.worldX - 1.0, 69.0, door.worldZ - 1.0, door.worldX + 2.0, 73.0, door.worldZ + 2.0) val isOpenable = when (door.type) { DoorType.Wither -> witherKeys > 0 @@ -27,6 +27,7 @@ object DungeonMap : Module( private val playerDropdown by DropdownSetting("Player Settings") val playerNamesScaling by NumberSetting("Player Names Scaling", 0.75f, 0.1f, 2f, 0.05f, desc = "Scale of player name labels.").withDependency { playerDropdown } val playerNameColor by ColorSetting("Player Name Color", Color(70, 70, 70), true, desc = "Color of player name labels.").withDependency { playerDropdown } + val playerHead by BooleanSetting("Own Player Head", false, desc = "Shows the player head on the map.").withDependency { playerDropdown } private val roomDropdown by DropdownSetting("Room Settings") val normalRoomColor by ColorSetting("Normal Room", Color(107, 58, 17), true, desc = "Color of normal rooms.").withDependency { roomDropdown } @@ -41,7 +42,7 @@ object DungeonMap : Module( private val doorDropdown by DropdownSetting("Door Settings") val normalDoorColor by ColorSetting("Normal Door", Color(107, 58, 17).darker(), desc = "Color of normal doors.").withDependency { doorDropdown } val witherDoorColor by ColorSetting("Wither Door", Colors.BLACK, true, desc = "Color of wither doors.").withDependency { doorDropdown } - val bloodDoorColor by ColorSetting("Blood Door", Color(231, 0, 0).darker(), true, desc = "Color of blood room doors.").withDependency { doorDropdown } + val bloodDoorColor by ColorSetting("Blood Door", Color(255, 0, 0), true, desc = "Color of blood room doors.").withDependency { doorDropdown } val fairyDoorColor by ColorSetting("Fairy Door", Color(224, 0, 255).darker(), true, desc = "Color of fairy room doors.").withDependency { doorDropdown } val unknownDoorColor by ColorSetting("Unknown Door", Color(40, 40, 40).darker(), true, desc = "Color of doors with no discovered room on the other side.").withDependency { doorDropdown } @@ -8,6 +8,7 @@ import com.odtheking.odin.events.core.EventBus import com.odtheking.odin.events.core.on import com.odtheking.odin.features.impl.dungeon.map.tile.* import com.odtheking.odin.utils.Color +import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.IVec2 import com.odtheking.odin.utils.skyblock.dungeon.Floor import net.minecraft.world.entity.player.Player @@ -20,6 +21,8 @@ object DungeonScan { var roomSize = 16 var roomGap = 20 + + val connectionGap: Int get() = roomSize + ROOM_SPACING / 2 var startX = 5 var startY = 5 @@ -86,7 +89,6 @@ object DungeonScan { fun updateViewableDoors() { pathHints.clear() - viewableDoors.clear() for ((_, door) in doors) { val originTile = tiles[door.originTileIndex] val destTile = tiles[door.destinationTileIndex] @@ -98,9 +100,9 @@ object DungeonScan { pathHints.add(it) } - viewableDoors.add(door to when (door.type) { + door.color = when (door.type) { DoorType.Wither -> DungeonMap.witherDoorColor - DoorType.Blood -> DungeonMap.bloodDoorColor + DoorType.Blood -> if (!originView || !destView) DungeonMap.bloodDoorColor.darker(0.5f) else DungeonMap.bloodDoorColor DoorType.Fairy -> DungeonMap.fairyDoorColor else -> { if (!originView || !destView) DungeonMap.unknownDoorColor @@ -108,7 +110,7 @@ object DungeonScan { .firstOrNull { it.type != RoomType.NORMAL && it.type != RoomType.FAIRY } ?.type?.let { room -> roomTypeColor(room) } ?: DungeonMap.normalDoorColor } - }) + } } } } \ No newline at end of file @@ -28,7 +28,7 @@ internal fun GuiGraphicsExtractor.renderMap() { for (room in DungeonScan.rooms) renderRoomText(room) - renderPlayers() + if (!DungeonUtils.inBoss) renderPlayers() pose().popMatrix() } @@ -67,7 +67,7 @@ private fun GuiGraphicsExtractor.renderDoors() { val rg = DungeonScan.roomGap val half = (rs - 8) / 2f - for ((door, color) in DungeonScan.viewableDoors) { + for ((_, door) in DungeonScan.doors) { val offset = door.rotation.offset pose().pushMatrix() @@ -75,7 +75,7 @@ private fun GuiGraphicsExtractor.renderDoors() { door.position.x * rg + offset.x * rs + offset.z * half, door.position.z * rg + offset.z * rs + offset.x * half ) - fill(0, 0, 4 + 4 * offset.z, 4 + 4 * offset.x, color.rgba) + fill(0, 0, 4 + 4 * offset.z, 4 + 4 * offset.x, door.color.rgba) pose().popMatrix() } @@ -126,7 +126,7 @@ private fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { return } - val (cx, cz) = textCenter(room) + val (cx, cz) = room.center?.let { it.x to it.z } ?: return val fontH = mc.font.lineHeight val textColor = when (room.checkmark) { @@ -141,7 +141,7 @@ private fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { for ((i, line) in lines.withIndex()) { pose().pushMatrix() - pose().translate(cx, cz - totalH / 2f + i * fontH * DungeonMap.textScaling) + pose().translate(cx.toFloat(), cz.toFloat() - totalH / 2f + i * fontH * DungeonMap.textScaling) pose().scale(DungeonMap.textScaling) centeredText(mc.font, line, 0, -fontH / 2, textColor) pose().popMatrix() @@ -179,35 +179,16 @@ private fun GuiGraphicsExtractor.renderPlayers() { val (selfX, selfZ) = DungeonScan.playerRenderPosition(mc.player, IVec2(0, 0)) pose().translate(selfX, selfZ) pose().rotate(Math.toRadians(180.0 + (mc.player?.yRot ?: 0f)).toFloat()) - blit(RenderPipelines.GUI_TEXTURED, marker, -2, -3, 2f, 0f, 5, 7, 8, 8) + DungeonUtils.currentDungeonPlayer + if (DungeonMap.playerHead) { + mc.player?.skin?.let { skin -> + fill(-5, -5, 5, 5, DungeonUtils.currentDungeonPlayer.clazz.color.rgba) + PlayerFaceExtractor.extractRenderState(this, skin, -4, -4, 8) + } + } else blit(RenderPipelines.GUI_TEXTURED, marker, -2, -3, 2f, 0f, 5, 7, 8, 8) pose().popMatrix() } -private fun textCenter(room: DungeonRoom): Pair<Float, Float> { - val rs = DungeonScan.roomSize.takeIf { it != -1 } ?: return 0f to 0f - val rg = DungeonScan.roomGap - val half = rs / 2 - - fun tileCenter(tile: IVec2) = tile.x * rg + half to tile.z * rg + half - - val rotation = room.rotation - if (rotation == null) { - val tile = room.tiles.minBy { it.x * 1000 + it.z } - return tileCenter(tile).let { it.first.toFloat() to it.second.toFloat() } - } - - val centers = room.tiles.map { tileCenter(it) } - val x = (centers.minOf { it.first } + centers.maxOf { it.first }) / 2f - val z = when (room.shape) { - RoomShape.L -> - if (rotation == RoomRotation.NORTH || rotation == RoomRotation.WEST) centers.minOf { it.second }.toFloat() - else centers.maxOf { it.second }.toFloat() - else -> - (centers.minOf { it.second } + centers.maxOf { it.second }) / 2f - } - return x to z -} - fun roomTypeColor(type: RoomType): Color = when (type) { RoomType.NORMAL -> DungeonMap.normalRoomColor RoomType.PUZZLE -> DungeonMap.puzzleRoomColor @@ -89,7 +89,8 @@ object MapScan { private fun mapTiles(colors: ByteArray, roomTypes: Array<RoomType?>, roomColors: ByteArray, centerColors: ByteArray) { val halfRoom = DungeonScan.roomSize / 2 - val connectionGap = DungeonScan.roomSize + DungeonScan.ROOM_SPACING / 2 + val connectionGap = DungeonScan.connectionGap + val sideCheckOffset = 4 for (index in 0 until 36) { val tileX = index % 6 @@ -109,21 +110,25 @@ object MapScan { } } - if (tileX < 5 && getPx(colors, originX + connectionGap, originZ) == EMPTY) { + if (tileX < 5) { val doorColor = getPx(colors, originX + connectionGap, originZ + halfRoom) - if (doorColor != EMPTY) addOrFixDoor(IVec2(tileX, tileZ), DoorRotation.Horizontal, DoorType.fromColor(doorColor)) + val sideColor = getPx(colors, originX + connectionGap, originZ + halfRoom - sideCheckOffset) + if (sideColor == EMPTY && doorColor != EMPTY) + addOrFixDoor(IVec2(tileX, tileZ), DoorRotation.Horizontal, DoorType.fromColor(doorColor)) } - if (tileZ < 5 && getPx(colors, originX, originZ + connectionGap) == EMPTY) { + if (tileZ < 5) { val doorColor = getPx(colors, originX + halfRoom, originZ + connectionGap) - if (doorColor != EMPTY) addOrFixDoor(IVec2(tileX, tileZ), DoorRotation.Vertical, DoorType.fromColor(doorColor)) + val sideColor = getPx(colors, originX + halfRoom - sideCheckOffset, originZ + connectionGap) + if (sideColor == EMPTY && doorColor != EMPTY) + addOrFixDoor(IVec2(tileX, tileZ), DoorRotation.Vertical, DoorType.fromColor(doorColor)) } } } private fun processRooms(colors: ByteArray, roomTypes: Array<RoomType?>, roomColors: ByteArray, centerColors: ByteArray) { val visited = BooleanArray(36) - val connectionGap = DungeonScan.roomSize + DungeonScan.ROOM_SPACING / 2 + val connectionGap = DungeonScan.connectionGap for (startIndex in 0 until 36) { val roomType = roomTypes[startIndex] ?: continue @@ -91,31 +91,15 @@ object WorldScan { val tile = DungeonScan.tiles.getOrNull(tilePos.x + tilePos.z * 6) if (tile?.room?.data == null) scanRoom(chunk, chunkPosition) } - else if (rowEven || columnEven) scanDoor(chunk, chunkPosition, if (columnEven) DoorRotation.Horizontal else DoorRotation.Vertical) } } - private fun scanDoor(chunk: LevelChunk, chunkPosition: IVec2, rotation: DoorRotation) { - if (DungeonScan.doors.contains(chunkPosition)) return - val position = (chunkPosition * 16) + 7 - - for (y in 86..160) { - if (!chunk.getBlockState(position.x, y, position.z).isAir) return - } - if (chunk.getBlockState(position.x, 68, position.z).isAir) return - - val type = when (chunk.getBlockState(position.x, 69, position.z).block) { - Blocks.COAL_BLOCK -> DoorType.Wither - Blocks.RED_TERRACOTTA -> DoorType.Blood - else -> DoorType.Normal - } - val doorPos = ((chunkPosition - 1) / 2) + 6 - DungeonScan.doors[chunkPosition] = DungeonDoor(doorPos, rotation, type) - } - private fun scanRoom(chunk: LevelChunk, chunkPosition: IVec2) { val (core, highestBlock) = getRoomCore(chunk, (chunkPosition * 16) + 7) - val data = RoomData.getRoomData(core) ?: return devMessage("Unknown room data for core: $core $chunkPosition") + val data = RoomData.getRoomData(core) ?: run { + if (core == -318865360) return + else return devMessage("Unknown room data for core: $core $chunkPosition") + } val tilePosition = (chunkPosition / 2) + 6 val tile = DungeonScan.tiles.getOrNull(tilePosition.x + (tilePosition.z * 6)) ?: return @@ -1,14 +1,20 @@ package com.odtheking.odin.features.impl.dungeon.map.tile +import com.odtheking.odin.utils.Color +import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.IVec2 class DungeonDoor( val position: IVec2, val rotation: DoorRotation, - var type: DoorType + var type: DoorType, + var color: Color = Colors.WHITE, ) { - val originTileIndex by lazy { position.x + position.z * 6 } - val destinationTileIndex by lazy { position.x + rotation.offset.x + (position.z + rotation.offset.z) * 6 } + val originTileIndex = position.x + position.z * 6 + val destinationTileIndex = position.x + rotation.offset.x + (position.z + rotation.offset.z) * 6 + + val worldX = (position.x - 6) * 32 + 7 + rotation.offset.x * 16 + val worldZ = (position.z - 6) * 32 + 7 + rotation.offset.z * 16 } enum class DoorType { @@ -2,12 +2,15 @@ package com.odtheking.odin.features.impl.dungeon.map.tile import com.odtheking.odin.OdinMod.mc import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints +import com.odtheking.odin.features.impl.dungeon.map.DungeonScan import com.odtheking.odin.utils.IVec2 import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.rotateAroundNorth import com.odtheking.odin.utils.rotateToNorth +import com.odtheking.odin.utils.sortKey import net.minecraft.core.BlockPos import net.minecraft.world.level.block.Blocks +import kotlin.math.roundToInt class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData? = null) { val tiles: ArrayList<IVec2> = ArrayList(4) @@ -25,6 +28,9 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData var isKnown1x1: Boolean = false + var center: IVec2? = null + private set + val isViewable: Boolean get() = walkedInto || checkmark != MapCheckmark.UNDISCOVERED val name: String? get() = data?.name @@ -33,7 +39,37 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData tiles.add(segment.position) topLeft = IVec2(tiles.minOf { it.x }, tiles.minOf { it.z }) highestBlock?.let { if (tiles.size == data?.shape?.tileAmount) inferLayout(it) } + recalculateCenter() + } + } + + private fun recalculateCenter() { + if (tiles.isEmpty()) return + val rs = DungeonScan.roomSize.takeIf { it != -1 } ?: return + + val rg = DungeonScan.roomGap + val half = rs / 2 + + fun tileCenter(tile: IVec2) = tile.x * rg + half to tile.z * rg + half + + val rot = rotation + if (rot == null) { + val tile = tiles.minBy { it.sortKey } + val (x, z) = tileCenter(tile) + center = IVec2(x, z) + return + } + + val centers = tiles.map { tileCenter(it) } + val x = (centers.minOf { it.first } + centers.maxOf { it.first }) / 2f + val z = when (shape) { + RoomShape.L -> + if (rot == RoomRotation.NORTH || rot == RoomRotation.WEST) centers.minOf { it.second }.toFloat() + else centers.maxOf { it.second }.toFloat() + else -> + (centers.minOf { it.second } + centers.maxOf { it.second }) / 2f } + center = IVec2(x.roundToInt(), z.roundToInt()) } fun inferLayoutFromMap() { @@ -78,8 +114,7 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData } private fun resolveGeometryRotation(): RoomRotation? { - val bottomRight = tiles.maxByOrNull { it.x * 1000 + it.z } ?: return null - val topLeft = tiles.minByOrNull { it.x * 1000 + it.z } ?: return null + val bottomRight = tiles.maxByOrNull { it.sortKey } ?: return null return if (shape == RoomShape.L) { val other = tiles.find { it != topLeft && it != bottomRight } ?: return null @@ -95,8 +130,7 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData } private fun resolveClayPos(rotation: RoomRotation, height: Int): BlockPos? { - val bottomRight = tiles.maxByOrNull { it.x * 1000 + it.z } ?: return null - val topLeft = tiles.minByOrNull { it.x * 1000 + it.z } ?: return null + val bottomRight = tiles.maxByOrNull { it.sortKey } ?: return null val (tlX, tlZ) = getRealPosition(topLeft.x, topLeft.z) val (brX, brZ) = getRealPosition(bottomRight.x, bottomRight.z) @@ -145,8 +179,7 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData return BlockPos(x + rotation.dx, y, z + rotation.dz) } - fun getRealPosition() = topLeft.x * 32 - 185 to topLeft.z * 32 - 185 - fun getRealPosition(x: Int, z: Int) = x * 32 - 185 to z * 32 - 185 + fun getRealPosition(x: Int = topLeft.x, z: Int = topLeft.z) = x * 32 - 185 to z * 32 - 185 fun getRelativeCoords(pos: BlockPos): BlockPos { val clay = clayPos ?: return BlockPos.ZERO @@ -57,4 +57,6 @@ value class IVec2 private constructor(val data: Long) { fun ChunkPos.toIVec2(): IVec2 { return IVec2(x, z) -} \ No newline at end of file +} + +val IVec2.sortKey: Int get() = x * 1000 + z \ No newline at end of file |
Fixed bat slain not being detected Fixed MapInfo text space issue查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 19 ++++++++----------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Mimic.kt | 3 ++- 2 files changed, 10 insertions(+), 12 deletions(-) @@ -80,7 +80,7 @@ object MapInfo : Module( val unknownSecretsText = if (unknown == 0) { buildString { - append("§7D: §c") + append("§7Deaths: §c") append(colorizeDeaths(cachedDeathCount)) } } else { @@ -91,12 +91,9 @@ object MapInfo : Module( } val mimicText = buildString { - append("§7M: ") - append(if (mimicKilled) "§a✔" else "§c✘") - append(" §8| §7P: ") - append(if (princeKilled) "§a✔" else "§c✘") - append(" §8| §7B: ") - append(if (batKilled) "§a✔" else "§c✘") + append("${if (mimicKilled) "§a" else "§c"}\uD83D\uDCE6") + append(" §8| ${if (princeKilled) "§a" else "§c"}\uD83E\uDD34") + append(" §8| ${if (batKilled) "§a" else "§c"}\uD83E\uDD87") } val cryptText = buildString { @@ -261,20 +258,20 @@ object MapInfo : Module( on<RoomEnterEvent> { currentRoomSecrets = null - if (room?.data?.type == RoomType.BLOOD) { + if (room?.name == "Blood") portalAABB = AABB.encapsulatingFullBlocks(room.getRealCoords(BlockPos(16, 69, 29)), room.getRealCoords(BlockPos(14, 69, 29))).inflate(0.0, 4.0, 0.0) - } } on<RenderEvent.Extract> { - if (!highlightPortal || !DungeonUtils.inClear || DungeonUtils.score < 300) return@on - portalAABB?.let{ pos -> + if (!highlightPortal || !DungeonUtils.inClear || cachedScore < 300) return@on + portalAABB?.let { pos -> drawFilledBox(pos, Colors.MINECRAFT_GREEN.withAlpha(0.5f), depth = true) } } on<LevelEvent.Load> { shownTitle = false + portalAABB = null } } @@ -42,11 +42,12 @@ object Mimic : Module( on<ChatPacketEvent> { if (value.matches(princeRegex)) princeKilled() + if (value.matches(batRegex)) batKilled() } } private fun mimicKilled() { - if (DungeonUtils.mimicKilled || DungeonUtils.inBoss) return + if (DungeonUtils.mimicKilled || !DungeonUtils.inClear) return if (mimicMessageToggle) sendCommand("pc Mimic Killed!") DungeonListener.dungeonStats.mimicKilled = true } |
Fixed PositionalMessages radius being incorrect查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/PositionalMessages.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) @@ -84,9 +84,10 @@ object PositionalMessages : Module( private fun handleAtString(posMessage: PosMessage) { val msgSent = sentMessages.getOrDefault(posMessage, false) val player = mc.player ?: return - if (player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= (posMessage.distance ?: return)) { + val radius = posMessage.distance ?: return + if (player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= radius * radius) { if (!msgSent) Timer().schedule(posMessage.delay) { - if (player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= posMessage.distance) + if (player.distanceToSqr(posMessage.x, posMessage.y, posMessage.z) <= radius * radius) sendCommand("pc ${posMessage.message}") } sentMessages[posMessage] = true |
Fixed PlayerDisplay element hider查看变更src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PlayerDisplay.kt | 8 ++++---- src/main/kotlin/com/odtheking/odin/utils/network/WebUtils.kt | 16 +++------------- src/main/kotlin/com/odtheking/odin/utils/skyblock/SplitsManager.kt | 2 +- 3 files changed, 8 insertions(+), 18 deletions(-) @@ -111,10 +111,10 @@ object PlayerDisplay : Module( } private val speedColor by ColorSetting("Speed color", Colors.WHITE, true, "The color of the speed text.") - private val HEALTH_REGEX = Regex("[\\d|,]+/[\\d|,]+❤") - private val MANA_REGEX = Regex("[\\d|,]+/[\\d|,]+✎( Mana)?") - private val OVERFLOW_MANA_REGEX = Regex("§?[\\d|,]+ʬ") - private val DEFENSE_REGEX = Regex("[\\d|,]+§a❈ Defense") + private val HEALTH_REGEX = Regex("[\\d|,]+/[\\d|,]+\\uE010") + private val MANA_REGEX = Regex("[\\d|,]+/[\\d|,]+\\uE003( Mana)?") + private val OVERFLOW_MANA_REGEX = Regex("§?[\\d|,]+\\uE017") + private val DEFENSE_REGEX = Regex("[\\d|,]+§a\\uE008 Defense") @JvmStatic fun modifyText(text: Component): Component { @@ -79,7 +79,7 @@ object WebUtils { if (!cont.isActive) return@whenComplete if (response.statusCode() in 200..299) cont.resume(Result.success(response)) - else cont.resume(Result.failure(InputStreamException(response.statusCode(), response.body()))) + else cont.resume(Result.failure(InputStreamException(response.body(), request.uri().toString()))) } } } @@ -90,15 +90,5 @@ object WebUtils { return mayor.get("name")?.asString == "Paul" && mayor.getAsJsonArray("perks")?.any { it.asJsonObject.get("name")?.asString == "EZPZ" } == true } - class InputStreamException( - code: Int, - body: String - ) : Exception( - buildString { - append(code) - if (body.isNotBlank()) { - append(": ") - append(body) - } - } - )} \ No newline at end of file + class InputStreamException(body: String, link: String) : Exception("$body : $link") +} \ No newline at end of file @@ -196,4 +196,4 @@ private val dungeonSplits = listOf( // https://regex101.com/r/BXKhOI/1 private val BLOOD_OPEN_REGEX = Regex("^\\[BOSS] The Watcher: (Congratulations, you made it through the Entrance\\.|Ah, you've finally arrived\\.|Ah, we meet again\\.\\.\\.|So you made it this far\\.\\.\\. interesting\\.|You've managed to scratch and claw your way here, eh\\?|I'm starting to get tired of seeing you around here\\.\\.\\.|Oh\\.\\. hello\\?|Things feel a little more roomy now, eh\\?)$|^The BLOOD DOOR has been opened!$") -private val MORT_REGEX = Regex("\\[NPC] Mort: Here, I found this map when I first entered the dungeon\\.|\\[NPC] Mort: Right-click the Orb for spells, and Left-click \\(or Drop\\) to use your Ultimate!") \ No newline at end of file +val MORT_REGEX = Regex("\\[NPC] Mort: Here, I found this map when I first entered the dungeon\\.|\\[NPC] Mort: Right-click the Orb for spells, and Left-click \\(or Drop\\) to use your Ultimate!") \ No newline at end of file |
Fixed secrets spawn timer查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) @@ -11,6 +11,8 @@ import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.render.textDim +import com.odtheking.odin.utils.skyblock.MORT_REGEX +import com.odtheking.odin.utils.skyblock.SplitsManager import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import com.odtheking.odin.utils.toFixed import net.minecraft.network.protocol.game.ClientboundSetTimePacket @@ -80,21 +82,21 @@ object TickTimers : Module( } private var outboundsTime = -1 + private var secretsCounter = 0 private val secretsHud by HUD("Secrets Hud", "Displays a timer for secret spawn ticks.") { - if (it) textDim(formatTimer(15, 20, "§7Secret:"), 0, 0, Colors.MINECRAFT_DARK_RED) - else if (secretsTime >= 0) textDim(formatTimer(secretsTime, 20, "§7Secret:"), 0, 0, Colors.MINECRAFT_DARK_RED) + if (it) textDim(formatTimer(15, 20, "§7Secret:"), 0, 0, Colors.MINECRAFT_DARK_RED) + else if (DungeonUtils.openRoomCount != 0) textDim(formatTimer(20 - secretsCounter % 20, 20, "§7Secret:"), 0, 0, Colors.MINECRAFT_DARK_RED) else 0 to 0 } - private var secretsTime = -1 - init { on<ChatPacketEvent> { when { - necronHud.enabled && value.matches(necronRegex) -> necronTime = 60 - goldorHud.enabled && value.matches(goldorRegex) -> goldorTickTime = 60 - goldorHud.enabled && value.matches(coreOpeningRegex) -> { + value.matches(MORT_REGEX) -> secretsCounter = 0 + value.matches(necronRegex) -> necronTime = 60 + value.matches(goldorRegex) -> goldorTickTime = 60 + value.matches(coreOpeningRegex) -> { goldorStartTime = -1 goldorTickTime = -1 } @@ -102,11 +104,11 @@ object TickTimers : Module( if (goldorHud.enabled) goldorStartTime = 104 if (stormHud.enabled) padTickTime = -1 } - (stormHud.enabled || lightningHud.enabled) && value.matches(stormPadRegex) -> { - if (stormHud.enabled) padTickTime = 20 - if (lightningHud.enabled) lightningTickTime = 560 + value.matches(stormPadRegex) -> { + padTickTime = 20 + lightningTickTime = 560 } - pyHud.enabled && !pyTriggered && value.matches(stormPyRegex) -> { + !pyTriggered && value.matches(stormPyRegex) -> { pyTriggered = true pyTickTime = 95 } @@ -115,29 +117,25 @@ object TickTimers : Module( on<TickEvent.Server> { if (!DungeonUtils.inDungeons) return@on - if (outboundsTime == 0 && outboundsHud.enabled) outboundsTime = 40 - if (outboundsTime >= 0 && outboundsHud.enabled) outboundsTime-- - if (secretsTime == 0 && secretsHud.enabled && !DungeonUtils.inBoss) secretsTime = 20 - if (secretsTime >= 0 && secretsHud.enabled) secretsTime-- + if (outboundsTime == 0) outboundsTime = 40 + if (outboundsTime >= 0) outboundsTime-- + secretsCounter++ if (!DungeonUtils.inBoss) return@on if (goldorTickTime == 0 && goldorStartTime <= 0 && goldorHud.enabled) goldorTickTime = 60 - if (goldorStartTime >= 0 && goldorHud.enabled) goldorStartTime-- - if (goldorTickTime >= 0 && goldorHud.enabled) goldorTickTime-- + if (goldorStartTime >= 0) goldorStartTime-- + if (goldorTickTime >= 0) goldorTickTime-- if (padTickTime == 0 && stormHud.enabled) padTickTime = 20 - if (padTickTime >= 0 && stormHud.enabled) padTickTime-- - if (lightningTickTime >= 0 && lightningHud.enabled) lightningTickTime-- - if (pyTickTime >= 0 && pyHud.enabled) pyTickTime-- - if (necronTime >= 0 && necronHud.enabled) necronTime-- + if (padTickTime >= 0) padTickTime-- + if (lightningTickTime >= 0) lightningTickTime-- + if (pyTickTime >= 0) pyTickTime-- + if (necronTime >= 0) necronTime-- } onReceive<ClientboundSetTimePacket> { if (!DungeonUtils.inClear) return@onReceive if (DungeonUtils.openRoomCount == 0) { if (outboundsHud.enabled) outboundsTime = 40 - (gameTime % 40).toInt() - } else { - if (secretsHud.enabled) secretsTime = 30 - (gameTime % 20).toInt() - outboundsTime = -1 - } + } else outboundsTime = -1 } on<LevelEvent.Load> { @@ -148,8 +146,8 @@ object TickTimers : Module( pyTickTime = -1 pyTriggered = false necronTime = -1 - secretsTime = -1 outboundsTime = -1 + secretsCounter = 0 } } @@ -162,4 +160,4 @@ object TickTimers : Module( val timeDisplay = if (displayInTicks) "$time${if (symbolDisplay) "t" else ""}" else "${(time / 20f).toFixed()}${if (symbolDisplay) "s" else ""}" return "${if (showPrefix) "$prefix " else ""}$color$timeDisplay" } -} +} \ No newline at end of file |
Cleaned up map impl查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 17 +++++++++--------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonScan.kt | 2 +-
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 42 +++++++++++++++++++++---------------------
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapScan.kt | 7 ++-----
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/WorldScan.kt | 11 +++++++----
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonDoor.kt | 9 +++++----
src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 3 +--
src/main/kotlin/com/odtheking/odin/utils/ChatUtils.kt | 2 +-
src/main/kotlin/com/odtheking/odin/utils/IVec2.kt | 12 ++++++++----
src/main/resources/assets/odin/map/{x.png => cross.png} | Bin
src/main/resources/assets/odin/map/{green.png => green_check.png} | Bin
src/main/resources/assets/odin/map/marker.png | Bin 236 -> 0 bytes
src/main/resources/assets/odin/map/{white.png => white_check.png} | Bin
src/main/resources/assets/odin/textures/map/cross.png | Bin 205 -> 0 bytes
src/main/resources/assets/odin/textures/map/green_check.png | Bin 188 -> 0 bytes
src/main/resources/assets/odin/textures/map/question.png | Bin 162 -> 0 bytes
src/main/resources/assets/odin/textures/map/white_check.png | Bin 186 -> 0 bytes
17 files changed, 55 insertions(+), 50 deletions(-)@@ -7,6 +7,7 @@ import com.odtheking.odin.clickgui.settings.impl.DropdownSetting import com.odtheking.odin.clickgui.settings.impl.NumberSetting import com.odtheking.odin.features.Module import com.odtheking.odin.utils.Color +import com.odtheking.odin.utils.Color.Companion.darker import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.render.hollowFill @@ -14,10 +15,9 @@ import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.client.gui.GuiGraphicsExtractor object DungeonMap : Module( - "Dungeon Map", + name = "Dungeon Map", description = "Displays the dungeon map." ) { - private val disableBoss by BooleanSetting("Disable in Boss", true, desc = "Disables the map during boss fights.") val backgroundOutline by ColorSetting("Background Outline", Colors.BLACK, true, desc = "The color of the background border.") @@ -28,12 +28,6 @@ object DungeonMap : Module( val playerNamesScaling by NumberSetting("Player Names Scaling", 0.75f, 0.1f, 2f, 0.05f, desc = "Scale of player name labels.").withDependency { playerDropdown } val playerNameColor by ColorSetting("Player Name Color", Color(70, 70, 70), true, desc = "Color of player name labels.").withDependency { playerDropdown } - private val doorDropdown by DropdownSetting("Door Settings") - val normalDoorColor by ColorSetting("Normal Door", Color(107, 58, 17), desc = "Color of normal doors.").withDependency { doorDropdown } - val witherDoorColor by ColorSetting("Wither Door", Colors.BLACK, true, desc = "Color of wither doors.").withDependency { doorDropdown } - val bloodDoorColor by ColorSetting("Blood Door", Color(231, 0, 0), true, desc = "Color of blood room doors.").withDependency { doorDropdown } - val fairyDoorColor by ColorSetting("Fairy Door", Color(224, 0, 255), true, desc = "Color of fairy room doors.").withDependency { doorDropdown } - private val roomDropdown by DropdownSetting("Room Settings") val normalRoomColor by ColorSetting("Normal Room", Color(107, 58, 17), true, desc = "Color of normal rooms.").withDependency { roomDropdown } val puzzleRoomColor by ColorSetting("Puzzle Room", Color(117, 0, 133), true, desc = "Color of puzzle rooms.").withDependency { roomDropdown } @@ -44,6 +38,13 @@ object DungeonMap : Module( val championRoomColor by ColorSetting("Champion Room", Color(254, 223, 0), true, desc = "Color of champion rooms.").withDependency { roomDropdown } val unknownRoomColor by ColorSetting("Unknown Room", Color(40, 40, 40), true, desc = "Color of unknown rooms hinted by a door with no discovered room on the other side.").withDependency { roomDropdown } + private val doorDropdown by DropdownSetting("Door Settings") + val normalDoorColor by ColorSetting("Normal Door", Color(107, 58, 17).darker(), desc = "Color of normal doors.").withDependency { doorDropdown } + val witherDoorColor by ColorSetting("Wither Door", Colors.BLACK, true, desc = "Color of wither doors.").withDependency { doorDropdown } + val bloodDoorColor by ColorSetting("Blood Door", Color(231, 0, 0).darker(), true, desc = "Color of blood room doors.").withDependency { doorDropdown } + val fairyDoorColor by ColorSetting("Fairy Door", Color(224, 0, 255).darker(), true, desc = "Color of fairy room doors.").withDependency { doorDropdown } + val unknownDoorColor by ColorSetting("Unknown Door", Color(40, 40, 40).darker(), true, desc = "Color of doors with no discovered room on the other side.").withDependency { doorDropdown } + val disablePred by BooleanSetting("Disable Prediction", false, desc = "Disables special-column room type prediction.") private val mapHud by HUD("Dungeon Map", "Displays the dungeon map.", false) { example -> @@ -103,7 +103,7 @@ object DungeonScan { DoorType.Blood -> DungeonMap.bloodDoorColor DoorType.Fairy -> DungeonMap.fairyDoorColor else -> { - if (!originView || !destView) DungeonMap.unknownRoomColor + if (!originView || !destView) DungeonMap.unknownDoorColor else listOfNotNull(originTile.room, destTile.room) .firstOrNull { it.type != RoomType.NORMAL && it.type != RoomType.FAIRY } ?.type?.let { room -> roomTypeColor(room) } ?: DungeonMap.normalDoorColor @@ -11,21 +11,21 @@ import net.minecraft.client.renderer.RenderPipelines import net.minecraft.resources.Identifier private val marker = Identifier.withDefaultNamespace("textures/map/decorations/frame.png") -private val cross = Identifier.fromNamespaceAndPath("odin", "textures/map/cross.png") -private val green = Identifier.fromNamespaceAndPath("odin", "textures/map/green_check.png") -private val white = Identifier.fromNamespaceAndPath("odin", "textures/map/white_check.png") -private val question = Identifier.fromNamespaceAndPath("odin", "textures/map/question.png") +private val cross = Identifier.fromNamespaceAndPath("odin", "map/cross.png") +private val green = Identifier.fromNamespaceAndPath("odin", "map/green_check.png") +private val white = Identifier.fromNamespaceAndPath("odin", "map/white_check.png") +private val question = Identifier.fromNamespaceAndPath("odin", "map/question.png") internal fun GuiGraphicsExtractor.renderMap() { pose().pushMatrix() pose().translate(DungeonScan.startX.toFloat(), DungeonScan.startY.toFloat()) + renderDoors() + for (room in DungeonScan.rooms) { if (room.isViewable) fillRoom(room, roomTypeColor(room.type).rgba, DungeonScan.roomSize, DungeonScan.roomGap) } - renderDoors() - for (room in DungeonScan.rooms) renderRoomText(room) renderPlayers() @@ -92,15 +92,15 @@ private fun GuiGraphicsExtractor.renderDoors() { 2 -> { val half = rs / 2 - fill(x, y, x + half, y + rs, colors[0].rgba) - fill(x + half, y, x + rs, y + rs, colors[1].rgba) + fill(x, y, x + half, y + rs, colors[0].darker(0.5f).rgba) + fill(x + half, y, x + rs, y + rs, colors[1].darker(0.5f).rgba) } 3 -> { val third = rs / 3 - fill(x, y, x + third, y + rs, colors[0].rgba) - fill(x + third, y, x + third * 2, y + rs, colors[1].rgba) - fill(x + third * 2, y, x + rs, y + rs, colors[2].rgba) + fill(x, y, x + third, y + rs, colors[0].darker(0.5f).rgba) + fill(x + third, y, x + third * 2, y + rs, colors[1].darker(0.5f).rgba) + fill(x + third * 2, y, x + rs, y + rs, colors[2].darker(0.5f).rgba) } } renderIcon(IVec2(x, y), question) @@ -108,8 +108,8 @@ private fun GuiGraphicsExtractor.renderDoors() { } fun GuiGraphicsExtractor.renderIcon(pos: IVec2, identifier: Identifier) { - val rs = DungeonScan.roomSize - blit(RenderPipelines.GUI_TEXTURED, identifier, pos.x, pos.z, rs.toFloat(), rs.toFloat(), rs, rs, rs, rs) + val rs = DungeonScan.roomSize - 4 + blit(RenderPipelines.GUI_TEXTURED, identifier, pos.x + 2, pos.z + 2, rs.toFloat(), rs.toFloat(), rs, rs, rs, rs) } private fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { @@ -136,7 +136,7 @@ private fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { else -> Color(100, 100, 100) }.rgba - val lines = scannedRoom?.name?.split(" ") ?: return + val lines = scannedRoom.name?.split(" ") ?: return val totalH = (lines.size - 1) * fontH * DungeonMap.textScaling for ((i, line) in lines.withIndex()) { @@ -151,13 +151,6 @@ private fun GuiGraphicsExtractor.renderRoomText(room: DungeonRoom) { private fun GuiGraphicsExtractor.renderPlayers() { val showNames = mc.player?.mainHandItem?.itemId?.equalsOneOf("INFINITE_SPIRIT_LEAP", "SPIRIT_LEAP") == true - pose().pushMatrix() - val (selfX, selfZ) = DungeonScan.playerRenderPosition(mc.player, IVec2(0, 0)) - pose().translate(selfX, selfZ) - pose().rotate(Math.toRadians(180.0 + (mc.player?.yRot ?: 0f)).toFloat()) - blit(RenderPipelines.GUI_TEXTURED, marker, -2, -3, 2f, 0f, 5, 7, 8, 8) - pose().popMatrix() - for (player in DungeonUtils.dungeonTeammatesNoSelf) { if (player.isDead) continue val (px, pz) = DungeonScan.playerRenderPosition(player.entity, player.mapPos) @@ -181,6 +174,13 @@ private fun GuiGraphicsExtractor.renderPlayers() { pose().popMatrix() } + + pose().pushMatrix() + val (selfX, selfZ) = DungeonScan.playerRenderPosition(mc.player, IVec2(0, 0)) + pose().translate(selfX, selfZ) + pose().rotate(Math.toRadians(180.0 + (mc.player?.yRot ?: 0f)).toFloat()) + blit(RenderPipelines.GUI_TEXTURED, marker, -2, -3, 2f, 0f, 5, 7, 8, 8) + pose().popMatrix() } private fun textCenter(room: DungeonRoom): Pair<Float, Float> { @@ -209,11 +209,8 @@ object MapScan { } private fun addOrFixDoor(position: IVec2, rotation: DoorRotation, doorType: DoorType) { - val chunkPos = IVec2(-12 + 2 * position.x + rotation.offset.x, -12 + 2 * position.z + rotation.offset.z) - val originIndex = position.x + position.z * 6 - val destPos = IVec2(position.x + rotation.offset.x, position.z + rotation.offset.z) - val destIndex = destPos.x + destPos.z * 6 - DungeonScan.doors.getOrPut(chunkPos) { DungeonDoor(position, rotation, doorType, originIndex, destIndex) }.type = doorType + DungeonScan.doors.getOrPut(IVec2(-12 + 2 * position.x + rotation.offset.x, -12 + 2 * position.z + rotation.offset.z)) + { DungeonDoor(position, rotation, doorType) }.type = doorType } private fun getPx(colors: ByteArray, x: Int, z: Int): Byte = @@ -86,7 +86,11 @@ object WorldScan { val rowEven = chunkPosition.x % 2 == 0 if (chunkPosition.x in -12..-2 && chunkPosition.z in -12..-2) { - if (rowEven && columnEven) scanRoom(chunk, chunkPosition) + if (rowEven && columnEven) { + val tilePos = (chunkPosition / 2) + 6 + val tile = DungeonScan.tiles.getOrNull(tilePos.x + tilePos.z * 6) + if (tile?.room?.data == null) scanRoom(chunk, chunkPosition) + } else if (rowEven || columnEven) scanDoor(chunk, chunkPosition, if (columnEven) DoorRotation.Horizontal else DoorRotation.Vertical) } } @@ -106,7 +110,7 @@ object WorldScan { else -> DoorType.Normal } val doorPos = ((chunkPosition - 1) / 2) + 6 - DungeonScan.doors[chunkPosition] = DungeonDoor(doorPos, rotation, type, doorPos.x + doorPos.z * 6, doorPos.x + rotation.offset.x + (doorPos.z + rotation.offset.z) * 6) + DungeonScan.doors[chunkPosition] = DungeonDoor(doorPos, rotation, type) } private fun scanRoom(chunk: LevelChunk, chunkPosition: IVec2) { @@ -126,6 +130,7 @@ object WorldScan { } if (tile.room !== room) { + room.highestBlock = highestBlock tile.room = room room.addSegment(tile) } @@ -134,8 +139,6 @@ object WorldScan { room.data = data room.type = data.type } - - if (room.tiles.size == data.shape.tileAmount) room.inferLayout(highestBlock) } private val stringBuilder = StringBuilder(1024) @@ -5,10 +5,11 @@ import com.odtheking.odin.utils.IVec2 class DungeonDoor( val position: IVec2, val rotation: DoorRotation, - var type: DoorType, - val originTileIndex: Int, - val destinationTileIndex: Int, -) + var type: DoorType +) { + val originTileIndex by lazy { position.x + position.z * 6 } + val destinationTileIndex by lazy { position.x + rotation.offset.x + (position.z + rotation.offset.z) * 6 } +} enum class DoorType { Normal, @@ -32,6 +32,7 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData if (!tiles.contains(segment.position)) { tiles.add(segment.position) topLeft = IVec2(tiles.minOf { it.x }, tiles.minOf { it.z }) + highestBlock?.let { if (tiles.size == data?.shape?.tileAmount) inferLayout(it) } } } @@ -53,8 +54,6 @@ class DungeonRoom(var type: RoomType, initialPosition: IVec2, var data: RoomData } fun inferLayout(highestBlock: Int) { - this.highestBlock = highestBlock - val roomData = data ?: return if (applyFairyFallback(highestBlock)) return @@ -27,7 +27,7 @@ fun modMessage(message: Component, prefix: String = "§3Odin §8»§r ", chatSty } fun devMessage(message: Any?) { - if (!ClickGUIModule.devMessage) return + if (!ClickGUIModule.devMessage) return println(message) modMessage(message, "§3Odin§bDev §8»§r ") } @@ -30,10 +30,18 @@ value class IVec2 private constructor(val data: Long) { return IVec2(x + rhs, z + rhs) } + operator fun plus(rhs: IVec2): IVec2 { + return IVec2(x + rhs.x, z + rhs.z) + } + operator fun minus(rhs: Int): IVec2 { return IVec2(x - rhs, z - rhs) } + operator fun minus(rhs: IVec2): IVec2 { + return IVec2(x - rhs.x, z - rhs.z) + } + operator fun times(rhs: Int): IVec2 { return IVec2(x * rhs, z * rhs) } @@ -45,10 +53,6 @@ value class IVec2 private constructor(val data: Long) { override fun toString(): String { return "IVec2(x=$x, z=$z)" } - - fun flip(): IVec2 { - return IVec2(z, x) - } } fun ChunkPos.toIVec2(): IVec2 { |
Added bat slain support to Mimic module查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 12 +++++++++--- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Mimic.kt | 12 +++++++++++- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 4 ++++ src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonUtils.kt | 4 ++++ 4 files changed, 28 insertions(+), 4 deletions(-) @@ -41,6 +41,7 @@ object MapInfo : Module( private var cachedKnownSecrets = 0 private var cachedMimicKilled = false private var cachedPrinceKilled = false + private var cachedBatKilled = false private var cachedCryptCount = 0 private var cachedDeathCount = 0 @@ -53,6 +54,7 @@ object MapInfo : Module( val totalSecrets = cachedTotalSecrets val mimicKilled = cachedMimicKilled val princeKilled = cachedPrinceKilled + val batKilled = cachedBatKilled val cryptCount = cachedCryptCount val showRemaining = fullAddRemaining && alternate @@ -78,7 +80,7 @@ object MapInfo : Module( val unknownSecretsText = if (unknown == 0) { buildString { - append("§7Deaths: §c") + append("§7D: §c") append(colorizeDeaths(cachedDeathCount)) } } else { @@ -93,6 +95,8 @@ object MapInfo : Module( append(if (mimicKilled) "§a✔" else "§c✘") append(" §8| §7P: ") append(if (princeKilled) "§a✔" else "§c✘") + append(" §8| §7B: ") + append(if (batKilled) "§a✔" else "§c✘") } val cryptText = buildString { @@ -169,12 +173,13 @@ object MapInfo : Module( private val compactScore: HudElement by HUD("Compact Score", "Displays a compact score hud with score info.") { if ((!DungeonUtils.inDungeons || (disableInBoss && DungeonUtils.inBoss)) && !it) return@HUD 0 to 0 - val score = cachedScore val mimicKilled = cachedMimicKilled val princeKilled = cachedPrinceKilled + val batKilled = cachedBatKilled + + val missing = (if (mimicKilled) 0 else 2) + (if (princeKilled) 0 else 1) + (if (batKilled) 0 else 1) - val missing = (if (mimicKilled) 0 else 2) + (if (princeKilled) 0 else 1) val scoreText = buildString { append("§7Score: ") append(colorizeScore(score)) @@ -235,6 +240,7 @@ object MapInfo : Module( cachedKnownSecrets = DungeonUtils.knownSecrets cachedMimicKilled = DungeonUtils.mimicKilled cachedPrinceKilled = DungeonUtils.princeKilled + cachedBatKilled = DungeonUtils.batKilled cachedCryptCount = DungeonUtils.cryptCount.coerceAtMost(5) cachedDeathCount = DungeonUtils.deathCount } @@ -15,7 +15,7 @@ import net.minecraft.world.level.Level object Mimic : Module( name = "Mimic", - description = "Highlights and announces mimic kills in dungeons." + description = "Announces Mimic, Prince and Bat kills in dungeons." ) { private val mimicMessageToggle by BooleanSetting("Send Mimic Message", true, desc = "Toggles the mimic killed message.") private val reset by ActionSetting("Mimic Killed", desc = "Sends Mimic killed message in party chat.") { mimicKilled() } @@ -23,7 +23,11 @@ object Mimic : Module( private val princeMessageToggle by BooleanSetting("Send Prince Message", true, desc = "Toggles the prince killed message.") private val princeReset by ActionSetting("Prince Killed", desc = "Sends Prince killed message in party chat.") { princeKilled() } + private val batMessageToggle by BooleanSetting("Send Bat Message", true, desc = "Toggles the bat killed message.") + private val batReset by ActionSetting("Bat Killed", desc = "Sends Bat killed message in party chat.") { batKilled() } + private val princeRegex = Regex("^A Prince falls\\. \\+1 Bonus Score$") + private val batRegex = Regex("^A Bat has been slain\\. \\+1 Bonus Score$") init { onReceive<ClientboundEntityEventPacket> { @@ -52,4 +56,10 @@ object Mimic : Module( if (princeMessageToggle) sendCommand("pc Prince Killed!") DungeonListener.dungeonStats.princeKilled = true } + + private fun batKilled() { + if (DungeonUtils.batKilled || !DungeonUtils.inClear) return + if (batMessageToggle) sendCommand("pc Bat Killed!") + DungeonListener.dungeonStats.batKilled = true + } } \ No newline at end of file @@ -111,6 +111,9 @@ object DungeonListener { "prince killed", "prince slain", "prince killed!", "prince dead", "prince dead!", -> dungeonStats.princeKilled = true + "bat killed", "bat slain", "bat killed!", "bat dead", "bat dead!" -> + dungeonStats.batKilled = true + "blaze done!", "blaze done", "blaze puzzle solved!" -> puzzles.find { it == Puzzle.BLAZE }.let { it?.status = PuzzleStatus.Completed } } @@ -203,6 +206,7 @@ object DungeonListener { var elapsedTime: String = "0s", private var _mimicKilled: Boolean = false, var princeKilled: Boolean = false, + var batKilled: Boolean = false, var doorOpener: String = "Unknown", var bloodDone: Boolean = false, var puzzleCount: Int = 0, @@ -95,6 +95,9 @@ object DungeonUtils { inline val princeKilled: Boolean get() = DungeonListener.dungeonStats.princeKilled + inline val batKilled: Boolean + get() = DungeonListener.dungeonStats.batKilled + inline val currentRoom: DungeonRoom? get() = WorldScan.currentRoom @@ -106,6 +109,7 @@ object DungeonUtils { var score = cryptCount.coerceAtMost(5) if (mimicKilled) score += 2 if (princeKilled) score += 1 + if (batKilled) score += 1 if ((isPaul && togglePaul == 0) || togglePaul == 2) score += 10 return score } |
Made network error messages contain body查看变更src/main/kotlin/com/odtheking/odin/commands/CataCommand.kt | 4 +--- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/CommandKeybinds.kt | 2 +- src/main/kotlin/com/odtheking/odin/utils/network/WebUtils.kt | 20 +++++++++++++++----- 3 files changed, 17 insertions(+), 9 deletions(-) @@ -28,9 +28,7 @@ fun fetchAndDisplayCataStats(result: Result<HypixelData.PlayerInfo>) { playerInfo.memberData?.let { displayCataStats(playerInfo.name, it) } ?: modMessage("§cNo profile found for §6${playerInfo.name}§c!") }, - onFailure = { error -> - modMessage("§cFailed to fetch dungeon stats: ${error.message}") - } + onFailure = { modMessage("§c${it.message}") } ) } @@ -23,7 +23,7 @@ object CommandKeybinds : Module( if (!enabled || !LocationUtils.isInSkyblock) return@onPress sendCommand("armor") } - private val equipmentSets by KeybindSetting("Equipment Wardrobe", GLFW.GLFW_KEY_UNKNOWN, desc = "Opens the equipment wardrobe menu.").onPress { + private val equipmentSets by KeybindSetting("Equip Wardrobe", GLFW.GLFW_KEY_UNKNOWN, desc = "Opens the equipment wardrobe menu.").onPress { if (!enabled || !LocationUtils.isInSkyblock) return@onPress sendCommand("equipment") } @@ -29,12 +29,12 @@ object WebUtils { suspend fun fetchString(url: String): Result<String> = executeRequest(createGetRequest(url)) .mapCatching { response -> response.body() } - .onFailure { logger.warn("Failed to fetch from $url: ${it.message}") } + .onFailure { logger.warn("Failed to fetch from $url: ${it.message} ${it.cause}") } suspend fun getInputStream(url: String): Result<InputStream> = executeRequest(createGetRequest(url)) .map { response -> response.body().byteInputStream() } - .onFailure { logger.warn("Failed to get input stream from $url: ${it.message}") } + .onFailure { logger.warn("Failed to get input stream from $url: ${it.message} ${it.cause}") } suspend fun postData(url: String, body: String): Result<String> = executeRequest( @@ -79,7 +79,7 @@ object WebUtils { if (!cont.isActive) return@whenComplete if (response.statusCode() in 200..299) cont.resume(Result.success(response)) - else cont.resume(Result.failure(InputStreamException(response.statusCode(), request.uri().toString()))) + else cont.resume(Result.failure(InputStreamException(response.statusCode(), response.body()))) } } } @@ -90,5 +90,15 @@ object WebUtils { return mayor.get("name")?.asString == "Paul" && mayor.getAsJsonArray("perks")?.any { it.asJsonObject.get("name")?.asString == "EZPZ" } == true } - class InputStreamException(code: Int, url: String) : Exception("Failed to get input stream from $url: HTTP $code") -} \ No newline at end of file + class InputStreamException( + code: Int, + body: String + ) : Exception( + buildString { + append(code) + if (body.isNotBlank()) { + append(": ") + append(body) + } + } + )} \ No newline at end of file |
Fixed secret spawn timer being incorrectly offset查看变更src/main/kotlin/com/odtheking/odin/features/impl/boss/TickTimers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -135,7 +135,7 @@ object TickTimers : Module( if (DungeonUtils.openRoomCount == 0) { if (outboundsHud.enabled) outboundsTime = 40 - (gameTime % 40).toInt() } else { - if (secretsHud.enabled) secretsTime = 20 - (gameTime % 20).toInt() + if (secretsHud.enabled) secretsTime = 30 - (gameTime % 20).toInt() outboundsTime = -1 } } |
Fixed PetKeybinds pages regex查看变更src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PetKeybinds.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) @@ -35,7 +35,7 @@ object PetKeybinds : Module( private val pet9 by KeybindSetting("Pet 9", GLFW.GLFW_KEY_9, "Pet 9 on the list.").withDependency { advanced } private val petSlots = intArrayOf(10, 11, 12, 13, 14, 15, 16, 19, 20) - private val petsRegex = Regex("Pets(?: \\((\\d)/(\\d)\\))?") + private val petsRegex = Regex("(?:\\((\\d)/(\\d)\\)\\s*)?Pets") init { on<ScreenEvent.MouseClick> { @@ -47,7 +47,6 @@ object PetKeybinds : Module( } } - private fun onClick(screen: AbstractContainerScreen<*>, keyCode: Int): Boolean { val (current, total) = petsRegex.find(screen.title.string)?.destructured?.let { (it.component1().toIntOrNull() ?: 1) to (it.component2().toIntOrNull() ?: 1) |
Fixed SkyblockPlayer stat tracking查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/SkyblockPlayer.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) @@ -22,10 +22,10 @@ object SkyblockPlayer { current overflow mana */ - private val HEALTH_REGEX = Regex("([\\d|,]+)/([\\d|,]+)❤") - private val MANA_REGEX = Regex("([\\d|,]+)/([\\d|,]+)✎") - private val OVERFLOW_MANA_REGEX = Regex("([\\d|,]+)ʬ") - private val DEFENSE_REGEX = Regex("([\\d|,]+)❈ Defense") + private val HEALTH_REGEX = Regex("([\\d|,]+)/([\\d|,]+)\\uE010") + private val MANA_REGEX = Regex("([\\d|,]+)/([\\d|,]+)\\uE003") + private val OVERFLOW_MANA_REGEX = Regex("([\\d|,]+)\\uE017") + private val DEFENSE_REGEX = Regex("([\\d|,]+)\\uE008 Defense") var currentHealth: Int = 0 private set |
Cleaned up StartsWithHandler查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/StartsWithHandler.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) @@ -35,15 +35,9 @@ class StartsWithHandler(private val letter: String): TerminalHandler(TerminalTyp override fun click(slotIndex: Int, button: Int, simulateClick: Boolean) { val screenHandler = (mc.screen as? ContainerScreen)?.menu ?: return - if (canClick(slotIndex, button) && clickedSlot == null) { + if (canClick(slotIndex, button) && clickedSlot == null) clickedSlot = screenHandler.containerId to slotIndex - val clickedItem = screenHandler.slots.getOrNull(slotIndex)?.item - if (clickedItem == Items.NETHER_STAR || clickedItem == Items.EXPERIENCE_BOTTLE) { - clickedSlots.add(slotIndex) - } - } - super.click(slotIndex, button, simulateClick) } |
| Fix Starts With X solver (#117) fix starts with x solver not highlighting netherstar and xp bottle 查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/terminals/terminalhandler/StartsWithHandler.kt | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) @@ -15,28 +15,37 @@ class StartsWithHandler(private val letter: String): TerminalHandler(TerminalTyp private var clickedSlot: Pair<Int, Int>? = null - override fun solve(items: List<ItemStack>): List<Int> { - clickedSlot?.let { - val screenHandler = (mc.screen as? ContainerScreen)?.menu - if (it.first != screenHandler?.containerId) { - val item = items[it.second].item - if (item == Items.NETHER_STAR || item == Items.EXPERIENCE_BOTTLE) clickedSlots.add(it.second) - clickedSlot = null + override fun solve(items: List<ItemStack>): List<Int> { + clickedSlot?.let { + val screenHandler = (mc.screen as? ContainerScreen)?.menu + if (it.first != screenHandler?.containerId) { + val item = items[it.second].item + if (item == Items.NETHER_STAR || item == Items.EXPERIENCE_BOTTLE) clickedSlots.add(it.second) + clickedSlot = null + } } - } - return items.mapIndexedNotNull { index, item -> - if (item.hoverName.string.startsWith(letter, true) && !item.hasGlint() && index !in clickedSlots) index else null + return items.mapIndexedNotNull { index, item -> + if (item.hoverName.string.startsWith(letter, true) && + index !in clickedSlots && + (!item.hasGlint() || item.item == Items.NETHER_STAR || item.item == Items.EXPERIENCE_BOTTLE) + ) index else null + } } - } - override fun click(slotIndex: Int, button: Int, simulateClick: Boolean) { - val screenHandler = (mc.screen as? ContainerScreen)?.menu ?: return - if (canClick(slotIndex, button) && clickedSlot == null) - clickedSlot = screenHandler.containerId to slotIndex + override fun click(slotIndex: Int, button: Int, simulateClick: Boolean) { + val screenHandler = (mc.screen as? ContainerScreen)?.menu ?: return + if (canClick(slotIndex, button) && clickedSlot == null) { + clickedSlot = screenHandler.containerId to slotIndex - super.click(slotIndex, button, simulateClick) - } + val clickedItem = screenHandler.slots.getOrNull(slotIndex)?.item + if (clickedItem == Items.NETHER_STAR || clickedItem == Items.EXPERIENCE_BOTTLE) { + clickedSlots.add(slotIndex) + } + } + + super.click(slotIndex, button, simulateClick) + } - override fun renderSlot(slotIndex: Int): Pair<Color, String?> = TerminalSolver.startsWithColor to null -} \ No newline at end of file + override fun renderSlot(slotIndex: Int): Pair<Color, String?> = TerminalSolver.startsWithColor to null +} |
| Keybind enhancements for the Loadouts update (#115) * fix: wardrobe keybinds not working after 0.26
* fix: change equipment keybind to /stats
* fix: clarify command keybinds after 0.26
* feat: add support for equipment in wardrobe keybinds 查看变更src/main/kotlin/com/odtheking/odin/features/impl/skyblock/CommandKeybinds.kt | 16 ++++++++++++---- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/WardrobeKeybinds.kt | 8 ++++---- 2 files changed, 16 insertions(+), 8 deletions(-) @@ -19,14 +19,22 @@ object CommandKeybinds : Module( if (!enabled || !LocationUtils.isInSkyblock) return@onPress sendCommand("storage") } - private val wardrobe by KeybindSetting("Wardrobe", GLFW.GLFW_KEY_UNKNOWN, desc = "Opens the wardrobe menu.").onPress { + private val armorSets by KeybindSetting("Armor Wardrobe", GLFW.GLFW_KEY_UNKNOWN, desc = "Opens the armor wardrobe menu.").onPress { if (!enabled || !LocationUtils.isInSkyblock) return@onPress - sendCommand("wardrobe") + sendCommand("armor") } - private val equipment by KeybindSetting("Equipment", GLFW.GLFW_KEY_UNKNOWN, desc = "Opens the equipment menu.").onPress { + private val equipmentSets by KeybindSetting("Equipment Wardrobe", GLFW.GLFW_KEY_UNKNOWN, desc = "Opens the equipment wardrobe menu.").onPress { if (!enabled || !LocationUtils.isInSkyblock) return@onPress sendCommand("equipment") } + private val loadouts by KeybindSetting("Loadouts", GLFW.GLFW_KEY_UNKNOWN, desc = "Opens the loadouts menu.").onPress { + if (!enabled || !LocationUtils.isInSkyblock) return@onPress + sendCommand("loadout") + } + private val stats by KeybindSetting("Stats", GLFW.GLFW_KEY_UNKNOWN, desc = "Opens the stats menu.").onPress { + if (!enabled || !LocationUtils.isInSkyblock) return@onPress + sendCommand("stats") + } private val dhub by KeybindSetting("Dungeon Hub", GLFW.GLFW_KEY_UNKNOWN, desc = "Warps to the dungeon hub.").onPress { if (!enabled || !LocationUtils.isInSkyblock) return@onPress sendCommand("warp dungeon_hub") @@ -35,4 +43,4 @@ object CommandKeybinds : Module( if (!enabled || !LocationUtils.isInSkyblock) return@onPress sendCommand("potionbag") } -} \ No newline at end of file +} @@ -20,7 +20,7 @@ object WardrobeKeybinds : Module( private val nextPageKeybind by KeybindSetting("Next Page", GLFW.GLFW_KEY_RIGHT, desc = "Keybind to go to the next page in the wardrobe.") private val previousPageKeybind by KeybindSetting("Previous Page", GLFW.GLFW_KEY_LEFT, desc = "Keybind to go to the previous page in the wardrobe.") private val unequipKeybind by KeybindSetting("Unequip", GLFW.GLFW_KEY_UNKNOWN, desc = "Keybind to unequip the currently equipped item in the wardrobe.") - private val disallowUnequippingEquipped by BooleanSetting("Disable Unequip", desc = "Prevents unequipping equipped armor.") + private val disallowUnequippingEquipped by BooleanSetting("Disable Unequip", desc = "Prevents unequipping equipped set.") private val advanced by DropdownSetting("Show Settings") private val wardrobe1 by KeybindSetting("Wardrobe 1", GLFW.GLFW_KEY_1, desc = "Keybind to equip the first wardrobe slot.").withDependency { advanced } @@ -33,7 +33,7 @@ object WardrobeKeybinds : Module( private val wardrobe8 by KeybindSetting("Wardrobe 8", GLFW.GLFW_KEY_8, desc = "Keybind to equip the eighth wardrobe slot.").withDependency { advanced } private val wardrobe9 by KeybindSetting("Wardrobe 9", GLFW.GLFW_KEY_9, desc = "Keybind to equip the ninth wardrobe slot.").withDependency { advanced } - private val wardrobeRegex = Regex("Wardrobe \\((\\d)/(\\d)\\)") + private val wardrobeRegex = Regex("\\((\\d)/(\\d)\\) (Armor|Equipment) Sets") private val equippedRegex = Regex("Slot (\\d): Equipped") init { @@ -62,7 +62,7 @@ object WardrobeKeybinds : Module( val keyIndex = arrayOf(wardrobe1, wardrobe2, wardrobe3, wardrobe4, wardrobe5, wardrobe6, wardrobe7, wardrobe8, wardrobe9) .indexOfFirst { it.value == keyCode }.takeIf { it != -1 } ?: return false - if (equippedIndex == keyIndex + 36 && disallowUnequippingEquipped) return modMessage("§cArmor already equipped.").let { false } + if (equippedIndex == keyIndex + 36 && disallowUnequippingEquipped) return modMessage("§cSet already equipped.").let { false } keyIndex + 36 } } @@ -70,4 +70,4 @@ object WardrobeKeybinds : Module( mc.player?.clickSlot(screen.menu.containerId, index) return true } -} \ No newline at end of file +} |
| Worldscan scanRoom message kept spamming -> changed to devMessage (#116) * fix change Worldscan modMessage to devMessage
* Updated the blaze regex 查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/WorldScan.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BlazeSolver.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) @@ -111,7 +111,7 @@ object WorldScan { private fun scanRoom(chunk: LevelChunk, chunkPosition: IVec2) { val (core, highestBlock) = getRoomCore(chunk, (chunkPosition * 16) + 7) - val data = RoomData.getRoomData(core) ?: return modMessage("Unknown room data for core: $core $chunkPosition") + val data = RoomData.getRoomData(core) ?: return devMessage("Unknown room data for core: $core $chunkPosition") val tilePosition = (chunkPosition / 2) + 6 val tile = DungeonScan.tiles.getOrNull(tilePosition.x + (tilePosition.z * 6)) ?: return @@ -19,7 +19,7 @@ object BlazeSolver { private var blazes = mutableListOf<ArmorStand>() private var roomType = 0 private var lastBlazeCount = 10 - private val blazeHealthRegex = Regex("^\\[Lv15] ♨ Blaze [\\d,]+/([\\d,]+)❤$") + private val blazeHealthRegex = Regex("^\\[Lv15] Blaze [\\d,]+/([\\d,]+)❤$") fun getBlaze() { if (!DungeonUtils.inDungeons || DungeonUtils.currentRoom?.data?.name?.equalsOneOf("Lower Blaze", "Higher Blaze") == false) return |
Added LoadoutKeybinds查看变更src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt | 2 +- src/main/kotlin/com/odtheking/odin/features/impl/skyblock/LoadoutKeybinds.kt | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PetKeybinds.kt | 2 +- 3 files changed, 79 insertions(+), 2 deletions(-) @@ -74,7 +74,7 @@ object ModuleManager { //skyblock ChatCommands, NoCursorReset, Ragnarock, SpringBoots, WardrobeKeybinds, PetKeybinds, AutoSprint, - CommandKeybinds, SlotBinds, Splits, + CommandKeybinds, SlotBinds, Splits, LoadoutKeybinds, // nether SupplyHelper, BuildHelper, RemovePerks, NoPre, PearlWaypoints, FreshTools, KuudraInfo, Misc, Vesuvius, @@ -0,0 +1,77 @@ +package com.odtheking.odin.features.impl.skyblock + +import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency +import com.odtheking.odin.clickgui.settings.impl.BooleanSetting +import com.odtheking.odin.clickgui.settings.impl.DropdownSetting +import com.odtheking.odin.clickgui.settings.impl.KeybindSetting +import com.odtheking.odin.events.ScreenEvent +import com.odtheking.odin.events.core.on +import com.odtheking.odin.features.Module +import com.odtheking.odin.utils.clickSlot +import com.odtheking.odin.utils.modMessage +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import org.lwjgl.glfw.GLFW +import java.security.Key + +object LoadoutKeybinds : Module( + name = "Loadout Keybinds", + description = "Allows you to use keybinds to navigate the loadout.", + key = null +) { + private val nextPageKeybind by KeybindSetting("Next Page", GLFW.GLFW_KEY_RIGHT, desc = "Keybind to go to the next page in the loadout.") + private val previousPageKeybind by KeybindSetting("Previous Page", GLFW.GLFW_KEY_LEFT, desc = "Keybind to go to the previous page in the loadout.") + + private val advanced by DropdownSetting("Show Settings") + private val loadout1 by KeybindSetting("Loadout 1", GLFW.GLFW_KEY_1, desc = "Keybind to equip the first loadout slot.").withDependency { advanced } + private val loadout2 by KeybindSetting("Loadout 2", GLFW.GLFW_KEY_2, desc = "Keybind to equip the second loadout slot.").withDependency { advanced } + private val loadout3 by KeybindSetting("Loadout 3", GLFW.GLFW_KEY_3, desc = "Keybind to equip the third loadout slot.").withDependency { advanced } + private val loadout4 by KeybindSetting("Loadout 4", GLFW.GLFW_KEY_4, desc = "Keybind to equip the fourth loadout slot.").withDependency { advanced } + private val loadout5 by KeybindSetting("Loadout 5", GLFW.GLFW_KEY_5, desc = "Keybind to equip the fifth loadout slot.").withDependency { advanced } + private val loadout6 by KeybindSetting("Loadout 6", GLFW.GLFW_KEY_6, desc = "Keybind to equip the sixth loadout slot.").withDependency { advanced } + private val loadout7 by KeybindSetting("Loadout 7", GLFW.GLFW_KEY_7, desc = "Keybind to equip the seventh loadout slot.").withDependency { advanced } + private val loadout8 by KeybindSetting("Loadout 8", GLFW.GLFW_KEY_8, desc = "Keybind to equip the eighth loadout slot.").withDependency { advanced } + private val loadout9 by KeybindSetting("Loadout 9", GLFW.GLFW_KEY_9, desc = "Keybind to equip the ninth loadout slot.").withDependency { advanced } + private val loadout10 by KeybindSetting("Loadout 10", GLFW.GLFW_KEY_0, desc = "Keybind to equip the tenth loadout slot.").withDependency { advanced } + private val loadout11 by KeybindSetting("Loadout 11", GLFW.GLFW_KEY_MINUS, desc = "Keybind to equip the eleventh loadout slot.").withDependency { advanced } + private val loadout12 by KeybindSetting("Loadout 12", GLFW.GLFW_KEY_EQUAL, desc = "Keybind to equip the twelfth loadout slot.").withDependency { advanced } + + private val loadoutRegex = Regex("\\((\\d)/(\\d)\\) Loadout") + private val loadoutSlots = intArrayOf( + 14, 15, 16, + 23, 24, 25, + 32, 33, 34, + 41, 42, 43 + ) + + init { + on<ScreenEvent.MouseClick> { + if (screen is AbstractContainerScreen<*> && onClick(screen, click.button())) cancel() + } + + on<ScreenEvent.KeyPress> { + if (screen is AbstractContainerScreen<*> && onClick(screen, input.key)) cancel() + } + } + + private fun onClick(screen: AbstractContainerScreen<*>, keyCode: Int): Boolean { + val (current, total) = loadoutRegex.find(screen.title.string)?.destructured?.let { + it.component1().toIntOrNull() to it.component2().toIntOrNull() + } ?: return false + if (current == null || total == null) return false + + val index = when (keyCode) { + nextPageKeybind.value -> if (current < total) 44 else return false + previousPageKeybind.value -> if (current > 1) 17 else return false + else -> { + val keyIndex = arrayOf(loadout1, loadout2, loadout3, loadout4, loadout5, loadout6, loadout7, loadout8, loadout9, loadout10, loadout11, loadout12) + .indexOfFirst { it.value == keyCode } + if (keyIndex == -1) return false + + loadoutSlots.getOrNull(keyIndex) ?: return false + } + } + + mc.player?.clickSlot(screen.menu.containerId, index) + return true + } +} \ No newline at end of file @@ -34,6 +34,7 @@ object PetKeybinds : Module( private val pet8 by KeybindSetting("Pet 8", GLFW.GLFW_KEY_8, "Pet 8 on the list.").withDependency { advanced } private val pet9 by KeybindSetting("Pet 9", GLFW.GLFW_KEY_9, "Pet 9 on the list.").withDependency { advanced } + private val petSlots = intArrayOf(10, 11, 12, 13, 14, 15, 16, 19, 20) private val petsRegex = Regex("Pets(?: \\((\\d)/(\\d)\\))?") init { @@ -46,7 +47,6 @@ object PetKeybinds : Module( } } - private val petSlots = intArrayOf(10, 11, 12, 13, 14, 15, 16, 19, 20) private fun onClick(screen: AbstractContainerScreen<*>, keyCode: Int): Boolean { val (current, total) = petsRegex.find(screen.title.string)?.destructured?.let { |
Update DungeonMap module name查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) @@ -13,7 +13,10 @@ import com.odtheking.odin.utils.render.hollowFill import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import net.minecraft.client.gui.GuiGraphicsExtractor -object DungeonMap : Module("Bad map", description = "Displays the dungeon map.") { +object DungeonMap : Module( + "Dungeon Map", + description = "Displays the dungeon map." +) { private val disableBoss by BooleanSetting("Disable in Boss", true, desc = "Disables the map during boss fights.") |
| dungeon map (#78) * begin on dungeon scanning refactor and dungeon map
* Added room state and player location tracking
Fixed rotations
* Fixed merge
* remove unnecessary null saftey
* Added doors and rooms support to DungeonMapScan
* Fixed segments not being added to rooms properly
* fix merge
* cleaned up some stuff I guess
* Basic map works
* Improve map rendering
* Removed old scanning and migrated to new scanning
* Update module name
* Started on client side map
* Made Map consistently stable
* Port map to 26.1
* Possibly fix out of sync map item scanning
* Cleanup map impl
Added DoorHighlight
* Added better singleplayer support
* Added Room prediction
Added RoomClear module
Fixed Layers and Redstone Crypt not getting scanned properly
* Removed debug message
* Fixed MapUpdateEvent syntax
---------
Co-authored-by: odtheking <odtheking1@gmail.com> 查看变更src/main/kotlin/com/odtheking/odin/OdinMod.kt | 6 +- src/main/kotlin/com/odtheking/odin/commands/DevCommand.kt | 58 +++++----- src/main/kotlin/com/odtheking/odin/events/CustomEvents.kt | 15 ++- src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt | 7 +- src/main/kotlin/com/odtheking/odin/features/impl/boss/ExtraStats.kt | 15 +-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DoorHighlight.kt | 112 +++++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/DungeonMap.kt | 189 ------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/KeyHighlight.kt | 66 ----------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/MapInfo.kt | 5 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/RoomClear.kt | 28 +++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypointEditor.kt | 5 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/DungeonWaypointPacks.kt | 25 +++-- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/dungeonwaypoints/SecretWaypoints.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/Door.kt | 93 ---------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungMap.kt | 322 ----------------------------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonMap.kt | 71 ++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/DungeonScan.kt | 114 +++++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRenderer.kt | 221 ++++++++++++++++++++++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapRoom.kt | 244 ---------------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapScan.kt | 221 ++++++++++++++++++++++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/MapScanner.kt | 229 ------------------------------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/SpecialColumn.kt | 187 ++++++++++++++----------------- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/Tile.kt | 24 ---- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/Vec2i.kt | 15 --- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/WorldScan.kt | 185 ++++++++++++++++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonDoor.kt | 34 ++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/DungeonRoom.kt | 163 +++++++++++++++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/map/tile/RoomData.kt | 90 +++++++++++++++ src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BeamsSolver.kt | 5 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/BoulderSolver.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/IceFillSolver.kt | 5 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/PuzzleSolvers.kt | 12 +- src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/QuizSolver.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/TPMazeSolver.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/WaterSolver.kt | 1 - src/main/kotlin/com/odtheking/odin/features/impl/dungeon/puzzlesolvers/WeirdosSolver.kt | 2 - src/main/kotlin/com/odtheking/odin/utils/IVec2.kt | 56 ++++++++++ src/main/kotlin/com/odtheking/odin/utils/VecUtils.kt | 26 ++--- src/main/kotlin/com/odtheking/odin/utils/skyblock/LocationUtils.kt | 8 +- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonEnums.kt | 17 +-- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonListener.kt | 23 ++-- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/DungeonUtils.kt | 19 +--- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/ScanUtils.kt | 180 ------------------------------ src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/tiles/Room.kt | 23 ---- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/tiles/RoomShape.kt | 20 ---- src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/tiles/RoomState.kt | 5 - src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/tiles/RoomType.kt | 5 - src/main/kotlin/com/odtheking/odin/utils/skyblock/dungeon/tiles/Rotations.kt | 12 -- src/main/resources/assets/odin/rooms.json | 7 +- src/main/resources/assets/odin/textures/map/cross.png | Bin 0 -> 205 bytes src/main/resources/assets/odin/textures/map/green_check.png | Bin 0 -> 188 bytes src/main/resources/assets/odin/textures/map/question.png | Bin 0 -> 162 bytes src/main/resources/assets/odin/textures/map/white_check.png | Bin 0 -> 186 bytes 53 files changed, 1497 insertions(+), 1677 deletions(-) @@ -4,6 +4,7 @@ import com.odtheking.odin.commands.* import com.odtheking.odin.events.EventDispatcher import com.odtheking.odin.events.core.EventBus import com.odtheking.odin.features.ModuleManager +import com.odtheking.odin.features.impl.dungeon.map.DungeonScan import com.odtheking.odin.features.impl.render.Shenanigans import com.odtheking.odin.utils.IrisCompatability import com.odtheking.odin.utils.ServerUtils @@ -15,7 +16,6 @@ import com.odtheking.odin.utils.render.RoundRectPIPRenderer import com.odtheking.odin.utils.skyblock.* import com.odtheking.odin.utils.skyblock.dungeon.DungeonListener import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import com.odtheking.odin.utils.skyblock.dungeon.ScanUtils import com.odtheking.odin.utils.skyblock.dungeon.terminals.TerminalUtils import com.odtheking.odin.utils.ui.rendering.NVGPIPRenderer import com.odtheking.odin.utils.ui.widget.CustomGUIImpl @@ -73,9 +73,9 @@ object OdinMod : ClientModInitializer { this, LocationUtils, TickTasks, KuudraUtils, SkyblockPlayer, ServerUtils, EventDispatcher, DungeonListener, PartyUtils, TerminalUtils, - ScanUtils, DungeonUtils, SplitsManager, + DungeonUtils, SplitsManager, DungeonScan, IrisCompatability, RenderBatchManager, - ModuleManager, CustomGUIImpl, Shenanigans + ModuleManager, CustomGUIImpl, Shenanigans, ).forEach { EventBus.subscribe(it) } PictureInPictureRendererRegistry.register { context -> @@ -10,6 +10,8 @@ import com.odtheking.odin.features.impl.boss.MelodyMessage.melodyWebSocket import com.odtheking.odin.features.impl.boss.WitherDragonState import com.odtheking.odin.features.impl.boss.WitherDragons import com.odtheking.odin.features.impl.boss.WitherDragonsEnum +import com.odtheking.odin.features.impl.dungeon.map.DungeonScan +import com.odtheking.odin.features.impl.dungeon.map.WorldScan import com.odtheking.odin.features.impl.nether.NoPre import com.odtheking.odin.features.impl.render.ClickGUIModule.webSocketUrl import com.odtheking.odin.features.impl.render.PlayerSize @@ -23,9 +25,7 @@ import com.odtheking.odin.utils.skyblock.PartyUtils import com.odtheking.odin.utils.skyblock.Supply import com.odtheking.odin.utils.skyblock.dungeon.Blessing import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils.getRelativeCoords -import com.odtheking.odin.utils.skyblock.dungeon.ScanUtils -import com.odtheking.odin.utils.skyblock.dungeon.ScanUtils.getRoomCenter +import com.odtheking.odin.utils.skyblock.dungeon.Floor import com.odtheking.odin.utils.ui.rendering.NVGRenderer import kotlinx.coroutines.launch import net.minecraft.core.registries.BuiltInRegistries @@ -127,7 +127,7 @@ val devCommand = Commodore("oddev") { |DungeonTime: ${DungeonUtils.dungeonTime} |currentDungeonPlayer: ${DungeonUtils.currentDungeonPlayer.name}, ${DungeonUtils.currentDungeonPlayer.clazz}, ${DungeonUtils.currentDungeonPlayer.isDead} |doorOpener: ${DungeonUtils.doorOpener} - |currentRoom: ${DungeonUtils.currentRoom?.data?.name}, roomsPassed: ${DungeonUtils.passedRooms.map { it.data.name }} + |currentRoom: ${WorldScan.currentRoom?.data?.name}, roomsPassed: ${DungeonScan.rooms.filter { it.walkedInto }.map { it.data?.name }} |Teammates: ${DungeonUtils.dungeonTeammates.joinToString { "§${it.clazz.colorCode}${it.name} (${it.clazz} [${it.clazzLvl}])§r" }} |TeammatesNoSelf: ${DungeonUtils.dungeonTeammatesNoSelf.map { it.name }} |LeapTeammates: ${DungeonUtils.leapTeammates.map { it.name }} @@ -157,13 +157,8 @@ val devCommand = Commodore("oddev") { append(" §8│ §eIn §f${(dragon.timeToSpawn / 20f).toFixed()}§es") } - val spawnInfo = buildString { - append(" §8│ §7Spawned: §fx${dragon.timesSpawned}") - } - - val uuid = buildString { - append(" §8│ §7UUID: §f${dragon.entityUUID}") - } + val spawnInfo = buildString { append(" §8│ §7Spawned: §fx${dragon.timesSpawned}") } + val uuid = buildString { append(" §8│ §7UUID: §f${dragon.entityUUID}") } val flags = buildString { val flagList = mutableListOf<String>() @@ -179,30 +174,39 @@ val devCommand = Commodore("oddev") { } } - literal("roomdata").runs { + literal("roomdata").runs { x: Int?, z: Int? -> val player = mc.player ?: return@runs - val vec2 = Vec2(player.x.toInt(), player.z.toInt()) - val chunk = mc.level?.getChunk(vec2.x shr 4, vec2.z shr 4) ?: return@runs - val roomCenter = getRoomCenter(vec2.x, vec2.z) - val room = ScanUtils.scanRoom(roomCenter) - val core = ScanUtils.getCore(roomCenter) + val tileX = ((x ?: player.blockX) + 201) shr 5 + val tileZ = ((z ?: player.blockZ) + 201) shr 5 + + val roomPos = IVec2(tileX * 32 - 185, tileZ * 32 - 185) + val chunk = mc.level?.getChunk(roomPos.x shr 4, roomPos.z shr 4) ?: return@runs + val core = WorldScan.getRoomCore(chunk, roomPos) + val room = DungeonScan.tiles[tileX + tileZ * 6].room ?: return@runs modMessage("§cNo room data found for the current tile.") + modMessage( """ - Middle: ${roomCenter.x}, ${roomCenter.z} - Room: ${ScanUtils.coreToRoomData[core]?.name} - Core: $core - Rotation: ${room?.rotation ?: "NONE"} - Positions: ${room?.roomComponents?.joinToString { "(${it.x}, ${it.z})" } ?: "None"} - Height: ${ScanUtils.getTopLayerOfRoom(vec2, chunk)} - """.trimIndent(), "") - setClipboardContent(core.toString()) - modMessage("§aCopied $core to clipboard!") + Middle: ${roomPos.x} ${roomPos.z} + Room: ${room.name} ${room.shape} + Core: ${core.first} height: ${core.second} + Rotation: ${room.rotation ?: "NONE"} ${room.clayPos} + Positions: ${room.tiles.joinToString { tile -> "${room.getRealPosition(tile.x, tile.z)}" }} + """.trimIndent(), "" + ) + setClipboardContent(core.first.toString()) + modMessage("§aCopied §f${core.first} §ato clipboard!") + } + + literal("setfloor").runs { floorNumber: Int -> + Floor.entries.find { it.floorNumber == floorNumber }?.let { floor -> + DungeonScan.initClient(floor) + } ?: modMessage("§cInvalid floor number: $floorNumber. Valid floors are 1-7.") } literal("relative").runs { mc.hitResult?.let { if (it !is BlockHitResult) return@runs - DungeonUtils.currentRoom?.getRelativeCoords(it.blockPos)?.let { vec2 -> + WorldScan.currentRoom?.getRelativeCoords(it.blockPos)?.let { vec2 -> modMessage("Relative coords: ${vec2.x}, ${vec2.z}") } } @@ -3,9 +3,11 @@ package com.odtheking.odin.events import com.mojang.blaze3d.platform.InputConstants import com.odtheking.odin.events.core.CancellableEvent import com.odtheking.odin.events.core.Event +import com.odtheking.odin.features.impl.dungeon.map.tile.DungeonRoom +import com.odtheking.odin.features.impl.dungeon.map.tile.MapCheckmark import com.odtheking.odin.utils.render.RenderConsumer +import com.odtheking.odin.utils.skyblock.dungeon.Floor import com.odtheking.odin.utils.skyblock.dungeon.terminals.terminalhandler.TerminalHandler -import com.odtheking.odin.utils.skyblock.dungeon.tiles.Room import net.fabricmc.fabric.api.client.rendering.v1.level.AbstractLevelRenderContext import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext import net.minecraft.client.multiplayer.ClientLevel @@ -31,8 +33,6 @@ class MessageSentEvent(val message: String) : CancellableEvent() class RenderBossBarEvent(val bossBar: BossEvent) : CancellableEvent() -class RoomEnterEvent(val room: Room?) : CancellableEvent() - interface SecretPickupEvent : Event { // all are currently packet based but can probably use mixins class Interact(val blockPos: BlockPos, val blockState: BlockState) : SecretPickupEvent class Item(val entity: ItemEntity) : SecretPickupEvent @@ -68,4 +68,11 @@ abstract class PartyEvent(val members: List<String>) : Event { abstract class PacketEvent(val packet: Packet<*>) : CancellableEvent() { // ideally used less class Receive(packet: Packet<*>) : PacketEvent(packet) class Send(packet: Packet<*>) : PacketEvent(packet) -} \ No newline at end of file +} + +class RoomEnterEvent(val room: DungeonRoom?) : Event +class FloorEnterEvent(val floor: Floor) : Event +data object MapUpdateEvent : Event +class CheckmarkUpdateEvent(val room: DungeonRoom, val checkmark: MapCheckmark) : Event + +object LocationChangeEvent : Event \ No newline at end of file @@ -14,6 +14,7 @@ import com.odtheking.odin.features.ModuleManager.configs import com.odtheking.odin.features.impl.boss.* import com.odtheking.odin.features.impl.dungeon.* import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints +import com.odtheking.odin.features.impl.dungeon.map.DungeonMap import com.odtheking.odin.features.impl.dungeon.puzzlesolvers.PuzzleSolvers import com.odtheking.odin.features.impl.nether.* import com.odtheking.odin.features.impl.render.* @@ -59,9 +60,9 @@ object ModuleManager { registerModules(config = ModuleConfig(file = File(OdinMod.configFile, "odin-config.json")), // dungeon PuzzleSolvers, BlessingDisplay, LeapMenu, SecretClicked, MapInfo, Mimic, DungeonQueue, - KeyHighlight, BloodCamp, PositionalMessages, TerracottaTimer, BreakerDisplay, LividSolver, - InvincibilityTimer, SpiritBear, DungeonWaypoints, ExtraStats, BetterPartyFinder, Croesus, MageBeam, DungeonMap, - SecretsCounter, PuzzleHud, + DoorHighlight, BloodCamp, PositionalMessages, TerracottaTimer, BreakerDisplay, LividSolver, + InvincibilityTimer, SpiritBear, DungeonWaypoints, ExtraStats, BetterPartyFinder, Croesus, MageBeam, + SecretsCounter, DungeonMap, PuzzleHud, RoomClear, // boss TerminalSimulator, TerminalSolver, TerminalTimes, TerminalSounds, TickTimers, ArrowAlign, @@ -136,11 +136,8 @@ object ExtraStats : Module( val defeatedText = if (extraStats.bossKilled == null) "§c§lFAILED §a- §e${DungeonUtils.dungeonTime}" else "§aDefeated §c${extraStats.bossKilled} §ain §e${DungeonUtils.dungeonTime}${if (extraStats.timePB) " §d§l(NEW RECORD!)" else ""}" - val passedRoomsText = "Passed rooms: \n${DungeonUtils.passedRooms.joinToString("\n") { room -> "§a${room.data.name}" }}" - - val message = Component.literal(getChatBreak()).withStyle { - it.withHoverEvent(HoverEvent.ShowText(Component.literal(passedRoomsText))) - } .append("\n\n") + val message = Component.literal(getChatBreak()) + .append("\n\n") .append(getCenteredText((if (DungeonUtils.floor?.isMM == true) "§cMaster Mode" else "§cThe Catacombs") + " §r- §e${DungeonUtils.floor?.name}")) .append("\n\n") .append(getCenteredText(defeatedText)) @@ -190,13 +187,7 @@ object ExtraStats : Module( ).append("\n") } - message.append("\n") - .append( - Component.literal(getChatBreak()).withStyle { - it.withClickEvent(ClickEvent.SuggestCommand(passedRoomsText)) - .withHoverEvent(HoverEvent.ShowText(Component.literal(passedRoomsText))) - } - ) + message.append("\n").append(Component.literal(getChatBreak())) modMessage(message, "") } @@ -0,0 +1,112 @@ +package com.odtheking.odin.features.impl.dungeon + +import com.odtheking.odin.clickgui.settings.impl.BooleanSetting +import com.odtheking.odin.clickgui.settings.impl.ColorSetting +import com.odtheking.odin.events.ChatPacketEvent +import com.odtheking.odin.events.LevelEvent +import com.odtheking.odin.events.RenderEvent +import com.odtheking.odin.events.core.on +import com.odtheking.odin.events.core.onReceive +import com.odtheking.odin.features.Module +import com.odtheking.odin.features.impl.dungeon.map.DungeonScan +import com.odtheking.odin.features.impl.dungeon.map.tile.DoorType +import com.odtheking.odin.utils.Color +import com.odtheking.odin.utils.Color.Companion.withAlpha +import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.alert +import com.odtheking.odin.utils.equalsOneOf +import com.odtheking.odin.utils.render.drawStyledBox +import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.decoration.ArmorStand +import net.minecraft.world.phys.AABB + +object DoorHighlight : Module( + name = "Door Highlight", + description = "Highlights wither and blood doors and keys in dungeons." +) { + private val announceKeySpawn by BooleanSetting("Announce Key Spawn", true, desc = "Announces when a key is spawned.") + private val doorHighlightColor by ColorSetting("Door Highlight Color", Colors.MINECRAFT_RED.withAlpha(0.8f), true, desc = "Color for locked doors.") + private val openableColor by ColorSetting("Openable Door Color", Colors.MINECRAFT_GREEN.withAlpha(0.8f), true, desc = "Color for doors that can be opened with a held key.") + private val witherColor by ColorSetting("Wither Color", Colors.BLACK.withAlpha(0.8f), true, desc = "The color of the box.") + private val bloodColor by ColorSetting("Blood Color", Colors.MINECRAFT_RED.withAlpha(0.8f), true, desc = "The color of the box.") + + private var currentKey: KeyType? = null + private var witherKeys = 0 + private var bloodKey = false + private var bloodOpened = false + + private val witherKeyObtainRegex = Regex("^(\\[[^]]*?])? ?(\\w{1,16}) has obtained Wither Key!?$") + private val witherKeyPickedUpRegex = Regex("^A Wither Key was picked up!$") + private val witherDoorOpenRegex = Regex("^(\\[[^]]*?])? ?(\\w{1,16}) opened a WITHER door!$") + private val bloodKeyObtainRegex = Regex("^(\\[[^]]*?])? ?(\\w{1,16}) has obtained Blood Key!$") + private val bloodKeyPickedUpRegex = Regex("^A Blood Key was picked up!$") + private val bloodDoorOpenRegex = Regex("^The BLOOD DOOR has been opened!$") + + init { + on<ChatPacketEvent> { + if (!DungeonUtils.inClear) return@on + when { + witherKeyObtainRegex.matches(value) || witherKeyPickedUpRegex.matches(value) -> witherKeys++ + witherDoorOpenRegex.matches(value) -> witherKeys = (witherKeys - 1).coerceAtLeast(0) + bloodKeyObtainRegex.matches(value) || bloodKeyPickedUpRegex.matches(value) -> bloodKey = true + bloodDoorOpenRegex.matches(value) -> { bloodKey = false; bloodOpened = true } + } + } + + onReceive<ClientboundSetEntityDataPacket> { + if (!DungeonUtils.inClear) return@onReceive + val entity = mc.level?.getEntity(id) as? ArmorStand ?: return@onReceive + if (currentKey?.entity == entity) return@onReceive + currentKey = KeyType.entries.find { it.displayName == entity.name.string } ?: return@onReceive + currentKey?.entity = entity + + if (announceKeySpawn) alert("§${currentKey?.colorCode}${entity.name.string}§7 spawned!") + } + + on<RenderEvent.Extract> { + if (!DungeonUtils.inClear) return@on + + DungeonScan.viewableDoors.forEach { (door, _) -> + if (!door.type.equalsOneOf(DoorType.Wither, DoorType.Blood)) return@forEach + if (door.type == DoorType.Blood && bloodOpened) return@forEach + + val worldX = (door.position.x - 6) * 32 + 7 + door.rotation.offset.x * 16 + val worldZ = (door.position.z - 6) * 32 + 7 + door.rotation.offset.z * 16 + val box = AABB(worldX - 1.0, 69.0, worldZ - 1.0, worldX + 2.0, 73.0, worldZ + 2.0) + + val isOpenable = when (door.type) { + DoorType.Wither -> witherKeys > 0 + DoorType.Blood -> bloodKey + else -> false + } + drawStyledBox(box, if (isOpenable) openableColor else doorHighlightColor, 2, false) + } + + if (currentKey == null || currentKey?.entity == null) return@on + currentKey?.let { keyType -> + if (keyType.entity?.isAlive == false) { + currentKey = null + return@on + } + val position = keyType.entity?.position() ?: return@on + drawStyledBox(AABB.unitCubeFromLowerCorner(position.add(-0.5, 1.0, -0.5)), keyType.color(), 2, true) + } + } + + on<LevelEvent.Load> { + currentKey = null + witherKeys = 0 + bloodKey = false + bloodOpened = false + } + } + + private enum class KeyType(val displayName: String, val color: () -> Color, val colorCode: Char) { + Wither("Wither Key", { witherColor }, '8'), + Blood("Blood Key", { bloodColor }, 'c'); + + var entity: Entity? = null + } +} \ No newline at end of file @@ -1,189 +0,0 @@ -package com.odtheking.odin.features.impl.dungeon - -import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency -import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.clickgui.settings.impl.DropdownSetting -import com.odtheking.odin.clickgui.settings.impl.NumberSetting -import com.odtheking.odin.events.TickEvent -import com.odtheking.odin.events.LevelEvent -import com.odtheking.odin.events.core.on -import com.odtheking.odin.events.core.onReceive -import com.odtheking.odin.features.Module -import com.odtheking.odin.features.impl.dungeon.map.DungMap -import com.odtheking.odin.features.impl.dungeon.map.MapScanner -import com.odtheking.odin.features.impl.dungeon.map.SpecialColumn -import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.equalsOneOf -import com.odtheking.odin.utils.itemId -import com.odtheking.odin.utils.render.hollowFill -import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import com.odtheking.odin.utils.skyblock.dungeon.tiles.RoomType -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents -import net.minecraft.client.gui.GuiGraphicsExtractor -import net.minecraft.client.gui.components.PlayerFaceExtractor -import net.minecraft.client.renderer.RenderPipelines -import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket -import net.minecraft.resources.Identifier -import java.awt.Color as AwtColor - -object DungeonMap : Module( - name = "Dungeon Map", - description = "Customizable dungeon map with room colors, door colors, and player names." -) { - private val disableBoss by BooleanSetting("Disable in Boss", true, desc = "Disables the map during boss fights.") - val disablePred by BooleanSetting("Disable Predictions", false, desc = "Disables map predictions for unseen rooms.") - - private val backgroundColor by ColorSetting("Background Color", Color(0, 0, 0, 0.7f), true, desc = "The background color of the map.") - private val backgroundOutline by ColorSetting("Background Outline", Colors.gray26, false, desc = "The color of the background border.").withDependency { backgroundSize != 0f } - private val backgroundSize by NumberSetting("Background Size", 5f, 0f, 20f, 1f, desc = "The size of the background border.") - - val textScaling by NumberSetting("Text Scaling", 0.45f, 0.1f, 1f, 0.05f, desc = "Scale of room names.") - - private val playerDropdown by DropdownSetting("Player Settings") - private val playerHeadBackgroundSize by NumberSetting("Player Head BG Size", 1, 0, 10, 1, desc = "Size of player head background.").withDependency { playerDropdown } - private val playerNamesScaling by NumberSetting("Player Names Scaling", 0.75f, 0.1f, 2f, 0.05f, desc = "Scale of player names.").withDependency { playerDropdown } - private val playerNameColor by ColorSetting("Player Name Color", Color(70, 70, 70), false, desc = "Color of player names.").withDependency { playerDropdown } - - private val doorDropdown by DropdownSetting("Door Settings") - val doorThickness by NumberSetting("Door Thickness", 9, 1, 20, 1, desc = "Thickness of doors on map.").withDependency { doorDropdown } - val unopenedDoorColor by ColorSetting("Unopened Door", Color(30, 30, 30), false, desc = "Color of unopened doors.").withDependency { doorDropdown } - val bloodDoorColor by ColorSetting("Blood Door", Colors.MINECRAFT_RED, false, desc = "Color of blood room doors.").withDependency { doorDropdown } - val witherDoorColor by ColorSetting("Wither Door", Colors.BLACK, false, desc = "Color of wither doors.").withDependency { doorDropdown } - val normalDoorColor by ColorSetting("Normal Door", Color(107, 58, 17), false, desc = "Color of normal doors.").withDependency { doorDropdown } - val puzzleDoorColor by ColorSetting("Puzzle Door", Color(117, 0, 133), false, desc = "Color of puzzle doors.").withDependency { doorDropdown } - val championDoorColor by ColorSetting("Champion Door", Color(254, 223, 0), false, desc = "Color of champion doors.").withDependency { doorDropdown } - val trapDoorColor by ColorSetting("Trap Door", Color(216, 127, 51), false, desc = "Color of trap doors.").withDependency { doorDropdown } - val entranceDoorColor by ColorSetting("Entrance Door", Color(20, 133, 0), false, desc = "Color of entrance doors.").withDependency { doorDropdown } - val fairyDoorColor by ColorSetting("Fairy Door", Color(244, 19, 139), false, desc = "Color of fairy room doors.").withDependency { doorDropdown } - val rareDoorColor by ColorSetting("Rare Door", Color(255, 203, 89), false, desc = "Color of rare doors.").withDependency { doorDropdown } - - // Room Settings - private val roomDropdown by DropdownSetting("Room Settings") - val darkenMultiplier by NumberSetting("Darken Multiplier", 0.4f, 0f, 1f, 0.05f, desc = "Multiplier for darkening rooms.").withDependency { roomDropdown } - val unopenedRoomColor by ColorSetting("Unopened Room", Color(30, 30, 30), false, desc = "Color of unopened rooms.").withDependency { roomDropdown } - val bloodRoomColor by ColorSetting("Blood Room", Color(255, 0, 0), false, desc = "Color of blood rooms.").withDependency { roomDropdown } - val normalRoomColor by ColorSetting("Normal Room", Color(107, 58, 17), false, desc = "Color of normal rooms.").withDependency { roomDropdown } - val puzzleRoomColor by ColorSetting("Puzzle Room", Color(117, 0, 133), false, desc = "Color of puzzle rooms.").withDependency { roomDropdown } - val championRoomColor by ColorSetting("Champion Room", Color(254, 223, 0), false, desc = "Color of champion rooms.").withDependency { roomDropdown } - val trapRoomColor by ColorSetting("Trap Room", Color(216, 127, 51), false, desc = "Color of trap rooms.").withDependency { roomDropdown } - val entranceRoomColor by ColorSetting("Entrance Room", Color(20, 133, 0), false, desc = "Color of entrance rooms.").withDependency { roomDropdown } - val fairyRoomColor by ColorSetting("Fairy Room", Color(244, 19, 139), false, desc = "Color of fairy rooms.").withDependency { roomDropdown } - val rareRoomColor by ColorSetting("Rare Room", Color(255, 203, 89), false, desc = "Color of rare rooms.").withDependency { roomDropdown } - - private val mapHud by HUD("Dungeon Map", "Displays the dungeon map with customizable colors.", false) { example -> - when { - (!DungeonUtils.inDungeons || (disableBoss && DungeonUtils.inBoss)) && !example -> 0 to 0 - example -> renderExampleMap() - else -> renderDungeonMap() - } - } - - private val marker = Identifier.fromNamespaceAndPath("odin", "map/marker.png") - val x = Identifier.fromNamespaceAndPath("odin", "map/x.png") - val green = Identifier.fromNamespaceAndPath("odin", "map/green.png") - val white = Identifier.fromNamespaceAndPath("odin", "map/white.png") - val question = Identifier.fromNamespaceAndPath("odin", "map/question.png") - - private fun GuiGraphicsExtractor.renderExampleMap(): Pair<Int, Int> { - val roomsX = 116 - val roomsZ = 116 - val offset = backgroundSize.toInt() - - fill(0, 0, roomsX + offset * 2, roomsZ + offset * 2, backgroundColor.rgba) - centeredText(mc.font, "MAP", roomsX / 2 + offset, roomsZ / 2 + offset - mc.font.lineHeight, AwtColor.WHITE.rgb) - - return (roomsX + offset * 2) to (roomsZ + offset * 2) - } - - private fun GuiGraphicsExtractor.renderDungeonMap(): Pair<Int, Int> { - val matrices = pose() - val mapSize = DungMap.calculateMapSize() - val roomsX = mapSize.x * 16 + (mapSize.x - 1) * 4 - val roomsZ = mapSize.z * 16 + (mapSize.z - 1) * 4 - val offset = backgroundSize.toInt() * 2 - - matrices.pushMatrix() - - fill(0, 0, roomsX + offset, roomsZ + offset, backgroundColor.rgba) - hollowFill(0, 0, roomsX + offset, roomsZ + offset, 1, backgroundOutline) - matrices.translate(backgroundSize, backgroundSize) - - MapScanner.rooms.forEach { it.render(this) } - - for (door in MapScanner.doors) { - if (!door.seen) continue - door.render(this) - } - - val textFactor = 1 / textScaling - - MapScanner.rooms.forEach { room -> - if (room.type == RoomType.ENTRANCE || room.type == RoomType.BLOOD) return@forEach - room.renderName(this, textFactor) - } - - if (!DungeonUtils.inBoss) { - val renderNames = mc.player?.mainHandItem?.itemId?.equalsOneOf("INFINITE_SPIRIT_LEAP", "SPIRIT_LEAP") == true - - for (player in DungeonUtils.dungeonTeammates) { - if (player.isDead) continue - - val (posX, posZ) = player.mapRenderPosition() - - matrices.pushMatrix() - matrices.translate(posX, posZ) - - if (renderNames) { - matrices.pushMatrix() - matrices.scale(playerNamesScaling) - centeredText(mc.font, player.name, 0, 8, playerNameColor.rgba) - matrices.popMatrix() - } - - player.playerSkin?.let { skin -> - matrices.rotate(Math.toRadians(180.0 + player.mapRenderYaw()).toFloat()) - - if (player.playerSkin == mc.player?.skin) { - blit(RenderPipelines.GUI_TEXTURED, marker, 5, 5, 10f, 10f, -10, -10, 10, 10) - return@let - } - - if (playerHeadBackgroundSize != 0) { - val size = 5 + playerHeadBackgroundSize - fill(-size, -size, size, size, player.clazz.color.rgba) - } - - PlayerFaceExtractor.extractRenderState(this, skin, -5, -5, 10) - } - - matrices.popMatrix() - } - } - - matrices.popMatrix() - - return (roomsX + offset) to (roomsZ + offset) - } - - init { - on<LevelEvent.Load> { - SpecialColumn.unload() - MapScanner.unload() - DungMap.unload() - } - - on<TickEvent.End> { - MapScanner.scan(world) - } - - ClientChunkEvents.CHUNK_LOAD.register { _, _ -> - DungMap.onChunkLoad() - } - - onReceive<ClientboundMapItemDataPacket> { - DungMap.rescanMapItem(this) - } - } -} \ No newline at end of file @@ -1,66 +0,0 @@ -package com.odtheking.odin.features.impl.dungeon - -import com.odtheking.odin.clickgui.settings.impl.BooleanSetting -import com.odtheking.odin.clickgui.settings.impl.ColorSetting -import com.odtheking.odin.events.RenderEvent -import com.odtheking.odin.events.LevelEvent -import com.odtheking.odin.events.core.on -import com.odtheking.odin.events.core.onReceive -import com.odtheking.odin.features.Module -import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.Color.Companion.withAlpha -import com.odtheking.odin.utils.Colors -import com.odtheking.odin.utils.alert -import com.odtheking.odin.utils.render.drawStyledBox -import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket -import net.minecraft.world.entity.Entity -import net.minecraft.world.entity.decoration.ArmorStand -import net.minecraft.world.phys.AABB - -object KeyHighlight : Module( - name = "Key Highlight", - description = "Highlights wither and blood keys in dungeons." -) { - private val announceKeySpawn by BooleanSetting("Announce Key Spawn", true, desc = "Announces when a key is spawned.") - private val witherColor by ColorSetting("Wither Color", Colors.BLACK.withAlpha(0.8f), true, desc = "The color of the box.") - private val bloodColor by ColorSetting("Blood Color", Colors.MINECRAFT_RED.withAlpha(0.8f), true, desc = "The color of the box.") - - private var currentKey: KeyType? = null - - init { - onReceive<ClientboundSetEntityDataPacket> { - if (!DungeonUtils.inClear) return@onReceive - val entity = mc.level?.getEntity(id) as? ArmorStand ?: return@onReceive - if (currentKey?.entity == entity) return@onReceive - currentKey = KeyType.entries.find { it.displayName == entity.name.string } ?: return@onReceive - currentKey?.entity = entity - - if (announceKeySpawn) alert("§${currentKey?.colorCode}${entity.name.string}§7 spawned!") - } - - on<RenderEvent.Extract> { - if (!DungeonUtils.inClear) return@on - if (currentKey == null || currentKey?.entity == null) return@on - currentKey?.let { keyType -> - if (keyType.entity?.isAlive == false) { - currentKey = null - return@on - } - val position = keyType.entity?.position() ?: return@on - drawStyledBox(AABB.unitCubeFromLowerCorner(position.add(-0.5, 1.0, -0.5)), keyType.color(), 2, true) - } - } - - on<LevelEvent.Load> { - currentKey = null - } - } - - private enum class KeyType(val displayName: String, val color: () -> Color, val colorCode: Char) { - Wither("Wither Key", { witherColor }, '8'), - Blood("Blood Key", { bloodColor }, 'c'); - - var entity: Entity? = null - } -} \ No newline at end of file @@ -2,12 +2,13 @@ package com.odtheking.odin.features.impl.dungeon import com.odtheking.odin.clickgui.settings.Setting.Companion.withDependency import com.odtheking.odin.clickgui.settings.impl.* +import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.events.RoomEnterEvent -import com.odtheking.odin.events.LevelEvent import com.odtheking.odin.events.core.on import com.odtheking.odin.events.core.onReceive import com.odtheking.odin.features.Module +import com.odtheking.odin.features.impl.dungeon.map.tile.RoomType import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.Colors import com.odtheking.odin.utils.alert @@ -19,8 +20,6 @@ import com.odtheking.odin.utils.render.getStringWidth import com.odtheking.odin.utils.render.text import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils.getRealCoords -import com.odtheking.odin.utils.skyblock.dungeon.tiles.RoomType import net.minecraft.core.BlockPos import net.minecraft.network.protocol.game.ClientboundSystemChatPacket import net.minecraft.world.phys.AABB @@ -0,0 +1,28 @@ +package com.odtheking.odin.features.impl.dungeon + +import com.odtheking.odin.clickgui.settings.impl.SelectorSetting +import com.odtheking.odin.events.CheckmarkUpdateEvent +import com.odtheking.odin.events.core.on +import com.odtheking.odin.features.Module +import com.odtheking.odin.features.impl.dungeon.map.tile.MapCheckmark +import com.odtheking.odin.features.impl.dungeon.map.tile.RoomType +import com.odtheking.odin.utils.alert +import com.odtheking.odin.utils.equalsOneOf +import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils + +object RoomClear : Module( + name = "Room Clear", + description = "Notifies you when a room is cleared in dungeons." +) { + private val mode by SelectorSetting("Mode", "Both", listOf("Both", "Green", "White"), desc = "Notification for when a room turns green or white.") + + init { + on<CheckmarkUpdateEvent> { + if (DungeonUtils.currentRoom != room || !checkmark.equalsOneOf(MapCheckmark.GREEN, MapCheckmark.WHITE)) return@on + if (room.type == RoomType.FAIRY || room.type == RoomType.ENTRANCE) return@on + + if (mode == 0 || (mode == 1 && checkmark == MapCheckmark.GREEN) || (mode == 2 && checkmark == MapCheckmark.WHITE)) + alert(if (checkmark == MapCheckmark.GREEN) "§aRoom Complete!" else "Room Cleared!") + } + } +} \ No newline at end of file @@ -6,6 +6,7 @@ import com.odtheking.odin.events.InputEvent import com.odtheking.odin.events.RenderEvent import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints.DungeonWaypoint import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints.WaypointType +import com.odtheking.odin.features.impl.dungeon.map.tile.DungeonRoom import com.odtheking.odin.features.impl.render.Etherwarp import com.odtheking.odin.utils.Color.Companion.withAlpha import com.odtheking.odin.utils.devMessage @@ -16,8 +17,6 @@ import com.odtheking.odin.utils.render.drawBoxes import com.odtheking.odin.utils.render.drawStyledBox import com.odtheking.odin.utils.render.drawText import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils.getRelativeCoords -import com.odtheking.odin.utils.skyblock.dungeon.tiles.Room import kotlinx.coroutines.launch import net.minecraft.core.BlockPos import net.minecraft.world.phys.AABB @@ -94,7 +93,7 @@ private fun DungeonWaypoints.cacheEtherwarpTarget() { } private fun DungeonWaypoints.openWaypointTitlePrompt( - room: Room, + room: DungeonRoom, blockPos: BlockPos, aabb: AABB, editableWaypoints: MutableList<DungeonWaypoint>, @@ -4,10 +4,9 @@ import com.odtheking.odin.config.WaypointPackFileUtils import com.odtheking.odin.config.WaypointPackState import com.odtheking.odin.config.normalized import com.odtheking.odin.features.ModuleManager +import com.odtheking.odin.features.impl.dungeon.map.tile.DungeonRoom import com.odtheking.odin.utils.modMessage import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils.getRealCoords -import com.odtheking.odin.utils.skyblock.dungeon.tiles.Room import net.minecraft.world.phys.AABB suspend fun DungeonWaypoints.loadWaypoints() { @@ -79,23 +78,25 @@ internal fun DungeonWaypoints.resetClickedWaypoints() { DungeonUtils.currentRoom?.setWaypoints() } -fun Room.setWaypoints() { - waypoints = DungeonWaypoints.allActiveWaypoints[data.name] +fun DungeonRoom.setWaypoints() { + val name = data?.name ?: return + waypoints = DungeonWaypoints.allActiveWaypoints[name] ?.mapTo(mutableSetOf()) { waypoint -> waypoint.copy(blockPos = getRealCoords(waypoint.blockPos)) } ?: mutableSetOf() } -fun DungeonWaypoints.getWaypoints(room: Room): MutableList<DungeonWaypoints.DungeonWaypoint> = - allActiveWaypoints.getOrPut(room.data.name) { mutableListOf() } +fun DungeonWaypoints.getWaypoints(room: DungeonRoom): MutableList<DungeonWaypoints.DungeonWaypoint> = + allActiveWaypoints.getOrPut(room.data?.name ?: return mutableListOf()) { mutableListOf() } -fun DungeonWaypoints.getEditableWaypoints(room: Room): MutableList<DungeonWaypoints.DungeonWaypoint> = - loadedPacks.getOrPut(editPackId) { mutableMapOf() }.getOrPut(room.data.name) { mutableListOf() } +fun DungeonWaypoints.getEditableWaypoints(room: DungeonRoom): MutableList<DungeonWaypoints.DungeonWaypoint> = + loadedPacks.getOrPut(editPackId) { mutableMapOf() }.getOrPut(room.data?.name ?: return mutableListOf()) { mutableListOf() } -fun DungeonWaypoints.syncRoomToActive(room: Room) { - val mergedRoom = mergeRoomWaypoints(room.data.name) - if (mergedRoom.isEmpty()) allActiveWaypoints.remove(room.data.name) - else allActiveWaypoints[room.data.name] = mergedRoom +fun DungeonWaypoints.syncRoomToActive(room: DungeonRoom) { + val name = room.data?.name ?: return@syncRoomToActive + val mergedRoom = mergeRoomWaypoints(name) + if (mergedRoom.isEmpty()) allActiveWaypoints.remove(name) + else allActiveWaypoints[name] = mergedRoom room.setWaypoints() } @@ -5,7 +5,6 @@ import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoint import com.odtheking.odin.features.impl.dungeon.dungeonwaypoints.DungeonWaypoints.WaypointType import com.odtheking.odin.utils.devMessage import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils.getRelativeCoords import net.minecraft.core.BlockPos import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket import net.minecraft.world.phys.Vec3 @@ -1,93 +0,0 @@ -package com.odtheking.odin.features.impl.dungeon.map - -import com.odtheking.odin.features.impl.dungeon.DungeonMap -import com.odtheking.odin.utils.Color -import com.odtheking.odin.utils.Color.Companion.darker -import com.odtheking.odin.utils.equalsOneOf -import com.odtheking.odin.utils.skyblock.dungeon.tiles.RoomState -import com.odtheking.odin.utils.skyblock.dungeon.tiles.RoomType -import net.minecraft.client.gui.GuiGraphicsExtractor - -class Door (val pos: Vec2i, var type: Type, val rooms: MutableList<MapRoom.Tile>) { - - init { - rooms.forEach { it.owner?.doors?.add(this) } - } - - enum class Type { BLOOD, NORMAL, WITHER } - - var locked = type.equalsOneOf(Type.WITHER, Type.BLOOD) - val seen get() = rooms.any { it.owner?.state !in setOf(RoomState.UNDISCOVERED, RoomState.UNOPENED) } - - private fun size(): Vec2i { - val xOffset = ((pos.x + 185) shr 4) % 2 - val zOffset = ((pos.z + 185) shr 4) % 2 - return Vec2i( - (xOffset xor 1) * DungeonMap.doorThickness + xOffset * 4, - (zOffset xor 1) * DungeonMap.doorThickness + zOffset * 4 - ) - } - - inline val placement: Vec2i - get() { - val x = (pos.x + 185) shr 4 - val z = (pos.z + 185) shr 4 - val xEven = x % 2 - val zEven = z % 2 - val thicknessBasedOffset = (16 - DungeonMap.doorThickness) / 2 - val xOffset = (x shr 1) * 20 + xEven * 16 + (xEven xor 1) * thicknessBasedOffset - val yOffset = (z shr 1) * 20 + zEven * 16 + (zEven xor 1) * thicknessBasedOffset - - return Vec2i(xOffset, yOffset) - } - - fun render(graphics: GuiGraphicsExtractor) { - val size = size() - if (size != Vec2i(0, 0)) { - val matrices = graphics.pose() - matrices.pushMatrix() - matrices.translate(placement.x.toFloat(), placement.z.toFloat()) - - graphics.fill(0, 0, size.x, size.z, color().rgba) - - matrices.popMatrix() - } - } - - private fun color(): Color { - val hasUnopenedRoom = rooms.any { it.owner?.state in setOf(RoomState.UNDISCOVERED, RoomState.UNOPENED) } - - return when { - hasUnopenedRoom && type != Type.NORMAL -> getLockedDoorColor() - hasUnopenedRoom -> DungeonMap.unopenedDoorColor - else -> getOpenDoorColor() - } - } - - private fun getLockedDoorColor(): Color = when (type) { - Type.BLOOD -> if (locked) DungeonMap.bloodDoorColor.darker(DungeonMap.darkenMultiplier) else DungeonMap.bloodDoorColor - Type.WITHER -> if (locked) DungeonMap.witherDoorColor.darker(DungeonMap.darkenMultiplier) else DungeonMap.witherDoorColor - Type.NORMAL -> DungeonMap.unopenedDoorColor - } - - private fun getOpenDoorColor(): Color = when (type) { - Type.BLOOD -> DungeonMap.bloodDoorColor - Type.WITHER -> if (locked) DungeonMap.witherDoorColor - else rooms.firstOrNull { it.owner?.data?.type == RoomType.FAIRY }?.let { DungeonMap.fairyDoorColor } - ?: DungeonMap.normalDoorColor - Type.NORMAL -> getDoorColorByRoomType() - } - - private fun getDoorColorByRoomType(): Color { - val specialRoom = rooms.firstOrNull { it.owner?.data?.type !in setOf(RoomType.NORMAL, RoomType.FAIRY) } - return when (specialRoom?.owner?.data?.type) { - RoomType.ENTRANCE -> DungeonMap.entranceDoorColor - RoomType.BLOOD -> DungeonMap.bloodDoorColor - RoomType.CHAMPION -> DungeonMap.championDoorColor - RoomType.PUZZLE -> DungeonMap.puzzleDoorColor - RoomType.RARE -> DungeonMap.rareDoorColor - RoomType.TRAP -> DungeonMap.trapDoorColor - else -> DungeonMap.normalDoorColor - } - } -} \ No newline at end of file @@ -1,322 +0,0 @@ -package com.odtheking.odin.features.impl.dungeon.map - -import com.odtheking.odin.utils.equalsOneOf -import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils -import com.odtheking.odin.utils.skyblock.dungeon.tiles.RoomShape -import com.odtheking.odin.utils.skyblock.dungeon.tiles.RoomType -import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket -import net.minecraft.world.level.saveddata.maps.MapDecorationTypes -import kotlin.jvm.optionals.getOrNull - -object DungMap { - var mapCenter: Vec2i = Vec2i(0, 0) - var startCoords: Vec2i? = null - var mapSize: Vec2i? = null - var roomSize: Int? = null - var shouldScan = false - - private const val MAP_SIZE = 128 - private const val EMPTY: Byte = 0 - - fun unload() { - mapCenter = Vec2i(0, 0) - shouldScan = false - startCoords = null - mapSize = null - roomSize = null - } - - fun onChunkLoad() { - shouldScan = true - if (mapSize == null) { - mapSize = when (DungeonUtils.floor?.floorNumber) { - 0 -> Vec2i(4, 4) - 1 -> Vec2i(4, 5) - 2, 3 -> Vec2i(5, 5) - else -> Vec2i(6, 6) - } - } - } - - fun rescanMapItem(packet: ClientboundMapItemDataPacket) { - if (!DungeonUtils.inClear || packet.mapId().id and 1000 != 0) return - updatePlayerPositions(packet) - - val colors = packet.colorPatch.getOrNull()?.mapColors() ?: return - if (colors.size < MAP_SIZE * MAP_SIZE || colors[0] != EMPTY) return - - if (startCoords == null && !initializeMapCoordinates(colors)) return - - updateRoomTiles(colors) - updateRoomStates(colors) - updateDoorStates(colors) - - if (mapSize != null) SpecialColumn.updateSpecialColumn() - } - - private fun initializeMapCoordinates(colors: ByteArray): Boolean { - val (greenStart, greenLength) = findGreenRoom(colors) - if (!greenLength.equalsOneOf(16, 18)) return false - - val (start, center, size) = when (DungeonUtils.floor?.floorNumber) { - 0 -> Triple(Vec2i(22, 22), Vec2i(-137, -137), Vec2i(4, 4)) - 1 -> Triple(Vec2i(22, 11), Vec2i(-137, -121), Vec2i(4, 5)) - 2, 3 -> Triple(Vec2i(11, 11), Vec2i(-121, -121), Vec2i(5, 5)) - else -> calculateDynamicMapSize(greenStart, greenLength) - } - - roomSize = greenLength - startCoords = start - mapCenter = center - mapSize = size - - if ((DungeonUtils.isFloor(6, 5) && size.x == 6 && size.z == 6) || (DungeonUtils.isFloor(4) && size.x == 6 && size.z == 5)) - SpecialColumn.column = 5 - - return true - } - - private fun calculateDynamicMapSize(greenStart: Int, greenLength: Int): Triple<Vec2i, Vec2i, Vec2i> { - val start = Vec2i((greenStart and 127) % (greenLength + 4), (greenStart shr 7) % (greenLength + 4)) - - val extra = Vec2i(if (start.x == 5) 1 else 0, if (start.z == 5) 1 else 0) - val size = Vec2i(5, 5).add(extra) - val center = Vec2i(-121, -121).add(Vec2i(extra.x * 16, extra.z * 16)) - - return Triple(start, center, size) - } - - private fun updatePlayerPositions(packet: ClientboundMapItemDataPacket) { - packet.decorations.getOrNull()?.let { decorations -> - val playerIterator = DungeonUtils.dungeonTeammatesNoSelf.iterator() - - decorations.forEach { decoration -> - if (decoration.type.value() == MapDecorationTypes.FRAME.value()) return@forEach - - val player = playerIterator.asSequence().firstOrNull { !it.isDead } ?: return@forEach - player.mapPos = Vec2i(decoration.x.toInt(), decoration.y.toInt()) - player.yaw = decoration.rot() * 360 / 16f - } - } - } - - private fun updateRoomTiles(colors: ByteArray) { - val rs = roomSize ?: return - val sc = startCoords ?: return - val ms = mapSize ?: return - - val tileGrid = Array(ms.x) { Array(ms.z) { -1 } } - var nextId = 0 - for (i in 0 until ms.x) { - for (j in 0 until ms.z) { - val idx = Vec2i(i, j).multiply(rs + 4).add(sc).mapIndex() - if (idx < colors.size && colors[idx].toInt() != 0) { - tileGrid[i][j] = nextId++ - } - } - } - - val queue = ArrayDeque<Vec2i>() - for (i in 0 until ms.x) for (j in 0 until ms.z) - if (tileGrid[i][j] != -1) queue += Vec2i(i, j) - - val visited = Array(ms.x) { BooleanArray(ms.z) } - while (queue.isNotEmpty()) { - val cur = queue.removeFirst() - if (visited[cur.x][cur.z]) continue - visited[cur.x][cur.z] = true - val id = tileGrid[cur.x][cur.z] - - val nr = cur.x + 1 - if (nr < ms.x && tileGrid[nr][cur.z] != -1 && !visited[nr][cur.z]) { - val di = sc.add(Vec2i(rs + 1 + cur.x * (rs + 4), cur.z * (rs + 4) + 1)).mapIndex() - if (di < colors.size && colors[di].toInt() != 0) { - tileGrid[nr][cur.z] = id - queue += Vec2i(nr, cur.z) - } - } - - val nb = cur.z + 1 - if (nb < ms.z && tileGrid[cur.x][nb] != -1 && !visited[cur.x][nb]) { - val di = sc.add(Vec2i(cur.x * (rs + 4) + 1, rs + 1 + cur.z * (rs + 4))).mapIndex() - if (di < colors.size && colors[di].toInt() != 0) { - tileGrid[cur.x][nb] = id - queue += Vec2i(cur.x, nb) - } - } - } - - data class UniqueRoom(val id: Int, val coords: Vec2i, val tiles: MutableList<Vec2i>) - val uniques = mutableListOf<UniqueRoom>() - tileGrid.forEachIndexed { i, col -> - col.forEachIndexed { j, id -> - if (id == -1) return@forEachIndexed - (uniques.find { it.id == id } ?: run { - val u = UniqueRoom(id, Vec2i(i, j), mutableListOf()) - uniques.add(u) - u - }).tiles.add(Vec2i(i, j)) - } - } - - uniques.forEach { unique -> - val idx = unique.coords.multiply(rs + 4).add(sc).mapIndex() - val type = when (colors[idx].toInt()) { - 18 -> RoomType.BLOOD - 30 -> RoomType.ENTRANCE - 85 -> RoomType.UNKNOWN - 63 -> RoomType.NORMAL - 62 -> RoomType.TRAP - 66 -> RoomType.PUZZLE - 74 -> RoomType.CHAMPION - 82 -> RoomType.FAIRY - else -> return@forEach - } - - val shape = if (type == RoomType.UNKNOWN) RoomShape.UNKNOWN - else when (unique.tiles.size) { - 1 -> RoomShape.S1x1 - 2 -> RoomShape.S2x1 - 3 -> { - val (a, b, c) = unique.tiles - if ((a.x == b.x && a.x == c.x) || (a.z == b.z && a.z == c.z)) RoomShape.S3x1 else RoomShape.L - } - 4 -> { - val (a, b, c) = unique.tiles - if ((a.x == b.x && a.x == c.x) || (a.z == b.z && a.z == c.z)) RoomShape.S4x1 else RoomShape.S2x2 - } - else -> return@forEach - } - - val found = unique.tiles.firstNotNullOfOrNull { tile -> - MapScanner.roomsList[tile.x * 6 + tile.z].owner - } - - if (found != null) { - if (type != RoomType.UNKNOWN && found.type == RoomType.UNKNOWN) { - found.type = type - found.shape = shape - } - unique.tiles.forEach { tile -> - val other = MapScanner.roomsList[tile.x * 6 + tile.z].owner - if (other != null && other != found) { - other.doors.forEach { found.doors.add(it) } - MapScanner.rooms.remove(other) - } - if (!found.places.contains(tile)) { - found.roomTile(tile.multiply(32).add(-185, -185))?.let { roomTile -> - MapScanner.list[tile.roomListIndex()] = roomTile - } - } - } - return@forEach - } - - val room = MapRoom(type, shape) - MapScanner.rooms.add(room) - unique.tiles.forEach { tile -> - room.roomTile(tile.multiply(32).add(-185, -185))?.let { roomTile -> - MapScanner.list[tile.roomListIndex()] = roomTile - } - } - } - } - - private fun updateRoomStates(colors: ByteArray) { - val rs = roomSize ?: return - val halfRoomSize = rs / 2 - val startCenter = startCoords?.add(Vec2i(halfRoomSize, halfRoomSize)) ?: return - val tileSize = rs + 4 - - MapScanner.rooms.forEach { room -> - val topLeftPlacement = room.places.minWith { a, b -> - a.x * 1000 + a.z - b.x * 1000 - b.z - } - - var color = getColorAtPlacement(topLeftPlacement, startCenter, tileSize, colors).toInt() - var placement = topLeftPlacement - - if (color == 0) { - room.places.firstNotNullOfOrNull { testPlacement -> - val testColor = getColorAtPlacement(testPlacement, startCenter, tileSize, colors) - if (testColor != 0.toByte()) Pair(testPlacement, testColor) else null - }?.let { (visiblePlacement, visibleColor) -> - placement = visiblePlacement - color = visibleColor.toInt() - } - } - - room.updateState(placement, color) - } - } - - private fun updateDoorStates(colors: ByteArray) { - val rs = roomSize ?: return - val halfRoomSize = rs / 2 - val startCenter = startCoords?.add(Vec2i(halfRoomSize, halfRoomSize)) ?: return - - for (a in 0..4) { - for (b in 0..5) { - val doorOffset = halfRoomSize + a * (rs + 4) - val midRoomOffset = b * (rs + 4) - - val horizontalDoorIndex = startCenter.add(Vec2i(doorOffset, midRoomOffset)).mapIndex() - if (horizontalDoorIndex < colors.size) { - (MapScanner.list[(a * 2 + 1) * 11 + b * 2] as? Door)?.let { door -> - updateDoorFromColor(door, colors[horizontalDoorIndex]) - } - } - - val verticalDoorIndex = startCenter.add(Vec2i(midRoomOffset, doorOffset)).mapIndex() - if (verticalDoorIndex < colors.size) { - (MapScanner.list[(b * 2) * 11 + (a * 2 + 1)] as? Door)?.let { door -> - updateDoorFromColor(door, colors[verticalDoorIndex]) - } - } - } - } - } - - fun findGreenRoom(mapData: ByteArray): Pair<Int, Int> { - var start = -1 - var length = 0 - - for (i in mapData.indices) { - if (mapData[i].toInt() == 30) { - if (length++ == 0) start = i - } else { - if (length >= 16) return start to length - length = 0 - } - } - - return start to length - } - - private fun getColorAtPlacement(placement: Vec2i, startCenter: Vec2i, tileSize: Int, colors: ByteArray): Byte { - val mapIndex = startCenter.add(placement.multiply(tileSize)).mapIndex() - return if (mapIndex < colors.size) colors[mapIndex] else 0 - } - - private fun updateDoorFromColor(door: Door, color: Byte) { - door.locked = when (color.toInt()) { - 119 -> { - door.rooms.forEach { it.owner?.rushRoom = true } - door.type = Door.Type.WITHER - true - } - 82 -> { - door.rooms.forEach { it.owner?.rushRoom = true } - false - } - …… diff 过长已截断 …… |
Fixed Petkeybinds not working with 8th and 9th keys查看变更src/main/kotlin/com/odtheking/odin/features/impl/skyblock/PetKeybinds.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) @@ -46,6 +46,8 @@ object PetKeybinds : Module( } } + private val petSlots = intArrayOf(10, 11, 12, 13, 14, 15, 16, 19, 20) + private fun onClick(screen: AbstractContainerScreen<*>, keyCode: Int): Boolean { val (current, total) = petsRegex.find(screen.title.string)?.destructured?.let { (it.component1().toIntOrNull() ?: 1) to (it.component2().toIntOrNull() ?: 1) @@ -60,8 +62,10 @@ object PetKeybinds : Module( .takeIf { it != -1 }?.plus(10) ?: return false.also { modMessage("§cCouldn't find equipped pet") } else -> { - arrayOf(pet1, pet2, pet3, pet4, pet5, pet6, pet7, pet8, pet9).indexOfFirst { it.value == keyCode } - .takeIf { it != -1 }?.plus(10) ?: return false + val petIndex = arrayOf(pet1, pet2, pet3, pet4, pet5, pet6, pet7, pet8, pet9).indexOfFirst { it.value == keyCode } + if (petIndex == -1) return false + + petSlots.getOrNull(petIndex) ?: return false } } |
Removed duplicate error message查看变更src/main/kotlin/com/odtheking/odin/features/impl/dungeon/BetterPartyFinder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -68,7 +68,7 @@ object BetterPartyFinder : Module( val kickedReasons = mutableListOf<String>() - val currentProfile = profile.getOrElse { return@launch modMessage(it.message) }.memberData ?: return@launch modMessage("Could not find member data for $name") + val currentProfile = profile.getOrNull()?.memberData ?: return@launch modMessage("Could not find member data for $name") val dungeon = if (!mmToggle) currentProfile.dungeons.dungeonTypes.catacombs else currentProfile.dungeons.dungeonTypes.mastermode dungeon.fastestTimeSPlus["$floor"]?.let { |
chore: add Lotus Atoll island (#107)查看变更src/main/kotlin/com/odtheking/odin/utils/skyblock/Island.kt | 1 + 1 file changed, 1 insertion(+) @@ -23,5 +23,6 @@ enum class Island(val displayName: String) { Rift("The Rift"), BackwaterBayou("Backwater Bayou"), Galatea("Galatea"), + LotusAtoll("Lotus Atoll"), Unknown("(Unknown)"); } \ No newline at end of file |
implement separate version range property (#106)查看变更gradle.properties | 1 + src/main/resources/fabric.mod.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) @@ -3,6 +3,7 @@ org.gradle.jvmargs=-Xmx4G org.gradle.parallel=true #Fabric Properties minecraft_version=26.1.2 +minecraft_version_range=~26.1 loader_version=0.19.3 loom_version=1.16-SNAPSHOT # Mod Properties @@ -30,7 +30,7 @@ "depends": { "fabricloader": ">=${loader_version}", "fabric-api": ">=${fabric_api_version}", - "minecraft": "~${minecraft_version}", + "minecraft": "${minecraft_version_range}", "fabric-language-kotlin": ">=${fabric_kotlin_version}" }, "custom": { |