├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── config.js ├── core ├── CustomRegisters.js ├── Event.js ├── EventEnums.js └── Feature.js ├── features ├── commands │ └── InventoryLog.js ├── dungeons │ ├── AutoRequeueDungeons.js │ ├── BlazeSolver.js │ ├── BossSplits.js │ ├── BoulderSolver.js │ ├── BoxStarMobs.js │ ├── ChestProfit.js │ ├── CreeperBeamsSolver.js │ ├── CroesusClicks.js │ ├── CroesusProfit.js │ ├── CryptsDisplay.js │ ├── DeathsDisplay.js │ ├── ExtraStats.js │ ├── HideNoStarTag.js │ ├── LividSolver.js │ ├── MilestoneDisplay.js │ ├── MimicKilled.js │ ├── PuzzleDisplay.js │ ├── RemoveDmgTag.js │ ├── RunSplits.js │ ├── RunsLogger.js │ ├── SecretsClickedBox.js │ ├── SecretsSound.js │ ├── TeleportMazeSolver.js │ ├── ThreeWeirdosSolver.js │ ├── TicTacToeAlgorithm.js │ ├── TicTacToeSolver.js │ ├── TriviaSolver.js │ └── WaterBoardSolver.js ├── garden │ ├── GardenDisplay.js │ ├── GardenEvents.js │ ├── PestsDisplay.js │ ├── VisitorBzButton.js │ └── VisitorProfit.js ├── gui │ ├── AbstractGui.js │ ├── Button.js │ ├── CancelMessage.js │ ├── CommandAliases.js │ ├── KeyShortcuts.js │ └── TitleMessage.js ├── kuudra │ ├── CratesWaypoints.js │ └── KuudraSplits.js ├── mining │ ├── ComissionDisplay.js │ ├── EmissaryWaypoints.js │ └── PowderDisplay.js ├── misc │ ├── ArmorDisplay.js │ ├── AttributeShardDisplay.js │ ├── BlockOverlay.js │ ├── BonzoMaskInvincibility.js │ ├── ChampionDisplay.js │ ├── ChatWaypoint.js │ ├── CompactDisplay.js │ ├── CopyChat.js │ ├── CultivatingDisplay.js │ ├── DrillFuelDisplay.js │ ├── EnchantedBookDisplay.js │ ├── EquipmentDisplay.js │ ├── EtherwarpOverlay.js │ ├── FactoryHelper.js │ ├── HideEmptyTooltip.js │ ├── InventoryButtons.js │ ├── InventoryHistory.js │ ├── InventoryHud.js │ ├── ItemRarity.js │ ├── MiddleClickGuis.js │ ├── NoCursorReset.js │ ├── NoDeathAnimation.js │ ├── NoEndermanTeleport.js │ ├── NoLightning.js │ ├── PhoenixInvincibility.js │ ├── QuiverDisplay.js │ ├── RagnarokAxeCooldown.js │ ├── RemoveFrontView.js │ ├── RenderItems.js │ ├── SearchBar.js │ ├── SlotLocking.js │ ├── SmolderingPolarizationDisplay.js │ ├── SystemTimeDisplay.js │ ├── ToggleSprint.js │ └── WorldAgeDisplay.js ├── rift │ ├── BoxBerberis.js │ ├── EffigiesWaypoint.js │ ├── GlyphRender.js │ ├── LavaMaze.js │ ├── MushroomTimer.js │ ├── Tubulator.js │ └── WoodenButtons.js └── slayers │ ├── BossSlainTime.js │ ├── BossSpawnTime.js │ └── SlayerBossDisplay.js ├── index.js ├── metadata.json └── shared ├── ChestMenu.js ├── Command.js ├── CustomSplits.js ├── DGlStateManager.js ├── DraggableGui.js ├── EtherwarpHelper.js ├── InventoryButton.js ├── Location.js ├── Persistence.js ├── PuzzleRoomScanner.js ├── Render.js ├── TextHelper.js └── Vec3.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: docilelm 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.toml 3 | Dev.js 4 | !metadata.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Doc 2 | ChatTriggers Module [https://www.chattriggers.com/modules/v/Doc] 3 | 4 | # Features 5 | 6 | ## Dungeons 7 | + Box Star Mobs 8 | + Draws a box at star mobs 9 | +
Image
10 | + Show Secrets Clicked 11 | + Draws a box at the clicked wither essence/chests/redstone skull 12 | + Run Splits 13 | + Displays your current dungeon run's splits 14 | +
Image
15 | + Chest Profit 16 | + Displays the current chest's profit 17 | +
Image
18 | + Show Croesus Clicks 19 | + Highlights chests you've _CLICKED_ 20 | + The list resets on world change 21 | +
Image
22 | + Croesus Profit 23 | + Displays the current chests profit inside of croesus 24 | + Show Extra Stats 25 | + Automatically sends the /showextrastats command at the end of a dungeon run 26 | + Boss Splits 27 | + Displays your current dungeon's boss splits 28 | +
Image
29 | 30 | ## Mining 31 | + Emissary Waypoints 32 | + Draws a waypoint on every emissary in the dwarven mines 33 | +
Image
34 | + Gemstone Mining Profit 35 | + Displays your current session's mined gemstones and profit made 36 | +
Image
37 | 38 | ## Fishing 39 | + Boss Bar 40 | + Displays a boss bar with [hp/max hp] of loot sharing mobs 41 | + Timer Title 42 | + Displays the hypixel's timer as title 43 | 44 | ## Garden 45 | + Visitor Profit Display 46 | + Displays most of the visitor's profit also calculating the special item 47 | +
Image
48 | 49 | ## Slayer 50 | + Boss Slain Timer 51 | + Sends a chat message with the time it took to kill the slayer 52 | 53 | ## Tracker 54 | + Ghost Tracker 55 | + Displays your current session's kills, drops, magic find, profit and kill combo 56 | +
Image
57 | + Trophy Fishing Tracker 58 | + Displays the trophy fishes you've caught in the current session 59 | +
Image
60 | + Powder Mining Tracker 61 | + Displays your current session's chests, powder gained 62 | +
Image
63 | 64 | ## Kuudra 65 | + Fatal Tempo Display 66 | + Displays your current fatal tempo time, hits and percent 67 | +
Image
68 | + Crates Waypoints 69 | + Draws a waypoint at the crates 70 | + Stops rendering them once p1 of kuudra is done 71 | 72 | ## Misc 73 | + Ragnarok Axe Cooldown 74 | + Displays the current ragnarok axe cooldown 75 | + RngMeter 76 | + Displays your current rng meter for dungeons or slayers 77 | +
Dungeons
78 | +
Slayers
79 | 80 | ## Commands 81 | + /ping 82 | + Inventory Logs 83 | + saves your current inventory so you can open or remove it later on 84 | + in case you do multiple things and constantly forget about your hotbar/inventory 85 | + /invlogs \ \ 86 | + type can be [add, open, remove] name will be the name which the inventory was saved with or will be saved with 87 | -------------------------------------------------------------------------------- /core/Event.js: -------------------------------------------------------------------------------- 1 | import { customTriggers } from "./CustomRegisters" 2 | 3 | export class Event { 4 | constructor(name, cb, args) { 5 | // Fields needed for this event 6 | this.name = name 7 | this.cb = cb 8 | this.args = args 9 | 10 | // The register itself 11 | this.isCustom = typeof this.name === "number" 12 | this._register = this.isCustom 13 | // Custom triggers are number enums 14 | ? customTriggers.get(this.name)?.(this.cb, this.args) 15 | // Normal are just strings 16 | : register(this.name, this.cb).unregister() 17 | 18 | this.isCustom && Array.isArray(this._register) 19 | ? this._register.forEach(it => it.unregister()) 20 | : this._register.unregister() 21 | 22 | // Always start unregistered 23 | this.hasRegistered = false 24 | } 25 | 26 | /** 27 | * - Registers this [event]'s trigger 28 | * @returns this for method chaining 29 | */ 30 | register() { 31 | if (this.hasRegistered) return this 32 | 33 | if (this.isCustom && Array.isArray(this._register)) { 34 | for (let idx = 0; idx < this._register.length; idx++) 35 | this._register[idx].register() 36 | this.hasRegistered = true 37 | 38 | return this 39 | } 40 | 41 | this._register.register() 42 | this.hasRegistered = true 43 | 44 | return this 45 | } 46 | 47 | /** 48 | * - Unregisters this [event]'s trigger 49 | * @returns this for method chaining 50 | */ 51 | unregister() { 52 | if (!this.hasRegistered) return this 53 | 54 | if (this.isCustom && Array.isArray(this._register)) { 55 | for (let idx = 0; idx < this._register.length; idx++) 56 | this._register[idx].unregister() 57 | this.hasRegistered = false 58 | 59 | return this 60 | } 61 | 62 | this._register.unregister() 63 | this.hasRegistered = false 64 | 65 | return this 66 | } 67 | } -------------------------------------------------------------------------------- /core/EventEnums.js: -------------------------------------------------------------------------------- 1 | let idx = 0 2 | 3 | const EnumParticleTypes = net.minecraft.util.EnumParticleTypes 4 | 5 | // Just for auto completion 6 | export const ParticleEnums = { 7 | BARRIER: EnumParticleTypes.BARRIER, 8 | BLOCK_CRACK: EnumParticleTypes.BLOCK_CRACK, 9 | BLOCK_DUST: EnumParticleTypes.BLOCK_DUST, 10 | CLOUD: EnumParticleTypes.CLOUD, 11 | CRIT: EnumParticleTypes.CRIT, 12 | CRIT_MAGIC: EnumParticleTypes.CRIT_MAGIC, 13 | DRIP_LAVA: EnumParticleTypes.DRIP_LAVA, 14 | DRIP_WATER: EnumParticleTypes.DRIP_WATER, 15 | ENCHANTMENT_TABLE: EnumParticleTypes.ENCHANTMENT_TABLE, 16 | EXPLOSION_HUGE: EnumParticleTypes.EXPLOSION_HUGE, 17 | EXPLOSION_LARGE: EnumParticleTypes.EXPLOSION_LARGE, 18 | EXPLOSION_NORMAL: EnumParticleTypes.EXPLOSION_NORMAL, 19 | FIREWORKS_SPARK: EnumParticleTypes.FIREWORKS_SPARK, 20 | FLAME: EnumParticleTypes.FLAME, 21 | FOOTSTEP: EnumParticleTypes.FOOTSTEP, 22 | HEART: EnumParticleTypes.HEART, 23 | ITEM_CRACK: EnumParticleTypes.ITEM_CRACK, 24 | ITEM_TAKE: EnumParticleTypes.ITEM_TAKE, 25 | LAVA: EnumParticleTypes.LAVA, 26 | MOB_APPEARANCE: EnumParticleTypes.MOB_APPEARANCE, 27 | NOTE: EnumParticleTypes.NOTE, 28 | PORTAL: EnumParticleTypes.PORTAL, 29 | REDSTONE: EnumParticleTypes.REDSTONE, 30 | SLIME: EnumParticleTypes.SLIME, 31 | SMOKE_LARGE: EnumParticleTypes.SMOKE_LARGE, 32 | SMOKE_NORMAL: EnumParticleTypes.SMOKE_NORMAL, 33 | SNOW_SHOVEL: EnumParticleTypes.SNOW_SHOVEL, 34 | SNOWBALL: EnumParticleTypes.SNOWBALL, 35 | SPELL: EnumParticleTypes.SPELL, 36 | SPELL_INSTANT: EnumParticleTypes.SPELL_INSTANT, 37 | SPELL_MOB: EnumParticleTypes.SPELL_MOB, 38 | SPELL_MOB_AMBIENT: EnumParticleTypes.SPELL_MOB_AMBIENT, 39 | SPELL_WITCH: EnumParticleTypes.SPELL_WITCH, 40 | SUSPENDED: EnumParticleTypes.SUSPENDED, 41 | SUSPENDED_DEPTH: EnumParticleTypes.SUSPENDED_DEPTH, 42 | TOWN_AURA: EnumParticleTypes.TOWN_AURA, 43 | VILLAGER_ANGRY: EnumParticleTypes.VILLAGER_ANGRY, 44 | VILLAGER_HAPPY: EnumParticleTypes.VILLAGER_HAPPY, 45 | WATER_BUBBLE: EnumParticleTypes.WATER_BUBBLE, 46 | WATER_DROP: EnumParticleTypes.WATER_DROP, 47 | WATER_SPLASH: EnumParticleTypes.WATER_SPLASH, 48 | WATER_WAKE: EnumParticleTypes.WATER_WAKE, 49 | } 50 | 51 | export default { 52 | STEP: idx++, 53 | CHAT: idx++, 54 | SOUNDPLAY: idx++, 55 | COMMAND: idx++, 56 | RENDERENTITY: idx++, 57 | POSTRENDERENTITY: idx++, 58 | ENTITYDEATH: idx++, 59 | PACKET: { 60 | CLIENT: { 61 | BLOCKPLACEMENT: idx++, 62 | WINDOWCLICK: idx++, 63 | DIGGING: idx++, 64 | WINDOWCLOSE: idx++, 65 | HELDITEMCHANGE: idx++ 66 | }, 67 | SERVER: { 68 | CHAT: idx++, 69 | ACTIONBAR: idx++, 70 | SCOREBOARD: idx++, 71 | TABUPDATE: idx++, 72 | TABADD: idx++, 73 | TABHEADERFOOTER: idx++, 74 | WINDOWITEMS: idx++, 75 | WINDOWOPEN: idx++, 76 | WINDOWCLOSE: idx++, 77 | PLAYERPOSLOOK: idx++, 78 | COLLECTITEM: idx++, 79 | ENTITYLOOKMOVE: idx++, 80 | SPAWNPARTICLE: idx++, 81 | SPAWNMOB: idx++, 82 | BLOCKCHANGE: idx++, 83 | SETSLOT: idx++, 84 | MULTIBLOCKCHANGE: idx++ 85 | }, 86 | CUSTOM: { 87 | BLESSINGCHANGE: idx++, 88 | WINDOWCLOSE: idx++, 89 | TICK: idx++, 90 | OPENEDCHEST: idx++, 91 | MULTIBLOCKCHANGE: idx++ 92 | } 93 | }, 94 | FORGE: { 95 | ENTITYJOIN: idx++ 96 | } 97 | } -------------------------------------------------------------------------------- /features/commands/InventoryLog.js: -------------------------------------------------------------------------------- 1 | import { ChestMenu } from "../../shared/ChestMenu" 2 | import { addCommand } from "../../shared/Command" 3 | import { createSkull } from "../../shared/InventoryButton" 4 | import { Persistence } from "../../shared/Persistence" 5 | import { TextHelper } from "../../shared/TextHelper" 6 | 7 | const logs = Persistence.getDataFromFile("InventoryLogs.json") 8 | const chest = new ChestMenu(`${TextHelper.PREFIX2} §bInventory§f`, 5) 9 | 10 | let data = [] 11 | 12 | const createCustomItem = (nbt) => { 13 | // If it's not a skull we use default item creation 14 | if (nbt.id !== "minecraft:skull") { 15 | const item = new Item(net.minecraft.item.ItemStack.func_77949_a(NBT.parse(nbt).rawNBT)) 16 | const lore = nbt.tag.display.Lore 17 | if (lore) item.setLore(lore) 18 | 19 | return item 20 | } 21 | 22 | // Otherwise we start doing funny skull stuff 23 | // it's done this way so it doesn't break the users' textures on other skull items 24 | const texture = nbt.tag.SkullOwner.Properties.textures[0].Value 25 | const item = createSkull(texture) 26 | 27 | const display = nbt.tag.display 28 | const lore = display.Lore 29 | const name = display.Name 30 | 31 | item.setName(name) 32 | item.setStackSize(nbt.Count) 33 | if (lore) item.setLore(lore) 34 | 35 | return item 36 | } 37 | 38 | addCommand("invlog", "Command which opens or logs your current inventory with a specified name", (type, name) => { 39 | type = type?.toLowerCase() 40 | 41 | switch (type) { 42 | case "list": 43 | ChatLib.chat(`${TextHelper.PREFIX} &aInventory names saved \n&6- &b${Object.keys(logs).join("\n&6- &b")}`) 44 | break 45 | 46 | case "add": { 47 | if (name in logs) return ChatLib.chat(`${TextHelper.PREFIX} &cInventory with name &b${name}&c already exists`) 48 | if (!name) return ChatLib.chat(`${TextHelper.PREFIX} &cInvalid inventory name passed`) 49 | logs[name] = {} 50 | 51 | const items = Player.getInventory().getItems() 52 | for (let idx = 0; idx < items.length; idx++) { 53 | let item = items[idx] 54 | if (!item) continue 55 | 56 | logs[name][idx] = item.getNBT().toObject() 57 | } 58 | 59 | ChatLib.chat(`${TextHelper.PREFIX} &aSuccessfully added &b${name} &ato inventory logs`) 60 | break 61 | } 62 | 63 | case "open": { 64 | if (!(name in logs)) return ChatLib.chat(`${TextHelper.PREFIX} &cInventory with name &b${name}&c does not exist`) 65 | const items = logs[name] 66 | Object.entries(items).forEach(([slot, nbt]) => { 67 | slot = slot < 9 68 | ? slot + 36 69 | : slot >= 36 70 | ? slot % 36 71 | : slot 72 | data[slot] = createCustomItem(nbt) 73 | }) 74 | 75 | chest 76 | .setItems(data) 77 | .setTitle(`${TextHelper.PREFIX2} &b&lInventory&f &6${name}`) 78 | .open() 79 | break 80 | } 81 | 82 | case "remove": { 83 | if (!(name in logs)) return ChatLib.chat(`${TextHelper.PREFIX} &cInventory with name &b${name}&c does not exist`) 84 | delete logs[name] 85 | ChatLib.chat(`${TextHelper.PREFIX} &aSuccessfully removed &b${name} &afrom inventory logs`) 86 | break 87 | } 88 | 89 | default: 90 | ChatLib.chat(`${TextHelper.PREFIX} &cPlease add a valid mode &7modes: (add, list, remove, open)`) 91 | break 92 | } 93 | }) 94 | 95 | register("gameUnload", () => { 96 | Persistence.saveDataToFile("InventoryLogs.json", logs, true, false) 97 | }) -------------------------------------------------------------------------------- /features/dungeons/AutoRequeueDungeons.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import Location from "../../shared/Location" 6 | import { TextHelper } from "../../shared/TextHelper" 7 | 8 | let shouldDownTime = null 9 | let commandReceived = false 10 | 11 | const feat = new Feature("autoRequeueDungeons") 12 | .addEvent( 13 | new Event(EventEnums.PACKET.SERVER.CHAT, (name, msg) => { 14 | if (msg.toLowerCase() === "r") { 15 | ChatLib.chat(`${TextHelper.PREFIX} &a${shouldDownTime} is ready`) 16 | shouldDownTime = null 17 | return 18 | } 19 | if (msg.toLowerCase() !== "dt") return 20 | 21 | shouldDownTime = name 22 | ChatLib.chat(`${TextHelper.PREFIX} &bUser &6${shouldDownTime} &bneeds downtime`) 23 | }, /^Party > (?:\[\d+\] .? ?)?(?:\[[^\]]+\] )?(\w{1,16}): !(\w{1,2})(?: [\w ]+)?$/) 24 | ) 25 | .addEvent( 26 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 27 | if (!Location.inWorld("catacombs")) return 28 | 29 | if (shouldDownTime) { 30 | ChatLib.say(`${shouldDownTime} needs downtime`) 31 | return 32 | } 33 | 34 | commandReceived = true 35 | feat.update() 36 | 37 | if (config().autoRequeueDungeonsChestMode) return 38 | 39 | ChatLib.command("instancerequeue") 40 | }, /^ *> EXTRA STATS <$/) 41 | ) 42 | .addSubEvent( 43 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 44 | ChatLib.command("instancerequeue") 45 | }, /^ [A-z]+ CHEST REWARDS$/), 46 | () => commandReceived && config().autoRequeueDungeonsChestMode && Location.inWorld("catacombs") 47 | ) 48 | .onUnregister(() => { 49 | commandReceived = false 50 | if (shouldDownTime) { 51 | ChatLib.chat(`${TextHelper.PREFIX} &aDowntime has been resetted.`) 52 | shouldDownTime = null 53 | } 54 | }) -------------------------------------------------------------------------------- /features/dungeons/BlazeSolver.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { onPuzzleRotationExit, onPuzzleScheduledRotation } from "../../shared/PuzzleRoomScanner" 6 | import { RenderHelper } from "../../shared/Render" 7 | import { TextHelper } from "../../shared/TextHelper" 8 | 9 | // Credits: https://github.com/UnclaimedBloom6/BloomModule/blob/main/features/BlazeSolver.js 10 | 11 | const blazeHealthRegex = /^\[Lv15\] Blaze [\d,]+\/([\d,]+)❤$/ 12 | const Blocks = net.minecraft.init.Blocks 13 | const BlockBedrock = Blocks.field_150357_h 14 | const BlockBanner = Blocks.field_180393_cK 15 | const relativeCoords = { 16 | bannerTop: [-4, 59, -5], 17 | bedrockTop: [5, 68, -8], 18 | bannerBottom1: [-3, 69, 10], 19 | bannerBottom2: [3, 69, 10] 20 | } 21 | 22 | let inBlaze = false 23 | let isTop = false 24 | let blazes = [] 25 | let enteredRoomAt = null 26 | let lastBlazes = null 27 | 28 | const reset = () => { 29 | inBlaze = false 30 | isTop = false 31 | blazes = [] 32 | enteredRoomAt = null 33 | lastBlazes = null 34 | } 35 | 36 | const feat = new Feature("blazeSolver", "catacombs") 37 | .addSubEvent( 38 | new Event(EventEnums.STEP, () => { 39 | blazes = World.getAllEntitiesOfType(net.minecraft.entity.item.EntityArmorStand) 40 | .filter(it => blazeHealthRegex.test(it.getName().removeFormatting())) 41 | .map(it => [it, Math.floor(it.getName().removeFormatting().match(blazeHealthRegex)[1].replace(/,/g, ""))]) 42 | 43 | if (blazes.length === 9) enteredRoomAt = Date.now() 44 | if (blazes.length === 0 && enteredRoomAt && lastBlazes === 1) { 45 | TextHelper.sendPuzzleMsg("Blaze", enteredRoomAt) 46 | if (config().blazeSolverDone) ChatLib.command("pc Blaze Done") 47 | 48 | reset() 49 | feat.update() 50 | 51 | return 52 | } 53 | 54 | blazes.sort((a, b) => a[1] - b[1]) 55 | 56 | if (isTop) blazes.reverse() 57 | 58 | lastBlazes = blazes.length 59 | 60 | feat.update() 61 | }, 5), 62 | () => inBlaze 63 | ) 64 | .addSubEvent( 65 | new Event("renderWorld", () => { 66 | let nextBlazes = [] 67 | 68 | for (let idx = 0; idx < blazes.length; idx++) { 69 | let entity = blazes[idx][0] 70 | let [ x, y, z ] = [ entity.getX(), entity.getY() - 2, entity.getZ() ] 71 | let [ r, g, b ] = idx == 0 ? [0, 255, 0] : idx == 1 ? [250, 250, 51] : [255, 255, 255] 72 | 73 | if (idx <= 1) nextBlazes[idx] = [ x, y, z ] 74 | 75 | RenderHelper.drawEntityBoxFilled(x, y, z, 0.6, 1.8, r, g, b, 255) 76 | } 77 | 78 | if (!nextBlazes[1] || !config().blazeSolverLine) return 79 | 80 | // Draw line to next blaze 81 | RenderHelper.drawLineThroughPoints(nextBlazes, 0, 255, 0, 255, false, 2) 82 | }), 83 | () => inBlaze && blazes.length 84 | ) 85 | .addSubEvent( 86 | new Event(EventEnums.RENDERENTITY, (_, __, ___, event) => { 87 | cancel(event) 88 | }, net.minecraft.entity.monster.EntityBlaze), 89 | () => inBlaze && config().blazeSolverHide 90 | ) 91 | .onUnregister(() => { 92 | reset() 93 | }) 94 | 95 | onPuzzleScheduledRotation((rotation) => { 96 | if (!config().blazeSolver && !config().blazeSolverLine) return 97 | 98 | // Top 99 | const bannerTop = World.getBlockAt(...TextHelper.getRealCoord(relativeCoords.bannerTop, rotation)).type.mcBlock 100 | const bedrockTop = World.getBlockAt(...TextHelper.getRealCoord(relativeCoords.bedrockTop, rotation)).type.mcBlock 101 | // Bottom 102 | const bannerBottom1 = World.getBlockAt(...TextHelper.getRealCoord(relativeCoords.bannerBottom1, rotation)).type.mcBlock 103 | const bannerBottom2 = World.getBlockAt(...TextHelper.getRealCoord(relativeCoords.bannerBottom2, rotation)).type.mcBlock 104 | 105 | inBlaze = (bannerTop === BlockBanner && bedrockTop === BlockBedrock) || (bannerBottom1 === BlockBanner && bannerBottom2 === BlockBanner) 106 | isTop = bannerTop === BlockBanner && bedrockTop === BlockBedrock 107 | 108 | if (!inBlaze) return 109 | 110 | ChatLib.chat(`${TextHelper.PREFIX} &aBlaze room detected`) 111 | 112 | feat.update() 113 | }) 114 | 115 | onPuzzleRotationExit(() => { 116 | if (!inBlaze) return 117 | 118 | reset() 119 | feat.update() 120 | }) -------------------------------------------------------------------------------- /features/dungeons/BossSplits.js: -------------------------------------------------------------------------------- 1 | import AtomxApi from "../../../Atomx/AtomxApi" 2 | import { scheduleTask } from "../../core/CustomRegisters" 3 | import { Event } from "../../core/Event" 4 | import EventEnums from "../../core/EventEnums" 5 | import Feature from "../../core/Feature" 6 | import CustomSplits from "../../shared/CustomSplits" 7 | import DraggableGui from "../../shared/DraggableGui" 8 | import Location from "../../shared/Location" 9 | import { Persistence } from "../../shared/Persistence" 10 | 11 | const editGui = new DraggableGui("bossSplits").setCommandName("editbosssplits") 12 | const dungeonFloorRegex = AtomxApi.getRegexData().Dungeons.Floor 13 | const bossSplits = Persistence.getDataFromFileOrLink("BossSplits.json", "https://raw.githubusercontent.com/DocilElm/Doc-Data/refs/heads/main/dungeons/BossSplits.json") 14 | const defaultString = [ 15 | `&dTerracotta&f: &a10s`, 16 | `&bGiants&f: &a10s`, 17 | `&aSadan&f: &a10s`, 18 | ].join("\n") 19 | 20 | let previousFloor = null 21 | let split = null 22 | 23 | const onFloor = (floor, feat) => { 24 | if (floor === previousFloor) return 25 | 26 | const floorNum = parseInt(floor.replace(/F|M/, "")) 27 | const floorName = floorNum < 7 ? `F${floorNum}` : floor 28 | 29 | previousFloor = floor 30 | split = new CustomSplits(bossSplits[floorName], () => true) 31 | split.onTimeUpdate = () => scheduleTask(() => feat.update()) 32 | split.getEvents().forEach(it => feat.addSubEvent(it, () => Location.inWorld("catacombs"))) 33 | feat.update() 34 | } 35 | 36 | editGui.onDraw(() => { 37 | Renderer.translate(editGui.getX(), editGui.getY()) 38 | Renderer.scale(editGui.getScale()) 39 | Renderer.drawStringWithShadow(defaultString, 0, 0) 40 | Renderer.finishDraw() 41 | }) 42 | 43 | const feat = new Feature("dungeonBossSplits", "catacombs") 44 | .addEvent( 45 | new Event(EventEnums.PACKET.SERVER.SCOREBOARD, (floor) => { 46 | onFloor(floor, feat) 47 | }, dungeonFloorRegex) 48 | ) 49 | .addSubEvent( 50 | new Event("renderOverlay", () => { 51 | Renderer.retainTransforms(true) 52 | Renderer.translate(editGui.getX(), editGui.getY()) 53 | Renderer.scale(editGui.getScale()) 54 | Renderer.drawStringWithShadow("&bBoss Splits", 0, 0) 55 | 56 | Renderer.drawStringWithShadow(split.buildStr(), 0, 10) 57 | 58 | Renderer.retainTransforms(false) 59 | Renderer.finishDraw() 60 | }), 61 | () => split !== null && split?.timers?.[0]?.timer !== null 62 | ) 63 | .onUnregister(() => { 64 | // References BE GONE! 65 | if (split) { 66 | const events = split.events 67 | for (let idx = events.length - 1; idx >= 0; idx--) { 68 | events[idx].unregister() 69 | events.splice(idx, 1) 70 | } 71 | 72 | const subevents = feat.subevents 73 | for (let idx = subevents.length - 1; idx >= 0; idx--) { 74 | // "renderOverlay" event _should_ always be the first sub event so we ignore it 75 | if (idx === 0) continue 76 | 77 | subevents.splice(idx, 1) 78 | } 79 | split.onTimeUpdate = null 80 | } 81 | split = null 82 | previousFloor = null 83 | }) -------------------------------------------------------------------------------- /features/dungeons/BoulderSolver.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { Persistence } from "../../shared/Persistence" 6 | import { onPuzzleRotationExit, onPuzzleScheduledRotation } from "../../shared/PuzzleRoomScanner" 7 | import { RenderHelper } from "../../shared/Render" 8 | import { TextHelper } from "../../shared/TextHelper" 9 | 10 | const solutions = Persistence.getDataFromFileOrLink("BoulderDataNew.json", "https://raw.githubusercontent.com/DocilElm/Doc-Data/main/dungeons/BoulderData.json")?.solutions 11 | const relativeCoords = { 12 | ironbar: [ 0, 70, -12 ], 13 | chest: [ 0, 66, -14 ], 14 | firstbox: [ -9, 66, -9 ] 15 | } 16 | 17 | /** @type {BoulderBox[]} */ 18 | let renderBlocks = [] 19 | let enteredRoomAt = null 20 | 21 | /** 22 | * - Gets the current variant of boulder 23 | * @param {number} rotation 24 | * @returns {string} 25 | * @link Credits to [Bloom](https://github.com/UnclaimedBloom6) 26 | */ 27 | const getBoulderGrid = (rotation) => { 28 | const [ rx, ry, rz ] = relativeCoords.firstbox 29 | let str = "" 30 | 31 | for (let z = 0; z <= 15; z += 3) { 32 | for (let x = 0; x <= 18; x += 3) { 33 | let block = World.getBlockAt(...TextHelper.getRealCoord([rx + x, ry, rz + z], rotation)) 34 | 35 | if (block.type.getID() === 0) str += "0" 36 | else str += "1" 37 | } 38 | } 39 | 40 | return str 41 | } 42 | 43 | class BoulderBox { 44 | constructor(render, click, rotation) { 45 | this.render = TextHelper.getRealCoord(render, rotation) 46 | this.click = TextHelper.getRealCoord(click, rotation) 47 | } 48 | 49 | onClick(click) { 50 | return click[0] === this.click[0] && click[1] === this.click[1] && click[2] === this.click[2] 51 | } 52 | 53 | toString() { 54 | return `BoulderBox=[render=${this.render}, click=${this.click}]` 55 | } 56 | } 57 | 58 | const feat = new Feature("boulderSolver", "catacombs") 59 | .addSubEvent( 60 | new Event("renderWorld", () => { 61 | for (let idx = 0; idx < renderBlocks.length; idx++) { 62 | let data = renderBlocks[idx] 63 | let block = World.getBlockAt(...data.render) 64 | 65 | RenderHelper.outlineBlock(block, 0, 243, 200, 255) 66 | RenderHelper.filledBlock(block, 0, 243, 200, 50) 67 | } 68 | }), 69 | () => enteredRoomAt && renderBlocks.length 70 | ) 71 | .addSubEvent( 72 | new Event(EventEnums.PACKET.CLIENT.BLOCKPLACEMENT, (/**@type {Block}*/block, pos) => { 73 | for (let idx = renderBlocks.length - 1; idx >= 0; idx--) { 74 | let data = renderBlocks[idx] 75 | 76 | if (!data.onClick(pos)) continue 77 | 78 | renderBlocks.splice(idx, 1) 79 | } 80 | 81 | feat.update() 82 | }), 83 | () => enteredRoomAt && renderBlocks.length 84 | ) 85 | .addSubEvent( 86 | new Event(EventEnums.PACKET.CUSTOM.OPENEDCHEST, () => { 87 | TextHelper.sendPuzzleMsg("Boulder", enteredRoomAt) 88 | renderBlocks = [] 89 | enteredRoomAt = null 90 | }), 91 | () => enteredRoomAt 92 | ) 93 | .onUnregister(() => { 94 | renderBlocks = [] 95 | enteredRoomAt = null 96 | }) 97 | 98 | onPuzzleScheduledRotation((rotation) => { 99 | if (!config().boulderSolver) return 100 | 101 | const block = World.getBlockAt(...TextHelper.getRealCoord(relativeCoords.ironbar, rotation)) 102 | 103 | if (block.type.mcBlock !== net.minecraft.init.Blocks.field_150411_aY) return 104 | 105 | const theGrid = getBoulderGrid(rotation) 106 | const currentSolution = solutions[theGrid] 107 | if (!currentSolution) return ChatLib.chat(`${TextHelper.PREFIX} &cBoulder room variant not found in the data`) 108 | 109 | ChatLib.chat(`${TextHelper.PREFIX} &aBoulder room detected`) 110 | enteredRoomAt = Date.now() 111 | 112 | for (let idx = 0; idx < currentSolution.render.length; idx++) { 113 | let render = currentSolution.render[idx] 114 | let click = currentSolution.click[idx] 115 | 116 | renderBlocks.push(new BoulderBox(render, click, rotation)) 117 | } 118 | 119 | feat.update() 120 | }) 121 | 122 | onPuzzleRotationExit(() => { 123 | renderBlocks = [] 124 | enteredRoomAt = null 125 | feat.update() 126 | }) -------------------------------------------------------------------------------- /features/dungeons/BoxStarMobs.js: -------------------------------------------------------------------------------- 1 | import { scheduleTask } from "../../core/CustomRegisters" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { RenderHelper } from "../../shared/Render" 6 | 7 | const mobs = new HashMap() 8 | 9 | let useSeverTicks = false 10 | 11 | const scanEntityName = (mcEntity, entityId, feat) => { 12 | const name = mcEntity./* getName */func_70005_c_() 13 | if (!name.includes("✯ ")) return 14 | 15 | const entityBelowId = entityId - (name.includes("Withermancer") ? 3 : 1) 16 | const entityBelow = World.getWorld()./* getEntityByID */func_73045_a(entityBelowId) 17 | if (!entityBelow) return 18 | if (entityBelow instanceof net.minecraft.entity.monster.EntityEnderman) { 19 | mobs.put(entityBelowId, [ 20 | /* width */0.6, 21 | /* height */0.7, 22 | /* red */255, 23 | /* green */51, 24 | /* blue */255, 25 | /* alpha */255 26 | ]) 27 | feat.update() 28 | return 29 | } 30 | 31 | mobs.put(entityBelowId, [ 32 | /* width */entityBelow./* width */field_70130_N, 33 | /* height */entityBelow./* height */field_70131_O + 0.2, // "magic number" - an attempt to try to make the hitbox go over the armor the entity is wearing 34 | /* red */0, 35 | /* green */255, 36 | /* blue */255, 37 | /* alpha */255 38 | ]) 39 | feat.update() 40 | } 41 | 42 | const feat = new Feature("boxStarMobs", "catacombs") 43 | .addEvent( 44 | new Event(EventEnums.PACKET.SERVER.SCOREBOARD, () => { 45 | useSeverTicks = true 46 | }, /^Time Elapsed\: 03s$/) 47 | ) 48 | .addEvent( 49 | new Event(EventEnums.FORGE.ENTITYJOIN, (mcEntity, entityId) => { 50 | // Scan with client ticks if the server packets aren't able to arrive yet 51 | if (!useSeverTicks) { 52 | Client.scheduleTask(3, () => scanEntityName(mcEntity, entityId, feat)) 53 | return 54 | } 55 | 56 | scheduleTask(() => scanEntityName(mcEntity, entityId, feat)) 57 | }) 58 | ) 59 | .addSubEvent( 60 | new Event("renderEntity", (entity, _, pticks) => { 61 | const entityId = entity.entity./* getEntityId */func_145782_y() 62 | const data = mobs.get(entityId) 63 | if (!data) return 64 | if (entity.isDead()) return mobs.remove(entityId) 65 | 66 | const [ width, height, r, g, b, a ] = data 67 | 68 | RenderHelper.drawEntityBox( 69 | entity.getX(), 70 | entity.getY(), 71 | entity.getZ(), 72 | width, 73 | height, 74 | r, g, b, a, 2, false, true, pticks 75 | ) 76 | }), 77 | () => mobs.size() 78 | ) 79 | .onUnregister(() => { 80 | mobs.clear() 81 | useSeverTicks = false 82 | }) -------------------------------------------------------------------------------- /features/dungeons/ChestProfit.js: -------------------------------------------------------------------------------- 1 | import Price from "../../../Atomx/skyblock/Price" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import DraggableGui from "../../shared/DraggableGui" 6 | import { TextHelper } from "../../shared/TextHelper" 7 | 8 | // Credits: https://github.com/UnclaimedBloom6/BloomModule/blob/main/features/dungeonChestProfit/DungeonChestProfit.js 9 | 10 | const editGui = new DraggableGui("dungeonProfit").setCommandName("editdungeonProfit") 11 | const essenceRegex = /^(Undead|Wither) Essence x(\d+)$/ 12 | const chestNames = ["Wood Chest", "Gold Chest", "Diamond Chest", "Emerald Chest", "Obsidian Chest", "Bedrock Chest"] 13 | const formattedChest = { 14 | "Wood Chest": "&fWood Chest", 15 | "Gold Chest": "&6Gold Chest", 16 | "Diamond Chest": "&bDiamond Chest", 17 | "Emerald Chest": "&2Emerald Chest", 18 | "Obsidian Chest": "&5Obsidian Chest", 19 | "Bedrock Chest": "&8Bedrock Chest", 20 | } 21 | 22 | let inChest = false 23 | let currentChest = null 24 | let chestData = [] 25 | 26 | const getValue = (item) => { 27 | if (!item) return 0 28 | const itemName = item.getName().removeFormatting() 29 | 30 | if (essenceRegex.test(itemName)) { 31 | let [ _, type, amount ] = itemName.match(essenceRegex) 32 | 33 | return Math.floor(Price.getSellPrice(`ESSENCE_${type}`.toUpperCase()) * Math.floor(amount)) 34 | } 35 | 36 | return Math.floor(Price.getSellPrice(TextHelper.getSkyblockItemID(item))) || 0 37 | } 38 | 39 | editGui.onDraw(() => { 40 | Renderer.retainTransforms(true) 41 | Renderer.translate(editGui.getX(), editGui.getY()) 42 | Renderer.scale(editGui.getScale()) 43 | Renderer.drawStringWithShadow(`\n&aFree Chest\n&aExample Item &8x25\n&aTotal Profit&f: &a1,000`, 0, 0) 44 | Renderer.retainTransforms(false) 45 | Renderer.finishDraw() 46 | }) 47 | 48 | const feat = new Feature("dungeonProfitDisplay", "catacombs") 49 | .addEvent( 50 | new Event(EventEnums.PACKET.SERVER.WINDOWOPEN, (title) => { 51 | inChest = chestNames.includes(title) 52 | if (inChest) currentChest = title 53 | feat.update() 54 | }) 55 | ) 56 | .addSubEvent( 57 | new Event(EventEnums.PACKET.SERVER.WINDOWITEMS, (items) => { 58 | const fidx = chestData.findIndex(it => it.name.removeFormatting() === currentChest) 59 | if (fidx !== -1) chestData.splice(fidx, 1) 60 | 61 | if (!items[31]) return 62 | const chestItem = new Item(items[31]) 63 | const chestLore = chestItem.getLore() 64 | 65 | let chestPrice = Math.floor(chestLore[7]?.removeFormatting()?.replace(/([,]+| Coins)/g, "")) || 0 66 | chestPrice += (Price.getSellPrice(chestLore[8]?.removeFormatting()?.replace(/ /g, "_")?.toUpperCase()) || 0) 67 | 68 | let data = { 69 | name: formattedChest[currentChest], 70 | items: [], 71 | profit: 0, 72 | display: "" 73 | } 74 | 75 | for (let idx = 9; idx < 18; idx++) { 76 | if (!items[idx]) continue 77 | let item = new Item(items[idx]) 78 | if (item.getID() === 160) continue 79 | 80 | let itemName = item.getName().removeFormatting() === "Enchanted Book" ? item.getLore()[1] : item.getName() 81 | 82 | data.items.push(`\n${itemName}`) 83 | data.profit += getValue(item) 84 | } 85 | 86 | data.profit = data.profit - chestPrice 87 | let profitColor = data.profit < 0 ? "&c" : "&a" 88 | 89 | data.display = `${data.name}${data.items.join("")}\n&bTotal Profit&f: ${profitColor}${TextHelper.addCommasTrunc(data.profit)}\n\n` 90 | 91 | chestData.push(data) 92 | 93 | inChest = false 94 | feat.update() 95 | }), 96 | () => inChest && currentChest 97 | ) 98 | .addSubEvent( 99 | new Event(EventEnums.PACKET.CUSTOM.WINDOWCLOSE, () => { 100 | const fidx = chestData.findIndex(it => it.name.removeFormatting() === currentChest) 101 | if (fidx === -1) return 102 | 103 | chestData.splice(fidx, 1) 104 | }), 105 | () => inChest 106 | ) 107 | .addSubEvent( 108 | new Event("renderOverlay", () => { 109 | if (editGui.isOpen()) return 110 | 111 | Renderer.retainTransforms(true) 112 | Renderer.translate(editGui.getX(), editGui.getY()) 113 | Renderer.scale(editGui.getScale()) 114 | 115 | let str = "" 116 | 117 | for (let idx = 0; idx < chestData.length; idx++) { 118 | str += chestData[idx].display 119 | } 120 | 121 | Renderer.drawStringWithShadow(str, 0, 0) 122 | 123 | Renderer.retainTransforms(false) 124 | Renderer.finishDraw() 125 | }), 126 | () => chestData.length 127 | ) 128 | .onUnregister(() => { 129 | chestData = [] 130 | inChest = false 131 | currentChest = null 132 | }) -------------------------------------------------------------------------------- /features/dungeons/CreeperBeamsSolver.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { Persistence } from "../../shared/Persistence" 6 | import { onPuzzleRotationExit, onPuzzleScheduledRotation } from "../../shared/PuzzleRoomScanner" 7 | import { RenderHelper } from "../../shared/Render" 8 | import { TextHelper } from "../../shared/TextHelper" 9 | 10 | const lanterPairs = Persistence.getDataFromFileOrLink("CreeperBeamsSolutions.json", "https://raw.githubusercontent.com/DocilElm/Doc-Data/main/dungeons/CreeperBeamsSolutions.json") 11 | const relativeCoords = { 12 | lantern: [0, 74, 0], 13 | stone: [1, 73, 0] 14 | } 15 | const pairColors = [ 16 | [0, 200, 255], 17 | [0, 255, 0], 18 | [255, 102, 102], 19 | [255, 255, 51], 20 | [255, 153, 51] 21 | ] 22 | 23 | let solutions = [] 24 | let enteredRoomAt = null 25 | 26 | const equals = (pos, pos2) => Math.floor(pos.x) === Math.floor(pos2.x) && Math.floor(pos.y) === Math.floor(pos2.y) && Math.floor(pos.z) === Math.floor(pos2.z) 27 | 28 | const checkBlocks = (feat, pos, packetBlock) => { 29 | for (let idx = solutions.length - 1; idx >= 0; idx--) { 30 | let data = solutions[idx] 31 | let blocks = data.blocks 32 | 33 | if ( 34 | (equals(pos, blocks[0].pos) || equals(pos, blocks[1].pos)) && 35 | packetBlock === net.minecraft.init.Blocks.field_180397_cI 36 | ) { 37 | data.blacklisted = true 38 | continue 39 | } 40 | if ( 41 | (equals(pos, blocks[0].pos) || equals(pos, blocks[1].pos)) && 42 | packetBlock !== net.minecraft.init.Blocks.field_180397_cI && data.blacklisted 43 | ) 44 | data.blacklisted = false 45 | } 46 | 47 | if (solutions.filter((it) => !it.blacklisted).length === 0) { 48 | TextHelper.sendPuzzleMsg("Creeper Beams", enteredRoomAt) 49 | solutions = [] 50 | enteredRoomAt = null 51 | } 52 | 53 | feat.update() 54 | } 55 | 56 | const feat = new Feature("creeperBeamsSolver", "catacombs") 57 | .addSubEvent( 58 | new Event("renderWorld", () => { 59 | for (let idx = 0; idx < 4; idx++) { 60 | let data = solutions[idx] 61 | if (!data || data.blacklisted) continue 62 | 63 | let [ block, block1 ] = data.blocks 64 | let [ r, g, b ] = data.color 65 | 66 | RenderHelper.outlineFilledBlock(block, r, g, b, 255) 67 | RenderHelper.outlineFilledBlock(block1, r, g, b, 255) 68 | 69 | if (!config().creeperBeamsSolverLine) continue 70 | 71 | RenderHelper.drawLineThroughPoints(data.coords, r, g, b, 255, false, 2) 72 | } 73 | }), 74 | () => solutions.length && enteredRoomAt 75 | ) 76 | .addSubEvent( 77 | new Event(EventEnums.PACKET.SERVER.BLOCKCHANGE, (_, pos, packetBlock) => { 78 | checkBlocks(feat, pos, packetBlock) 79 | }), 80 | () => solutions.length && enteredRoomAt 81 | ) 82 | .addSubEvent( 83 | new Event(EventEnums.PACKET.CUSTOM.MULTIBLOCKCHANGE, (_, pos, packetBlock) => { 84 | checkBlocks(feat, pos, packetBlock) 85 | }), 86 | () => solutions.length && enteredRoomAt 87 | ) 88 | .onUnregister(() => { 89 | solutions = [] 90 | enteredRoomAt = null 91 | }) 92 | 93 | onPuzzleScheduledRotation((rotation) => { 94 | if (!config().creeperBeamsSolver) return 95 | 96 | const stoneBlock = World.getBlockAt(...TextHelper.getRealCoord(relativeCoords.stone, rotation)) 97 | const lanternBlock = World.getBlockAt(...TextHelper.getRealCoord(relativeCoords.lantern, rotation)) 98 | if (stoneBlock.type.mcBlock !== net.minecraft.init.Blocks.field_150348_b || lanternBlock.type.mcBlock !== net.minecraft.init.Blocks.field_180398_cJ) return 99 | 100 | ChatLib.chat(`${TextHelper.PREFIX} &aCreeper Beams detected`) 101 | 102 | let idx = 0 103 | 104 | for (let k in lanterPairs) { 105 | if (idx >= 4) break 106 | 107 | let v = lanterPairs[k] 108 | 109 | let block = World.getBlockAt(...TextHelper.getRealCoord(v[0], rotation)) 110 | let block1 = World.getBlockAt(...TextHelper.getRealCoord(v[1], rotation)) 111 | if (block.type.mcBlock !== net.minecraft.init.Blocks.field_180398_cJ || block1.type.mcBlock !== net.minecraft.init.Blocks.field_180398_cJ) continue 112 | 113 | solutions.push({ 114 | blocks: [block, block1], 115 | coords: [ 116 | [ Math.floor(block.getX()) + 0.5, Math.floor(block.getY()) + 0.5, Math.floor(block.getZ()) + 0.5 ], 117 | [ Math.floor(block1.getX()) + 0.5, Math.floor(block1.getY()) + 0.5, Math.floor(block1.getZ()) + 0.5 ] 118 | ], 119 | color: pairColors[idx], 120 | blacklisted: false 121 | }) 122 | 123 | idx++ 124 | } 125 | 126 | enteredRoomAt = Date.now() 127 | 128 | feat.update() 129 | }) 130 | 131 | onPuzzleRotationExit(() => { 132 | solutions = [] 133 | enteredRoomAt = null 134 | feat.update() 135 | }) -------------------------------------------------------------------------------- /features/dungeons/CroesusClicks.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { RenderHelper } from "../../shared/Render" 5 | 6 | // Credits: https://github.com/UnclaimedBloom6/BloomModule/blob/main/Bloom/features/CakeNumbers.js 7 | 8 | const slotsClicked = new Map() 9 | 10 | let inCroesus = false 11 | let cachedItems = null 12 | let currentPage = null 13 | 14 | const feat = new Feature("showCroesusClicks", "dungeon hub") 15 | .addEvent( 16 | new Event(EventEnums.PACKET.SERVER.WINDOWOPEN, (name) => { 17 | inCroesus = name === "Croesus" 18 | cachedItems = null 19 | currentPage = null 20 | feat.update() 21 | }) 22 | ) 23 | .addEvent( 24 | new Event(EventEnums.PACKET.CUSTOM.WINDOWCLOSE, () => { 25 | inCroesus = false 26 | currentPage = null 27 | feat.update() 28 | }) 29 | ) 30 | .addSubEvent( 31 | new Event(EventEnums.PACKET.SERVER.WINDOWITEMS, (mcItems) => { 32 | cachedItems = mcItems.map(it => it && new Item(it)) 33 | const page = cachedItems?.[53]?.getID() === 160 ? "Page1" : cachedItems?.[53]?.getLore()?.[1]?.removeFormatting()?.replace(/ /g, "") 34 | 35 | if (!slotsClicked.has(page)) slotsClicked.set(page, new Map()) 36 | 37 | currentPage = slotsClicked.get(page) 38 | feat.update() 39 | }), 40 | () => inCroesus 41 | ) 42 | .addSubEvent( 43 | new Event(EventEnums.PACKET.CLIENT.WINDOWCLICK, (_, slot) => { 44 | if (slot <= 0 || slot >= 44 || currentPage.has(slot)) return 45 | if (!cachedItems[slot] || cachedItems[slot]?.getID() === 160 || cachedItems[slot]?.getID() === 262) return 46 | 47 | currentPage.set(slot, RenderHelper.getSlotRenderPosition(slot)) 48 | }), 49 | () => inCroesus && currentPage 50 | ) 51 | .addSubEvent( 52 | new Event("renderOverlay", () => { 53 | if (!currentPage) return 54 | 55 | for (let v of currentPage.values()) { 56 | let [ x, y ] = v 57 | 58 | Renderer.retainTransforms(true) 59 | Renderer.translate(x + .5, y, 100) 60 | Renderer.scale(0.9) 61 | Renderer.drawRect(Renderer.color(0, 255, 0, 150), 0, 0, 16, 16) 62 | Renderer.retainTransforms(false) 63 | } 64 | }), 65 | () => inCroesus && currentPage 66 | ) 67 | .onUnregister(() => { 68 | slotsClicked.clear() 69 | inCroesus = false 70 | cachedItems = null 71 | currentPage = null 72 | }) -------------------------------------------------------------------------------- /features/dungeons/CryptsDisplay.js: -------------------------------------------------------------------------------- 1 | import Dungeons from "../../../Atomx/skyblock/Dungeons" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("cryptsDisplay").setCommandName("editcryptsDisplay") 7 | 8 | editGui.onDraw(() => { 9 | Renderer.translate(editGui.getX(), editGui.getY()) 10 | Renderer.scale(editGui.getScale()) 11 | Renderer.drawStringWithShadow("&aCrypts&f: &65", 0, 0) 12 | Renderer.finishDraw() 13 | }) 14 | 15 | new Feature("cryptsDisplay", "catacombs") 16 | .addEvent( 17 | new Event("renderOverlay", () => { 18 | if (editGui.isOpen()) return 19 | 20 | const crypts = Dungeons.getCryptsAmount() 21 | 22 | Renderer.translate(editGui.getX(), editGui.getY()) 23 | Renderer.scale(editGui.getScale()) 24 | Renderer.drawStringWithShadow(`&aCrypts&f: ${crypts >= 5 ? "&6" : "&c"}${crypts}`, 0, 0) 25 | Renderer.finishDraw() 26 | }) 27 | ) -------------------------------------------------------------------------------- /features/dungeons/DeathsDisplay.js: -------------------------------------------------------------------------------- 1 | import Dungeons from "../../../Atomx/skyblock/Dungeons" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("deathsDisplay").setCommandName("editdeathsDisplay") 7 | 8 | editGui.onDraw(() => { 9 | Renderer.translate(editGui.getX(), editGui.getY()) 10 | Renderer.scale(editGui.getScale()) 11 | Renderer.drawStringWithShadow("&8&lDeaths&f: &43", 0, 0) 12 | Renderer.finishDraw() 13 | }) 14 | 15 | new Feature("deathsDisplay", "catacombs") 16 | .addEvent( 17 | new Event("renderOverlay", () => { 18 | if (editGui.isOpen()) return 19 | 20 | const amount = Dungeons.getTeamDeaths() 21 | 22 | Renderer.translate(editGui.getX(), editGui.getY()) 23 | Renderer.scale(editGui.getScale()) 24 | Renderer.drawStringWithShadow(`&8&lDeaths&f: ${amount >= 3 ? "&4" : "&c"}${amount}`, 0, 0) 25 | Renderer.finishDraw() 26 | }) 27 | ) -------------------------------------------------------------------------------- /features/dungeons/ExtraStats.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | 5 | new Feature("showExtraStats", "catacombs") 6 | .addEvent( 7 | new Event(EventEnums.PACKET.SERVER.CHAT, () => ChatLib.command("showextrastats"), /^ *> EXTRA STATS <$/) 8 | ) -------------------------------------------------------------------------------- /features/dungeons/HideNoStarTag.js: -------------------------------------------------------------------------------- 1 | import { scheduleTask } from "../../core/CustomRegisters" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | 6 | const blazeHealthRegex = /^\[Lv15\] Blaze [\d,]+\/([\d,]+)❤$/ 7 | const noStarTagRegex = /^(?:\[Lv\d+\] )?[\w ]+ [\d,.]+\w(?:\/[\d,.]+\w)?❤$/ 8 | 9 | new Feature("hideNoneStarredTags", "catacombs") 10 | .addEvent( 11 | new Event(EventEnums.FORGE.ENTITYJOIN, (entity) => { 12 | scheduleTask(() => { 13 | const name = entity.func_95999_t()?.removeFormatting() 14 | if (!name || blazeHealthRegex.test(name) || !noStarTagRegex.test(name)) return 15 | 16 | entity.func_70106_y() 17 | }) 18 | }, net.minecraft.entity.item.EntityArmorStand) 19 | ) -------------------------------------------------------------------------------- /features/dungeons/LividSolver.js: -------------------------------------------------------------------------------- 1 | import { scheduleTask } from "../../core/CustomRegisters" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { RenderHelper } from "../../shared/Render" 6 | import { TextHelper } from "../../shared/TextHelper" 7 | 8 | // Credits: https://github.com/UnclaimedBloom6/BloomModule/blob/main/features/LividSolver.js 9 | 10 | const livids = [ 11 | [0, "Vendetta", "§f"], 12 | [2, "Crossed", "§d"], 13 | [4, "Arcade", "§e"], 14 | [5, "Smile", "§a"], 15 | [7, "Doctor", "§7"], 16 | [10, "Purple", "§5"], 17 | [11, "Scream", "§9"], 18 | [13, "Frog", "§2"], 19 | [14, "Hockey", "§c"] 20 | ] 21 | 22 | let livid = null 23 | let lividData = null 24 | let inf5 = false 25 | 26 | const getIdFromBlock = (blockIn) => { 27 | return net.minecraft.block.Block./* getIdFromBlock */func_149682_b(blockIn) 28 | } 29 | 30 | const feat = new Feature("lividSolver", "catacombs") 31 | .addEvent( 32 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 33 | inf5 = true 34 | feat.update() 35 | }, /^\[BOSS\] Livid\: Welcome\, you\'ve arrived right on time\. I am Livid, the Master of Shadows\.$/) 36 | ) 37 | .addEvent( 38 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 39 | // If livid data is not set whenever the livids spawn 40 | // we default to red color livid 41 | scheduleTask(() => { 42 | if (livid) return 43 | 44 | lividData = livids[8] 45 | feat.update() 46 | }, 2) 47 | }, /^\[BOSS\] Livid\: I respect you for making it to here\, but I\'ll be your undoing\.$/) 48 | ) 49 | .addSubEvent( 50 | new Event(EventEnums.PACKET.SERVER.MULTIBLOCKCHANGE, (mcBlocks) => { 51 | mcBlocks.forEach((it) => { 52 | const pos = it./* getPos */func_180090_a() 53 | const [ x, y, z ] = [ 54 | pos./* getX */func_177958_n(), 55 | pos./* getY */func_177956_o(), 56 | pos./* getZ */func_177952_p() 57 | ] 58 | 59 | if (x !== 5 || y !== 108 || z !== 43) return 60 | 61 | const blockState = it./* getBlockState */func_180088_c() 62 | const blockIn = blockState./* getBlock */func_177230_c() 63 | const blockId = getIdFromBlock(blockIn) 64 | if (blockId !== 35) return livid = null 65 | 66 | const metadata = blockIn./* getMetaFromState */func_176201_c(blockState) 67 | lividData = livids.find(a => a[0] == metadata) 68 | ChatLib.chat(`${TextHelper.PREFIX} &bLivid Solver&f: ${lividData[2]}${lividData[1]} Livid`) 69 | }) 70 | feat.update() 71 | }), 72 | () => inf5 73 | ) 74 | .addSubEvent( 75 | new Event(EventEnums.FORGE.ENTITYJOIN, (mcEntity) => { 76 | scheduleTask(() => { 77 | const name = mcEntity./* getName */func_70005_c_() 78 | if (name !== `${lividData?.[1]} Livid`) return 79 | 80 | livid = mcEntity 81 | feat.update() 82 | }) 83 | }, net.minecraft.client.entity.EntityOtherPlayerMP), 84 | () => lividData 85 | ) 86 | .addSubEvent( 87 | new Event("renderWorld", () => { 88 | RenderHelper.drawEntityBox( 89 | livid./* posX */field_70165_t, 90 | livid./* posY */field_70163_u, 91 | livid./* posZ */field_70161_v, 92 | 0.6, 93 | 1.8, 94 | 0, 255, 255, 255, 2, false 95 | ) 96 | }), 97 | () => livid 98 | ) 99 | .onUnregister(() => { 100 | livid = null 101 | lividData = null 102 | inf5 = false 103 | }) -------------------------------------------------------------------------------- /features/dungeons/MilestoneDisplay.js: -------------------------------------------------------------------------------- 1 | import Dungeons from "../../../Atomx/skyblock/Dungeons" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("milestoneDisplay").setCommandName("editmilestoneDisplay") 7 | 8 | editGui.onDraw(() => { 9 | Renderer.translate(editGui.getX(), editGui.getY()) 10 | Renderer.scale(editGui.getScale()) 11 | Renderer.drawStringWithShadow("&bMilestone&f: &69", 0, 0) 12 | Renderer.finishDraw() 13 | }) 14 | 15 | new Feature("milestoneDisplay", "catacombs") 16 | .addEvent( 17 | new Event("renderOverlay", () => { 18 | if (editGui.isOpen()) return 19 | 20 | const amount = Dungeons.getCurrentMilestoneNum() 21 | 22 | Renderer.translate(editGui.getX(), editGui.getY()) 23 | Renderer.scale(editGui.getScale()) 24 | Renderer.drawStringWithShadow(`&bMilestone&f: ${amount >= 3 ? "&6" : "&c"}${amount}`, 0, 0) 25 | Renderer.finishDraw() 26 | }) 27 | ) -------------------------------------------------------------------------------- /features/dungeons/MimicKilled.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | 6 | let msgSent = false 7 | 8 | new Feature("sendMimicDead", "catacombs") 9 | .addEvent( 10 | new Event(EventEnums.ENTITYDEATH, (entity) => { 11 | if (msgSent || !entity.entity.func_70631_g_() || entity.entity.func_82169_q(0)) return 12 | ChatLib.command(`pc ${config().mimicDeadMessage}`) 13 | msgSent = true 14 | }, net.minecraft.entity.monster.EntityZombie) 15 | ) 16 | .onUnregister(() => msgSent = false) -------------------------------------------------------------------------------- /features/dungeons/PuzzleDisplay.js: -------------------------------------------------------------------------------- 1 | import AtomxApi from "../../../Atomx/AtomxApi" 2 | import Dungeons from "../../../Atomx/skyblock/Dungeons" 3 | import config from "../../config" 4 | import { Event } from "../../core/Event" 5 | import EventEnums from "../../core/EventEnums" 6 | import Feature from "../../core/Feature" 7 | import DraggableGui from "../../shared/DraggableGui" 8 | 9 | const editGui = new DraggableGui("puzzlesDisplay").setCommandName("editpuzzlesDisplay") 10 | const PuzzleEnums = { 11 | 0: "&6✦", 12 | 1: "&a✔", 13 | 2: "&c✖" 14 | } 15 | const puzzleRegex = AtomxApi.getRegexData()?.Dungeons?.PuzzlesAmount 16 | 17 | let puzzles = {} 18 | let puzzleCount = 0 19 | 20 | editGui.onDraw(() => { 21 | Renderer.translate(editGui.getX(), editGui.getY()) 22 | Renderer.scale(editGui.getScale()) 23 | Renderer.drawStringWithShadow("&d&lPuzzles&f: &65\n&d&lBoulder &6✦\n&d&lThree Weirdos &a✔", 0, 0) 24 | Renderer.finishDraw() 25 | }) 26 | 27 | const feat = new Feature("puzzlesDisplay", "catacombs") 28 | .addEvent( 29 | new Event(EventEnums.PACKET.SERVER.TABADD, (amount) => { 30 | puzzleCount = +amount 31 | feat.update() 32 | }, puzzleRegex) 33 | ) 34 | .addSubEvent( 35 | new Event("renderOverlay", () => { 36 | if (editGui.isOpen()) return 37 | 38 | Renderer.translate(editGui.getX(), editGui.getY()) 39 | Renderer.scale(editGui.getScale()) 40 | Renderer.drawStringWithShadow(`&d&lPuzzles&f: ${puzzleCount >= 4 ? "&6" : "&a"}${puzzleCount}\n${Object.values(puzzles).join("\n")}`, 0, 0) 41 | Renderer.finishDraw() 42 | }), 43 | () => puzzleCount !== null 44 | ) 45 | .onUnregister(() => { 46 | puzzles = {} 47 | puzzleCount = 0 48 | }) 49 | 50 | Dungeons.onPuzzleEvent((puzzleName, event, failedBy) => { 51 | if (!config().puzzlesDisplay) return 52 | 53 | puzzles[puzzleName] = `&d&l${puzzleName} ${PuzzleEnums[event]} ${failedBy ?? ""}` 54 | }) -------------------------------------------------------------------------------- /features/dungeons/RemoveDmgTag.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | 5 | // Credits: https://github.com/UnclaimedBloom6/BloomModule/blob/main/Bloom/features/HideGrayNumbers.js 6 | 7 | new Feature("removeDamageTag", "catacombs") 8 | .addEvent( 9 | new Event(EventEnums.PACKET.SERVER.SPAWNMOB, (entityID) => { 10 | const entity = World.getWorld().func_73045_a(entityID) 11 | if (!entity || !(entity instanceof net.minecraft.entity.item.EntityArmorStand)) return 12 | const name = entity.func_95999_t() 13 | if (!name || !/^.?\d[\d,.]+.*?$/.test(name?.removeFormatting())) return 14 | 15 | entity.func_70106_y() 16 | }) 17 | ) -------------------------------------------------------------------------------- /features/dungeons/RunSplits.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import CustomSplits from "../../shared/CustomSplits" 5 | import DraggableGui from "../../shared/DraggableGui" 6 | import Location from "../../shared/Location" 7 | import { Persistence } from "../../shared/Persistence" 8 | 9 | const editGui = new DraggableGui("runSplits").setCommandName("editrunSplits") 10 | const RunSplits = Persistence.getDataFromFileOrLink("RunSplits.json", "https://raw.githubusercontent.com/DocilElm/Doc-Data/main/dungeons/RunSplits.json") 11 | const split = new CustomSplits(RunSplits, () => Location.inWorld("catacombs")) 12 | const exampleStr = split.buildExampleStr() 13 | split.formatTime = config().dungeonRunSplitsFormat 14 | 15 | config().getConfig().registerListener("dungeonRunSplitsFormat", (_, n) => { 16 | split.formatTime = n 17 | }) 18 | 19 | editGui.onDraw(() => { 20 | Renderer.retainTransforms(true) 21 | Renderer.translate(editGui.getX(), editGui.getY()) 22 | Renderer.scale(editGui.getScale()) 23 | Renderer.drawStringWithShadow("&aRun Splits", 0, 0) 24 | Renderer.drawStringWithShadow(exampleStr, 0, 10) 25 | Renderer.retainTransforms(false) 26 | Renderer.finishDraw() 27 | }) 28 | 29 | const feat = new Feature("dungeonRunSplits", "catacombs") 30 | .addEvent( 31 | new Event("renderOverlay", () => { 32 | Renderer.retainTransforms(true) 33 | Renderer.translate(editGui.getX(), editGui.getY()) 34 | Renderer.scale(editGui.getScale()) 35 | Renderer.drawStringWithShadow("&aRun Splits", 0, 0) 36 | 37 | Renderer.drawStringWithShadow(split.buildStr(), 0, 10) 38 | 39 | Renderer.retainTransforms(false) 40 | Renderer.finishDraw() 41 | }) 42 | ) 43 | .onUnregister(() => split.reset()) 44 | 45 | split.getEvents().forEach(it => feat.addEvent(it)) -------------------------------------------------------------------------------- /features/dungeons/RunsLogger.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { TextHelper } from "../../shared/TextHelper" 3 | import Dungeon from "../../../tska/skyblock/dungeon/Dungeon" 4 | import Feature from "../../core/Feature" 5 | import { Event } from "../../core/Event" 6 | import EventEnums from "../../core/EventEnums" 7 | import { addCommand } from "../../shared/Command" 8 | import { Persistence } from "../../shared/Persistence" 9 | 10 | const extraStatsRegex = /^ *> EXTRA STATS <$/ 11 | const floorStatsRegex = /^ *(Master Mode )?The Catacombs - Floor ([VI]+) Stats$/ 12 | const teamScoreRegex = /^ *Team Score: (\d+) \((\w\+?)\)$/ 13 | const defeatedRegex = /^ *☠ Defeated (?:[\w, ]+) in ([\dms ]+)$/ 14 | const deathsRegex = /^ *Deaths: (\d+)$/ 15 | const secretsFoundRegex = /^ *Secrets Found: (\d+)$/ 16 | const date = new Date() 17 | const todayDate = `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}` 18 | 19 | let currFloor = null 20 | let hasAdded = false 21 | let currentData = { 22 | deaths: 0, 23 | secrets: 0, 24 | time: null, 25 | milestone: 0, 26 | score: 0, 27 | scoreStr: null, 28 | taken: null 29 | } 30 | 31 | const reset = () => { 32 | currFloor = null 33 | currentData = { 34 | deaths: 0, 35 | secrets: 0, 36 | time: null, 37 | milestone: 0, 38 | score: 0, 39 | scoreStr: null, 40 | taken: null 41 | } 42 | } 43 | 44 | const getArrayData = (k, arr) => { 45 | if (!arr) return 46 | let totalSecrets = arr.reduce((a, b) => a + b.secrets, 0) 47 | let totalRuns = arr.length 48 | let averageSecrets = totalSecrets / totalRuns 49 | let sRuns = arr.filter((it) => it.scoreStr === "S").length 50 | let sPlusRuns = arr.filter((it) => it.scoreStr === "S+").length 51 | let otherRuns = arr.filter((it) => it.scoreStr !== "S" && it.scoreStr !== "S+").length 52 | 53 | return `&f- &b${k} Stats S &e${sRuns} &bS+ &6${sPlusRuns} &bOther &7${otherRuns} &bSecret Average &a${averageSecrets.toFixed(2)}\n` 54 | } 55 | 56 | const getDataFromDate = (date, floor) => { 57 | if (!floor) { 58 | let data = Persistence.runsData[date] 59 | if (!data) return 60 | 61 | let str = "" 62 | 63 | for (let k of Object.keys(data)) { 64 | let arr = data[k] 65 | if (!arr) continue 66 | 67 | str += getArrayData(k, arr) 68 | } 69 | 70 | return str.trim() 71 | } 72 | 73 | floor = floor.toUpperCase() 74 | return getArrayData(floor, Persistence.runsData?.[date]?.[floor])?.trim() 75 | } 76 | 77 | const feat = new Feature("runslogger", "catacombs") 78 | .addEvent( 79 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 80 | if (config().showExtraStats) return 81 | ChatLib.command("showextrastats") 82 | }, extraStatsRegex) 83 | ) 84 | .addEvent( 85 | new Event(EventEnums.PACKET.SERVER.CHAT, (mm, roman) => { 86 | const floor = TextHelper.decodeNumeral(roman) 87 | currFloor = mm ? `M${floor}` : `F${floor}` 88 | feat.update() 89 | }, floorStatsRegex) 90 | ) 91 | .addSubEvent( 92 | new Event(EventEnums.PACKET.SERVER.CHAT, (score, scoreStr) => { 93 | currentData.score = +score 94 | currentData.scoreStr = scoreStr 95 | }, teamScoreRegex), 96 | () => currFloor 97 | ) 98 | .addSubEvent( 99 | new Event(EventEnums.PACKET.SERVER.CHAT, (time) => { 100 | currentData.time = time 101 | feat.update() 102 | }, defeatedRegex), 103 | () => currFloor 104 | ) 105 | .addSubEvent( 106 | new Event(EventEnums.PACKET.SERVER.CHAT, (deaths) => { 107 | currentData.deaths = +deaths 108 | }, deathsRegex), 109 | () => currFloor && currentData.time 110 | ) 111 | .addSubEvent( 112 | new Event(EventEnums.PACKET.SERVER.CHAT, (secrets) => { 113 | if (hasAdded) return reset() 114 | currentData.secrets = +secrets 115 | currentData.milestone = Dungeon.getMilestone(true) 116 | currentData.taken = Date.now() 117 | 118 | if (!(todayDate in Persistence.runsData)) Persistence.runsData[todayDate] = {} 119 | if (!(currFloor in Persistence.runsData[todayDate])) Persistence.runsData[todayDate][currFloor] = [] 120 | 121 | Persistence.runsData[todayDate][currFloor].push(currentData) 122 | 123 | hasAdded = true 124 | reset() 125 | }, secretsFoundRegex), 126 | () => currFloor && currentData.time 127 | ) 128 | .onUnregister(() => { 129 | reset() 130 | hasAdded = false 131 | }) 132 | 133 | addCommand("runs", "&bShows your runs logged data", (date, floor) => { 134 | new Thread(() => { 135 | if (!date) { 136 | const msg = new Message(`${TextHelper.PREFIX} &aRuns Logger Data&f:`) 137 | 138 | for (let k of Object.keys(Persistence.runsData)) { 139 | msg.addTextComponent( 140 | new TextComponent(`\n&f- &b${k}`) 141 | .setClick("run_command", `/doc runs ${k}`) 142 | .setHover("show_text", `&aClick to run /doc runs ${k}`) 143 | ) 144 | } 145 | 146 | msg.chat() 147 | return 148 | } 149 | 150 | if (date.toLowerCase() === "today") date = todayDate 151 | const data = getDataFromDate(date, floor) 152 | const space = floor ? " " : "\n" 153 | ChatLib.chat(`${TextHelper.PREFIX} &aRuns Logger Stats&f:${space}${data || "&cNo data found for the specified date"}`) 154 | }).start() 155 | }) -------------------------------------------------------------------------------- /features/dungeons/SecretsClickedBox.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { RenderHelper } from "../../shared/Render" 6 | 7 | // Wither skull / Redstone skull 8 | const allowedIDs = new Set(["e0f3e929-869e-3dca-9504-54c666ee6f23", "fed95410-aba1-39df-9b95-1d4f361eb66e"]) 9 | const secretBlocks = new Set(["minecraft:chest", "minecraft:lever", "minecraft:skull", "minecraft:trapped_chest"]) 10 | const lockedRegex = /^That chest is locked!$/ 11 | const blocksToHighlight = new Map() 12 | 13 | const checkSkullTexture = (bp) => allowedIDs.has(World.getWorld()?.func_175625_s(bp)?.func_152108_a()?.id?.toString()) 14 | 15 | let locked = false 16 | 17 | const feat = new Feature("showSecretsClicked", "catacombs") 18 | .addEvent( 19 | new Event(EventEnums.PACKET.CLIENT.BLOCKPLACEMENT, (ctBlock, _, bp) => { 20 | const blockName = ctBlock.type.getRegistryName() 21 | 22 | if (!secretBlocks.has(blockName) || blockName === "minecraft:skull" && !checkSkullTexture(bp)) return 23 | 24 | const blockString = ctBlock.toString() 25 | if (blocksToHighlight.has(blockString)) return 26 | 27 | blocksToHighlight.set(blockString, { 28 | block: ctBlock 29 | }) 30 | 31 | feat.update() 32 | 33 | Client.scheduleTask(20, () => { 34 | blocksToHighlight.delete(blockString) 35 | locked = false 36 | feat.update() 37 | }) 38 | }, false) 39 | ) 40 | .addEvent( 41 | new Event(EventEnums.PACKET.SERVER.CHAT, () => locked = true, lockedRegex) 42 | ) 43 | .addSubEvent( 44 | new Event("renderWorld", () => { 45 | blocksToHighlight.forEach(obj => { 46 | const block = obj.block 47 | const isChest = block.type.mcBlock instanceof net.minecraft.block.BlockChest 48 | 49 | const r = locked && isChest ? 255 : config().showSecretsClickedColor[0] 50 | const g = locked && isChest ? 0 : config().showSecretsClickedColor[1] 51 | const b = locked && isChest ? 0 : config().showSecretsClickedColor[2] 52 | 53 | RenderHelper.filledBlock(block, r, g, b, 51, true) 54 | RenderHelper.outlineBlock(block, r, g, b, 255, true, 2) 55 | }) 56 | }), () => blocksToHighlight.size 57 | ) -------------------------------------------------------------------------------- /features/dungeons/SecretsSound.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | 6 | const secretItems = new Set(["Healing VIII Splash Potion", "Healing Potion 8 Splash Potion", "Decoy", "Inflatable Jerry", "Spirit Leap", "Trap", "Training Weights", "Defuse Kit", "Dungeon Chest Key", "Treasure Talisman", "Revive Stone", "Architect's First Draft"]) 7 | const allowedIDs = new Set(["e0f3e929-869e-3dca-9504-54c666ee6f23", "fed95410-aba1-39df-9b95-1d4f361eb66e"]) 8 | const secretBlocks = new Set(["minecraft:chest", "minecraft:lever", "minecraft:skull", "minecraft:trapped_chest"]) 9 | // [SoundType, Pitch] 10 | const soundsList = [ 11 | ["mob.blaze.hit", 2], 12 | ["fire.ignite", 1], 13 | ["random.orb", 1], 14 | ["random.break", 2], 15 | ["mob.guardian.land.hit", 2] 16 | ] 17 | const itemEntities = new Map() 18 | 19 | let currentBlockClicked = null 20 | 21 | const playSound = () => { 22 | World.playSound( 23 | soundsList[config().secretsSoundType][0], // Sound 24 | 1, 25 | soundsList[config().secretsSoundType][1] // Pitch 26 | ) 27 | } 28 | 29 | const checkSkullTexture = (blockPos) => { 30 | const textureID = World.getWorld().func_175625_s(blockPos)?.func_152108_a()?.id?.toString() 31 | 32 | if (!textureID) return 33 | 34 | return allowedIDs.has(textureID) 35 | } 36 | 37 | const feat = new Feature("secretsSound", "catacombs") 38 | .addEvent( 39 | new Event(EventEnums.FORGE.ENTITYJOIN, (entity, entityID) => { 40 | itemEntities.set(entityID, entity) 41 | 42 | feat.update() 43 | }, net.minecraft.entity.item.EntityItem) 44 | ) 45 | .addSubEvent( 46 | new Event(EventEnums.PACKET.SERVER.COLLECTITEM, (entityID) => { 47 | if (!itemEntities.has(entityID)) return 48 | 49 | const entity = itemEntities.get(entityID) 50 | const name = entity.func_92059_d()?.func_82833_r() 51 | if (!name || !secretItems.has(name.removeFormatting())) return 52 | 53 | playSound() 54 | itemEntities.delete(entityID) 55 | }), 56 | () => itemEntities.size 57 | ) 58 | .addEvent( 59 | new Event(EventEnums.PACKET.CLIENT.BLOCKPLACEMENT, (ctBlock, _, blockPos) => { 60 | const blockName = ctBlock.type.getRegistryName() 61 | 62 | if ( 63 | !secretBlocks.has(blockName) || 64 | blockName === "minecraft:skull" && !checkSkullTexture(blockPos) || 65 | ctBlock.toString() === currentBlockClicked 66 | ) return 67 | 68 | playSound() 69 | currentBlockClicked = ctBlock.toString() 70 | 71 | // Reset the last block clicked after 20 ticks 72 | Client.scheduleTask(20, () => currentBlockClicked = null) 73 | }, false) 74 | ) 75 | .addEvent( 76 | new Event(EventEnums.SOUNDPLAY, (_, __, vol) => vol === 0.10000000149011612 && playSound(), "mob.bat.hurt") 77 | ) 78 | .addEvent( 79 | new Event(EventEnums.SOUNDPLAY, (_, __, vol) => vol === 0.10000000149011612 && playSound(), "mob.bat.death") 80 | ) 81 | .onUnregister(() => { 82 | currentBlockClicked = null 83 | itemEntities.clear() 84 | }) -------------------------------------------------------------------------------- /features/dungeons/ThreeWeirdosSolver.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { RenderHelper } from "../../shared/Render" 5 | 6 | // Credits: https://github.com/UnclaimedBloom6/BloomModule/blob/main/features/ThreeWeirdosSolver.js 7 | 8 | const solutions = [ 9 | /The reward is not in my chest!/, 10 | /At least one of them is lying, and the reward is not in \w+'s chest.?/, 11 | /My chest doesn't have the reward\. We are all telling the truth.?/, 12 | /My chest has the reward and I'm telling the truth!/, 13 | /The reward isn't in any of our chests.?/, 14 | /Both of them are telling the truth\. Also, \w+ has the reward in their chest.?/, 15 | ] 16 | const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]] 17 | const npcRegex = /^\[NPC\] (\w+): (.*)$/ 18 | const puzzleDoneRegex = /^PUZZLE SOLVED! (\w{1,16}) wasn't fooled by \w+! Good job!$/ 19 | const puzzleFailedRegex = /^PUZZLE FAIL\! (\w{1,16}) was fooled by \w+! Yikes!$/ 20 | 21 | let currentChest = null 22 | 23 | const handleChest = (npcName) => { 24 | if (currentChest) return 25 | 26 | const armorStand = World.getAllEntitiesOfType(net.minecraft.entity.item.EntityArmorStand).find(a => a?.getName()?.removeFormatting() === npcName) 27 | if (!armorStand) return 28 | 29 | let [ x, y, z ] = [ Math.floor(armorStand.getX()), armorStand.getY(), Math.floor(armorStand.getZ()) ] 30 | 31 | for (let dir of directions) { 32 | let [dx, dz] = dir 33 | let block = World.getBlockAt(x+dx, y, z+dz) 34 | 35 | if (block.type.getID() !== 54) continue 36 | 37 | currentChest = block 38 | return 39 | } 40 | } 41 | 42 | const feat = new Feature("threeWeirdosSolver", "catacombs") 43 | .addEvent( 44 | new Event(EventEnums.PACKET.SERVER.CHAT, (npcName, message, event) => { 45 | if (!solutions.some(solution => solution.test(message))) return 46 | 47 | cancel(event) 48 | ChatLib.chat(`&e[NPC] &b&l${npcName}&f: &a&l${message}`) 49 | handleChest(npcName) 50 | 51 | feat.update() 52 | }, npcRegex) 53 | ) 54 | .addSubEvent( 55 | new Event("renderWorld", () => { 56 | RenderHelper.outlineFilledBlock(currentChest, 0, 255, 0, 255) 57 | }), 58 | () => currentChest 59 | ) 60 | .addSubEvent( 61 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 62 | currentChest = null 63 | feat.update() 64 | }, puzzleDoneRegex), 65 | () => currentChest 66 | ) 67 | .addSubEvent( 68 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 69 | currentChest = null 70 | feat.update() 71 | }, puzzleFailedRegex), 72 | () => currentChest 73 | ) 74 | .onUnregister(() => { 75 | currentChest = null 76 | }) -------------------------------------------------------------------------------- /features/dungeons/TicTacToeAlgorithm.js: -------------------------------------------------------------------------------- 1 | // Huge thanks to bloom (@unclaimedbloom6) 2 | // for helping me out understanding this algorithm 3 | 4 | const checkWinner = (board) => Array(8).fill().map((_,i)=>"012345678036147258048246".slice(i*3,i*3+3).split("").reduce((a,b)=>!!b?[...a,board[parseInt(b)]]:a,[]).filter(a=>!!a)).map(a=>a.length==3?[...new Set(a)]:0).reduce((a,b)=>!!b&&b.length==1?b[0]:a,null) 5 | 6 | const minmax = (board, depth = 0, isPlayer = false, alpha = -Infinity, beta = Infinity) => { 7 | const winner = checkWinner(board) 8 | if (winner === "O") return 100 - depth 9 | if (winner === "X") return depth - 100 10 | if (board.every(it => it)) return 0 11 | 12 | if (isPlayer) { 13 | let bestScore = -Infinity 14 | 15 | for (let idx = 0; idx < board.length; idx++) { 16 | if (board[idx]) continue 17 | 18 | board[idx] = "O" 19 | let score = minmax(board, depth + 1, false, alpha, beta) 20 | board[idx] = null 21 | 22 | bestScore = Math.max(bestScore, score) 23 | alpha = Math.max(alpha, score) 24 | 25 | if (beta <= alpha) break 26 | } 27 | 28 | return bestScore 29 | } 30 | 31 | let bestScore = Infinity 32 | 33 | for (let idx = 0; idx < board.length; idx++) { 34 | if (board[idx]) continue 35 | 36 | board[idx] = "X" 37 | let score = minmax(board, depth + 1, true, alpha, beta) 38 | board[idx] = null 39 | 40 | bestScore = Math.min(bestScore, score) 41 | beta = Math.min(beta, score) 42 | 43 | if (beta <= alpha) break 44 | } 45 | 46 | return bestScore 47 | } 48 | 49 | export const findBestMove = (board) => { 50 | let bestMove = -1 51 | let bestScore = -Infinity 52 | 53 | // Hardcode starting at the center or corner as player 54 | if (board.filter(it => !!it).length === 1) { 55 | if (!board[4]) return 4 56 | return 0 57 | } 58 | 59 | for (let idx = 0; idx < board.length; idx++) { 60 | if (board[idx]) continue 61 | 62 | board[idx] = "O" 63 | let score = minmax(board) 64 | board[idx] = null 65 | 66 | if (score < bestScore) continue 67 | 68 | bestScore = score 69 | bestMove = idx 70 | } 71 | 72 | return bestMove 73 | } -------------------------------------------------------------------------------- /features/garden/GardenDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | import DraggableGui from "../../shared/DraggableGui" 4 | import TabListData from "../../../Atomx/skyblock/TabListData" 5 | 6 | const defaultString = `&a&lGarden Display\n&aVisitor in&f: &b1m 10s\n&aJacob's contest in&f: &69m\n&eOrganic matter&f: &6100k\n&9Fuel&f: &6100k\n&aTime Left&f: &b1m 10s\n&aStored Compost&f: &6100` 7 | const editGui = new DraggableGui("gardenDisplay").setCommandName("editgardenDisplay") 8 | 9 | editGui.onDraw(() => { 10 | Renderer.translate(editGui.getX(), editGui.getY()) 11 | Renderer.scale(editGui.getScale()) 12 | Renderer.drawStringWithShadow(defaultString, 0, 0) 13 | Renderer.finishDraw() 14 | }) 15 | 16 | new Feature("gardenDisplay", "garden") 17 | .addEvent( 18 | new Event("renderOverlay", () => { 19 | if (editGui.isOpen()) return 20 | 21 | Renderer.translate(editGui.getX(), editGui.getY()) 22 | Renderer.scale(editGui.getScale()) 23 | Renderer.drawStringWithShadow( 24 | `&a&lGarden Display\n&aVisitor in&f: &b${TabListData.getNextVisitor()} &f(&b${TabListData.getTotalVisitors()}&f)\n&aJacob's contest in&f: &6${TabListData.getJacobContest()}\n&eOrganic matter&f: &6${TabListData.getOrganicMatter()}\n&9Fuel&f: &6${TabListData.getFuel()}\n&aTime Left&f: &b${TabListData.getTimeLeft()}\n&aStored Compost&f: &6${TabListData.getStoredCompost()}`, 25 | 0, 26 | 0 27 | ) 28 | Renderer.finishDraw() 29 | }) 30 | ) -------------------------------------------------------------------------------- /features/garden/GardenEvents.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { GardenApi } from "../../shared/Persistence" 5 | 6 | const visitorSet = new Set(Object.keys(GardenApi.Visitors)) 7 | 8 | let inVisitor = false 9 | let triggeredListeners = false 10 | 11 | // Events listeners 12 | const _onVisior = [] 13 | const _onVisiorClose = [] 14 | 15 | /** 16 | * - Runs the given function whenever a visitor gui is detected 17 | * - Passing through the Visitor's name and the AcceptButton to 18 | * - Loop through the lore and get the data from 19 | * @param {(name: string, acceptButton: Item) => void} fn 20 | */ 21 | export const onVisitor = (fn) => _onVisior.push(fn) 22 | 23 | /** 24 | * - Runs the given function whenever a visitor gui has been closed 25 | * - Checks whether the last gui was a visitor one and if it was not 26 | * - it will not run the function 27 | * @param {() => void} fn 28 | */ 29 | export const onVisitorClose = (fn) => _onVisiorClose.push(fn) 30 | 31 | const feat = new Feature("gardenEvents", "garden") 32 | .addEvent( 33 | new Event(EventEnums.PACKET.SERVER.WINDOWOPEN, (title) => { 34 | if (inVisitor && !visitorSet.has(title)) for (let fn of _onVisiorClose) fn() 35 | 36 | inVisitor = visitorSet.has(title) 37 | feat.update() 38 | }) 39 | ) 40 | .addSubEvent( 41 | new Event(EventEnums.PACKET.CUSTOM.WINDOWCLOSE, () => { 42 | inVisitor = false 43 | for (let fn of _onVisiorClose) fn() 44 | triggeredListeners = false 45 | 46 | feat.update() 47 | }), 48 | () => inVisitor 49 | ) 50 | .addSubEvent( 51 | new Event(EventEnums.PACKET.SERVER.WINDOWITEMS, (items) => { 52 | if (!items[13] || !items[29]) { 53 | inVisitor = false 54 | triggeredListeners = false 55 | feat.update() 56 | return 57 | } 58 | 59 | const visitorSkull = new Item(items[13]) 60 | const isVisitor = /^Offers Accepted: [\d]+$/.test(visitorSkull.getLore()?.[4]?.removeFormatting()) 61 | if (!isVisitor) { 62 | inVisitor = false 63 | triggeredListeners = false 64 | feat.update() 65 | return 66 | } 67 | 68 | const visitorName = visitorSkull.getName() 69 | 70 | for (let fn of _onVisior) { 71 | if (triggeredListeners) break 72 | fn(visitorName, new Item(items[29])) 73 | } 74 | 75 | triggeredListeners = true 76 | }), 77 | () => inVisitor 78 | ) 79 | .onUnregister(() => { 80 | inVisitor = false 81 | }) -------------------------------------------------------------------------------- /features/garden/PestsDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | import DraggableGui from "../../shared/DraggableGui" 4 | import TabListData from "../../../Atomx/skyblock/TabListData" 5 | 6 | const defaultString = `&4&lPests Display\n&cAlive&f: &c&l${Player.getName()}\n&cInfested Plots&f: &c&lnull\n&eSpray&f: &bnull\n&aBonus&f: &6null` 7 | const editGui = new DraggableGui("pestsDisplay").setCommandName("editpestsDisplay") 8 | 9 | editGui.onDraw(() => { 10 | Renderer.translate(editGui.getX(), editGui.getY()) 11 | Renderer.scale(editGui.getScale()) 12 | Renderer.drawStringWithShadow(defaultString, 0, 0) 13 | Renderer.finishDraw() 14 | }) 15 | 16 | new Feature("pestsDisplay", "garden") 17 | .addEvent( 18 | new Event("renderOverlay", () => { 19 | if (editGui.isOpen()) return 20 | 21 | Renderer.translate(editGui.getX(), editGui.getY()) 22 | Renderer.scale(editGui.getScale()) 23 | Renderer.drawStringWithShadow( 24 | `&4&lPests Display\n&cAlive&f: &c&l${TabListData.getPestsAlive()}\n&cInfested Plots&f: &c&l${TabListData.getInfestedPlots()}\n&eSpray&f: &b${TabListData.getCurrentSpray()}\n&aBonus&f: &6${TabListData.getBonusFortune()}`, 25 | 0, 26 | 0 27 | ) 28 | Renderer.finishDraw() 29 | }) 30 | ) -------------------------------------------------------------------------------- /features/garden/VisitorProfit.js: -------------------------------------------------------------------------------- 1 | import AtomxApi from "../../../Atomx/AtomxApi" 2 | import Price from "../../../Atomx/skyblock/Price" 3 | import config from "../../config" 4 | import { Event } from "../../core/Event" 5 | import EventEnums from "../../core/EventEnums" 6 | import Feature from "../../core/Feature" 7 | import DraggableGui from "../../shared/DraggableGui" 8 | import { TextHelper } from "../../shared/TextHelper" 9 | import { onVisitor } from "./GardenEvents" 10 | 11 | const editGui = new DraggableGui("visitorProfit").setCommandName("editvisitorProfit") 12 | const visitorsList = new Map() 13 | // Fun regex 14 | const requiredItemsRegex = /^ ([A-z ]+)([\d,]+)?$/ 15 | const copperRegex = /^ \+([\d,]+) Copper$/ 16 | const rareItemregex = /^ (?:◆)?([\w ]+)$/ 17 | const visitorDialogRegex = /^\[NPC\] ([\w\. ]+): (.+)$/ 18 | 19 | editGui.onDraw(() => { 20 | Renderer.translate(editGui.getX(), editGui.getY()) 21 | Renderer.scale(editGui.getScale()) 22 | Renderer.drawStringWithShadow(`&b${Player.getName()}\n &aEnchanted Life &8x1\n&aProfit&f: &c0 &7(&c0&7)`, 0, 0) 23 | Renderer.finishDraw() 24 | }) 25 | 26 | const feat = new Feature("visitorProfitDisplay", "garden") 27 | .addSubEvent( 28 | new Event("renderOverlay", () => { 29 | if (editGui.isOpen()) return 30 | 31 | let str = "" 32 | 33 | visitorsList.forEach(v => { 34 | const name = v.name 35 | const items = v.requiredItems.join("\n") 36 | const copper = v.copperAmount 37 | const profit = v.profit 38 | const profitFormat = profit <= 0 ? "&c" : "&a" 39 | const rareItem = v.rareItem ? `\n &e${v.rareItem}` : "" 40 | 41 | str += `\n${name}\n ${items}${rareItem}\n&bProfit&f: ${profitFormat}${TextHelper.addCommasTrunc(profit)} &7(&c${copper}&7)\n` 42 | }) 43 | 44 | Renderer.translate(editGui.getX(), editGui.getY()) 45 | Renderer.scale(editGui.getScale()) 46 | Renderer.drawStringWithShadow(str, 0, 0) 47 | Renderer.finishDraw() 48 | }), 49 | () => visitorsList.size 50 | ) 51 | .addSubEvent( 52 | new Event(EventEnums.PACKET.SERVER.CHAT, (name) => { 53 | if (!visitorsList.has(name)) return 54 | 55 | visitorsList.delete(name) 56 | feat.update() 57 | }, visitorDialogRegex), 58 | () => visitorsList.size 59 | ) 60 | .addSubEvent( 61 | new Event(EventEnums.STEP, () => { 62 | if (Player.getX() === Player.getLastX() && Player.getZ() === Player.getLastZ()) return 63 | 64 | World.getAllEntitiesOfType(net.minecraft.entity.item.EntityArmorStand) 65 | .forEach(it => { 66 | if (it.getName().startsWith("§f")) return 67 | const name = it.getName()?.removeFormatting() 68 | if (!name || !visitorsList.has(name) || it.distanceTo(Player.getPlayer()) < 15) return 69 | 70 | visitorsList.delete(name) 71 | feat.update() 72 | }) 73 | }, 1), 74 | () => visitorsList.size 75 | ) 76 | .onUnregister(() => { 77 | visitorsList.clear() 78 | }) 79 | 80 | onVisitor((visitorName, acceptButton) => { 81 | if (!config().visitorProfitDisplay) return 82 | 83 | const currentData = { 84 | name: visitorName, 85 | requiredItems: [], 86 | copperAmount: 0, 87 | totalPrice: 0, 88 | profit: 0, 89 | rareItem: null 90 | } 91 | const acceptLore = acceptButton.getLore() 92 | 93 | for (let idx = 2; idx < acceptLore.length; idx++) { 94 | let lore = acceptLore[idx].removeFormatting() 95 | 96 | let requiredItemsMatch = lore.match(requiredItemsRegex) 97 | if (idx < 5 && requiredItemsMatch) { 98 | let requiredName = requiredItemsMatch[1].replace(/ x/, "").replace(/ /g, "_").toUpperCase() 99 | let amount = Math.floor(requiredItemsMatch[2]) || 1 100 | 101 | let price = Price.getSellPrice(AtomxApi.getGardenItemID()[requiredName]) 102 | let totalPrice = price * amount 103 | 104 | currentData.requiredItems.push(acceptLore[idx]) 105 | currentData.totalPrice += (totalPrice || 0) 106 | 107 | continue 108 | } 109 | 110 | let copperMatch = lore.match(copperRegex) 111 | if (copperMatch) { 112 | currentData.copperAmount = Math.floor(copperMatch[1]) 113 | 114 | continue 115 | } 116 | 117 | let rareItemMatch = lore.match(rareItemregex) 118 | if (rareItemMatch && idx > 5) { 119 | let rareItemName = rareItemMatch[1].replace(/^ /, "") 120 | let rareName = AtomxApi.getGardenRareItems()[rareItemName] 121 | 122 | if (!rareName) continue 123 | 124 | currentData.rareItem = rareName 125 | ChatLib.chat(`${TextHelper.PREFIX} &aVisitor &b${visitorName.removeFormatting()}&a has &b${rareItemName}`) 126 | 127 | continue 128 | } 129 | } 130 | 131 | const totalItemPrice = currentData.totalPrice 132 | const totalCopperPrice = (Price.getSellPrice("ENCHANTMENT_GREEN_THUMB_1") / 1500) * currentData.copperAmount 133 | currentData.profit = (totalCopperPrice - totalItemPrice) 134 | 135 | if (currentData.rareItem) { 136 | const rareItemPrice = Price.getSellPrice(currentData.rareItem) 137 | const currentProfit = currentData.profit <= 0 ? -currentData.profit : +currentData.profit 138 | 139 | currentData.profit = (rareItemPrice - currentProfit) 140 | } 141 | 142 | visitorsList.set(visitorName.removeFormatting(), currentData) 143 | feat.update() 144 | }) -------------------------------------------------------------------------------- /features/gui/AbstractGui.js: -------------------------------------------------------------------------------- 1 | import ElementUtils from "../../../DocGuiLib/core/Element" 2 | import { CenterConstraint, OutlineEffect, ScrollComponent, UIBlock, UIText } from "../../../Elementa" 3 | import { Button } from "./Button" 4 | 5 | export const textInputScheme = { 6 | TextInput: { 7 | background: { color: [45, 58, 75, 80] } 8 | }, 9 | Keybind: { 10 | background: { color: [45, 58, 75, 80] } 11 | } 12 | } 13 | 14 | const Effect = Java.type("gg.essential.elementa.effects.Effect") 15 | 16 | /** 17 | * @param {number[]} color [0 - 255] 18 | * @param {number} thickness The thickness of the line 19 | * @param {UIComponent[]} comps The components this effect should use to calculate the width of the line 20 | * 21 | */ 22 | export const bottomLineEffect = (color, thickness = 1, comps = [], shouldPad = false) => { 23 | return new JavaAdapter(Effect, { 24 | getData() { 25 | const res = [null, 0] 26 | if (comps.length === 1) { 27 | res[0] = comps[0].getLeft() 28 | res[1] = comps[0].getWidth() 29 | return res 30 | } 31 | 32 | let pad = (comps[1].getLeft() - comps[0].getRight()) / 2 + 0.25 33 | 34 | for (let idx = 0; idx < comps.length; idx++) { 35 | let comp = comps[idx] 36 | if (idx === 0) res[0] = comp.getLeft() 37 | res[1] += comp.getWidth() + (shouldPad ? pad : 0) 38 | } 39 | 40 | return res 41 | }, 42 | beforeChildrenDraw() { 43 | const bounds = this.boundComponent 44 | const [ y, height ] = [ bounds.getTop(), bounds.getHeight() ] 45 | 46 | let [ startX, totalWidth ] = this.getData() 47 | 48 | Renderer.drawLine( 49 | Renderer.color(...color), 50 | startX, 51 | y + height + thickness, 52 | startX + totalWidth, 53 | y + height + thickness, 54 | thickness 55 | ) 56 | } 57 | }) 58 | } 59 | 60 | export class AbstractGui { 61 | constructor(name, columns, columnData = { startX: 5, padding: 28 }) { 62 | this.list = [] 63 | this.columns = columns 64 | this.columnData = columnData 65 | this.title = `&b&lDoc ${name}`.addColor() 66 | 67 | this.bgBoxComp = new UIBlock(ElementUtils.getJavaColor([3, 7, 17, 255])) 68 | .setX(new CenterConstraint()) 69 | .setY(new CenterConstraint()) 70 | .setWidth((25).percent()) 71 | .setHeight((50).percent()) 72 | .enableEffect(new OutlineEffect(ElementUtils.getJavaColor([4, 103, 132, 255]), 1)) 73 | 74 | this.titleComp = new UIText(this.title) 75 | .setX(new CenterConstraint()) 76 | .setY((5).percent()) 77 | .setChildOf(this.bgBoxComp) 78 | 79 | if (this.columns) { 80 | this._makeColumns() 81 | } 82 | 83 | this.scrollComp = new ScrollComponent() 84 | .setX(new CenterConstraint()) 85 | .setY((15).percent()) 86 | .setWidth((100).percent()) 87 | .setHeight((60).percent()) 88 | .setChildOf(this.bgBoxComp) 89 | 90 | this.addButton = new Button("&aAdd", [45, 58, 75, 255], [35, 196, 3, 200]) 91 | this.addButton.onClick(() => this.onAdd()) 92 | this.addButton 93 | .component 94 | .setX(new CenterConstraint()) 95 | .setY((80).percent()) 96 | .setWidth((40).percent()) 97 | .setHeight((10).percent()) 98 | .setChildOf(this.bgBoxComp) 99 | } 100 | 101 | _makeColumns() { 102 | if (this.columns.length === 1) { 103 | new UIText(this.columns[0]) 104 | .setX(new CenterConstraint()) 105 | .setY((10).percent()) 106 | .setChildOf(this.bgBoxComp) 107 | return 108 | } 109 | 110 | for (let idx = 0; idx < this.columns.length; idx++) { 111 | let text = this.columns[idx] 112 | let x = this.columnData.startX + (this.columnData.padding * idx) 113 | new UIText(text) 114 | .setX((x).percent()) 115 | .setY((10).percent()) 116 | .setChildOf(this.bgBoxComp) 117 | } 118 | } 119 | 120 | /** 121 | * - Meant to be overriden 122 | */ 123 | onAdd() {} 124 | } -------------------------------------------------------------------------------- /features/gui/Button.js: -------------------------------------------------------------------------------- 1 | import ElementUtils from "../../../DocGuiLib/core/Element" 2 | import { CenterConstraint, OutlineEffect, UIBlock, UIText } from "../../../Elementa" 3 | 4 | export class Button { 5 | constructor(text, color, outlineColor) { 6 | this.component = new UIBlock(ElementUtils.getJavaColor(color)).enableEffect(new OutlineEffect(ElementUtils.getJavaColor(outlineColor), 0.5)) 7 | this.text = text 8 | this.textComponent = new UIText(text.addColor()) 9 | .setX(new CenterConstraint()) 10 | .setY(new CenterConstraint()) 11 | .setChildOf(this.component) 12 | this.listeners = [] 13 | 14 | this.component.onMouseClick((comp, event) => { 15 | for (let idx = 0; idx < this.listeners.length; idx++) { 16 | let fn = this.listeners[idx] 17 | fn(comp, event) 18 | } 19 | }) 20 | } 21 | 22 | /** 23 | * @param {(UIComponent, UIClickEvent) => void} fn 24 | */ 25 | onClick(fn) { 26 | this.listeners.push(fn) 27 | } 28 | } -------------------------------------------------------------------------------- /features/gui/CancelMessage.js: -------------------------------------------------------------------------------- 1 | import ElementUtils from "../../../DocGuiLib/core/Element" 2 | import HandleGui from "../../../DocGuiLib/core/Gui" 3 | import TextInputElement from "../../../DocGuiLib/elements/TextInput" 4 | import { CenterConstraint, CramSiblingConstraint, UIBlock, UIText } from "../../../Elementa" 5 | import { addCommand } from "../../shared/Command" 6 | import { Persistence } from "../../shared/Persistence" 7 | import { TextHelper } from "../../shared/TextHelper" 8 | import { AbstractGui, bottomLineEffect, textInputScheme } from "./AbstractGui" 9 | import { Button } from "./Button" 10 | 11 | const gui = new HandleGui() 12 | 13 | class CMessage { 14 | constructor(parent, criteria, list) { 15 | this.parent = parent 16 | this.criteria = criteria 17 | this.list = list 18 | this.list.push(this) 19 | this.id = this.list.length - 1 20 | this.dirty = false 21 | this._register = null 22 | this.init() 23 | } 24 | 25 | init() { 26 | this.mainBox = new UIBlock(ElementUtils.getJavaColor([0, 0, 0, 0])) 27 | .setX((5).percent()) 28 | .setY(new CramSiblingConstraint(5)) 29 | .setWidth((98).percent()) 30 | .setHeight((10).percent()) 31 | .setChildOf(this.parent) 32 | 33 | this.criteriaInput = new TextInputElement(this.criteria, 1, 1, 55, 90) 34 | this.criteriaInput 35 | ._setPosition( 36 | (15).percent(), 37 | (0).percent() 38 | ) 39 | ._create(textInputScheme) 40 | .setChildOf(this.mainBox) 41 | 42 | this.removeButton = new Button("&cX", [0, 0, 0, 0], [196, 3, 3, 255]) 43 | this.removeButton 44 | .component 45 | .setX(new CramSiblingConstraint(5)) 46 | .setY((1).percent()) 47 | .setWidth((8).percent()) 48 | .setHeight((90).percent()) 49 | .setChildOf(this.mainBox) 50 | 51 | this.removeButton.onClick(() => this.remove()) 52 | 53 | this.mainBox.enableEffect(bottomLineEffect([45, 58, 75, 150], 1.5, [ 54 | this.criteriaInput.bgBox 55 | ])) 56 | } 57 | 58 | create(internal = false) { 59 | const inputValue = this.criteriaInput.getText() 60 | if (!inputValue && !internal) return 61 | if (this._register) this.removeRegister() 62 | 63 | if (!internal) this.criteria = inputValue 64 | Persistence.data.cancelMessage[this.criteria] = 0 65 | 66 | this._register = register("chat", (event) => { 67 | if (this.dirty) return this.removeRegister() 68 | cancel(event) 69 | }).setCriteria(this.criteria).setPriority(Priority.LOWEST) 70 | } 71 | 72 | /** 73 | * - Sets this [CancelMessage] dirty meaning it was deleted 74 | */ 75 | markDirty() { 76 | this.dirty = true 77 | } 78 | 79 | remove() { 80 | this.removeRegister() 81 | this.parent.removeChild(this.mainBox) 82 | delete Persistence.data.cancelMessage[this.criteria] 83 | this.list.splice(this.id, 1) 84 | 85 | ChatLib.chat(`${TextHelper.PREFIX} &cRemoved Cancel Message with criteria &b${this.criteria}`) 86 | } 87 | 88 | removeRegister() { 89 | if (!this._register) return 90 | 91 | this.markDirty() 92 | this._register.unregister() 93 | this._register = null 94 | } 95 | } 96 | 97 | const cancelMsg = new class CancelMessage extends AbstractGui { 98 | constructor() { 99 | super("Cancel Message", ["Criteria"]) 100 | } 101 | 102 | _addMsg(criteria) { 103 | if (this.list.some(it => it.criteria === criteria)) return 104 | new CMessage(this.scrollComp, criteria, this.list).create(true) 105 | } 106 | 107 | onAdd() { 108 | new CMessage(this.scrollComp, "", this.list) 109 | } 110 | 111 | onSave() { 112 | for (let idx = 0; idx < this.list.length; idx++) { 113 | let cmsg = this.list[idx] 114 | cmsg.create() 115 | } 116 | 117 | ChatLib.chat(`${TextHelper.PREFIX} &aSuccessfully created Cancel Messages`) 118 | } 119 | } 120 | 121 | gui._drawNormal(cancelMsg.bgBoxComp) 122 | 123 | Object.keys(Persistence.data.cancelMessage)?.forEach(key => { 124 | cancelMsg._addMsg(key) 125 | }) 126 | 127 | addCommand("cmsg", "Opens the CancelMessage UI", () => gui.ctGui.open()) 128 | gui.registers.onClose(() => cancelMsg.onSave()) -------------------------------------------------------------------------------- /features/gui/CommandAliases.js: -------------------------------------------------------------------------------- 1 | import ElementUtils from "../../../DocGuiLib/core/Element" 2 | import HandleGui from "../../../DocGuiLib/core/Gui" 3 | import TextInputElement from "../../../DocGuiLib/elements/TextInput" 4 | import { CramSiblingConstraint, UIBlock } from "../../../Elementa" 5 | import { addCommand } from "../../shared/Command" 6 | import { Persistence } from "../../shared/Persistence" 7 | import { TextHelper } from "../../shared/TextHelper" 8 | import { AbstractGui, bottomLineEffect, textInputScheme } from "./AbstractGui" 9 | import { Button } from "./Button" 10 | 11 | const gui = new HandleGui() 12 | 13 | let commandCooldown = null 14 | 15 | class Alias { 16 | constructor(parent, command, alias, list) { 17 | this.command = command 18 | this.alias = alias 19 | this.parent = parent 20 | this.list = list 21 | this.list.push(this) 22 | this.id = this.list.length - 1 23 | this.init() 24 | } 25 | 26 | init() { 27 | this.mainBox = new UIBlock(ElementUtils.getJavaColor([0, 0, 0, 0])) 28 | .setX((5).percent()) 29 | .setY(new CramSiblingConstraint(5)) 30 | .setWidth((98).percent()) 31 | .setHeight((10).percent()) 32 | .setChildOf(this.parent) 33 | 34 | this.aliasInput = new TextInputElement(this.alias, 1, 1, 25, 90) 35 | this.aliasInput._setPosition( 36 | (10).percent(), 37 | (0).percent() 38 | ) 39 | this.aliasInput._create(textInputScheme).setChildOf(this.mainBox) 40 | 41 | this.commandInput = new TextInputElement(this.command, 1, 1, 25, 90) 42 | this.commandInput._setPosition( 43 | (40).percent(), 44 | (0).percent() 45 | ) 46 | this.commandInput._create(textInputScheme).setChildOf(this.mainBox) 47 | 48 | this.removeButton = new Button("&cX", [0, 0, 0, 0], [196, 3, 3, 255]) 49 | this.removeButton 50 | .component 51 | .setX(new CramSiblingConstraint(5)) 52 | .setY((1).percent()) 53 | .setWidth((8).percent()) 54 | .setHeight((90).percent()) 55 | .setChildOf(this.mainBox) 56 | 57 | this.removeButton.onClick(() => this.remove()) 58 | 59 | this.mainBox.enableEffect(bottomLineEffect([45, 58, 75, 150], 1.5, [ 60 | this.aliasInput.bgBox, 61 | this.commandInput.bgBox 62 | ], true)) 63 | } 64 | 65 | create() { 66 | const cmdValue = this.commandInput.getText() 67 | const aliasValue = this.aliasInput.getText() 68 | if (!cmdValue || !aliasValue) return 69 | 70 | this.command = cmdValue.replace(/\//, "") 71 | this.alias = aliasValue.replace(/\//, "") 72 | 73 | Persistence.data.commandAliases[this.alias] = { command: this.command } 74 | } 75 | 76 | remove() { 77 | this.parent.removeChild(this.mainBox) 78 | delete Persistence.data.commandAliases[this.alias] 79 | this.list.splice(this.id, 1) 80 | 81 | ChatLib.chat(`${TextHelper.PREFIX} &cRemoved Command Alias with alias &b${this.alias}`) 82 | } 83 | 84 | /** 85 | * - Checks whether the command sent matches this [alias] for this [AliasCommand] 86 | * - if it does run the [command] for this class 87 | * @param {string} msg 88 | * @param {CancellableEvent} event 89 | * @returns 90 | */ 91 | messageSent(msg, event) { 92 | if (msg === this.alias) { 93 | cancel(event) 94 | if (commandCooldown && (Date.now() - commandCooldown) <= 3000) return 95 | ChatLib.command(this.command, TextHelper.shouldSendAsClient(this.command.split(" ")?.[0])) 96 | commandCooldown = Date.now() 97 | 98 | return 99 | } 100 | 101 | const cmd = msg.split(" ")[0] 102 | const args = msg.split(`${cmd} `)[1] 103 | 104 | if (cmd !== this.alias || !args) return 105 | 106 | cancel(event) 107 | if (commandCooldown && (Date.now() - commandCooldown) <= 3000) return 108 | ChatLib.command(`${this.command} ${args}`, TextHelper.shouldSendAsClient(this.command.split(" ")?.[0])) 109 | commandCooldown = Date.now() 110 | } 111 | } 112 | 113 | const cmdAlias = new class CommandAliases extends AbstractGui { 114 | constructor() { 115 | super("Command Aliases", ["Alias", "Criteria"], { startX: 15, padding: 30 }) 116 | 117 | register("messageSent", (msg, event) => { 118 | if (!msg.startsWith("/")) return 119 | 120 | for (let idx = 0; idx < this.list.length; idx++) { 121 | let alias = this.list[idx] 122 | alias.messageSent(msg.replace(/\//g, ""), event) 123 | } 124 | }) 125 | } 126 | 127 | _addAlias(alias, command) { 128 | if (this.list.some(it => it.alias.toLowerCase() === alias.toLowerCase() && it.command.toLowerCase() === command.toLowerCase())) return 129 | new Alias(this.scrollComp, command, alias, this.list) 130 | } 131 | 132 | onAdd() { 133 | new Alias(this.scrollComp, "", "", this.list) 134 | } 135 | 136 | onSave() { 137 | for (let idx = 0; idx < this.list.length; idx++) { 138 | let alias = this.list[idx] 139 | alias.create() 140 | } 141 | 142 | ChatLib.chat(`${TextHelper.PREFIX} &aSuccessfully created Command Aliases`) 143 | } 144 | } 145 | 146 | gui._drawNormal(cmdAlias.bgBoxComp) 147 | 148 | Object.keys(Persistence.data.commandAliases)?.forEach(key => { 149 | const obj = Persistence.data.commandAliases[key] 150 | cmdAlias._addAlias(key, obj.command) 151 | }) 152 | 153 | addCommand("aliasc", "Opens the Command Aliases UI", () => gui.ctGui.open()) 154 | gui.registers.onClose(() => cmdAlias.onSave()) -------------------------------------------------------------------------------- /features/kuudra/CratesWaypoints.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { RenderHelper } from "../../shared/Render" 5 | 6 | const criterias = [ 7 | "[NPC] Elle: Okay adventurers, I will go and fish up Kuudra!", 8 | "[NPC] Elle: Phew! The Ballista is finally ready! It should be strong enough to tank Kuudra's blows now!", 9 | "[NPC] Elle: OMG! Great work collecting my supplies!", 10 | /^(?:\[\w{1,3}\+*\] )?\w{1,16} recovered a Fuel Cell and charged the Ballista! \(100%\)$/, 11 | "[NPC] Elle: POW! SURELY THAT'S IT! I don't think he has any more in him!" 12 | ] 13 | const EntityGiantZombie = net.minecraft.entity.monster.EntityGiantZombie 14 | const entities = new Map() 15 | 16 | let shouldRender = false 17 | let crates = [] 18 | 19 | const feat = new Feature("cratesWaypoints", "kuudra") 20 | .addEvent( 21 | new Event(EventEnums.FORGE.ENTITYJOIN, (entity, entityID) => { 22 | if (entities.has(entityID) || entity.field_70163_u > 67) return 23 | 24 | entities.set(entityID, new Entity(entity)) 25 | feat.update() 26 | }, EntityGiantZombie) 27 | ) 28 | .addSubEvent( 29 | new Event(EventEnums.STEP, () => { 30 | crates = [] 31 | 32 | entities.forEach((/** @type {Entity}*/v, k) => { 33 | if (v.isDead()) return entities.delete(k) 34 | 35 | const yaw = v.getYaw() 36 | const distance = v.distanceTo(Player.getPlayer()) 37 | 38 | crates.push([ 39 | v.getX() + 5 * Math.cos((yaw + 130) * (Math.PI / 180)), 40 | v.getZ() + 5 * Math.sin((yaw + 130) * (Math.PI / 180)), 41 | distance.toFixed(2) 42 | ]) 43 | }) 44 | 45 | feat.update() 46 | }, 5), 47 | () => entities.size && shouldRender 48 | ) 49 | .addSubEvent( 50 | new Event("renderWorld", () => { 51 | for (let arr of crates) { 52 | let [ x, z, distance ] = arr 53 | let yText = distance <= 15 ? 78 : 80 54 | 55 | RenderHelper.renderBeaconBeam(x, x, z, 10, 217, 204, 255, false) 56 | Tessellator.drawString(`§a${distance}m`, x + 0.5, yText, z + 0.5, Renderer.WHITE, true) 57 | } 58 | }), 59 | () => crates.length && shouldRender 60 | ) 61 | .onUnregister(() => { 62 | crates = [] 63 | shouldRender = false 64 | entities.clear() 65 | }) 66 | 67 | criterias.forEach((it, idx) => feat.addEvent( 68 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 69 | if (idx > 1) { 70 | shouldRender = false 71 | feat.update() 72 | return 73 | } 74 | 75 | shouldRender = true 76 | feat.update() 77 | }, it) 78 | )) -------------------------------------------------------------------------------- /features/kuudra/KuudraSplits.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | import { TextHelper } from "../../shared/TextHelper" 6 | 7 | const editGui = new DraggableGui("kuudrSplitsDisplay").setCommandName("editkuudrSplitsDisplay") 8 | 9 | let phasesMsg = [ 10 | { 11 | criteria: "[NPC] Elle: Okay adventurers, I will go and fish up Kuudra!", 12 | time: null, 13 | displayText: `&bSupplies&f:`, 14 | sent: false 15 | }, 16 | { 17 | criteria: "[NPC] Elle: OMG! Great work collecting my supplies!", 18 | time: null, 19 | displayText: `&bBuild&f:`, 20 | sent: false 21 | }, 22 | { 23 | criteria: "[NPC] Elle: Phew! The Ballista is finally ready! It should be strong enough to tank Kuudra's blows now!", 24 | time: null, 25 | displayText: `&bFuel/Stun&f:`, 26 | sent: false 27 | }, 28 | { 29 | criteria: "[NPC] Elle: POW! SURELY THAT'S IT! I don't think he has any more in him!", 30 | time: null, 31 | displayText: `&bClear&f:`, 32 | sent: false 33 | }, 34 | { 35 | criteria: /^ *Tokens Earned\: [\d,.]+$/, 36 | time: null, 37 | displayText: null, 38 | sent: false 39 | } 40 | ] 41 | 42 | editGui.onDraw(() => { 43 | Renderer.retainTransforms(true) 44 | Renderer.translate(editGui.getX(), editGui.getY()) 45 | Renderer.scale(editGui.getScale()) 46 | Renderer.drawStringWithShadow("&bSupplies&f: &c0s\n&bBuild&f: &c0s\n&bFuel/Stun&f: &c0s\n&bClear&f: &c0s", 0, 0) 47 | Renderer.retainTransforms(false) 48 | Renderer.finishDraw() 49 | }) 50 | 51 | const feat = new Feature("kuudraSplits", "kuudra") 52 | .addEvent( 53 | new Event("renderOverlay", () => { 54 | if (editGui.isOpen()) return 55 | 56 | Renderer.retainTransforms(true) 57 | Renderer.translate(editGui.getX(), editGui.getY()) 58 | Renderer.scale(editGui.getScale()) 59 | 60 | for (let idx = 0; idx < phasesMsg.length; idx++) { 61 | let v = phasesMsg[idx] 62 | let v2 = phasesMsg[idx + 1] 63 | if (!v.displayText) continue 64 | let y = 0 + (10 * idx) 65 | 66 | if (!v.time) { 67 | Renderer.drawStringWithShadow(`${v.displayText} &c0s`, 0, y) 68 | continue 69 | } 70 | 71 | if (v2?.time) { 72 | Renderer.drawStringWithShadow(`${v.displayText} &a${TextHelper.getSecondsSince(v2.time, v.time)}`, 0, y) 73 | if (!v.sent) { 74 | ChatLib.chat(`${TextHelper.PREFIX} ${v.displayText} &a${TextHelper.getSecondsSince(v2.time, v.time)}`) 75 | v.sent = true 76 | } 77 | continue 78 | } 79 | 80 | Renderer.drawStringWithShadow(`${v.displayText} &a${TextHelper.getSecondsSince(Date.now(), v.time)}`, 0, y) 81 | } 82 | 83 | Renderer.retainTransforms(false) 84 | Renderer.finishDraw() 85 | }) 86 | ) 87 | .onUnregister(() => { 88 | phasesMsg.forEach(it => { 89 | it.time = null 90 | it.sent = false 91 | }) 92 | }) 93 | 94 | phasesMsg.forEach(it => feat.addEvent( 95 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 96 | if (it.time) return 97 | it.time = Date.now() 98 | }, it.criteria) 99 | )) -------------------------------------------------------------------------------- /features/mining/ComissionDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("comissionsDisplay").setCommandName("editcomissionsDisplay") 7 | const comissionsRegex = /^ ([\w' ]+): (?:[\d,.]+%|DONE)$/ 8 | const mapList = new Set() 9 | 10 | editGui.onDraw(() => { 11 | Renderer.retainTransforms(true) 12 | Renderer.translate(editGui.getX(), editGui.getY()) 13 | Renderer.scale(editGui.getScale()) 14 | Renderer.drawStringWithShadow("&bCliffside Veins Titanium&f: &c10%\n&bGoblin Raid Slayer&f: &aDONE", 0, 0) 15 | Renderer.retainTransforms(false) 16 | Renderer.finishDraw() 17 | }) 18 | 19 | const feat = new Feature("comissionDisplay", ["Dwarven Mines", "Crystal Hollows", "Mineshaft"]) 20 | .addEvent( 21 | new Event(EventEnums.STEP, () => { 22 | mapList.clear() 23 | 24 | TabList.getNames().forEach(name => { 25 | if (!comissionsRegex.test(name.removeFormatting())) return 26 | 27 | mapList.add(name) 28 | }) 29 | 30 | feat.update() 31 | }, 1) 32 | ) 33 | .addSubEvent( 34 | new Event("renderOverlay", () => { 35 | if (editGui.isOpen()) return 36 | 37 | let y = 0 38 | Renderer.retainTransforms(true) 39 | Renderer.translate(editGui.getX(), editGui.getY()) 40 | Renderer.scale(editGui.getScale()) 41 | 42 | mapList.forEach(format => { 43 | Renderer.drawStringWithShadow(`&b${format.replace("§r §r§f", "")}`, 0, 10 * y) 44 | y++ 45 | }) 46 | 47 | Renderer.retainTransforms(false) 48 | Renderer.finishDraw() 49 | }), 50 | () => mapList.size 51 | ) -------------------------------------------------------------------------------- /features/mining/EmissaryWaypoints.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | import { RenderHelper } from "../../shared/Render" 4 | 5 | const emissaryCoords = { 6 | "Emissary Ceanna": [42, 134, 22], 7 | "Emissary Wilson": [171, 150, 31], 8 | "Emissary Lilith": [58, 198, -8], 9 | "Emissary Fraiser": [-132, 174, -50], 10 | "Emissary Eliza": [-37, 200, -131], 11 | "Emissary Braum": [89, 198, -92], 12 | "Emissary Carlton": [-73, 153, -11] 13 | } 14 | 15 | new Feature("emissaryWaypoints", "dwarven mines") 16 | .addEvent( 17 | new Event("renderWorld", () => { 18 | Object.keys(emissaryCoords).forEach(it => { 19 | const [ x, y, z ] = emissaryCoords[it] 20 | 21 | RenderHelper.renderWaypoint(it, x, y, z, 0, 255, 255, 255, true) 22 | }) 23 | }) 24 | ) -------------------------------------------------------------------------------- /features/mining/PowderDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("powderDisplay").setCommandName("editpowderDisplay") 7 | const powderRegex = /^ (Mithril|Gemstone|Glacite): (?:[\d,.]+)$/ 8 | const powderColors = { 9 | "Mithril": "&a", 10 | "Gemstone": "&5", 11 | "Glacite": "&3" 12 | } 13 | 14 | let powderList = {} 15 | 16 | editGui.onDraw(() => { 17 | Renderer.retainTransforms(true) 18 | Renderer.translate(editGui.getX(), editGui.getY()) 19 | Renderer.scale(editGui.getScale()) 20 | Renderer.drawStringWithShadow("&aMithril&f: &210,000\n&5Gemstone&f: &d10,000\n&3Glacite&f: &b10,000", 0, 0) 21 | Renderer.retainTransforms(false) 22 | Renderer.finishDraw() 23 | }) 24 | 25 | const feat = new Feature("powderDisplay", ["Dwarven Mines", "Crystal Hollows"]) 26 | .addEvent( 27 | new Event(EventEnums.PACKET.SERVER.TABADD, (type, _, formatted) => { 28 | powderList[type] = formatted.replace("§r ", powderColors[type]) 29 | feat.update() 30 | }, powderRegex) 31 | ) 32 | .addEvent( 33 | new Event(EventEnums.PACKET.SERVER.TABUPDATE, (type, _, formatted) => { 34 | powderList[type] = formatted.replace("§r ", powderColors[type]) 35 | feat.update() 36 | }, powderRegex) 37 | ) 38 | .addSubEvent( 39 | new Event("renderOverlay", () => { 40 | if (editGui.isOpen()) return 41 | 42 | Renderer.retainTransforms(true) 43 | Renderer.translate(editGui.getX(), editGui.getY()) 44 | Renderer.scale(editGui.getScale()) 45 | Renderer.drawStringWithShadow(Object.values(powderList).join("\n"), 0, 0) 46 | Renderer.retainTransforms(false) 47 | Renderer.finishDraw() 48 | }), 49 | () => powderList.Mithril 50 | ) 51 | .onUnregister(() => powderList = {}) -------------------------------------------------------------------------------- /features/misc/ArmorDisplay.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("armorDisplay").setCommandName("editarmorDisplay") 7 | const slotColor = Renderer.color(100, 100, 100, 150) 8 | const slotBorderColor = Renderer.color(50, 50, 50, 150) 9 | const barrier = new Item("minecraft:barrier") 10 | 11 | const drawSlotBackground = (/** @type {Item} */item, x, y, internal = false) => { 12 | if (!item && !config().armorDisplayBarrier && !internal) return 13 | 14 | if (config().armorDisplayBackground) { 15 | Renderer.drawRect(slotColor, x, y, 16, 16) 16 | 17 | // Top line 18 | Renderer.drawLine(slotBorderColor, x, y, x + 16, y, 1) 19 | 20 | // Left line 21 | Renderer.drawLine(slotBorderColor, x, y, x, y + 16, 1) 22 | 23 | // Right line 24 | Renderer.drawLine(slotBorderColor, x + 16, y, x + 16, y + 16, 1) 25 | 26 | // Bottom line 27 | Renderer.drawLine(slotBorderColor, x, y + 16, x + 16, y + 16, 1) 28 | } 29 | 30 | if (!item) return barrier.draw(x, y) 31 | 32 | item.draw(x, y) 33 | } 34 | 35 | editGui.onDraw(() => { 36 | Renderer.retainTransforms(true) 37 | Renderer.translate(editGui.getX(), editGui.getY()) 38 | Renderer.scale(editGui.getScale()) 39 | 40 | drawSlotBackground(null, 0, 0, true) 41 | drawSlotBackground(null, 0, 18, true) 42 | drawSlotBackground(null, 0, 18, true) 43 | drawSlotBackground(null, 0, 18, true) 44 | 45 | Renderer.retainTransforms(false) 46 | Renderer.finishDraw() 47 | }) 48 | 49 | new Feature("armorDisplay") 50 | .addEvent( 51 | new Event("renderOverlay", () => { 52 | if (editGui.isOpen()) return 53 | 54 | Renderer.retainTransforms(true) 55 | Renderer.translate(editGui.getX(), editGui.getY()) 56 | Renderer.scale(editGui.getScale()) 57 | 58 | drawSlotBackground(Player.armor.getHelmet(), 0, 0) 59 | drawSlotBackground(Player.armor.getChestplate(), 0, 18) 60 | drawSlotBackground(Player.armor.getLeggings(), 0, 18) 61 | drawSlotBackground(Player.armor.getBoots(), 0, 18) 62 | 63 | Renderer.retainTransforms(false) 64 | Renderer.finishDraw() 65 | }) 66 | ) -------------------------------------------------------------------------------- /features/misc/AttributeShardDisplay.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import { TextHelper } from "../../shared/TextHelper" 5 | 6 | const cache = new Map() 7 | const cacheNumber = new Map() 8 | const formattedRegex = /^(§\w(?:§\w)*?)([\w ]+) ([IVXLCDM\d]+)(?: ✖)?$/ 9 | 10 | config().getConfig().registerListener("attributeShardAbbreviationColor", () => { 11 | cache.clear() 12 | }) 13 | 14 | const getName = (format, name) => { 15 | name = name.replace(/\-/g, " ") 16 | if (cache.has(`${format}${name}`)) return cache.get(`${format}${name}`) 17 | 18 | const spaces = name.split(" ") 19 | 20 | let str = spaces.length > 1 21 | ? `${spaces[0][0]}${spaces[1][0]}`.toUpperCase().removeFormatting()?.replace(/§z/g, "") 22 | : `${name.slice(0, 3)}`.toUpperCase().removeFormatting()?.replace(/§z/g, "") 23 | 24 | const result = config().attributeShardAbbreviationColor 25 | ? `${format}${str}` 26 | : str 27 | 28 | cache.set(`${format}${name}`, result) 29 | 30 | return result 31 | } 32 | 33 | const getLevel = (roman) => { 34 | if (cacheNumber.has(roman)) return cacheNumber.get(roman) 35 | 36 | const result = TextHelper.decodeNumeral(roman) 37 | cacheNumber.set(roman, result) 38 | 39 | return result 40 | } 41 | 42 | new Feature("attributeShardLevel") 43 | .addEvent( 44 | new Event("renderSlot", (/** @type {Slot} */slot) => { 45 | const item = slot.getItem() 46 | if (!item || item.getID() !== 409) return 47 | 48 | const lore = item.getLore() 49 | if (!lore || lore?.[0]?.removeFormatting() !== "Attribute Shard") return 50 | 51 | const enchantName = lore[1] 52 | const match = enchantName.match(formattedRegex) 53 | if (!match) return 54 | 55 | const level = getLevel(match[3]) 56 | 57 | Tessellator.pushMatrix() 58 | Tessellator.disableLighting() 59 | 60 | if (config().attributeShardAbbreviation) { 61 | Renderer.translate(slot.getDisplayX(), slot.getDisplayY(), 280) 62 | Renderer.scale(0.9) 63 | Renderer.drawStringWithShadow(getName(match[1], match[2]), 0, 0) 64 | } 65 | 66 | Renderer.translate(slot.getDisplayX() + (16 - Renderer.getStringWidth(level)), slot.getDisplayY() + 8, 280) 67 | Renderer.drawStringWithShadow(level, 0, 0) 68 | 69 | Tessellator.enableLighting() 70 | Tessellator.popMatrix() 71 | }) 72 | ) -------------------------------------------------------------------------------- /features/misc/BlockOverlay.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import { DGlStateManager } from "../../shared/DGlStateManager" 5 | import { RenderHelper } from "../../shared/Render" 6 | 7 | const Blocks = net.minecraft.init.Blocks 8 | const BlockFlowingLava = Blocks.field_150356_k 9 | const BlockLava = Blocks.field_150353_l 10 | const BlockFlowingWater = Blocks.field_150358_i 11 | const BlockWater = Blocks.field_150355_j 12 | const BlockAir = Blocks.field_150350_a 13 | const cachedColors = new Map() 14 | 15 | const getColor = (colors) => { 16 | const [ r, g, b, a ] = colors 17 | 18 | if (cachedColors.has(colors.toString())) return cachedColors.get(colors.toString()) 19 | 20 | const javaColor = new java.awt.Color(r / 255, g / 255, b / 255, a / 255) 21 | 22 | cachedColors.set(colors.toString(), javaColor) 23 | 24 | return javaColor 25 | } 26 | 27 | new Feature("blockOverlay") 28 | .addEvent( 29 | new Event("drawBlockHighlight", ({x, y, z}, event) => { 30 | const ctBlock = World.getBlockAt(x, y, z) 31 | const mcBlock = ctBlock.type.mcBlock 32 | 33 | if (mcBlock == BlockAir || 34 | mcBlock == BlockFlowingLava || 35 | mcBlock == BlockFlowingWater || 36 | mcBlock == BlockLava || 37 | mcBlock == BlockWater) return 38 | 39 | // If third person disable phase 40 | const phase = !(Client.settings.getSettings()?.field_74320_O === 1) 41 | const color = getColor(config().blockOverlayColor) 42 | const pticks = event.partialTicks 43 | 44 | const [ r, g, b, a ] = [color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()] 45 | const [ r1, g1, b1 ] = [color.getRed(), color.getGreen(), color.getBlue()] 46 | 47 | cancel(event) 48 | 49 | const [ rx, ry, rz ] = RenderHelper.getInterp(pticks) 50 | const aabb = RenderHelper.getCTBlockAxis(ctBlock) 51 | 52 | GL11.glLineWidth(2) 53 | DGlStateManager 54 | .pushMatrix() 55 | .disableTexture2D() 56 | .enableBlend() 57 | .disableAlpha() 58 | .tryBlendFuncSeparate(770, 771, 1, 0) 59 | .translate(-rx, -ry, -rz) 60 | 61 | if (phase) DGlStateManager.disableDepth() 62 | 63 | RenderHelper.drawOutlinedBoundingBox(aabb, r, g, b, a) 64 | if (config().blockOverlayFill) RenderHelper.drawFilledBoundingBox(aabb, r1, g1, b1, 50) 65 | 66 | if (phase) DGlStateManager.enableDepth() 67 | 68 | DGlStateManager 69 | .enableTexture2D() 70 | .disableBlend() 71 | .enableAlpha() 72 | .translate(rx, ry, rz) 73 | .popMatrix() 74 | }) 75 | ) -------------------------------------------------------------------------------- /features/misc/BonzoMaskInvincibility.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("bonzoMaskInvincibilityTimer").setCommandName("editbonzoMaskInvincibilityTimer") 7 | const procRegex = /^Your( ⚚)? Bonzo\'s Mask saved your life\!$/ 8 | 9 | let proc = null 10 | 11 | editGui.onDraw(() => { 12 | Renderer.translate(editGui.getX(), editGui.getY()) 13 | Renderer.scale(editGui.getScale()) 14 | Renderer.drawStringWithShadow("&9Bonzo's Mask&f: &a0.00s", 0, 0) 15 | Renderer.finishDraw() 16 | }) 17 | 18 | const feat = new Feature("bonzoMaskInvincibilityTimer") 19 | .addEvent( 20 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 21 | proc = 60 22 | 23 | feat.update() 24 | }, procRegex) 25 | ) 26 | .addSubEvent( 27 | new Event(EventEnums.PACKET.CUSTOM.TICK, () => { 28 | if (proc === 0) { 29 | proc = null 30 | feat.update() 31 | 32 | return 33 | } 34 | 35 | proc-- 36 | }), 37 | () => proc 38 | ) 39 | .addSubEvent( 40 | new Event("renderOverlay", () => { 41 | if (editGui.isOpen()) return 42 | 43 | const timeRemaining = (proc * 0.05).toFixed(2) 44 | 45 | Renderer.translate(editGui.getX(), editGui.getY()) 46 | Renderer.scale(editGui.getScale()) 47 | Renderer.drawStringWithShadow(`&9Bonzo's Mask&f: &a${timeRemaining}s`, 0, 0) 48 | Renderer.finishDraw() 49 | }), 50 | () => proc 51 | ) 52 | .onUnregister(() => proc = null) -------------------------------------------------------------------------------- /features/misc/ChampionDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | import { TextHelper } from "../../shared/TextHelper" 6 | 7 | const editGui = new DraggableGui("championDisplay").setCommandName("editchampionDisplay") 8 | const championLevel = [0, 50000, 100000, 250000, 500000, 1000000, 1500000, 2000000, 2500000, 3000000] 9 | const cachedLevel = new Map() 10 | 11 | let itemAttributes = null 12 | 13 | const getLevel = (amount) => { 14 | if (!amount) return 0 15 | if (cachedLevel.has(amount)) return cachedLevel.get(amount) 16 | 17 | for (let idx = 0; idx < championLevel.length; idx++) { 18 | let min = championLevel[idx] 19 | if (idx + 1 >= championLevel.length) return idx + 1 20 | let max = championLevel[idx + 1] 21 | 22 | if (amount >= min && amount <= max) { 23 | cachedLevel.set(amount, idx + 1) 24 | return idx + 1 25 | } 26 | } 27 | } 28 | 29 | editGui.onDraw(() => { 30 | Renderer.translate(editGui.getX(), editGui.getY()) 31 | Renderer.scale(editGui.getScale()) 32 | Renderer.drawStringWithShadow("&bChampion &610&f: &63,000,000", 0, 0) 33 | Renderer.finishDraw() 34 | }) 35 | 36 | const feat = new Feature("championxpDisplay") 37 | .addEvent( 38 | new Event(EventEnums.STEP, () => { 39 | const heldItem = Player.getHeldItem() 40 | const extraattributes = TextHelper.getExtraAttribute(Player.getHeldItem()) 41 | 42 | if (!heldItem || !extraattributes || !extraattributes?.champion_combat_xp) { 43 | itemAttributes = null 44 | feat.update() 45 | 46 | return 47 | } 48 | 49 | itemAttributes = extraattributes 50 | feat.update() 51 | }, 1) 52 | ) 53 | .addSubEvent( 54 | new Event("renderOverlay", () => { 55 | const str = `&bChampion &6${getLevel(itemAttributes.champion_combat_xp)}&f: &6${TextHelper.addCommasTrunc(itemAttributes?.champion_combat_xp ?? 0)}` 56 | 57 | Renderer.translate(editGui.getX(), editGui.getY()) 58 | Renderer.scale(editGui.getScale()) 59 | Renderer.drawStringWithShadow(str, 0, 0) 60 | Renderer.finishDraw() 61 | }), 62 | () => itemAttributes 63 | ) -------------------------------------------------------------------------------- /features/misc/ChatWaypoint.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { addCommand } from "../../shared/Command" 6 | import { Persistence } from "../../shared/Persistence" 7 | import { RenderHelper } from "../../shared/Render" 8 | import { TextHelper } from "../../shared/TextHelper" 9 | 10 | const chatRegex = /^(Co-op|Party)?(?: > )?(?:\[\d+\] .? ?)?(?:\[[^\]]+\] )?(\w{1,16}): x: (-?\d+), y: (-?\d+), z: (-?\d+) ?$/ 11 | const coords = new Map() 12 | 13 | const distanceTo = (x, y, z, x1, y1, z1) => Math.hypot(x - x1, y - y1, z - z1) 14 | 15 | const feat = new Feature("chatWaypoint") 16 | .addEvent( 17 | new Event(EventEnums.PACKET.SERVER.CHAT, (type, username, x1, y1, z1) => { 18 | if (Persistence.data.chatWaypointNames.some(it => it === username.toLowerCase())) return 19 | if (!type && !config().chatWaypointAll) return 20 | if (type === "Co-op" && !config().chatWaypointCoop) return 21 | if (type === "Party" && !config().chatWaypointParty) return 22 | 23 | const coord = [ Math.floor(x1), Math.floor(y1), Math.floor(z1) ] 24 | if (coords.has(`${coord}`)) return 25 | 26 | coords.set(`${coord}`, coord) 27 | ChatLib.chat(`${TextHelper.PREFIX} &aSet waypoint at &b${coord[0]}, ${coord[1]}, ${coord[2]}`) 28 | 29 | feat.update() 30 | }, chatRegex) 31 | ) 32 | .addSubEvent( 33 | new Event("renderWorld", () => { 34 | coords.forEach(it => { 35 | const [ x, y, z ] = it 36 | const distance = distanceTo(Player.getX(), Player.getY(), Player.getZ(), x, y, z) 37 | 38 | if (distance < 3) return coords.delete(`${it}`) 39 | 40 | RenderHelper.renderWaypoint( 41 | `${distance.toFixed(2)}m`, 42 | x, y, z, 43 | 0, 255, 0, 255 44 | ) 45 | }) 46 | }), 47 | () => coords.size 48 | ) 49 | .onUnregister(() => { 50 | coords.clear() 51 | }) 52 | 53 | // Blacklist command 54 | addCommand("ctw", "&6Chat waypoints Blacklist commands", (mode, name) => { 55 | if (!mode) 56 | return ChatLib.chat(`${TextHelper.PREFIX} &cMakes sure to add a mode &7(modes: add, remove, clear)&c and a username.&7(clear does not require a username)`) 57 | 58 | switch (mode.toLowerCase()) { 59 | case "add": 60 | Persistence.data.chatWaypointNames.push(name.toLowerCase()) 61 | Persistence.data.save() 62 | 63 | ChatLib.chat(`${TextHelper.PREFIX} &c${name.toLowerCase()} &aWas added to the blacklist`) 64 | break 65 | 66 | case "clear": 67 | coords.clear() 68 | ChatLib.chat(`${TextHelper.PREFIX} &aAll waypoints have been deleted`) 69 | break 70 | 71 | case "remove": { 72 | const idx = Persistence.data.chatWaypointNames.indexOf(name.toLowerCase()) 73 | Persistence.data.chatWaypointNames.splice(idx, 1) 74 | Persistence.data.save() 75 | 76 | ChatLib.chat(`${TextHelper.PREFIX} &b${name.toLowerCase()} &aWas removed from the blacklist`) 77 | break 78 | } 79 | 80 | default: 81 | ChatLib.chat(`${TextHelper.PREFIX} &cYou did not enter a correct mode.`) 82 | break 83 | } 84 | }) -------------------------------------------------------------------------------- /features/misc/CompactDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | import { TextHelper } from "../../shared/TextHelper" 6 | 7 | const editGui = new DraggableGui("compactDisplay").setCommandName("editcompactdisplay") 8 | const compactLevel = [0, 100, 500, 1500, 5000, 15000, 50000, 150000, 500000, 1000000] 9 | const cachedLevel = new Map() 10 | 11 | let totalXp = null 12 | 13 | const getLevel = (amount) => { 14 | if (!amount) return 0 15 | if (cachedLevel.has(amount)) return cachedLevel.get(amount) 16 | 17 | for (let idx = 0; idx < compactLevel.length; idx++) { 18 | let min = compactLevel[idx] 19 | if (idx + 1 >= compactLevel.length) return idx + 1 20 | let max = compactLevel[idx + 1] 21 | 22 | if (amount >= min && amount <= max) { 23 | cachedLevel.set(amount, idx + 1) 24 | return idx + 1 25 | } 26 | } 27 | } 28 | 29 | editGui.onDraw(() => { 30 | Renderer.translate(editGui.getX(), editGui.getY()) 31 | Renderer.scale(editGui.getScale()) 32 | Renderer.drawStringWithShadow("&bCompact &610&f: &61,000,000", 0, 0) 33 | Renderer.finishDraw() 34 | }) 35 | 36 | const feat = new Feature("compactDisplay") 37 | .addEvent( 38 | new Event(EventEnums.STEP, () => { 39 | const heldItem = Player.getHeldItem() 40 | const extraattributes = TextHelper.getExtraAttribute(Player.getHeldItem()) 41 | 42 | if (!heldItem || !extraattributes || !extraattributes?.compact_blocks) { 43 | totalXp = null 44 | feat.update() 45 | 46 | return 47 | } 48 | 49 | totalXp = extraattributes.compact_blocks 50 | feat.update() 51 | }, 1) 52 | ) 53 | .addSubEvent( 54 | new Event("renderOverlay", () => { 55 | if (editGui.isOpen()) return 56 | 57 | const str = `&bCompact &6${getLevel(totalXp)}&f: &6${TextHelper.addCommasTrunc(totalXp ?? 0)}` 58 | 59 | Renderer.translate(editGui.getX(), editGui.getY()) 60 | Renderer.scale(editGui.getScale()) 61 | Renderer.drawStringWithShadow(str, 0, 0) 62 | Renderer.finishDraw() 63 | }), 64 | () => totalXp !== null 65 | ) 66 | .onUnregister(() => { 67 | totalXp = null 68 | }) -------------------------------------------------------------------------------- /features/misc/CopyChat.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | import { TextHelper } from "../../shared/TextHelper" 4 | 5 | const Mouse = org.lwjgl.input.Mouse 6 | 7 | const getStr = (str, width, chatGui, y) => { 8 | const scale = Renderer.screen.getScale() 9 | const isCtrlDown = Client.isControlDown() 10 | const normalWidth = Renderer.getStringWidth(" ") * Renderer.screen.getScale() 11 | let count = 0 12 | 13 | for (let idx = 0; idx < width; idx++) { 14 | let comp = chatGui.func_146236_a(idx, y) 15 | if (!comp && count > 10) break 16 | if (!comp) { 17 | count++ 18 | idx += normalWidth 19 | continue 20 | } 21 | 22 | let text = isCtrlDown 23 | ? comp.func_150261_e()?.replace(/§/g, "&") 24 | : comp.func_150261_e()?.removeFormatting()?.replace(/§z/g, "") 25 | 26 | str += text 27 | idx += (Renderer.getStringWidth(comp.func_150261_e()) * scale) - 1 28 | count = 0 29 | } 30 | 31 | const ny = y - (10 * scale) 32 | const compBelow = chatGui.func_146236_a(15, ny) 33 | if (compBelow) { 34 | const msg = compBelow.func_150261_e().removeFormatting() 35 | if (/^ \w/.test(msg)) { 36 | str = getStr(str, width, chatGui, ny) 37 | } 38 | } 39 | 40 | return str 41 | } 42 | 43 | new Feature("copyChat") 44 | .addEvent( 45 | new Event("guiMouseClick", (_, __, mouseButton) => { 46 | if (!Client.isInChat() || mouseButton !== 1) return 47 | 48 | const y = Mouse.getY() 49 | const chatGui = Client.getChatGUI() 50 | const chatWidth = Client.getChatGUI().func_146228_f() * Renderer.screen.getScale() 51 | 52 | let str = getStr("", chatWidth, chatGui, y) 53 | 54 | ChatLib.command(`ct copy ${str}`, true) 55 | ChatLib.chat(`${TextHelper.PREFIX} &aCopied message to clipboard`) 56 | }) 57 | ) -------------------------------------------------------------------------------- /features/misc/CultivatingDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | import { TextHelper } from "../../shared/TextHelper" 6 | 7 | const editGui = new DraggableGui("cultivatingDisplay").setCommandName("editcultivatingdisplay") 8 | const cultivatingLevel = [0, 1000, 5000, 25000, 100000, 300000, 1500000, 5000000, 20000000, 100000000] 9 | const cachedLevel = new Map() 10 | 11 | let totalXp = null 12 | 13 | const getLevel = (amount) => { 14 | if (!amount) return 0 15 | if (cachedLevel.has(amount)) return cachedLevel.get(amount) 16 | 17 | for (let idx = 0; idx < cultivatingLevel.length; idx++) { 18 | let min = cultivatingLevel[idx] 19 | if (idx + 1 >= cultivatingLevel.length) return idx + 1 20 | let max = cultivatingLevel[idx + 1] 21 | 22 | if (amount >= min && amount <= max) { 23 | cachedLevel.set(amount, idx + 1) 24 | return idx + 1 25 | } 26 | } 27 | } 28 | 29 | editGui.onDraw(() => { 30 | Renderer.translate(editGui.getX(), editGui.getY()) 31 | Renderer.scale(editGui.getScale()) 32 | Renderer.drawStringWithShadow("&bCultivating &610&f: &6100,000,000", 0, 0) 33 | Renderer.finishDraw() 34 | }) 35 | 36 | const feat = new Feature("cultivatingDisplay") 37 | .addEvent( 38 | new Event(EventEnums.STEP, () => { 39 | const heldItem = Player.getHeldItem() 40 | const extraattributes = TextHelper.getExtraAttribute(Player.getHeldItem()) 41 | 42 | if (!heldItem || !extraattributes || !extraattributes?.farmed_cultivating) { 43 | totalXp = null 44 | feat.update() 45 | 46 | return 47 | } 48 | 49 | totalXp = extraattributes.farmed_cultivating 50 | feat.update() 51 | }, 1) 52 | ) 53 | .addSubEvent( 54 | new Event("renderOverlay", () => { 55 | if (editGui.isOpen()) return 56 | 57 | const str = `&bCultivating &6${getLevel(totalXp)}&f: &6${TextHelper.addCommasTrunc(totalXp ?? 0)}` 58 | 59 | Renderer.translate(editGui.getX(), editGui.getY()) 60 | Renderer.scale(editGui.getScale()) 61 | Renderer.drawStringWithShadow(str, 0, 0) 62 | Renderer.finishDraw() 63 | }), 64 | () => totalXp !== null 65 | ) 66 | .onUnregister(() => { 67 | totalXp = null 68 | }) -------------------------------------------------------------------------------- /features/misc/DrillFuelDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | import { TextHelper } from "../../shared/TextHelper" 6 | 7 | const editGui = new DraggableGui("drillFuelDisplay").setCommandName("editdrillfueldisplay") 8 | 9 | let currentFuel = null 10 | 11 | editGui.onDraw(() => { 12 | Renderer.translate(editGui.getX(), editGui.getY()) 13 | Renderer.scale(editGui.getScale()) 14 | Renderer.drawStringWithShadow("&bDrill Fuel&f: &a1,000", 0, 0) 15 | Renderer.finishDraw() 16 | }) 17 | 18 | const feat = new Feature("drillFuelDisplay") 19 | .addEvent( 20 | new Event(EventEnums.STEP, () => { 21 | const heldItem = Player.getHeldItem() 22 | const extraattributes = TextHelper.getExtraAttribute(Player.getHeldItem()) 23 | 24 | if (!heldItem || !extraattributes || !extraattributes?.drill_fuel) { 25 | currentFuel = null 26 | feat.update() 27 | 28 | return 29 | } 30 | 31 | currentFuel = extraattributes.drill_fuel 32 | feat.update() 33 | }, 1) 34 | ) 35 | .addSubEvent( 36 | new Event("renderOverlay", () => { 37 | if (editGui.isOpen()) return 38 | 39 | Renderer.translate(editGui.getX(), editGui.getY()) 40 | Renderer.scale(editGui.getScale()) 41 | Renderer.drawStringWithShadow(`&bDrill Fuel&f: &a${TextHelper.addCommasTrunc(currentFuel)}`, 0, 0) 42 | Renderer.finishDraw() 43 | }), 44 | () => currentFuel 45 | ) 46 | .onUnregister(() => { 47 | currentFuel = null 48 | }) -------------------------------------------------------------------------------- /features/misc/EnchantedBookDisplay.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import { TextHelper } from "../../shared/TextHelper" 5 | 6 | const cache = new Map() 7 | const cacheNumber = new Map() 8 | const formattedRegex = /^(§\w(?:§\w)*?)([\w\- ]+) ([IVXLCDM\d]+)$/ 9 | 10 | config().getConfig().registerListener("enchantedBookAbbreviationColor", () => { 11 | cache.clear() 12 | }) 13 | 14 | const getName = (format, name) => { 15 | name = name.replace(/\-/g, " ") 16 | if (cache.has(`${format}${name}`)) return cache.get(`${format}${name}`) 17 | 18 | const spaces = name.split(" ") 19 | const ultimate = format.endsWith("§d§l") 20 | 21 | let str = spaces.length > 1 22 | ? `${spaces[0][0]}${spaces[1][0]}`.toUpperCase().removeFormatting()?.replace(/§z/g, "") 23 | : `${name.slice(0, 3)}`.toUpperCase().removeFormatting()?.replace(/§z/g, "") 24 | 25 | const result = ultimate || config().enchantedBookAbbreviationColor 26 | ? `${format}${str}` 27 | : str 28 | 29 | cache.set(`${format}${name}`, result) 30 | 31 | return result 32 | } 33 | 34 | const getLevel = (roman) => { 35 | if (cacheNumber.has(roman)) return cacheNumber.get(roman) 36 | 37 | const result = TextHelper.decodeNumeral(roman) 38 | cacheNumber.set(roman, result) 39 | 40 | return result 41 | } 42 | 43 | new Feature("enchantedBookLevel") 44 | .addEvent( 45 | new Event("renderSlot", (/** @type {Slot} */slot) => { 46 | const item = slot.getItem() 47 | if (!item || item.getID() !== 403) return 48 | 49 | const lore = item.getLore() 50 | if (!lore || lore?.[0]?.removeFormatting() !== "Enchanted Book") return 51 | 52 | const enchantName = lore[1] 53 | const match = enchantName.match(formattedRegex) 54 | if (!match) return 55 | 56 | const level = getLevel(match[3]) 57 | if (level == null) return 58 | 59 | Tessellator.pushMatrix() 60 | Tessellator.disableLighting() 61 | 62 | if (config().enchantedBookAbbreviation) { 63 | Renderer.translate(slot.getDisplayX(), slot.getDisplayY(), 280) 64 | Renderer.scale(0.9) 65 | Renderer.drawStringWithShadow(getName(match[1], match[2]), 0, 0) 66 | } 67 | 68 | Renderer.translate(slot.getDisplayX() + (16 - Renderer.getStringWidth(level)), slot.getDisplayY() + 8, 280) 69 | Renderer.drawStringWithShadow(level, 0, 0) 70 | 71 | Tessellator.enableLighting() 72 | Tessellator.popMatrix() 73 | }) 74 | ) -------------------------------------------------------------------------------- /features/misc/EquipmentDisplay.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { InventoryButton } from "../../shared/InventoryButton" 6 | import { Persistence } from "../../shared/Persistence" 7 | import { RenderHelper } from "../../shared/Render" 8 | import { TextHelper } from "../../shared/TextHelper" 9 | 10 | const equipmentSlots = [10, 19, 28, 37] 11 | const armorSlots = [5, 6, 7, 8] 12 | const barrier = new Item("minecraft:barrier") 13 | const buttons = new Map() 14 | 15 | let inEquipment = false 16 | let inInv = false 17 | 18 | const updateButtons = (feat) => { 19 | if (!feat) return 20 | 21 | const equipmentList = Persistence.data.equipments 22 | if (!equipmentList) return 23 | 24 | for (let idx = 0; idx < equipmentList.length; idx++) { 25 | let v = equipmentList[idx] 26 | if (!v) continue 27 | 28 | let [ texture, lore ] = v 29 | let inList = buttons.get(armorSlots[idx]) 30 | 31 | // If the [slot] is in the list and the [texture] matches 32 | // this [texture] we continue (meaning we don't re-add the same butotn) 33 | if (inList && inList.texture === texture) continue 34 | 35 | // Otherwise if the [slot] is in the list and the 36 | // [texture] doesn't match this [texture] delete the old instance 37 | if (inList && inList.texture !== texture) { 38 | inList.delete() 39 | } 40 | 41 | // Creating the button itself 42 | if (texture === "no") { 43 | // If the [texture] is [no] this means that the slot was empty 44 | // so we can just create the button with a [barrier] item 45 | new InventoryButton(armorSlots[idx], null, buttons) 46 | .setOffset(-27) 47 | .setCommand("equipment") 48 | .setItem(barrier) 49 | .setCheckFn(() => Client.currentGui.get() instanceof net.minecraft.client.gui.inventory.GuiInventory && config().equipmentsDisplay && inInv) 50 | 51 | continue 52 | } 53 | 54 | if (!texture) continue 55 | 56 | // Otherwise if the [texture] exists 57 | // we'll create the item via the [createItemByTexture] method 58 | // and add its lore as the hover value 59 | new InventoryButton(armorSlots[idx], null, buttons) 60 | .setOffset(-27) 61 | .setCommand("equipment") 62 | .createItemByTexture(texture) 63 | .setCheckFn(() => Client.currentGui.get() instanceof net.minecraft.client.gui.inventory.GuiInventory && config().equipmentsDisplay && inInv) 64 | .onMouseHover((mx, my) => { 65 | RenderHelper.drawHoveringText(lore, mx, my) 66 | }) 67 | } 68 | 69 | // Updating our [Feature] instance 70 | // due to it having [SubEvents] that depend on this function adding/removing 71 | // items to the list/map 72 | feat.update() 73 | } 74 | 75 | const feat = new Feature("equipmentsDisplay") 76 | .addEvent( 77 | new Event(EventEnums.PACKET.SERVER.WINDOWOPEN, (title) => { 78 | inEquipment = title === "Your Equipment and Stats" 79 | feat.update() 80 | }) 81 | ) 82 | .addEvent( 83 | new Event("guiOpened", (event) => { 84 | inInv = event.gui instanceof net.minecraft.client.gui.inventory.GuiInventory 85 | feat.update() 86 | }) 87 | ) 88 | .addEvent( 89 | new Event(EventEnums.PACKET.CUSTOM.WINDOWCLOSE, () => { 90 | inInv = false 91 | feat.update() 92 | }) 93 | ) 94 | .addSubEvent( 95 | new Event(EventEnums.PACKET.SERVER.WINDOWITEMS, (items) => { 96 | for (let idx = 0; idx < equipmentSlots.length; idx++) { 97 | let slot = equipmentSlots[idx] 98 | let mcItem = items[slot] 99 | if (!mcItem) continue 100 | let item = new Item(mcItem) 101 | 102 | if (item.getName().removeFormatting() === "Empty Equipment Slot") { 103 | Persistence.data.equipments[idx] = ["no"] 104 | continue 105 | } 106 | 107 | let texture = item.getNBT()?.toObject()?.tag?.SkullOwner?.Properties?.textures?.[0]?.Value 108 | if (!texture) { 109 | ChatLib.chat(`${TextHelper.PREFIX} &cError while attempting to get texture data for item&f: ${item.getName()}`) 110 | continue 111 | } 112 | 113 | Persistence.data.equipments[idx] = [texture, item.getLore().map(it => it)] 114 | } 115 | 116 | Persistence.data.save() 117 | // Call the method to update equipments being displayed 118 | updateButtons(feat) 119 | 120 | inEquipment = false 121 | feat.update() 122 | }), 123 | () => inEquipment 124 | ) 125 | .addSubEvent( 126 | new Event("guiRender", () => { 127 | buttons.forEach(it => it.draw()) 128 | }), 129 | () => buttons.size && inInv 130 | ) 131 | .onRegister(() => { 132 | // Update the initial button list from cache (if it exists) 133 | updateButtons(feat) 134 | }) 135 | .onUnregister(() => { 136 | inInv = false 137 | inEquipment = false 138 | buttons.forEach(v => v.delete()) 139 | buttons.clear() 140 | }) -------------------------------------------------------------------------------- /features/misc/EtherwarpOverlay.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import EtherwarpHelper from "../../shared/EtherwarpHelper" 6 | import { RenderHelper } from "../../shared/Render" 7 | import { TextHelper } from "../../shared/TextHelper" 8 | 9 | const allowedID = new Set(["ASPECT_OF_THE_END", "ASPECT_OF_THE_VOID", "ETHERWARP_CONDUIT"]) 10 | 11 | let holdingAotv = false 12 | 13 | const feat = new Feature("etherwarpOverlay") 14 | .addEvent( 15 | new Event(EventEnums.PACKET.CLIENT.HELDITEMCHANGE, () => { 16 | holdingAotv = allowedID.has(TextHelper.getSkyblockItemID(Player.getHeldItem())) 17 | 18 | feat.update() 19 | }) 20 | ) 21 | .addSubEvent( 22 | new Event("renderWorld", () => { 23 | if (!Player.isSneaking()) return 24 | 25 | const [ status, block ] = EtherwarpHelper.getEtherwarpBlockSuccess(61) 26 | if (!status) return 27 | 28 | const [ r, g, b, a ] = [ 29 | config().etherwarpOverlayColor[0], 30 | config().etherwarpOverlayColor[1], 31 | config().etherwarpOverlayColor[2], 32 | config().etherwarpOverlayColor[3], 33 | ] 34 | 35 | RenderHelper.outlineBlock(block, r, g, b, a, true) 36 | RenderHelper.filledBlock(block, r, g, b, 50, true) 37 | }), 38 | () => holdingAotv 39 | ) -------------------------------------------------------------------------------- /features/misc/FactoryHelper.js: -------------------------------------------------------------------------------- 1 | import { scheduleTask } from "../../core/CustomRegisters" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { RenderHelper } from "../../shared/Render" 6 | import { TextHelper } from "../../shared/TextHelper" 7 | 8 | const rabbitSlots = [ 28, 29, 30, 31, 32, 33, 34 ] 9 | const chocolateCostRegex = /Cost ([\d,.]+) Chocolate/ 10 | const employedRabbitRegex = /^Rabbit \w+ - \[\d+\] \w+$/ 11 | const rabbitUpgradeRegex = /^Rabbit \w+ has been promoted to \[\d+\] \w+\!$/ 12 | const notEnoughChocoRegex = /^You don\'t have enough Chocolate\!$/ 13 | 14 | let renderIdx = null 15 | let inFactory = false 16 | 17 | const feat = new Feature("rabbitHelper") 18 | .addEvent( 19 | new Event(EventEnums.PACKET.SERVER.WINDOWOPEN, (title) => { 20 | scheduleTask(() => { 21 | inFactory = title === "Chocolate Factory" 22 | feat.update() 23 | }, 2) 24 | }) 25 | ) 26 | .addEvent( 27 | new Event(EventEnums.PACKET.CUSTOM.WINDOWCLOSE, () => { 28 | inFactory = false 29 | renderIdx = null 30 | feat.update() 31 | }) 32 | ) 33 | .addEvent( 34 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 35 | renderIdx = null 36 | inFactory = true 37 | feat.update() 38 | }, rabbitUpgradeRegex) 39 | ) 40 | .addEvent( 41 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 42 | renderIdx = null 43 | inFactory = true 44 | feat.update() 45 | }, notEnoughChocoRegex) 46 | ) 47 | .addSubEvent( 48 | new Event(EventEnums.PACKET.CUSTOM.TICK, () => { 49 | const container = Player.getContainer() 50 | const items = container.getItems() 51 | 52 | renderIdx = null 53 | 54 | const chocolatePurse = parseFloat(items?.[13]?.getName()?.removeFormatting()?.replace(/[,.]/g, "")) 55 | if (chocolatePurse == null) 56 | return ChatLib.chat(`${TextHelper.PREFIX} &clooks like we couldn't find the chocolates you currently have.`) 57 | if (!chocolatePurse) return 58 | 59 | const rabbitData = rabbitSlots.map((it, idx) => { 60 | const item = items[it] 61 | if (!item || item.getID() !== 397 || !employedRabbitRegex.test(item.getName().removeFormatting())) return 62 | 63 | const chocoCost = parseFloat(item.getLore().map(lore => lore.removeFormatting()).join(" ").match(chocolateCostRegex)?.[1]?.replace(/[,.]/g, "")) 64 | const costPerCPS = chocoCost / (idx + 1) 65 | 66 | return { 67 | idx: it, 68 | costPerCPS, 69 | canBuy: chocoCost < chocolatePurse 70 | } 71 | }) 72 | 73 | const mostEfficient = rabbitData.reduce((a, b) => { 74 | if (!("idx" in a) && b?.canBuy) return a = b 75 | if (!("idx" in a)) return a // if we still cannot buy it we keep returning 76 | 77 | if (b?.costPerCPS > a?.costPerCPS || !b?.canBuy) return a 78 | 79 | a = b 80 | 81 | return a 82 | }, {}) 83 | 84 | renderIdx = mostEfficient?.idx 85 | inFactory = false 86 | feat.update() 87 | }), 88 | () => inFactory && Player.getContainer().getName() === "Chocolate Factory" 89 | ) 90 | .addSubEvent( 91 | new Event("renderOverlay", () => { 92 | if (!Client.isInGui()) return 93 | 94 | let pos = RenderHelper.getSlotRenderPosition(renderIdx) 95 | if (!pos || pos?.[0] == null) return 96 | 97 | const [ x, y ] = pos 98 | 99 | Renderer.translate(0, 0, 100) 100 | Renderer.drawRect( 101 | Renderer.GREEN, 102 | x, 103 | y, 104 | 16, 105 | 16 106 | ) 107 | }), 108 | () => renderIdx 109 | ) 110 | .onUnregister(() => { 111 | renderIdx = null 112 | inFactory = false 113 | }) -------------------------------------------------------------------------------- /features/misc/HideEmptyTooltip.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | 4 | new Feature("hideEmptyTooltip") 5 | .addEvent( 6 | new Event("itemTooltip", (lore, _, event) => { 7 | if (lore.length > 1 || lore[0].trim() !== "§o §r") return 8 | 9 | cancel(event) 10 | }) 11 | ) -------------------------------------------------------------------------------- /features/misc/ItemRarity.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import { TextHelper } from "../../shared/TextHelper" 5 | 6 | const itemRegex = /^(?:§f§f)?(?:§\w(?:§\w)?(?:(?:✿|BUY|SELL|\[Lvl \d+\]) ))?(?:§\w\[.+\] )?(?:§\w+ (§\w)Rift Necklace$)?(§\w)?/ 7 | const colors = { 8 | "§f": [255, 255, 255], 9 | "§a": [77, 231, 77], 10 | "§9": [85, 85, 255], 11 | "§5": [151, 0, 151], 12 | "§6": [255, 170, 0], 13 | "§d": [255, 85, 255], 14 | "§b": [85, 255, 255], 15 | "§c": [255, 85, 85], 16 | "§4": [170, 0, 0] 17 | } 18 | 19 | new Feature("renderItemRarity") 20 | .addEvent( 21 | new Event("renderSlot", (slot) => { 22 | const item = slot.getItem() 23 | if (!item || !TextHelper.getSkyblockItemID(item)) return 24 | 25 | const itemName = item.getName() 26 | const match = itemName.match(itemRegex) 27 | const format = match?.[1] ?? match?.[2] 28 | 29 | if (!match || !(format in colors)) return 30 | 31 | const [ r, g, b ] = colors[format] 32 | const color = Renderer.color(r, g, b, Math.floor(config().renderItemRarityOpacity * 255)) 33 | const [ x1, y1, x2, y2 ] = [ 34 | slot.getDisplayX(), 35 | slot.getDisplayY(), 36 | slot.getDisplayX() + 16, 37 | slot.getDisplayY() + 16, 38 | ] 39 | 40 | Tessellator.pushMatrix() 41 | Tessellator.disableLighting() 42 | Tessellator.enableBlend() 43 | Tessellator.enableAlpha() 44 | 45 | if (config().renderItemRarityShape === 0) { 46 | // Top line 47 | Renderer.drawLine(color, x1, y1, x2, y1, 1.5) 48 | // Left line 49 | Renderer.drawLine(color, x1, y1, x1, y2, 1.5) 50 | // Right line 51 | Renderer.drawLine(color, x2, y1, x2, y2, 1.5) 52 | // Bottom line 53 | Renderer.drawLine(color, x1, y2, x2, y2, 1.5) 54 | } 55 | 56 | if (config().renderItemRarityShape === 1) 57 | Renderer.drawRect(color, x1, y1, 16, 16) 58 | 59 | if (config().renderItemRarityShape === 2) { 60 | // Top line 61 | Renderer.drawLine(color, x1, y1, x2, y1, 1.5) 62 | // Left line 63 | Renderer.drawLine(color, x1, y1, x1, y2, 1.5) 64 | // Right line 65 | Renderer.drawLine(color, x2, y1, x2, y2, 1.5) 66 | // Bottom line 67 | Renderer.drawLine(color, x1, y2, x2, y2, 1.5) 68 | 69 | Renderer.drawRect(color, x1, y1, 16, 16) 70 | } 71 | 72 | // Ternary just for pain 73 | if (config().renderItemRarityShape >= 3) 74 | Renderer.drawCircle(color, x1 + 8, y1 + 8, 9, config().renderItemRarityShape === 3 ? 6 : 10) 75 | 76 | Tessellator.enableLighting() 77 | Tessellator.disableAlpha() 78 | Tessellator.disableBlend() 79 | Tessellator.popMatrix() 80 | }) 81 | ) -------------------------------------------------------------------------------- /features/misc/MiddleClickGuis.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | import { TextHelper } from "../../shared/TextHelper" 4 | 5 | // Credits: https://github.com/Skytils/SkytilsMod/blob/1.x/src/main/kotlin/gg/skytils/skytilsmod/features/impl/misc/MiscFeatures.kt#L428 6 | 7 | const avoidGuis = [ 8 | "Wardrobe", 9 | "Drill Anvil", 10 | "Anvil", 11 | "Storage", 12 | "The Hex", 13 | "Composter", 14 | "Auctions", 15 | "Abiphone" 16 | ] 17 | const avoidGuisNeu = [ 18 | "Chronomatron (", 19 | "Ultrasequencer (", 20 | "Superpairs (" 21 | ] 22 | const hasneu = net.minecraftforge.fml.common.Loader.isModLoaded("notenoughupdates") 23 | 24 | new Feature("middleClickGui") 25 | .addEvent( 26 | new Event("guiMouseClick", (_, __, mbtn, gui, event) => { 27 | if (mbtn !== 0 || !(gui instanceof net.minecraft.client.gui.inventory.GuiChest)) return 28 | 29 | const container = Player.getContainer() 30 | const containerSize = container.getSize() - 36 31 | 32 | const slot = gui.getSlotUnderMouse()?.field_75222_d 33 | if (!slot || slot >= containerSize || avoidGuis.some(it => container.getName()?.startsWith(it))) return 34 | if (hasneu && avoidGuisNeu.some(it => container.getName()?.startsWith(it))) return 35 | 36 | const item = container.getItems()[slot] 37 | if ( 38 | item?.getName()?.removeFormatting() === "Reforge Item" || 39 | item?.getName()?.removeFormatting() === "Salvage Item" 40 | ) return 41 | if (TextHelper.getSkyblockItemID(item)) return 42 | 43 | cancel(event) 44 | container.click(slot, false, "MIDDLE") 45 | }) 46 | ) -------------------------------------------------------------------------------- /features/misc/NoCursorReset.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { TextHelper } from "../../shared/TextHelper" 6 | 7 | // Credits: https://github.com/Skytils/SkytilsMod/blob/1.x/src/main/kotlin/gg/skytils/skytilsmod/mixins/hooks/util/MouseHelperHook.kt 8 | 9 | const blacklistedMods = ["skytils", "skyblockaddons"] 10 | const hasBlacklisted = blacklistedMods.some(it => net.minecraftforge.fml.common.Loader.isModLoaded(it)) 11 | const Mouse = Java.type("org.lwjgl.input.Mouse") 12 | const GLDisplay = Java.type("org.lwjgl.opengl.Display") 13 | 14 | // Saving the original [mouseHelper] to re-set it once the module is unloaded 15 | let originalMouseHelper = Client.getMinecraft().field_71417_B // mouseHelper 16 | let windowClosed = null 17 | 18 | if (!hasBlacklisted) { 19 | // Set the mc instance's [mouseHelper] to a custom made one 20 | // which will have a check before [ungrabMouseCursor] calls [setCursorPosition] 21 | Client.getMinecraft().field_71417_B = new JavaAdapter(net.minecraft.util.MouseHelper, { 22 | // ungrabMouseCursor() 23 | func_74373_b() { 24 | // If no cursor reset is set to false 25 | // we implement the normal behavior 26 | if (!config().noCursorReset) { 27 | Mouse.setCursorPosition(GLDisplay.getWidth() / 2, GLDisplay.getHeight() / 2) 28 | Mouse.setGrabbed(false) 29 | 30 | return 31 | } 32 | 33 | // Check whether we should re-set the position of the cursor or not 34 | // by checking if the time between [windowClosed] and [now] (whenever this method is called) 35 | // is below 100ms 36 | if (!Client.isInGui() || !windowClosed || (Date.now() - windowClosed) > 100) { 37 | Mouse.setCursorPosition(GLDisplay.getWidth() / 2, GLDisplay.getHeight() / 2) 38 | windowClosed = null 39 | } 40 | 41 | Mouse.setGrabbed(false) 42 | } 43 | }) 44 | } 45 | 46 | config().getConfig().registerListener("noCursorReset", (_, v) => { 47 | if (!v || !hasBlacklisted) return 48 | 49 | ChatLib.chat(`${TextHelper.PREFIX} &cFeature &7[&6NoCursorReset&7]&c was enabled while having blacklisted mods meaning this feature will not work because you have either &6Skytils &cor &6SkyblockAddons&r`) 50 | }) 51 | 52 | new Feature("noCursorReset") 53 | .addEvent( 54 | new Event(EventEnums.PACKET.SERVER.WINDOWCLOSE, () => { 55 | windowClosed = Date.now() 56 | }) 57 | ) 58 | 59 | register("gameUnload", () => { 60 | if (hasBlacklisted) return 61 | 62 | // field_71417_B - mouseHelper 63 | Client.getMinecraft().field_71417_B = originalMouseHelper 64 | windowClosed = null 65 | }) -------------------------------------------------------------------------------- /features/misc/NoDeathAnimation.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | 4 | // Credits: https://github.com/UnclaimedBloom6/BloomModule/blob/main/Bloom/features/NoDeathAnimation.js 5 | // straight up copy and paste ^ 6 | 7 | const lividRegex = /^\w+ Livid$/ 8 | 9 | new Feature("noDeathAnimation") 10 | .addEvent( 11 | new Event("entityDeath", (entity) => { 12 | if (lividRegex.test(entity?.getName()?.removeFormatting())) return 13 | 14 | entity.getEntity().func_70106_y() 15 | }) 16 | ) -------------------------------------------------------------------------------- /features/misc/NoEndermanTeleport.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | 4 | const feat = new Feature("noEndermanTeleport") 5 | .addEvent( 6 | new Event(net.minecraftforge.event.entity.living.EnderTeleportEvent, (event) => { 7 | cancel(event) 8 | }) 9 | ) -------------------------------------------------------------------------------- /features/misc/NoLightning.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | 5 | // Credits: https://github.com/UnclaimedBloom6/BloomModule/blob/main/Bloom/features/HideLightning.js 6 | // straight up copy and paste ^ 7 | 8 | new Feature("noLightning") 9 | .addEvent( 10 | new Event(EventEnums.RENDERENTITY, (_, __, ___, event) => { 11 | cancel(event) 12 | }, net.minecraft.entity.effect.EntityLightningBolt) 13 | ) -------------------------------------------------------------------------------- /features/misc/PhoenixInvincibility.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("phoenixInvincibilityTimer").setCommandName("editphoenixInvincibilityTimer") 7 | const procRegex = /^Your Phoenix Pet saved you from certain death\!$/ 8 | 9 | let proc = null 10 | 11 | editGui.onDraw(() => { 12 | Renderer.translate(editGui.getX(), editGui.getY()) 13 | Renderer.scale(editGui.getScale()) 14 | Renderer.drawStringWithShadow("&cPhoenix Pet&f: &a0.00s", 0, 0) 15 | Renderer.finishDraw() 16 | }) 17 | 18 | const feat = new Feature("phoenixInvincibilityTimer") 19 | .addEvent( 20 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 21 | proc = 80 22 | 23 | feat.update() 24 | }, procRegex) 25 | ) 26 | .addSubEvent( 27 | new Event(EventEnums.PACKET.CUSTOM.TICK, () => { 28 | if (proc === 0) { 29 | proc = null 30 | feat.update() 31 | 32 | return 33 | } 34 | 35 | proc-- 36 | }), 37 | () => proc 38 | ) 39 | .addSubEvent( 40 | new Event("renderOverlay", () => { 41 | if (editGui.isOpen()) return 42 | 43 | const timeRemaining = (proc * 0.05).toFixed(2) 44 | 45 | Renderer.translate(editGui.getX(), editGui.getY()) 46 | Renderer.scale(editGui.getScale()) 47 | Renderer.drawStringWithShadow(`&cPhoenix Pet&f: &a${timeRemaining}s`, 0, 0) 48 | Renderer.finishDraw() 49 | }), 50 | () => proc 51 | ) 52 | .onUnregister(() => proc = null) -------------------------------------------------------------------------------- /features/misc/QuiverDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | import { Persistence } from "../../shared/Persistence" 6 | 7 | const editGui = new DraggableGui("quiverDisplay").setCommandName("editquiverdisplay") 8 | const MCItem = net.minecraft.item.Item 9 | 10 | editGui.onDraw(() => { 11 | Renderer.translate(editGui.getX(), editGui.getY()) 12 | Renderer.scale(editGui.getScale()) 13 | Renderer.drawStringWithShadow("&9Explosive Arrow &7(&e2329&7)", 0, 0) 14 | Renderer.finishDraw() 15 | }) 16 | 17 | const feat = new Feature("quiverDisplay") 18 | .addEvent( 19 | new Event(EventEnums.PACKET.SERVER.SETSLOT, (slotId, itemStack) => { 20 | if (slotId !== 44 || !itemStack) return 21 | const id = MCItem./* getIdFromItem */func_150891_b(itemStack./* getItem */func_77973_b()) 22 | if (id !== 288) return 23 | 24 | const item = new Item(itemStack) 25 | const arrowsLore = item.getLore()?.[5] 26 | if (!arrowsLore || !arrowsLore.includes("Active Arrow")) return 27 | // Lazy good 28 | Persistence.data.quiverArrows = arrowsLore.replace("§5§o§7Active Arrow: ", "") 29 | 30 | feat.update() 31 | }) 32 | ) 33 | .addEvent( 34 | new Event("renderOverlay", () => { 35 | if (editGui.isOpen() || !Persistence.data.quiverArrows) return 36 | 37 | Renderer.translate(editGui.getX(), editGui.getY()) 38 | Renderer.scale(editGui.getScale()) 39 | Renderer.drawStringWithShadow(Persistence.data.quiverArrows, 0, 0) 40 | Renderer.finishDraw() 41 | }) 42 | ) -------------------------------------------------------------------------------- /features/misc/RagnarokAxeCooldown.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("ragaxecd").setCommandName("editragaxecd") 7 | const ragaxeRegex = /^[\d,]+\/[\d,]+❤ [\d,]+❈ Defense CASTING IN 3s/ 8 | const ragaxeCancelRegex = /^Ragnarock was cancelled due to taking damage!$/ 9 | 10 | let castcancel = null 11 | let ticks = null 12 | 13 | editGui.onDraw(() => { 14 | Renderer.translate(editGui.getX(), editGui.getY()) 15 | Renderer.scale(editGui.getScale()) 16 | Renderer.drawStringWithShadow("&aAxe Cooldown&f: &a0.00s", 0, 0) 17 | Renderer.finishDraw() 18 | }) 19 | 20 | const feat = new Feature("ragnarokAxeTimer") 21 | .addEvent( 22 | new Event(EventEnums.PACKET.SERVER.ACTIONBAR, () => { 23 | if (castcancel && Date.now() - castcancel < 5000) return 24 | // TODO: add the check for mage and change the timer to their ability cd 25 | ticks = 400 26 | feat.update() 27 | }, ragaxeRegex) 28 | ) 29 | .addSubEvent( 30 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 31 | ticks = null 32 | castcancel = Date.now() 33 | feat.update() 34 | }, ragaxeCancelRegex), 35 | () => ticks 36 | ) 37 | .addSubEvent( 38 | new Event(EventEnums.PACKET.CUSTOM.TICK, () => { 39 | if (ticks === 0) { 40 | ticks = null 41 | feat.update() 42 | 43 | return 44 | } 45 | 46 | ticks-- 47 | }), 48 | () => ticks 49 | ) 50 | .addSubEvent( 51 | new Event("renderOverlay", () => { 52 | if (editGui.isOpen()) return 53 | 54 | const timeRemaining = (ticks * 0.05).toFixed(2) 55 | const format = ticks > 50 ? "&c" : "&a" 56 | 57 | Renderer.translate(editGui.getX(), editGui.getY()) 58 | Renderer.scale(editGui.getScale()) 59 | Renderer.drawStringWithShadow(`&aAxe Cooldown&f: ${format}${timeRemaining}`, 0, 0) 60 | Renderer.finishDraw() 61 | }), 62 | () => ticks 63 | ) 64 | .onUnregister(() => { 65 | ticks = null 66 | castcancel = null 67 | }) -------------------------------------------------------------------------------- /features/misc/RemoveFrontView.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | 4 | new Feature("removeFrontView") 5 | .addEvent( 6 | new Event("renderOverlay", () => { 7 | if (Client.settings.getSettings().field_74320_O !== 2) return 8 | 9 | Client.settings.getSettings().field_74320_O = 0 10 | }) 11 | ) -------------------------------------------------------------------------------- /features/misc/RenderItems.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { scheduleTask } from "../../core/CustomRegisters" 3 | import { Event } from "../../core/Event" 4 | import EventEnums from "../../core/EventEnums" 5 | import Feature from "../../core/Feature" 6 | import { Render3D } from "../../../tska/rendering/Render3D" 7 | import Location from "../../../tska/skyblock/Location" 8 | 9 | const entities = new HashMap() 10 | const itemRegex = /^(?:§f§f)?(?:§\w(?:§\w)?(?:(?:✿|BUY|SELL|\[Lvl \d+\]) ))?(?:§\w\[.+\] )?(?:§\w+ (§\w)Rift Necklace$)?(§\w)?/ 11 | const colors = { 12 | "§f": [255, 255, 255], 13 | "§a": [77, 231, 77], 14 | "§9": [85, 85, 255], 15 | "§5": [151, 0, 151], 16 | "§6": [255, 170, 0], 17 | "§d": [255, 85, 255], 18 | "§b": [85, 255, 255], 19 | "§c": [255, 85, 85], 20 | "§4": [170, 0, 0] 21 | } 22 | 23 | const feat = new Feature("renderItems") 24 | .addEvent( 25 | new Event(EventEnums.FORGE.ENTITYJOIN, (mcEntity) => { 26 | scheduleTask(() => { 27 | const mcItem = mcEntity./* getEntityItem */func_92059_d() 28 | const name = mcItem./* getDisplayName */func_82833_r() 29 | if (!name.startsWith("§")) return 30 | 31 | const m = name.match(itemRegex) 32 | let rarity = m?.[1] ?? m?.[2] 33 | if (!rarity in colors) rarity = "§f" 34 | 35 | entities.put(mcEntity, { 36 | color: (colors[rarity] || [255, 255, 255]), 37 | displayStr: `&ax${mcItem./* stackSize */field_77994_a} &f${config().renderItemsName === 1 ? name.removeFormatting() : name}` 38 | }) 39 | feat.update() 40 | }) 41 | }, net.minecraft.entity.item.EntityItem) 42 | ) 43 | .addSubEvent( 44 | new Event(EventEnums.POSTRENDERENTITY, (/** @type {Entity} */entity, /** @type {Vec3i}*/pos) => { 45 | const obj = entities.get(entity.entity) 46 | if (!obj) return 47 | if (entity.isDead()) return entities.remove(entity.entity) 48 | 49 | const [ x, y, z, width, height ] = [ 50 | pos.x, pos.y + (entity.getHeight() / 2), pos.z, 51 | entity.getWidth(), entity.getHeight() 52 | ] 53 | 54 | if (config().renderItemsMode === 1 || config().renderItemsMode === 2 && Location.inWorld("catacombs")) { 55 | const distance = entity.distanceTo(Player.getPlayer()) 56 | obj.color = distance < 3.5 ? [ 0, 255, 0 ] : [ 255, 0, 0 ] 57 | } 58 | 59 | const [ r, g, b ] = obj.color 60 | const displayText = obj.displayStr 61 | 62 | Render3D.renderEntityBox(x, y, z, width, height, r, g, b, 255, 2, true, false) 63 | Render3D.renderEntityBoxFilled(x, y, z, width, height, r, g, b, 150, true, false) 64 | 65 | if (config().renderItemsName !== 0) { 66 | const [ rx, ry, rz ] = Render3D.lerpViewEntity() 67 | Render3D.renderString(displayText, x + rx, y + 0.8 + ry, z + rz, [], false, 0.05, false) 68 | } 69 | }, net.minecraft.entity.item.EntityItem), 70 | () => entities.size() 71 | ) 72 | .onUnregister(() => { 73 | entities.clear() 74 | }) -------------------------------------------------------------------------------- /features/misc/SmolderingPolarizationDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | import { Persistence } from "../../shared/Persistence" 6 | import { TextHelper } from "../../shared/TextHelper" 7 | 8 | const editGui = new DraggableGui("smolderingPolarization").setCommandName("editsmolderingpolarization") 9 | 10 | let registerTime = null 11 | let inEffects = false 12 | 13 | editGui.onDraw(() => { 14 | Renderer.translate(editGui.getX(), editGui.getY()) 15 | Renderer.scale(editGui.getScale()) 16 | Renderer.drawStringWithShadow(`&aSmoldering Polarization&f: &b59m 59s`, 0, 0) 17 | Renderer.finishDraw() 18 | }) 19 | 20 | const formatTime = (time) => { 21 | const hours = Math.floor(time / 3600) 22 | const minutes = Math.floor((time % 3600) / 60) 23 | const seconds = Math.floor(time % 60) 24 | 25 | if (hours <= 0 && minutes > 0) return `${minutes}m ${seconds}s` 26 | if (hours <= 0 && minutes <= 0) return `&c${seconds}s` 27 | return `${hours}hr ${minutes}m ${seconds}s` 28 | } 29 | 30 | const feat = new Feature("smolderingPolarizationDisplay") 31 | .addEvent( 32 | new Event(/*EventEnums.PACKET.SERVER.CHAT*/ EventEnums.CHAT, () => { 33 | Persistence.data.smolderingPolarization.time += 3600000 34 | registerTime = Date.now() + (Persistence.data.smolderingPolarization.time || 1) 35 | feat.update() 36 | }, /^You ate a Re\-heated Gummy Polar Bear\!$/) 37 | ) 38 | .addEvent( 39 | new Event(EventEnums.PACKET.SERVER.WINDOWOPEN, (name) => { 40 | inEffects = /^(?:\(\d+\/\d+\) )?Active Effects$/.test(name) 41 | feat.update() 42 | }) 43 | ) 44 | .addEvent( 45 | new Event(EventEnums.PACKET.CUSTOM.WINDOWCLOSE, () => { 46 | inEffects = false 47 | feat.update() 48 | }) 49 | ) 50 | .addSubEvent( 51 | new Event(EventEnums.PACKET.SERVER.WINDOWITEMS, (mcItems) => { 52 | for (let idx = 0; idx < mcItems.length; idx++) { 53 | if (idx > 53) break 54 | let mcItem = mcItems[idx] 55 | if (!mcItem) continue 56 | 57 | let name = mcItem./* getDisplayName */func_82833_r() 58 | if (name?.removeFormatting() !== "Smoldering Polarization I") continue 59 | 60 | let lore = mcItem./* getTooltip */func_82840_a(Player.getPlayer(), Client.getMinecraft()./* gameSettings */field_71474_y./* advancedItemTooltips */field_82882_x) 61 | let remainingTime = lore[6]?.removeFormatting() 62 | if (!remainingTime) continue 63 | 64 | let match = remainingTime.match(/^Remaining\: (\d+\:)?(\d+\:)?(\d+)?$/) 65 | if (!match) continue 66 | 67 | let [ _, hours, minutes, seconds ] = match 68 | if (hours && !minutes) { 69 | let total = 0 70 | let mins = +hours.replace(":", "") 71 | 72 | total += mins * 60000 73 | total += seconds * 1000 74 | Persistence.data.smolderingPolarization.time = total 75 | registerTime = Date.now() + (Persistence.data.smolderingPolarization.time || 1) 76 | ChatLib.chat(`${TextHelper.PREFIX} &bDetected Smoldering Polarization`) 77 | break 78 | } 79 | 80 | let total = 0 81 | let hrs = +hours.replace(":", "") 82 | let mins = +minutes.replace(":", "") 83 | total += hrs * 3600000 84 | total += mins * 60000 85 | total += seconds * 1000 86 | 87 | Persistence.data.smolderingPolarization.time = total 88 | registerTime = Date.now() + (Persistence.data.smolderingPolarization.time || 1) 89 | ChatLib.chat(`${TextHelper.PREFIX} &bDetected Smoldering Polarization`) 90 | break 91 | } 92 | 93 | inEffects = false 94 | feat.update() 95 | }), 96 | () => inEffects 97 | ) 98 | .addSubEvent( 99 | new Event("renderOverlay", () => { 100 | if (editGui.isOpen()) return 101 | 102 | const msTime = registerTime - Date.now() 103 | if (msTime < 0) { 104 | registerTime = null 105 | Persistence.data.smolderingPolarization.time = 0 106 | return feat.update() 107 | } 108 | 109 | Renderer.translate(editGui.getX(), editGui.getY()) 110 | Renderer.scale(editGui.getScale()) 111 | Renderer.drawStringWithShadow(`&aSmoldering Polarization&f: &b${formatTime((msTime) / 1000)}`, 0, 0) 112 | Renderer.finishDraw() 113 | }), 114 | () => registerTime && Persistence.data.smolderingPolarization.time > 0 115 | ) 116 | .onRegister(() => { 117 | registerTime = Date.now() + (Persistence.data.smolderingPolarization.time || 1) 118 | feat.update() 119 | }) 120 | .onUnregister(() => { 121 | inEffects = false 122 | if (!registerTime) return 123 | 124 | const nwTime = registerTime - Date.now() 125 | Persistence.data.smolderingPolarization.time = nwTime < 0 ? 0 : nwTime 126 | 127 | registerTime = null 128 | }) -------------------------------------------------------------------------------- /features/misc/SystemTimeDisplay.js: -------------------------------------------------------------------------------- 1 | import config from "../../config" 2 | import { Event } from "../../core/Event" 3 | import Feature from "../../core/Feature" 4 | import DraggableGui from "../../shared/DraggableGui" 5 | 6 | const editGui = new DraggableGui("systemDisplay").setCommandName("editsystemDisplay") 7 | const colorList = ["&4", "&c", "&6", "&e", "&2", "&a", "&b", "&3", "&1", "&9", "&d", "&5", "&f", "&7", "&8", "&0"] 8 | 9 | editGui.onDraw(() => { 10 | Renderer.retainTransforms(true) 11 | Renderer.translate(editGui.getX(), editGui.getY()) 12 | Renderer.scale(editGui.getScale()) 13 | Renderer.drawStringWithShadow(`${colorList[config().systemTimeDisplayColor]}04:24:11 PM`, 0, 0) 14 | Renderer.retainTransforms(false) 15 | Renderer.finishDraw() 16 | }) 17 | 18 | const getTime = () => { 19 | const time = new Date(Date.now()) 20 | let seconds = time.getSeconds() 21 | let mins = time.getMinutes() 22 | let hours = time.getHours() 23 | hours = hours % 12 || 12 24 | 25 | seconds = seconds < 10 ? `0${seconds}` : seconds 26 | mins = mins < 10 ? `0${mins}` : mins 27 | hours = hours < 10 ? `0${hours}` : hours 28 | 29 | return `${hours}:${mins}:${seconds} ${time.getHours() >= 12 ? "PM" : "AM"}` 30 | } 31 | 32 | new Feature("systemTimeDisplay") 33 | .addEvent( 34 | new Event("renderOverlay", () => { 35 | if (editGui.isOpen()) return 36 | 37 | Renderer.retainTransforms(true) 38 | Renderer.translate(editGui.getX(), editGui.getY()) 39 | Renderer.scale(editGui.getScale()) 40 | Renderer.drawStringWithShadow(`${colorList[config().systemTimeDisplayColor]}${getTime()}`, 0, 0) 41 | Renderer.retainTransforms(false) 42 | Renderer.finishDraw() 43 | }) 44 | ) -------------------------------------------------------------------------------- /features/misc/ToggleSprint.js: -------------------------------------------------------------------------------- 1 | import { Keybind } from "../../../KeybindFix" 2 | import config from "../../config" 3 | import DraggableGui from "../../shared/DraggableGui" 4 | import { Persistence } from "../../shared/Persistence" 5 | import { TextHelper } from "../../shared/TextHelper" 6 | 7 | const toggleSprintKeybind = new Keybind("§fToggle Sprint", Keyboard.KEY_NONE, "Doc") 8 | const sprintKey = new KeyBind(Client.getMinecraft().field_71474_y.field_151444_V) 9 | const forwardKey = new KeyBind(Client.getMinecraft().field_71474_y.field_74351_w) 10 | const editGui = new DraggableGui("toggleSprintDisplay", config().toggleSprintText ?? "&bToggle Sprint&f: &aEnabled").setCommandName("edittoggleSprint") 11 | 12 | let hasEnabled = false 13 | 14 | editGui.onDraw(() => { 15 | Renderer.translate(editGui.getX(), editGui.getY()) 16 | Renderer.scale(editGui.getScale()) 17 | Renderer.drawStringWithShadow(config().toggleSprintText ?? "&bToggle Sprint&f: &aEnabled", 0, 0) 18 | Renderer.finishDraw() 19 | }) 20 | 21 | const renderOverlay = register("renderOverlay", () => { 22 | if (editGui.isOpen()) return 23 | 24 | Renderer.translate(editGui.getX(), editGui.getY()) 25 | Renderer.scale(editGui.getScale()) 26 | Renderer.drawStringWithShadow(config().toggleSprintText, 0, 0) 27 | Renderer.finishDraw() 28 | }).unregister() 29 | 30 | toggleSprintKeybind.registerKeyPress(() => { 31 | Persistence.data.toggleSprint = !Persistence.data.toggleSprint 32 | 33 | if (Persistence.data.toggleSprint && config().toggleSprintDisplay) renderOverlay.register() 34 | else if (config().toggleSprintDisplay) renderOverlay.unregister() 35 | 36 | ChatLib.chat(`${TextHelper.PREFIX} &bToggle Sprint&f: ${Persistence.data.toggleSprint ? "&aEnabled" : "&cDisabled"}`) 37 | }) 38 | 39 | forwardKey.registerKeyDown(() => { 40 | if (!Persistence.data.toggleSprint && hasEnabled) { 41 | sprintKey.setState(false) 42 | hasEnabled = false 43 | 44 | return 45 | } 46 | 47 | if (!World.isLoaded() || !Persistence.data.toggleSprint) return 48 | 49 | sprintKey.setState(true) 50 | hasEnabled = true 51 | }) -------------------------------------------------------------------------------- /features/misc/WorldAgeDisplay.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | import DraggableGui from "../../shared/DraggableGui" 4 | 5 | const editGui = new DraggableGui("worldAgeDisplay").setCommandName("editworldAgeDisplay") 6 | 7 | editGui.onDraw(() => { 8 | Renderer.retainTransforms(true) 9 | Renderer.translate(editGui.getX(), editGui.getY()) 10 | Renderer.scale(editGui.getScale()) 11 | Renderer.drawStringWithShadow("&bDay&f: &61.05", 0, 0) 12 | Renderer.retainTransforms(false) 13 | Renderer.finishDraw() 14 | }) 15 | 16 | new Feature("worldAgeDisplay") 17 | .addEvent( 18 | new Event("renderOverlay", () => { 19 | if (editGui.isOpen()) return 20 | 21 | Renderer.retainTransforms(true) 22 | Renderer.translate(editGui.getX(), editGui.getY()) 23 | Renderer.scale(editGui.getScale()) 24 | Renderer.drawStringWithShadow(`&bDay&f: &6${(World.getTime() / 24000).toFixed(2)}`, 0, 0) 25 | Renderer.retainTransforms(false) 26 | Renderer.finishDraw() 27 | }) 28 | ) -------------------------------------------------------------------------------- /features/rift/BoxBerberis.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums, { ParticleEnums } from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { RenderHelper } from "../../shared/Render" 5 | 6 | let block = null 7 | 8 | const feat = new Feature("boxBerberis", "the rift", "dreadfarm") 9 | .addEvent( 10 | new Event(EventEnums.PACKET.SERVER.SPAWNPARTICLE, (type, [x, y, z]) => { 11 | if (type !== ParticleEnums.FIREWORKS_SPARK || Math.hypot(Player.getX() - x, Player.getZ() - z) > 20) return 12 | 13 | const sideBlock = World.getBlockAt(x - 1, y, z - 1) 14 | const blockBelow = World.getBlockAt(x - 1, y - 1, z - 1) 15 | 16 | if ( 17 | sideBlock.type.mcBlock !== net.minecraft.init.Blocks.field_150330_I || 18 | blockBelow.type.mcBlock !== net.minecraft.init.Blocks.field_150458_ak 19 | ) return 20 | 21 | block = sideBlock 22 | feat.update() 23 | }) 24 | ) 25 | .addSubEvent( 26 | new Event("renderWorld", () => { 27 | RenderHelper.outlineFilledBlock(block, 0, 255, 255, 255, false) 28 | }), 29 | () => block 30 | ) 31 | .onUnregister(() => { 32 | block = null 33 | }) -------------------------------------------------------------------------------- /features/rift/EffigiesWaypoint.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import Location from "../../shared/Location" 5 | import { RenderHelper } from "../../shared/Render" 6 | 7 | const effigiesCoords = [ 8 | [ 150, 73, 95 ], 9 | [ 193, 87, 119 ], 10 | [ 235, 103, 147 ], 11 | [ 293, 90, 134 ], 12 | [ 262, 93, 94 ], 13 | [ 240, 123, 118 ] 14 | ] 15 | 16 | let waypoints = [] 17 | 18 | const feat = new Feature("effigiesWaypoint", "the rift") 19 | .addEvent( 20 | new Event(EventEnums.PACKET.SERVER.SCOREBOARD, (_, format) => { 21 | const m = format.match(/§(\w)⧯/g) 22 | 23 | for (let idx = 0; idx < m.length; idx++) { 24 | let it = m[idx]?.[1] 25 | if (!it) continue 26 | 27 | if (it === "c" && waypoints[idx]) { 28 | waypoints.splice(idx, 1) 29 | feat.update() 30 | continue 31 | } 32 | if (it === "c") continue 33 | 34 | waypoints[idx] = effigiesCoords[idx] 35 | } 36 | 37 | feat.update() 38 | }, /^Effigies: ⧯⧯⧯⧯⧯⧯$/) 39 | ) 40 | .addSubEvent( 41 | new Event("renderWorld", () => { 42 | for (let coord of waypoints) { 43 | if (!coord) continue 44 | let distance = Math.hypot(Player.getX() - coord[0], Player.getZ() - coord[2]) 45 | RenderHelper.renderWaypoint( 46 | `${distance.toFixed(2)}m`, 47 | coord[0], 48 | coord[1], 49 | coord[2], 50 | 0, 255, 255, 255 51 | ) 52 | } 53 | }), 54 | () => waypoints.length 55 | ) 56 | .onUnregister(() => { 57 | waypoints = [] 58 | }) 59 | 60 | Location.onAreaChange((areaName) => { 61 | if (areaName?.includes("stillgore")) return 62 | 63 | waypoints = [] 64 | feat.update() 65 | }) -------------------------------------------------------------------------------- /features/rift/GlyphRender.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | 4 | const glyphLocation = [ 5 | ["by the pond below", [-57.5, 70, -95.5]], 6 | ["in the windmill", [-77.5, 71, -148.5]], 7 | ["by the water tower", [-27.5, 72, -135.5]], 8 | ["in the barn", [-51.5, 71.5, -168.5]], 9 | ["next to a glutton", [-108.5, 70.5, -99.5]], 10 | ["at the top of the infested house's chimney", [-33.5, 86, -93.5]], 11 | ["in a lot of cake", [-94.5, 75.5, -85.5]], 12 | ["in a server room", [-57.5, 78.5, -14.5]] 13 | ] 14 | 15 | new Feature("glyphRender", "the rift", ["dreadfarm", "west village"]) 16 | .addEvent( 17 | new Event("renderWorld", () => { 18 | for (let it of glyphLocation) { 19 | let [ name, coord ] = it 20 | Tessellator.drawString(name, coord[0] + 0.5, coord[1], coord[2] + 0.5, Renderer.AQUA, false, .05, false) 21 | } 22 | }) 23 | ) -------------------------------------------------------------------------------- /features/rift/LavaMaze.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | import { Persistence } from "../../shared/Persistence" 4 | import { RenderHelper } from "../../shared/Render" 5 | 6 | const LavaMazeData = Persistence.getDataFromFileOrLink("LavaMaze.json", "https://raw.githubusercontent.com/DocilElm/Rift/main/Rift/data/LavaMaze.json") 7 | 8 | new Feature("lavaMazeRender", "the rift", "mirrorverse") 9 | .addEvent( 10 | new Event("renderWorld", () => { 11 | for (let coord of LavaMazeData) { 12 | RenderHelper.outlineFilledBlock( 13 | World.getBlockAt(coord[0], 51, coord[1]), 14 | 0, 255, 255, 255 15 | ) 16 | } 17 | }) 18 | ) -------------------------------------------------------------------------------- /features/rift/MushroomTimer.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { TextHelper } from "../../shared/TextHelper" 5 | 6 | let holding = false 7 | let ticks = 0 8 | let coord = null 9 | 10 | const feat = new Feature("mushroomTimer", "the rift", ["dreadfarm", "west village"]) 11 | .addEvent( 12 | new Event(EventEnums.PACKET.CLIENT.HELDITEMCHANGE, () => { 13 | holding = TextHelper.getSkyblockItemID(Player.getHeldItem()) === "FARMING_WAND" 14 | ticks = 0 15 | feat.update() 16 | }) 17 | ) 18 | .addSubEvent( 19 | new Event(EventEnums.PACKET.CUSTOM.TICK, () => { 20 | const lookingAt = Player.lookingAt() 21 | const lookingAtMushroom = lookingAt?.type?.mcBlock === net.minecraft.init.Blocks.field_150338_P 22 | if (!lookingAtMushroom) { 23 | ticks = 0 24 | feat.update() 25 | return 26 | } 27 | if (ticks && lookingAtMushroom) { 28 | ticks-- 29 | feat.update() 30 | return 31 | } 32 | 33 | ticks = 80 34 | coord = [ lookingAt.getX(), lookingAt.getY(), lookingAt.getZ() ] 35 | feat.update() 36 | }), 37 | () => holding 38 | ) 39 | .addSubEvent( 40 | new Event("renderWorld", () => { 41 | const time = ticks * 0.05 42 | const color = time > 1 ? Renderer.RED : Renderer.GREEN 43 | 44 | Tessellator.drawString(`${time.toFixed(2)}`, coord[0] + 0.5, coord[1] + 0.5, coord[2] + 0.5, color, false, 2) 45 | }), 46 | () => ticks 47 | ) 48 | .onUnregister(() => { 49 | holding = false 50 | ticks = 0 51 | }) -------------------------------------------------------------------------------- /features/rift/Tubulator.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import Feature from "../../core/Feature" 3 | import { Persistence } from "../../shared/Persistence" 4 | import { RenderHelper } from "../../shared/Render" 5 | 6 | const TubulatorData = Persistence.getDataFromFileOrLink("Tubulator.json", "https://raw.githubusercontent.com/DocilElm/Rift/main/Rift/data/Tubulator.json") 7 | 8 | new Feature("tubulatorRender", "the rift", "mirrorverse") 9 | .addEvent( 10 | new Event("renderWorld", () => { 11 | for (let coord of TubulatorData) { 12 | RenderHelper.outlineFilledBlock( 13 | World.getBlockAt(coord[0], coord[1], coord[2]), 14 | 0, 255, 255, 255 15 | ) 16 | } 17 | }) 18 | ) -------------------------------------------------------------------------------- /features/rift/WoodenButtons.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { addCommand } from "../../shared/Command" 5 | import { Persistence } from "../../shared/Persistence" 6 | import { RenderHelper } from "../../shared/Render" 7 | import { TextHelper } from "../../shared/TextHelper" 8 | 9 | const WoodenButtonsData = Persistence.getDataFromFileOrLink("WoodenButtons.json", "https://raw.githubusercontent.com/DocilElm/Rift/main/Rift/data/WoodenButtons.json") 10 | const waypoints = [[-67, 71, -122], [-86, 71, -129], [-115, 72, -103], [-90, 71, -111], [-83, 70, -85], [-106, 78, -95], [-46, 77, -91], [-42, 74, -85], [-33, 72, -85], [-89, 75, -75]] 11 | 12 | new Feature("woodenButtons", "the rift", ["dreadfarm", "west village"]) 13 | .addEvent( 14 | new Event("renderWorld", () => { 15 | for (let coord of waypoints) { 16 | RenderHelper.renderBeaconBeam(coord[0], coord[1], coord[2], 0, 255, 255, 255, true) 17 | } 18 | 19 | for (let idx = 0; idx < WoodenButtonsData.length; idx++) { 20 | let coord = WoodenButtonsData[idx] 21 | if (Persistence.data.clickedWoodenButtons.some(it => it === coord.toString())) continue 22 | 23 | Tessellator.drawString(idx + 1, coord[0] + 0.5, coord[1], coord[2] + 0.5, Renderer.GREEN, false, .05, false) 24 | } 25 | }) 26 | ) 27 | .addEvent( 28 | new Event(EventEnums.PACKET.CLIENT.BLOCKPLACEMENT, (_, coords) => { 29 | const idx = WoodenButtonsData.findIndex((it) => it.toString() === coords.toString()) 30 | if (idx === -1) return 31 | 32 | Persistence.data.clickedWoodenButtons.push(coords.toString()) 33 | }) 34 | ) 35 | 36 | addCommand("rsbtn", "Resets the buttons from WoodenButtons feature", () => { 37 | Persistence.data.clickedWoodenButtons = [] 38 | ChatLib.chat(`${TextHelper.PREFIX} &aRemoved all saved wooden buttons`) 39 | }) -------------------------------------------------------------------------------- /features/slayers/BossSlainTime.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { TextHelper } from "../../shared/TextHelper" 5 | 6 | let serverTicks = 0 7 | let spawnedAtTicks = 0 8 | let spawnedAtTime = null 9 | let killedAtTicks = null 10 | let killedAtTime = null 11 | 12 | const feat = new Feature("slayerBossTime") 13 | .addEvent( 14 | new Event(EventEnums.PACKET.CUSTOM.TICK, () => { 15 | serverTicks++ 16 | }) 17 | ) 18 | .addEvent( 19 | new Event(EventEnums.PACKET.SERVER.SCOREBOARD, () => { 20 | spawnedAtTicks = serverTicks 21 | spawnedAtTime = Date.now() 22 | feat.update() 23 | }, /^Slay the boss\!$/) 24 | ) 25 | .addSubEvent( 26 | new Event(EventEnums.PACKET.SERVER.SCOREBOARD, () => { 27 | killedAtTime = Date.now() 28 | killedAtTicks = serverTicks 29 | }, /^Boss slain\!$/), 30 | () => spawnedAtTime 31 | ) 32 | .addSubEvent( 33 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 34 | if (!killedAtTime) killedAtTime = Date.now() 35 | if (!killedAtTicks) killedAtTicks = serverTicks 36 | 37 | new TextComponent(`${TextHelper.PREFIX} &aBoss Slain! Real Time&f: &6${((killedAtTime - spawnedAtTime) / 1000).toFixed(2)}s &aServer Time&f: &6${((killedAtTicks - spawnedAtTicks) * 0.05).toFixed(2)}s`) 38 | .setHover("show_text", "&cServer time might not be 100% accurate") 39 | .chat() 40 | serverTicks = 0 41 | spawnedAtTicks = 0 42 | spawnedAtTime = null 43 | killedAtTime = null 44 | killedAtTicks = null 45 | feat.update() 46 | }, /^ SLAYER QUEST COMPLETE\!$/), 47 | () => spawnedAtTime 48 | ) 49 | .onUnregister(() => { 50 | serverTicks = 0 51 | spawnedAtTicks = 0 52 | spawnedAtTime = null 53 | killedAtTime = null 54 | killedAtTicks = null 55 | }) -------------------------------------------------------------------------------- /features/slayers/BossSpawnTime.js: -------------------------------------------------------------------------------- 1 | import { Event } from "../../core/Event" 2 | import EventEnums from "../../core/EventEnums" 3 | import Feature from "../../core/Feature" 4 | import { TextHelper } from "../../shared/TextHelper" 5 | 6 | let serverTicks = 0 7 | let startedAtTicks = 0 8 | let startedAtTime = Date.now() 9 | 10 | const feat = new Feature("slayerBossSpawnTime") 11 | .addEvent( 12 | new Event(EventEnums.PACKET.CUSTOM.TICK, () => { 13 | serverTicks++ 14 | }) 15 | ) 16 | .addEvent( 17 | new Event(EventEnums.PACKET.SERVER.CHAT, () => { 18 | startedAtTicks = serverTicks 19 | startedAtTime = Date.now() 20 | feat.update() 21 | }, /^ SLAYER QUEST STARTED!$/) 22 | ) 23 | .addSubEvent( 24 | new Event(EventEnums.PACKET.SERVER.SCOREBOARD, () => { 25 | new TextComponent(`${TextHelper.PREFIX} &aSlayer Boss Spawned! &aReal Time&f: &6${((Date.now() - startedAtTime) / 1000).toFixed(2)}s &aServer Time&f: &6${((serverTicks - startedAtTicks) * 0.05).toFixed(2)}s`) 26 | .setHover("show_text", "&cServer time might not be 100% accurate") 27 | .chat() 28 | 29 | serverTicks = 0 30 | startedAtTicks = 0 31 | startedAtTime = null 32 | feat.update() 33 | }, /^Slay the boss\!$/), 34 | () => startedAtTime 35 | ) 36 | .onUnregister(() => { 37 | serverTicks = 0 38 | startedAtTicks = 0 39 | startedAtTime = null 40 | }) -------------------------------------------------------------------------------- /features/slayers/SlayerBossDisplay.js: -------------------------------------------------------------------------------- 1 | import { scheduleTask } from "../../core/CustomRegisters" 2 | import { Event } from "../../core/Event" 3 | import EventEnums from "../../core/EventEnums" 4 | import Feature from "../../core/Feature" 5 | import { addCommand } from "../../shared/Command" 6 | import DraggableGui from "../../shared/DraggableGui" 7 | import { RenderHelper } from "../../shared/Render" 8 | import { TextHelper } from "../../shared/TextHelper" 9 | 10 | const editGui = new DraggableGui("slayerBossDisplay").setCommandName("editslayerbossdisplay") 11 | const spawnedByRegex = /^Spawned by: (\w+)$/ 12 | const entities = new HashMap() 13 | 14 | let currentBoss = { 15 | spawnedBy: null, 16 | timeEntity: null, 17 | hpEntity: null, 18 | id: null 19 | } 20 | let carryingUser = null 21 | 22 | const reset = () => { 23 | currentBoss = { 24 | spawnedBy: null, 25 | timeEntity: null, 26 | hpEntity: null, 27 | id: null 28 | } 29 | } 30 | 31 | editGui.onDraw(() => { 32 | Renderer.retainTransforms(true) 33 | Renderer.translate(editGui.getX(), editGui.getY()) 34 | Renderer.scale(editGui.getScale()) 35 | 36 | const time = "&c02:59" 37 | const spawnedBy = `&eSpawned by: &b${Player.getName()}` 38 | const name = "&c☠ &bVoidgloom Seraph IV &e64.2M&c❤" 39 | 40 | Renderer.drawStringWithShadow(time, Renderer.getStringWidth(spawnedBy.removeFormatting()) / 2, 0) 41 | Renderer.drawStringWithShadow(spawnedBy, 15, 10) 42 | Renderer.drawStringWithShadow(name, 0, 20) 43 | 44 | Renderer.retainTransforms(false) 45 | Renderer.finishDraw() 46 | }) 47 | 48 | const feat = new Feature("slayerBossDisplay") 49 | .addEvent( 50 | new Event(EventEnums.FORGE.ENTITYJOIN, (mcEntity, entityId) => { 51 | scheduleTask(() => { 52 | const entityName = mcEntity./* getName */func_70005_c_() 53 | const match = entityName?.removeFormatting()?.match(spawnedByRegex) 54 | if (!match) return 55 | 56 | const [ _, spawnedBy ] = match 57 | 58 | // -1 -> timer (02:59) 59 | // -2 -> armrostand name (☠ Voidgloom Seraph IV 64.2M❤) 60 | // -3 -> actual entity (Enderman) 61 | const obj = { 62 | spawnedBy: entityName, // This name should never change therefor we can set it static 63 | timeEntity: World.getWorld()./* getEntityByID */func_73045_a(entityId - 1), 64 | hpEntity: World.getWorld()./* getEntityByID */func_73045_a(entityId - 2), 65 | id: entityId - 3 66 | } 67 | entities.put(entityId - 3, obj) 68 | 69 | // Prioritize user's boss 70 | if (spawnedBy === Player.getName() || carryingUser === spawnedBy.toLowerCase()) 71 | currentBoss = obj 72 | 73 | feat.update() 74 | }) 75 | }) 76 | ) 77 | .addSubEvent( 78 | new Event("renderEntity", (entity, _, pticks) => { 79 | if (entity.entity.func_145782_y() !== currentBoss?.id) return 80 | RenderHelper.drawEntityBox( 81 | entity.getX(), 82 | entity.getY(), 83 | entity.getZ(), 84 | entity.getWidth(), 85 | entity.getHeight(), 86 | 0, 255, 255, 255, 2, false, true, pticks 87 | ) 88 | }), 89 | () => currentBoss 90 | ) 91 | .addSubEvent( 92 | new Event("renderOverlay", () => { 93 | if (editGui.isOpen()) return 94 | // I am lazy 95 | if (!currentBoss.hpEntity) return 96 | if (currentBoss.hpEntity./* isDead */field_70128_L) { 97 | entities.clear() 98 | reset() 99 | feat.update() 100 | return 101 | } 102 | 103 | Renderer.retainTransforms(true) 104 | Renderer.translate(editGui.getX(), editGui.getY()) 105 | Renderer.scale(editGui.getScale()) 106 | 107 | const spawnedByName = currentBoss.spawnedBy 108 | const timeEntityName = currentBoss.timeEntity./* getName */func_70005_c_() 109 | const hpEntityName = currentBoss.hpEntity./* getName */func_70005_c_() 110 | 111 | Renderer.drawStringWithShadow(timeEntityName, Renderer.getStringWidth(spawnedByName.removeFormatting()) / 2, 0) 112 | Renderer.drawStringWithShadow(spawnedByName, 15, 10) 113 | Renderer.drawStringWithShadow(hpEntityName, 0, 20) 114 | 115 | Renderer.retainTransforms(false) 116 | Renderer.finishDraw() 117 | }), 118 | () => currentBoss 119 | ) 120 | .addSubEvent( 121 | new Event("clicked", (_, __, mbtn, isDown) => { 122 | if (mbtn !== 2 || !isDown) return 123 | const entityId = Player.lookingAt().entity?.func_145782_y() 124 | if (!entityId || !entities.containsKey(entityId)) { 125 | if (currentBoss) reset() && feat.update() 126 | return 127 | } 128 | 129 | currentBoss = entities.get(entityId) 130 | feat.update() 131 | }), 132 | () => entities.size() 133 | ) 134 | .onUnregister(() => { 135 | entities.clear() 136 | reset() 137 | }) 138 | 139 | addCommand("slayercarry", "Set a user to the carry mode for Slayer Boss Display", (name) => { 140 | if (!name) { 141 | reset() 142 | carryingUser = null 143 | feat.update() 144 | ChatLib.chat(`${TextHelper.PREFIX} &cCarry user cleared.`) 145 | return 146 | } 147 | 148 | carryingUser = name.toLowerCase() 149 | ChatLib.chat(`${TextHelper.PREFIX} &aSuccessfully set user &b${name} &afor carry mode.`) 150 | }) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Dungeons 2 | import "./features/dungeons/BoxStarMobs" 3 | import "./features/dungeons/SecretsClickedBox" 4 | import "./features/dungeons/RunSplits" 5 | import "./features/dungeons/CroesusClicks" 6 | import "./features/dungeons/ExtraStats" 7 | import "./features/dungeons/CryptsDisplay" 8 | import "./features/dungeons/DeathsDisplay" 9 | import "./features/dungeons/MilestoneDisplay" 10 | import "./features/dungeons/PuzzleDisplay" 11 | import "./features/dungeons/MimicKilled" 12 | import "./features/dungeons/RemoveDmgTag" 13 | import "./features/dungeons/HideNoStarTag" 14 | import "./features/dungeons/SecretsSound" 15 | import "./features/dungeons/CroesusProfit" 16 | import "./features/dungeons/ChestProfit" 17 | import "./features/dungeons/BlazeSolver" 18 | import "./features/dungeons/BoulderSolver" 19 | import "./features/dungeons/CreeperBeamsSolver" 20 | import "./features/dungeons/ThreeWeirdosSolver" 21 | import "./features/dungeons/WaterBoardSolver" 22 | import "./features/dungeons/TriviaSolver" 23 | import "./features/dungeons/TicTacToeSolver" 24 | import "./features/dungeons/LividSolver" 25 | import "./features/dungeons/BossSplits" 26 | import "./features/dungeons/AutoRequeueDungeons" 27 | import "./features/dungeons/TeleportMazeSolver" 28 | import "./features/dungeons/RunsLogger" 29 | // Mining 30 | import "./features/mining/EmissaryWaypoints" 31 | import "./features/mining/PowderDisplay" 32 | import "./features/mining/ComissionDisplay" 33 | // Garden 34 | import "./features/garden/GardenDisplay" 35 | import "./features/garden/PestsDisplay" 36 | import "./features/garden/VisitorProfit" 37 | import "./features/garden/VisitorBzButton" 38 | // Gui 39 | import "./features/gui/CommandAliases" 40 | import "./features/gui/KeyShortcuts" 41 | import "./features/gui/CancelMessage" 42 | import "./features/gui/TitleMessage" 43 | // Kuudra 44 | import "./features/kuudra/KuudraSplits" 45 | import "./features/kuudra/CratesWaypoints" 46 | // Commands 47 | import "./features/commands/InventoryLog" 48 | // Rift 49 | import "./features/rift/BoxBerberis" 50 | import "./features/rift/MushroomTimer" 51 | import "./features/rift/WoodenButtons" 52 | import "./features/rift/GlyphRender" 53 | import "./features/rift/LavaMaze" 54 | import "./features/rift/Tubulator" 55 | import "./features/rift/EffigiesWaypoint" 56 | // Slayers 57 | import "./features/slayers/BossSlainTime" 58 | import "./features/slayers/BossSpawnTime" 59 | import "./features/slayers/SlayerBossDisplay" 60 | // Misc 61 | import "./features/misc/BlockOverlay" 62 | import "./features/misc/MiddleClickGuis" 63 | import "./features/misc/RemoveFrontView" 64 | import "./features/misc/NoDeathAnimation" 65 | import "./features/misc/NoLightning" 66 | import "./features/misc/ItemRarity" 67 | import "./features/misc/BonzoMaskInvincibility" 68 | import "./features/misc/PhoenixInvincibility" 69 | import "./features/misc/CopyChat" 70 | import "./features/misc/ChampionDisplay" 71 | import "./features/misc/EtherwarpOverlay" 72 | import "./features/misc/FactoryHelper" 73 | import "./features/misc/ArmorDisplay" 74 | import "./features/misc/WorldAgeDisplay" 75 | import "./features/misc/SystemTimeDisplay" 76 | import "./features/misc/RagnarokAxeCooldown" 77 | import "./features/misc/NoCursorReset" 78 | import "./features/misc/EnchantedBookDisplay" 79 | import "./features/misc/AttributeShardDisplay" 80 | import "./features/misc/ChatWaypoint" 81 | import "./features/misc/HideEmptyTooltip" 82 | import "./features/misc/InventoryButtons" 83 | import "./features/misc/EquipmentDisplay" 84 | import "./features/misc/RenderItems" 85 | import "./features/misc/SlotLocking" 86 | import "./features/misc/SearchBar" 87 | import "./features/misc/CultivatingDisplay" 88 | import "./features/misc/InventoryHud" 89 | import "./features/misc/CompactDisplay" 90 | import "./features/misc/DrillFuelDisplay" 91 | import "./features/misc/NoEndermanTeleport" 92 | import "./features/misc/InventoryHistory" 93 | import "./features/misc/QuiverDisplay" 94 | import "./features/misc/ToggleSprint" 95 | import "./features/misc/SmolderingPolarizationDisplay" -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Doc", 3 | "creator": "DocilElm", 4 | "description": "Quality of life features for hypixel skyblock", 5 | "version": "4.23.10", 6 | "entry": "index.js", 7 | "changelog": "", 8 | "requires": [ 9 | "requestV2", 10 | "PromiseV2", 11 | "PogData", 12 | "Atomx", 13 | "DocGuiLib", 14 | "Elementa", 15 | "KeybindFix", 16 | "Amaterasu", 17 | "tska" 18 | ] 19 | } -------------------------------------------------------------------------------- /shared/ChestMenu.js: -------------------------------------------------------------------------------- 1 | // Credits to https://chattriggers.com/modules/v/ChestMenu 2 | 3 | const InventoryBasic = net.minecraft.inventory.InventoryBasic 4 | const GuiChest = net.minecraft.client.gui.inventory.GuiChest 5 | 6 | export class ChestMenu { 7 | constructor(name, rows) { 8 | this.items = [] 9 | this.slotAmount = rows * 9 10 | this.basicinv = new InventoryBasic( 11 | name.addColor(), 12 | true, 13 | this.slotAmount 14 | ) 15 | this.gui = null 16 | } 17 | 18 | setTitle(title) { 19 | this.basicinv.func_110133_a(title.addColor()) 20 | return this 21 | } 22 | 23 | setItem(idx, item) { 24 | if (idx < 0 || idx >= this.slotAmount) return 25 | this.items[idx] = item 26 | this.basicinv.func_70299_a(idx, item?.itemStack || null) 27 | return this 28 | } 29 | 30 | setItems(list) { 31 | this.clear() 32 | 33 | for (let idx = 0; idx < list.length; idx++) { 34 | let item = list[idx] 35 | this.setItem(idx, item) 36 | } 37 | 38 | return this 39 | } 40 | 41 | clear() { 42 | this.items = [] 43 | this.basicinv.func_174888_l() 44 | return this 45 | } 46 | 47 | open() { 48 | if (!this.gui) { 49 | this.gui = new JavaAdapter(GuiChest, { 50 | // cancel all [keyTyped] inputs and only listen for 51 | // [ESC] and inventory key (Default is E so whenever E is pressed close gui) 52 | func_73869_a(_, keycode) { 53 | if (keycode === 1 || keycode === this.field_146297_k.field_71474_y.field_151445_Q.func_151463_i()) { 54 | Player.getPlayer().func_71053_j() 55 | } 56 | }, 57 | // cancel [handleMouseInput] so the user cannot click in the gui 58 | func_146274_d() {} 59 | }, Player.getPlayer().field_71071_by, this.basicinv) 60 | } 61 | GuiHandler.openGui(this.gui) 62 | } 63 | } -------------------------------------------------------------------------------- /shared/Command.js: -------------------------------------------------------------------------------- 1 | import config from "../config" 2 | import { TextHelper } from "./TextHelper" 3 | 4 | const commands = {} 5 | 6 | /** 7 | * @param {string} name 8 | * @param {string} description 9 | * @param {(...args: string) () => void} fn 10 | */ 11 | export const addCommand = (name, description, fn) => { 12 | const component = new TextComponent(`&a- ${name} &b${description}`) 13 | .setHover("show_text", `Click to run /doc ${name}`) 14 | .setClick("run_command", `/doc ${name}`) 15 | 16 | commands[name] = { 17 | description, 18 | chat: () => component.chat(), 19 | fn 20 | } 21 | } 22 | 23 | addCommand("help", "Shows this list") 24 | 25 | register("command", (...args) => { 26 | if (!args?.[0]) return config().getConfig().openGui() 27 | 28 | if (args[0].toLowerCase() === "help") { 29 | ChatLib.chat(`${TextHelper.PREFIX} &aCommand List`) 30 | Object.keys(commands).forEach(k => { 31 | commands[k].chat() 32 | }) 33 | 34 | return 35 | } 36 | 37 | const cmd = commands[args[0]] 38 | if (!cmd) return ChatLib.chat(`${TextHelper.PREFIX} &cInvalid command.`) 39 | 40 | cmd.fn?.(...args.slice(1)) 41 | }) 42 | .setTabCompletions((arg) => { 43 | if (arg.length > 1) return [] 44 | const allCommands = Object.keys(commands) 45 | if (!arg[0]) return allCommands 46 | 47 | const curr = allCommands.find(it => it.toLowerCase().startsWith(arg[0]?.toLowerCase())) 48 | if (!curr) return [] 49 | 50 | return [curr] 51 | }) 52 | .setName("doc") -------------------------------------------------------------------------------- /shared/DGlStateManager.js: -------------------------------------------------------------------------------- 1 | export class DGlStateManager { 2 | static pushMatrix() { 3 | GlStateManager.func_179094_E() 4 | 5 | return this 6 | } 7 | 8 | static popMatrix() { 9 | GlStateManager.func_179121_F() 10 | 11 | return this 12 | } 13 | 14 | static translate(x, y, z) { 15 | GlStateManager.func_179137_b(x, y, z) 16 | 17 | return this 18 | } 19 | 20 | static tryBlendFuncSeparate(srcFactor, dstFactor, srcFactorAlpha, dstFactorAlpha) { 21 | GlStateManager.func_179120_a(srcFactor, dstFactor, srcFactorAlpha, dstFactorAlpha) 22 | 23 | return this 24 | } 25 | 26 | static color(r, g, b, a) { 27 | GlStateManager.func_179131_c(r, g, b, a) 28 | 29 | return this 30 | } 31 | 32 | static bindTexture(int) { 33 | GlStateManager.func_179144_i(int) 34 | return this 35 | } 36 | 37 | static scale(x, y) { 38 | GlStateManager.func_179152_a(x, y || x, 1) 39 | return this 40 | } 41 | 42 | static enableBlend() { 43 | GlStateManager.func_179147_l() 44 | 45 | return this 46 | } 47 | 48 | static enableAlpha() { 49 | GlStateManager.func_179141_d() 50 | 51 | return this 52 | } 53 | 54 | static enableTexture2D() { 55 | GlStateManager.func_179098_w() 56 | 57 | return this 58 | } 59 | 60 | static enableDepth() { 61 | GlStateManager.func_179126_j() 62 | 63 | return this 64 | } 65 | 66 | static enableCull() { 67 | GlStateManager.func_179089_o() 68 | 69 | return this 70 | } 71 | 72 | static enableLighting() { 73 | GlStateManager.func_179145_e() 74 | 75 | return this 76 | } 77 | 78 | static disableTexture2D() { 79 | GlStateManager.func_179090_x() 80 | 81 | return this 82 | } 83 | 84 | static disableLighting() { 85 | GlStateManager.func_179140_f() 86 | 87 | return this 88 | } 89 | 90 | static disableAlpha() { 91 | GlStateManager.func_179118_c() 92 | 93 | return this 94 | } 95 | 96 | static disableBlend() { 97 | GlStateManager.func_179084_k() 98 | 99 | return this 100 | } 101 | 102 | static disableDepth() { 103 | GlStateManager.func_179097_i() 104 | 105 | return this 106 | } 107 | 108 | static disableCull() { 109 | GlStateManager.func_179129_p() 110 | 111 | return this 112 | } 113 | 114 | static resetColor() { 115 | GlStateManager.func_179117_G() 116 | 117 | return this 118 | } 119 | } -------------------------------------------------------------------------------- /shared/EtherwarpHelper.js: -------------------------------------------------------------------------------- 1 | // Full credits to BloomCore 2 | // pretty much the entire logic of this was taken from it 3 | 4 | import Vec3 from "./Vec3" 5 | 6 | // If one of these blocks is above the targeted etherwarp block, it is a valid teleport. 7 | // However if the block itself is being targetted, then it is not a valid block to etherwarp to. 8 | const validEtherwarpFeetBlocks = new Set([ 9 | "minecraft:air", 10 | "minecraft:fire", 11 | "minecraft:carpet", 12 | "minecraft:skull", 13 | "minecraft:lever", 14 | "minecraft:stone_button", 15 | "minecraft:wooden_button", 16 | "minecraft:torch", 17 | "minecraft:string", 18 | "minecraft:tripwire_hook", 19 | "minecraft:tripwire", 20 | "minecraft:rail", 21 | "minecraft:activator_rail", 22 | "minecraft:snow_layer", 23 | "minecraft:carrots", 24 | "minecraft:wheat", 25 | "minecraft:potatoes", 26 | "minecraft:nether_wart", 27 | "minecraft:pumpkin_stem", 28 | "minecraft:melon_stem", 29 | "minecraft:redstone_torch", 30 | "minecraft:redstone_wire", 31 | "minecraft:red_flower", 32 | "minecraft:yellow_flower", 33 | "minecraft:sapling", 34 | "minecraft:flower_pot", 35 | "minecraft:deadbush", 36 | "minecraft:tallgrass", 37 | "minecraft:ladder", 38 | "minecraft:double_plant", 39 | "minecraft:unpowered_repeater", 40 | "minecraft:powered_repeater", 41 | "minecraft:unpowered_comparator", 42 | "minecraft:powered_comparator", 43 | "minecraft:web", 44 | "minecraft:waterlily", 45 | "minecraft:water", 46 | "minecraft:lava", 47 | "minecraft:torch", 48 | "minecraft:vine", 49 | "minecraft:brown_mushroom", 50 | "minecraft:red_mushroom", 51 | "minecraft:piston_extension", 52 | ]) 53 | 54 | export default class EtherwarpHelper { 55 | /** 56 | * - Checks whether the given block is a valid block to etherwarp onto 57 | * @param {Block} block 58 | * @returns {boolean} 59 | */ 60 | static isValidEtherwarpBlock(block) { 61 | if (!block) return false 62 | if (!(block instanceof Block)) block = World.getBlockAt(...block) 63 | if (block.type.getID() == 0) return false 64 | 65 | // Checking the actual block to etherwarp ontop of 66 | // Can be at foot level, but not etherwarped onto directly. 67 | if (validEtherwarpFeetBlocks.has(block.type.getRegistryName())) return false 68 | 69 | // The block at foot level 70 | const blockAbove = World.getBlockAt(block.getX(), block.getY() + 1, block.getZ()) 71 | if (!validEtherwarpFeetBlocks.has(blockAbove.type.getRegistryName())) return false 72 | 73 | // The block at head height 74 | const blockAboveAbove = World.getBlockAt(block.getX(), block.getY() + 2, block.getZ()) 75 | 76 | return validEtherwarpFeetBlocks.has(blockAboveAbove.type.getRegistryName()) 77 | } 78 | 79 | /** 80 | * @param {number[]} start 81 | * @param {number[]} end 82 | * @param {number} distance 83 | * @returns {Block?} 84 | */ 85 | static traverseVoxels(start, end, distance = 60) { 86 | const direction = end.map((v, i) => v - start[i]) 87 | const step = direction.map(a => Math.sign(a)) 88 | const thing = direction.map(a => 1/a) 89 | const tDelta = thing.map((v, i) => Math.min(v * step[i], 1)) 90 | const tMax = thing.map((v, i) => Math.abs((Math.floor(start[i]) + Math.max(step[i], 0) - start[i]) * v)) 91 | const limit = distance + 20 92 | 93 | let startPos = start.map(it => Math.floor(it)) 94 | let endPos = end.map(it => Math.floor(it)) 95 | 96 | for (let idx = 0; idx < limit; idx++) { 97 | let block = World.getBlockAt(...startPos) 98 | if (block.type.getID() !== 0) return block 99 | 100 | if (startPos.every((v, i) => v == endPos[i])) break 101 | 102 | // Find the next direction to step in 103 | let minIdx = tMax.indexOf(Math.min(...tMax)) 104 | tMax[minIdx] += tDelta[minIdx] 105 | startPos[minIdx] += step[minIdx] 106 | } 107 | 108 | return null 109 | } 110 | 111 | static getEtherwarpBlockSuccess(distance = 60) { 112 | let lookVec = Vec3.fromPitchYaw(Player.getPitch(), Player.getYaw()).multiply(distance) 113 | let startPos = [Player.getRenderX(), Player.getRenderY() + 1.54, Player.getRenderZ()] 114 | let endPos = lookVec.getComponents().map((v, i) => v + startPos[i]) 115 | 116 | const ether = this.traverseVoxels(startPos, endPos, distance) 117 | 118 | return [this.isValidEtherwarpBlock(ether), ether] 119 | } 120 | } -------------------------------------------------------------------------------- /shared/PuzzleRoomScanner.js: -------------------------------------------------------------------------------- 1 | import { scheduleTask } from "../core/CustomRegisters" 2 | import { Event } from "../core/Event" 3 | import Feature from "../core/Feature" 4 | import { TextHelper } from "./TextHelper" 5 | 6 | const listeners = [] 7 | const listenersExit = [] 8 | const listenersSch = [] 9 | const offsetsToCheck = [ 10 | [0, 16], 11 | [-16, 0], 12 | [0, -16], 13 | [16, 0] 14 | ] 15 | const cachedRotations = new Map() 16 | 17 | let lastIdx = null 18 | 19 | /** 20 | * - Runs the given function whenever the rotation 21 | * - is found for the current room 22 | * @param {(rotation: number, posIdx: number) => void} fn 23 | * @returns 24 | */ 25 | export const onPuzzleRotation = (fn) => listeners.push(fn) 26 | 27 | /** 28 | * - Runs the given function whenever the rotation 29 | * - is found for the current room but schedules the task 30 | * - 2 server ticks after 31 | * @param {(rotation: number, posIdx: number) => void} fn 32 | * @returns 33 | */ 34 | export const onPuzzleScheduledRotation = (fn) => listenersSch.push(fn) 35 | 36 | /** 37 | * - Runs the given function whenever the player leaves a room 38 | * @param {() => void} fn 39 | * @returns 40 | */ 41 | export const onPuzzleRotationExit = (fn) => listenersExit.push(fn) 42 | 43 | // Big thank bloom 44 | const getPuzzleRotation = () => { 45 | const xIndex = Math.floor((Player.getX() + 200) / 32) 46 | const zIndex = Math.floor((Player.getZ() + 200) / 32) 47 | const centerX = xIndex * 32 - 200 + 15 48 | const centerZ = zIndex * 32 - 200 + 15 49 | 50 | let rotation = null 51 | 52 | for (let i = 0; i < offsetsToCheck.length; i++) { 53 | let [ dx, dz ] = offsetsToCheck[i] 54 | let [ rx, ry, rz ] = [ centerX + dx, 68, centerZ + dz ] 55 | 56 | let block = World.getBlockAt(rx, ry, rz) 57 | let bottomBlock = World.getBlockAt(rx, ry - 1, rz) 58 | let topBlock = World.getBlockAt(rx, ry + 1, rz) 59 | 60 | // early enter blood with blood door closed 61 | if ( 62 | bottomBlock.type.getID() === 7 && 63 | topBlock.type.getID() === 159 && 64 | block.type.getID() !== 0 65 | ) return i * 90 66 | 67 | if (bottomBlock.type.getID() !== 7 || topBlock.type.getID() !== 0) continue 68 | if (block.type.getID() === 0) continue 69 | 70 | // If the rotation has already been set, there is more than one door 71 | if (rotation !== null) return 72 | 73 | rotation = i*90 74 | } 75 | 76 | return rotation 77 | } 78 | 79 | new Feature("puzzleRoomScanner", "catacombs") 80 | .addEvent( 81 | new Event("tick", () => { 82 | const posIdx = TextHelper.getDungeonsPosIndex() 83 | 84 | if (lastIdx && lastIdx !== posIdx) { 85 | for (let idx = 0; idx < listenersExit.length; idx++) { 86 | listenersExit[idx]() 87 | } 88 | } 89 | 90 | if (lastIdx === posIdx) return 91 | 92 | lastIdx = posIdx 93 | const rotation = cachedRotations.get(posIdx) ?? getPuzzleRotation() 94 | if (rotation == null) return 95 | 96 | if (!cachedRotations.has(posIdx)) cachedRotations.set(posIdx, rotation) 97 | 98 | for (let idx = 0; idx < listeners.length; idx++) { 99 | listeners[idx](rotation, posIdx) 100 | } 101 | 102 | scheduleTask(() => { 103 | for (let idx = 0; idx < listenersSch.length; idx++) { 104 | listenersSch[idx](rotation, posIdx) 105 | } 106 | }, 2) 107 | }) 108 | ) 109 | .onUnregister(() => { 110 | lastIdx = null 111 | cachedRotations.clear() 112 | }) --------------------------------------------------------------------------------