aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSoopyboo32 <49228220+Soopyboo32@users.noreply.github.com>2021-10-31 09:49:42 +0800
committerSoopyboo32 <49228220+Soopyboo32@users.noreply.github.com>2021-10-31 09:49:42 +0800
commit48653ec89538f1650106a5e77463412cad4684c2 (patch)
tree09687cd579462e04d539fd4615369fa6dae13902
downloadSoopyV2-48653ec89538f1650106a5e77463412cad4684c2.tar.gz
SoopyV2-48653ec89538f1650106a5e77463412cad4684c2.tar.bz2
SoopyV2-48653ec89538f1650106a5e77463412cad4684c2.zip
first commit
-rw-r--r--class.js139
-rw-r--r--featureClass/class.js139
-rw-r--r--featureClass/featureManager.js384
-rw-r--r--featureClass/forgeEvents.js93
-rw-r--r--featureManager.js384
-rw-r--r--features/agentlaiThings/hiddenRequirement.js8
-rw-r--r--features/agentlaiThings/index.js194
-rw-r--r--features/agentlaiThings/metadata.json8
-rw-r--r--features/betterGuis/index.js197
-rw-r--r--features/betterGuis/metadata.json8
-rw-r--r--features/changeLogGUI/index.js210
-rw-r--r--features/changeLogGUI/metadata.json8
-rw-r--r--features/dataLoader/index.js128
-rw-r--r--features/dataLoader/metadata.json8
-rw-r--r--features/dungeonMap/index.js299
-rw-r--r--features/dungeonMap/metadata.json8
-rw-r--r--features/dungeonSolvers/index.js153
-rw-r--r--features/dungeonSolvers/metadata.json8
-rw-r--r--features/events/index.js463
-rw-r--r--features/events/metadata.json8
-rw-r--r--features/featureBase/index.js24
-rw-r--r--features/featureBase/metadata.json8
-rw-r--r--features/fragBot/index.js84
-rw-r--r--features/fragBot/metadata.json8
-rw-r--r--features/friendsGUI/index.js154
-rw-r--r--features/friendsGUI/metadata.json8
-rw-r--r--features/globalSettings/index.js51
-rw-r--r--features/globalSettings/metadata.json8
-rw-r--r--features/hud/HudTextElement.js77
-rw-r--r--features/hud/index.js315
-rw-r--r--features/hud/metadata.json8
-rw-r--r--features/improvements/index.js36
-rw-r--r--features/improvements/metadata.json8
-rw-r--r--features/mining/index.js201
-rw-r--r--features/mining/metadata.json8
-rw-r--r--features/settings/index.js249
-rw-r--r--features/settings/metadata.json8
-rw-r--r--features/settings/settingThings/button.js18
-rw-r--r--features/settings/settingThings/location.js332
-rw-r--r--features/settings/settingThings/settingBase.js106
-rw-r--r--features/settings/settingThings/textSetting.js33
-rw-r--r--features/settings/settingThings/toggle.js58
-rw-r--r--features/settings/settingsCommunicator.js29
-rw-r--r--features/slayers/index.js359
-rw-r--r--features/slayers/metadata.json8
-rw-r--r--features/soopyGui/GuiPage.js46
-rw-r--r--features/soopyGui/index.js192
-rw-r--r--features/soopyGui/metadata.json8
-rw-r--r--features/spamHider/index.js175
-rw-r--r--features/spamHider/metadata.json8
-rw-r--r--features/stat_next_to_name/index.js19
-rw-r--r--features/stat_next_to_name/metadata.json8
-rw-r--r--forgeEvents.js93
-rw-r--r--index.js12
-rw-r--r--logger.js28
-rw-r--r--metadata.js5
-rw-r--r--metadata.json16
-rw-r--r--utils/numberUtils.js50
-rw-r--r--utils/renderLib2d.js147
-rw-r--r--utils/renderUtils.js291
-rw-r--r--utils/stringUtils.js11
-rw-r--r--utils/utils.js116
62 files changed, 6270 insertions, 0 deletions
diff --git a/class.js b/class.js
new file mode 100644
index 0000000..1cfdd8b
--- /dev/null
+++ b/class.js
@@ -0,0 +1,139 @@
+/// <reference types="../../CTAutocomplete" />
+
+import logger from "../logger"
+
+/// <reference lib="es2015" />
+
+class Feature {
+ constructor(){
+ this.FeatureManager = undefined
+ this.events = {}
+ this.customEvents = {}
+ this.forgeEvents = {}
+ this.soopyEvents = {}
+
+ this.id = undefined
+
+ this.enabled = false
+ }
+
+ setId(id){
+ this.id = id
+ }
+ getId(){
+ return this.id
+ }
+
+ _onDisable(){
+ Object.values(this.events).forEach(e=>this.FeatureManager.unregisterEvent(e)) //calling parent unregister to avoid the set in unregister event
+ Object.values(this.customEvents).forEach(e=>this.FeatureManager.unregisterCustom(e)) //calling parent unregister to avoid the set in unregister event
+
+ this.onDisable()
+
+ this.events = {}
+ this.customEvents = {}
+ this.enabled = false
+ }
+
+ _onEnable(parent){
+ this.FeatureManager = parent
+
+ this.enabled = true
+
+ this.onEnable()
+ }
+
+ onDisable(){}
+ onEnable(){}
+
+ registerEvent(event, func){
+ let theEvent = this.FeatureManager.registerEvent(event, func, this)
+
+ this.events[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ unregisterEvent(event){
+ this.FeatureManager.unregisterEvent(event)
+
+ delete this.events[event.id]
+ }
+ registerSoopy(event, func){
+ let theEvent = this.FeatureManager.registerSoopy(event, func, this)
+
+ this.soopyEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ unregisterSoopy(event){
+ this.FeatureManager.unregisterSoopy(event)
+
+ delete this.soopyEvents[event.id]
+ }
+
+ registerForge(event, func){
+ let theEvent = this.FeatureManager.registerForge(event, func, this)
+
+ this.forgeEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ unregisterForge(event){
+ this.FeatureManager.unregisterForge(event)
+
+ delete this.forgeEvents[event.id]
+ }
+
+ registerChat(criteria, func){
+ let theEvent = this.FeatureManager.registerChat(criteria, func, this)
+
+ this.customEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+ registerStep(isFps, interval, func){
+ let theEvent = this.FeatureManager.registerStep(isFps, interval, func, this)
+
+ this.customEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ registerCustom(event, func){
+ let theEvent = this.FeatureManager.registerCustom(event, func, this)
+
+ this.customEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ registerCommand(name, func){
+ this.FeatureManager.commandFuncs[name] = func
+
+ this.FeatureManager.registerCommand(name, (...args)=>{
+ if(this.FeatureManager.commandFuncs[name]){
+ this.FeatureManager.commandFuncs[name].call(this, ...(args || []))
+ }else{
+ ChatLib.chat("&cThis command is not available atm")
+ }
+ }, this)
+ }
+ unregisterCommand(name){
+ delete this.FeatureManager.commandFuncs[name]
+ }
+
+ unregisterCustom(event){
+ this.FeatureManager.unregisterCustom(event)
+
+ delete this.customEvents[event.id]
+ }
+
+ createCustomEvent(eventId){
+ return this.FeatureManager.createCustomEvent(eventId)
+ }
+}
+
+export default Feature \ No newline at end of file
diff --git a/featureClass/class.js b/featureClass/class.js
new file mode 100644
index 0000000..1cfdd8b
--- /dev/null
+++ b/featureClass/class.js
@@ -0,0 +1,139 @@
+/// <reference types="../../CTAutocomplete" />
+
+import logger from "../logger"
+
+/// <reference lib="es2015" />
+
+class Feature {
+ constructor(){
+ this.FeatureManager = undefined
+ this.events = {}
+ this.customEvents = {}
+ this.forgeEvents = {}
+ this.soopyEvents = {}
+
+ this.id = undefined
+
+ this.enabled = false
+ }
+
+ setId(id){
+ this.id = id
+ }
+ getId(){
+ return this.id
+ }
+
+ _onDisable(){
+ Object.values(this.events).forEach(e=>this.FeatureManager.unregisterEvent(e)) //calling parent unregister to avoid the set in unregister event
+ Object.values(this.customEvents).forEach(e=>this.FeatureManager.unregisterCustom(e)) //calling parent unregister to avoid the set in unregister event
+
+ this.onDisable()
+
+ this.events = {}
+ this.customEvents = {}
+ this.enabled = false
+ }
+
+ _onEnable(parent){
+ this.FeatureManager = parent
+
+ this.enabled = true
+
+ this.onEnable()
+ }
+
+ onDisable(){}
+ onEnable(){}
+
+ registerEvent(event, func){
+ let theEvent = this.FeatureManager.registerEvent(event, func, this)
+
+ this.events[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ unregisterEvent(event){
+ this.FeatureManager.unregisterEvent(event)
+
+ delete this.events[event.id]
+ }
+ registerSoopy(event, func){
+ let theEvent = this.FeatureManager.registerSoopy(event, func, this)
+
+ this.soopyEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ unregisterSoopy(event){
+ this.FeatureManager.unregisterSoopy(event)
+
+ delete this.soopyEvents[event.id]
+ }
+
+ registerForge(event, func){
+ let theEvent = this.FeatureManager.registerForge(event, func, this)
+
+ this.forgeEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ unregisterForge(event){
+ this.FeatureManager.unregisterForge(event)
+
+ delete this.forgeEvents[event.id]
+ }
+
+ registerChat(criteria, func){
+ let theEvent = this.FeatureManager.registerChat(criteria, func, this)
+
+ this.customEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+ registerStep(isFps, interval, func){
+ let theEvent = this.FeatureManager.registerStep(isFps, interval, func, this)
+
+ this.customEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ registerCustom(event, func){
+ let theEvent = this.FeatureManager.registerCustom(event, func, this)
+
+ this.customEvents[theEvent.id] = theEvent
+
+ return theEvent
+ }
+
+ registerCommand(name, func){
+ this.FeatureManager.commandFuncs[name] = func
+
+ this.FeatureManager.registerCommand(name, (...args)=>{
+ if(this.FeatureManager.commandFuncs[name]){
+ this.FeatureManager.commandFuncs[name].call(this, ...(args || []))
+ }else{
+ ChatLib.chat("&cThis command is not available atm")
+ }
+ }, this)
+ }
+ unregisterCommand(name){
+ delete this.FeatureManager.commandFuncs[name]
+ }
+
+ unregisterCustom(event){
+ this.FeatureManager.unregisterCustom(event)
+
+ delete this.customEvents[event.id]
+ }
+
+ createCustomEvent(eventId){
+ return this.FeatureManager.createCustomEvent(eventId)
+ }
+}
+
+export default Feature \ No newline at end of file
diff --git a/featureClass/featureManager.js b/featureClass/featureManager.js
new file mode 100644
index 0000000..92b9cd8
--- /dev/null
+++ b/featureClass/featureManager.js
@@ -0,0 +1,384 @@
+/// <reference types="../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import LocationSetting from "../features/settings/settingThings/location";
+import logger from "../logger";
+const File = Java.type("java.io.File")
+import metadata from "../metadata.js"
+import { registerForge as registerForgeBase, unregisterForge as unregisterForgeBase} from "./forgeEvents.js"
+
+class FeatureManager {
+ constructor(){
+
+ this.enabled = true //make triggers work with this context
+
+ this.features = {};
+ this.events = {}
+ this.eventObjects = {}
+ this.soopyEventHandlers = {}
+
+ this.parent = undefined
+
+ this.commandFuncs = {}
+
+ this.lastEventId = 0
+
+ this.customEvents = {}
+ this.lastChatEventId = 0
+
+ this.forgeEvents = {}
+ this.lastForgeEventId = 0
+
+ this.lastSoopyEventId = 0
+
+ this.featureSettingsDataLastUpdated = false
+
+ this.featureMetas = {}
+
+ this.featureSettingsData = {}
+
+ new Thread(()=>{
+ this.loadFeatureMetas()
+
+ this.loadFeatureSettings()
+
+ Object.keys(this.featureMetas).forEach((feature)=>{
+ if(this.featureSettingsData[feature] && this.featureSettingsData[feature].enabled){
+ this.loadFeature(feature)
+ }
+ })
+ }).start()
+
+ this.registerStep(false, 30, ()=>{
+ if(this.featureSettingsDataLastUpdated){
+ new Thread(()=>{
+ this.saveFeatureSettings()
+ }).start()
+ }
+ }, this)
+
+ this.registerEvent("worldUnload", this.saveFeatureSettings, this)
+ this.registerEvent("gameUnload", this.saveFeatureSettings, this)
+
+ this.registerCommand("soopyunloadfeature", (args)=>{
+ new Thread(()=>{
+ this.unloadFeature(args)
+ }).start()
+ }, this)
+ this.registerCommand("soopyloadfeature", (args)=>{
+ new Thread(()=>{
+ this.loadFeature(args)
+ }).start()
+ }, this)
+ this.registerCommand("soopyreloadfeature", (args)=>{
+ new Thread(()=>{
+ this.unloadFeature(args)
+
+ this.loadFeature(args)
+ }).start()
+ }, this)
+ }
+
+ loadFeatureSettings(){
+ logger.logMessage("Loading settings", 4)
+
+ let data = FileLib.read("soopyAddonsData", "soopyaddonsbetafeaturesdata.json")
+
+ if(!data){
+ this.loadDefaultFeatureSettings();
+ return;
+ }
+
+ data = JSON.parse(data)
+
+ this.featureSettingsData = data
+
+ this.ensureNewSettingsExist()
+ }
+
+ saveFeatureSettings(){
+ if(!this.featureSettingsDataLastUpdated) return
+
+ FileLib.write("soopyAddonsData", "soopyaddonsbetafeaturesdata.json", JSON.stringify(this.featureSettingsData))
+
+ this.featureSettingsDataLastUpdated = false
+
+ logger.logMessage("Saved settings", 4)
+ }
+
+ loadDefaultFeatureSettings(){
+ Object.keys(this.featureMetas).forEach((feature)=>{
+ this.featureSettingsData[feature] = {
+ enabled: this.featureMetas[feature].defaultEnabled,
+ subSettings: {}
+ }
+ })
+
+ this.featureSettingsDataLastUpdated= true
+
+ logger.logMessage("Loaded default settings", 4)
+ }
+
+ ensureNewSettingsExist(){
+ Object.keys(this.featureMetas).forEach((feature)=>{
+ if(!this.featureSettingsData[feature]){
+ this.featureSettingsData[feature] = {
+ enabled: this.featureMetas[feature].defaultEnabled,
+ subSettings: {}
+ }
+ this.featureSettingsDataLastUpdated= true
+ logger.logMessage("Loaded default settings for " + feature, 4)
+ }
+ })
+ }
+
+ startCatchingEvent(event){
+ if(this.eventObjects[event]) return
+
+ //SBA compatability or something (removed)
+ // if(event === "renderOverlay"){
+ // let lastPartialTick = undefined
+ // this.eventObjects[event] = register(event, (...args)=>{
+ // let pTicks = Tessellator.getPartialTicks()
+ // if(pTicks !== lastPartialTick){
+ // lastPartialTick = pTicks
+ // this.triggerEvent(event, args)
+ // }
+ // })
+ // }else{
+ this.eventObjects[event] = register(event, (...args)=>{
+ this.triggerEvent(event, args)
+ })
+ //}
+
+ logger.logMessage("Registered " + event + " event", 4)
+ }
+
+ triggerEvent(event, args){
+ if(this.events[event])
+ try{
+ for(Event of Object.values(this.events[event])){
+ if(Event.context.enabled) Event.func.call(Event.context, ...args)
+ }
+ }catch(e){
+ logger.logMessage("Error in " + event + " event: " + JSON.stringify(e, undefined, 2), 2)
+ }
+ }
+ triggerSoopy(event, args){
+ if(this.soopyEventHandlers[event])
+ try{
+ for(Event of Object.values(this.soopyEventHandlers[event])){
+ if(Event.context.enabled) Event.func.call(Event.context, ...args)
+ }
+ }catch(e){
+ logger.logMessage("Error in soopy " + event + " event: " + JSON.stringify(e, undefined, 2), 2)
+ }
+ }
+
+ stopCatchingEvent(event){
+ if(!this.eventObjects[event]) return
+
+ this.eventObjects[event].unregister()
+ delete this.eventObjects[event]
+ delete this.events[event]
+ logger.logMessage("Unregistered " + event + " event", 4)
+ }
+
+ registerEvent(event, func, context){
+ if(!this.events[event]){
+ this.events[event] = []
+ this.startCatchingEvent(event)
+ }
+
+ let theEvent = {
+ func: func,
+ context: context,
+ id: this.lastEventId++,
+ event: event
+ }
+ this.events[event].push(theEvent)
+
+ return theEvent
+ }
+ registerSoopy(event, func, context){
+ if(!this.soopyEventHandlers[event]){
+ this.soopyEventHandlers[event] = []
+ }
+
+ let theEvent = {
+ func: func,
+ context: context,
+ id: this.lastSoopyEventId++,
+ event: event
+ }
+ this.soopyEventHandlers[event].push(theEvent)
+
+ return theEvent
+ }
+
+ registerChat(criteria, func, context){
+
+ let event = this.registerCustom("chat", func, context)
+
+ event.trigger.setChatCriteria(criteria)
+
+ return event
+ }
+ registerCommand(commandName, func, context){
+
+ let event = this.registerCustom("command", func, context)
+
+ event.trigger.setName(commandName)
+
+ return event
+ }
+ registerStep(isFps, interval, func, context){
+
+ let event = this.registerCustom("step", func, context)
+
+ event.trigger[isFps?"setFps":"setDelay"](interval) //TODO: make this group events similar to registerEvent()
+
+ return event
+ }
+
+ registerCustom(type, func, context){
+
+ let id = this.lastChatEventId++
+
+ this.customEvents[id] = {
+ func: func,
+ context: context,
+ trigger: register(type, (...args)=>{
+ try{
+ if(context.enabled) func.call(context, ...(args || []))
+ }catch(e){
+ logger.logMessage("Error in " + type + " event: " + JSON.stringify(e, undefined, 2), 2)
+ }
+ }),
+ id: id
+ }
+
+ return this.customEvents[id]
+ }
+
+ registerForge(event, func, context){
+ let id = this.lastForgeEventId++
+
+ this.forgeEvents[id] = {
+ func: func,
+ context: context,
+ trigger: registerForgeBase(event, (...args)=>{
+ try{
+ if(context.enabled) func.call(context, ...args)
+ }catch(e){
+ logger.logMessage("Error in " + event.class.toString() + " (forge) event: " + JSON.stringify(e, undefined, 2), 2)
+ }
+ }),
+ id: id
+ }
+
+ return this.forgeEvents[id]
+ }
+
+ unregisterForge(event){
+ unregisterForgeBase(this.forgeEvents[event.id].trigger)
+ delete this.forgeEvents[event.id]
+ }
+
+ unregisterCustom(event){
+ event.trigger.unregister()
+
+ delete this.customEvents[event.id]
+ }
+
+ unregisterEvent(event){
+ if(!this.events[event.event]) return
+
+ this.events[event.event] = this.events[event.event].filter((e)=>{
+ return e.id !== event.id
+ })
+
+ if(this.events[event.event].length === 0){
+ this.stopCatchingEvent(event.event)
+ delete this.events[event.event]
+ }
+ }
+
+ unregisterSoopy(event){
+ if(!this.soopyEventHandlers[event.event]) return
+
+ this.soopyEventHandlers[event.event] = this.soopyEventHandlers[event.event].filter((e)=>{
+ return e.id !== event.id
+ })
+
+ if(this.soopyEventHandlers[event.event].length === 0){
+ delete this.events[event.event]
+ }
+ }
+
+ loadFeatureMetas(){
+ featuresDir = new File("./config/ChatTriggers/modules/" + metadata.name + "/features")
+
+ featuresDir.list().forEach((pathName)=>{
+ if(pathName.includes(".")) return;
+
+ try{
+ let data = JSON.parse(FileLib.read( metadata.name + "/features/" + pathName,"metadata.json"))
+ if(data === null){
+ return;
+ }
+ data.id = pathName
+ this.featureMetas[pathName] = data
+ }catch(_){}
+ })
+ }
+
+ loadFeature(feature){ //run in seperate thread so onenable can do network requests
+ if(this.features[feature]) return
+
+ let LoadedFeature = require("../features/" + feature + "/index.js")
+
+ this.features[feature] = LoadedFeature
+
+ LoadedFeature.class.setId(feature)
+
+ LoadedFeature.class._onEnable(this)
+
+ logger.logMessage("Loaded feature " + feature, 3)
+
+ return this
+ }
+
+ unloadFeature(feature){ //run in seperate thread so ondisable can do network requests
+ if(!this.features[feature]) return
+
+ this.features[feature].class._onDisable()
+
+ delete this.features[feature]
+
+ logger.logMessage("Unloaded feature " + feature, 3)
+
+ return this
+ }
+
+ isFeatureLoaded(feature){
+ return !!this.features[feature]
+ }
+
+ getLoadedFeatures(){
+ return Object.keys(this.features)
+ }
+
+ createCustomEvent(eventId){
+ logger.logMessage("Registered custom " + eventId + " event", 4)
+
+ return {
+ trigger: (...args)=>{
+ this.triggerSoopy(eventId, args)
+ }
+ }
+ }
+}
+
+const featureManager = new FeatureManager();
+
+export default featureManager; \ No newline at end of file
diff --git a/featureClass/forgeEvents.js b/featureClass/forgeEvents.js
new file mode 100644
index 0000000..e852c49
--- /dev/null
+++ b/featureClass/forgeEvents.js
@@ -0,0 +1,93 @@
+importClass(net.minecraftforge.common.MinecraftForge) //i would have used the ct module but it is broken (line 78) (this is fixed verison)
+importPackage(net.minecraftforge.fml.common.eventhandler)
+importPackage(org.objectweb.asm)
+importClass(java.lang.ClassLoader)
+importClass(org.apache.commons.lang3.RandomStringUtils)
+importClass(java.util.function.Consumer)
+
+const L = s => `L${s};`
+
+const LoadedInsts = []
+
+function defineClassBytes(name, bytes) {
+ const classLoader = Packages.com.chattriggers.ctjs.CTJS.class.getClassLoader()
+
+ const defClass = ClassLoader.class.getDeclaredMethods()[23] // defineClass()
+
+ defClass.setAccessible(true)
+
+ const n = new java.lang.String(name)
+ const o = new java.lang.Integer(0)
+ const s = new java.lang.Integer(bytes.length)
+ return defClass.invoke(classLoader, n, bytes, o, s)
+}
+
+const registerForge = (e, cb) => {
+ const cw = new ClassWriter(0)
+
+ const event = Type.getType(e.class).internalName
+ const name = RandomStringUtils.randomAlphabetic(7)
+
+ const consumer = Type.getType(Consumer.class).internalName
+ const mcForge = Type.getType(MinecraftForge.class).internalName
+ const eventBus = Type.getType(EventBus.class).internalName
+ const subscribeEvent = Type.getType(SubscribeEvent.class).internalName
+ const obj = Type.getType(java.lang.Object.class).internalName
+
+ cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null, obj, null)
+ // cw.visitInnerClass("net/minecraftforge/event/entity/player/PlayerEvent$BreakSpeed", "net/minecraftforge/event/entity/player/PlayerEvent", "BreakSpeed", ACC_PUBLIC + ACC_STATIC);
+ {
+ cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, "callback", L(consumer), L(consumer + "<" + L(event) + ">"), null).visitEnd()
+ }
+ {
+ const con = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(" + L(consumer) + ")V", "(" + L(consumer + "<" + L(event) + ">") + ")V", null)
+ con.visitCode()
+ con.visitVarInsn(Opcodes.ALOAD, 0)
+ con.visitMethodInsn(Opcodes.INVOKESPECIAL, obj, "<init>", "()V", false)
+
+ con.visitVarInsn(Opcodes.ALOAD, 0)
+ con.visitVarInsn(Opcodes.ALOAD, 1)
+ con.visitFieldInsn(Opcodes.PUTFIELD, name, "callback", L(consumer))
+ con.visitFieldInsn(Opcodes.GETSTATIC, mcForge, "EVENT_BUS", L(eventBus))
+ con.visitVarInsn(Opcodes.ALOAD, 0)
+ con.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventBus, "register", "(" + L(obj) + ")V", false)
+ con.visitInsn(Opcodes.RETURN)
+ con.visitMaxs(2, 2)
+ con.visitEnd()
+ }
+ {
+ const mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "on", "(" + L(event) + ")V", null, null)
+ {
+ const av = mv.visitAnnotation(L(subscribeEvent), true)
+ av.visitEnd()
+ }
+ mv.visitCode()
+ mv.visitVarInsn(Opcodes.ALOAD, 0)
+ mv.visitFieldInsn(Opcodes.GETFIELD, name, "callback", L(consumer))
+ mv.visitVarInsn(Opcodes.ALOAD, 1)
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, consumer, "accept", "(" + L(obj) + ")V", true)
+ mv.visitInsn(Opcodes.RETURN)
+ mv.visitMaxs(2, 2)
+ mv.visitEnd()
+ }
+ cw.visitEnd()
+
+ const inst = defineClassBytes(name, cw.toByteArray())
+ .getDeclaredConstructor(Consumer.class)
+ .newInstance(new java.util.function.Consumer({
+ accept: function (t) { cb(t) }
+ }))
+ LoadedInsts.push(inst)
+ return inst;
+}
+
+const unregisterForge = inst => {
+ MinecraftForge.EVENT_BUS.unregister(inst)
+}
+
+register("gameUnload", () => {
+ LoadedInsts.forEach(unregisterForge)
+ LoadedInsts.length = 0
+})
+
+export { registerForge, unregisterForge }
diff --git a/featureManager.js b/featureManager.js
new file mode 100644
index 0000000..92b9cd8
--- /dev/null
+++ b/featureManager.js
@@ -0,0 +1,384 @@
+/// <reference types="../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import LocationSetting from "../features/settings/settingThings/location";
+import logger from "../logger";
+const File = Java.type("java.io.File")
+import metadata from "../metadata.js"
+import { registerForge as registerForgeBase, unregisterForge as unregisterForgeBase} from "./forgeEvents.js"
+
+class FeatureManager {
+ constructor(){
+
+ this.enabled = true //make triggers work with this context
+
+ this.features = {};
+ this.events = {}
+ this.eventObjects = {}
+ this.soopyEventHandlers = {}
+
+ this.parent = undefined
+
+ this.commandFuncs = {}
+
+ this.lastEventId = 0
+
+ this.customEvents = {}
+ this.lastChatEventId = 0
+
+ this.forgeEvents = {}
+ this.lastForgeEventId = 0
+
+ this.lastSoopyEventId = 0
+
+ this.featureSettingsDataLastUpdated = false
+
+ this.featureMetas = {}
+
+ this.featureSettingsData = {}
+
+ new Thread(()=>{
+ this.loadFeatureMetas()
+
+ this.loadFeatureSettings()
+
+ Object.keys(this.featureMetas).forEach((feature)=>{
+ if(this.featureSettingsData[feature] && this.featureSettingsData[feature].enabled){
+ this.loadFeature(feature)
+ }
+ })
+ }).start()
+
+ this.registerStep(false, 30, ()=>{
+ if(this.featureSettingsDataLastUpdated){
+ new Thread(()=>{
+ this.saveFeatureSettings()
+ }).start()
+ }
+ }, this)
+
+ this.registerEvent("worldUnload", this.saveFeatureSettings, this)
+ this.registerEvent("gameUnload", this.saveFeatureSettings, this)
+
+ this.registerCommand("soopyunloadfeature", (args)=>{
+ new Thread(()=>{
+ this.unloadFeature(args)
+ }).start()
+ }, this)
+ this.registerCommand("soopyloadfeature", (args)=>{
+ new Thread(()=>{
+ this.loadFeature(args)
+ }).start()
+ }, this)
+ this.registerCommand("soopyreloadfeature", (args)=>{
+ new Thread(()=>{
+ this.unloadFeature(args)
+
+ this.loadFeature(args)
+ }).start()
+ }, this)
+ }
+
+ loadFeatureSettings(){
+ logger.logMessage("Loading settings", 4)
+
+ let data = FileLib.read("soopyAddonsData", "soopyaddonsbetafeaturesdata.json")
+
+ if(!data){
+ this.loadDefaultFeatureSettings();
+ return;
+ }
+
+ data = JSON.parse(data)
+
+ this.featureSettingsData = data
+
+ this.ensureNewSettingsExist()
+ }
+
+ saveFeatureSettings(){
+ if(!this.featureSettingsDataLastUpdated) return
+
+ FileLib.write("soopyAddonsData", "soopyaddonsbetafeaturesdata.json", JSON.stringify(this.featureSettingsData))
+
+ this.featureSettingsDataLastUpdated = false
+
+ logger.logMessage("Saved settings", 4)
+ }
+
+ loadDefaultFeatureSettings(){
+ Object.keys(this.featureMetas).forEach((feature)=>{
+ this.featureSettingsData[feature] = {
+ enabled: this.featureMetas[feature].defaultEnabled,
+ subSettings: {}
+ }
+ })
+
+ this.featureSettingsDataLastUpdated= true
+
+ logger.logMessage("Loaded default settings", 4)
+ }
+
+ ensureNewSettingsExist(){
+ Object.keys(this.featureMetas).forEach((feature)=>{
+ if(!this.featureSettingsData[feature]){
+ this.featureSettingsData[feature] = {
+ enabled: this.featureMetas[feature].defaultEnabled,
+ subSettings: {}
+ }
+ this.featureSettingsDataLastUpdated= true
+ logger.logMessage("Loaded default settings for " + feature, 4)
+ }
+ })
+ }
+
+ startCatchingEvent(event){
+ if(this.eventObjects[event]) return
+
+ //SBA compatability or something (removed)
+ // if(event === "renderOverlay"){
+ // let lastPartialTick = undefined
+ // this.eventObjects[event] = register(event, (...args)=>{
+ // let pTicks = Tessellator.getPartialTicks()
+ // if(pTicks !== lastPartialTick){
+ // lastPartialTick = pTicks
+ // this.triggerEvent(event, args)
+ // }
+ // })
+ // }else{
+ this.eventObjects[event] = register(event, (...args)=>{
+ this.triggerEvent(event, args)
+ })
+ //}
+
+ logger.logMessage("Registered " + event + " event", 4)
+ }
+
+ triggerEvent(event, args){
+ if(this.events[event])
+ try{
+ for(Event of Object.values(this.events[event])){
+ if(Event.context.enabled) Event.func.call(Event.context, ...args)
+ }
+ }catch(e){
+ logger.logMessage("Error in " + event + " event: " + JSON.stringify(e, undefined, 2), 2)
+ }
+ }
+ triggerSoopy(event, args){
+ if(this.soopyEventHandlers[event])
+ try{
+ for(Event of Object.values(this.soopyEventHandlers[event])){
+ if(Event.context.enabled) Event.func.call(Event.context, ...args)
+ }
+ }catch(e){
+ logger.logMessage("Error in soopy " + event + " event: " + JSON.stringify(e, undefined, 2), 2)
+ }
+ }
+
+ stopCatchingEvent(event){
+ if(!this.eventObjects[event]) return
+
+ this.eventObjects[event].unregister()
+ delete this.eventObjects[event]
+ delete this.events[event]
+ logger.logMessage("Unregistered " + event + " event", 4)
+ }
+
+ registerEvent(event, func, context){
+ if(!this.events[event]){
+ this.events[event] = []
+ this.startCatchingEvent(event)
+ }
+
+ let theEvent = {
+ func: func,
+ context: context,
+ id: this.lastEventId++,
+ event: event
+ }
+ this.events[event].push(theEvent)
+
+ return theEvent
+ }
+ registerSoopy(event, func, context){
+ if(!this.soopyEventHandlers[event]){
+ this.soopyEventHandlers[event] = []
+ }
+
+ let theEvent = {
+ func: func,
+ context: context,
+ id: this.lastSoopyEventId++,
+ event: event
+ }
+ this.soopyEventHandlers[event].push(theEvent)
+
+ return theEvent
+ }
+
+ registerChat(criteria, func, context){
+
+ let event = this.registerCustom("chat", func, context)
+
+ event.trigger.setChatCriteria(criteria)
+
+ return event
+ }
+ registerCommand(commandName, func, context){
+
+ let event = this.registerCustom("command", func, context)
+
+ event.trigger.setName(commandName)
+
+ return event
+ }
+ registerStep(isFps, interval, func, context){
+
+ let event = this.registerCustom("step", func, context)
+
+ event.trigger[isFps?"setFps":"setDelay"](interval) //TODO: make this group events similar to registerEvent()
+
+ return event
+ }
+
+ registerCustom(type, func, context){
+
+ let id = this.lastChatEventId++
+
+ this.customEvents[id] = {
+ func: func,
+ context: context,
+ trigger: register(type, (...args)=>{
+ try{
+ if(context.enabled) func.call(context, ...(args || []))
+ }catch(e){
+ logger.logMessage("Error in " + type + " event: " + JSON.stringify(e, undefined, 2), 2)
+ }
+ }),
+ id: id
+ }
+
+ return this.customEvents[id]
+ }
+
+ registerForge(event, func, context){
+ let id = this.lastForgeEventId++
+
+ this.forgeEvents[id] = {
+ func: func,
+ context: context,
+ trigger: registerForgeBase(event, (...args)=>{
+ try{
+ if(context.enabled) func.call(context, ...args)
+ }catch(e){
+ logger.logMessage("Error in " + event.class.toString() + " (forge) event: " + JSON.stringify(e, undefined, 2), 2)
+ }
+ }),
+ id: id
+ }
+
+ return this.forgeEvents[id]
+ }
+
+ unregisterForge(event){
+ unregisterForgeBase(this.forgeEvents[event.id].trigger)
+ delete this.forgeEvents[event.id]
+ }
+
+ unregisterCustom(event){
+ event.trigger.unregister()
+
+ delete this.customEvents[event.id]
+ }
+
+ unregisterEvent(event){
+ if(!this.events[event.event]) return
+
+ this.events[event.event] = this.events[event.event].filter((e)=>{
+ return e.id !== event.id
+ })
+
+ if(this.events[event.event].length === 0){
+ this.stopCatchingEvent(event.event)
+ delete this.events[event.event]
+ }
+ }
+
+ unregisterSoopy(event){
+ if(!this.soopyEventHandlers[event.event]) return
+
+ this.soopyEventHandlers[event.event] = this.soopyEventHandlers[event.event].filter((e)=>{
+ return e.id !== event.id
+ })
+
+ if(this.soopyEventHandlers[event.event].length === 0){
+ delete this.events[event.event]
+ }
+ }
+
+ loadFeatureMetas(){
+ featuresDir = new File("./config/ChatTriggers/modules/" + metadata.name + "/features")
+
+ featuresDir.list().forEach((pathName)=>{
+ if(pathName.includes(".")) return;
+
+ try{
+ let data = JSON.parse(FileLib.read( metadata.name + "/features/" + pathName,"metadata.json"))
+ if(data === null){
+ return;
+ }
+ data.id = pathName
+ this.featureMetas[pathName] = data
+ }catch(_){}
+ })
+ }
+
+ loadFeature(feature){ //run in seperate thread so onenable can do network requests
+ if(this.features[feature]) return
+
+ let LoadedFeature = require("../features/" + feature + "/index.js")
+
+ this.features[feature] = LoadedFeature
+
+ LoadedFeature.class.setId(feature)
+
+ LoadedFeature.class._onEnable(this)
+
+ logger.logMessage("Loaded feature " + feature, 3)
+
+ return this
+ }
+
+ unloadFeature(feature){ //run in seperate thread so ondisable can do network requests
+ if(!this.features[feature]) return
+
+ this.features[feature].class._onDisable()
+
+ delete this.features[feature]
+
+ logger.logMessage("Unloaded feature " + feature, 3)
+
+ return this
+ }
+
+ isFeatureLoaded(feature){
+ return !!this.features[feature]
+ }
+
+ getLoadedFeatures(){
+ return Object.keys(this.features)
+ }
+
+ createCustomEvent(eventId){
+ logger.logMessage("Registered custom " + eventId + " event", 4)
+
+ return {
+ trigger: (...args)=>{
+ this.triggerSoopy(eventId, args)
+ }
+ }
+ }
+}
+
+const featureManager = new FeatureManager();
+
+export default featureManager; \ No newline at end of file
diff --git a/features/agentlaiThings/hiddenRequirement.js b/features/agentlaiThings/hiddenRequirement.js
new file mode 100644
index 0000000..3c0408d
--- /dev/null
+++ b/features/agentlaiThings/hiddenRequirement.js
@@ -0,0 +1,8 @@
+let allowedUUIDS = [
+ "f2bcfe6aa54c4eb9b37156b4f1d20beb",
+ "dc8c39647b294e03ae9ed13ebd65dd29"
+]
+
+export default ()=>{
+ return !allowedUUIDS.includes(Player.getUUID().toString().replace(/-/g, ""))
+} \ No newline at end of file
diff --git a/features/agentlaiThings/index.js b/features/agentlaiThings/index.js
new file mode 100644
index 0000000..aa3fdc3
--- /dev/null
+++ b/features/agentlaiThings/index.js
@@ -0,0 +1,194 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import { SoopyGui, SoopyRenderEvent } from "../../../guimanager";
+import SoopyBoxElement from "../../../guimanager/GuiElement/SoopyBoxElement";
+import SoopyGuiElement from "../../../guimanager/GuiElement/SoopyGuiElement";
+import renderLibs from "../../../guimanager/renderLibs";
+import Feature from "../../featureClass/class";
+import ToggleSetting from "../settings/settingThings/toggle";
+
+class AgentThings extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.nearPlayerData = []
+ while(this.nearPlayerData.length < 100){this.nearPlayerData.push({})}
+
+ this.recordNearestPlayers = new ToggleSetting("Record nearby players", "You can then view this data with /nearplayers", true, "record_near_players", this)
+
+ this.registerStep(false, 1, this.step)
+
+ this.nearPlayersGui = new SoopyGui().setOpenCommand("nearplayers")
+
+ this.nearPlayersGuiBox = new SoopyBoxElement().setLocation(0.25, 0.25, 0.5, 0.5)
+ this.nearPlayersGui.element.addChild(this.nearPlayersGuiBox)
+
+ this.nearPlayersRenderElement = new SoopyGuiElement()
+ this.nearPlayersGuiBox.addChild(this.nearPlayersRenderElement)
+
+
+ let selected = undefined
+ let selectedDist = undefined
+ let lastXY = 0
+ this.nearPlayersRenderElement.addEvent(new SoopyRenderEvent().setHandler((mouseX, mouseY)=>{
+ let moved = lastXY !== mouseX+mouseY
+ lastXY = mouseX+mouseY
+
+ let x = this.nearPlayersRenderElement.location.getXExact()
+ let y = this.nearPlayersRenderElement.location.getYExact()
+ let width = this.nearPlayersRenderElement.location.getWidthExact()
+ let height = this.nearPlayersRenderElement.location.getHeightExact()
+
+ x+=width*0.125
+ y+=height*0.125
+ width*=0.75
+ height*=0.75
+
+ Renderer.drawLine(Renderer.color(0, 0, 0), x, y+height, x+width, y+height, 2)//bottom axis line
+ Renderer.drawLine(Renderer.color(0, 0, 0), x, y, x, y+height, 2)//left axis line
+
+ renderLibs.drawStringCentered("&0100s ago", x, y+height+6, Renderer.screen.getWidth()/1000)
+ renderLibs.drawStringCentered("&050s ago", x+width/2, y+height+6, Renderer.screen.getWidth()/1000)
+ renderLibs.drawStringCentered("&0Now", x+width, y+height+6, Renderer.screen.getWidth()/1000) //bottom axis markers
+
+
+ renderLibs.drawStringCenteredVertically("&025m away", x+3-Renderer.getStringWidth("&025m away")*Renderer.screen.getWidth()/1000, y, Renderer.screen.getWidth()/1000)
+ renderLibs.drawStringCenteredVertically("&020m away", x+3-Renderer.getStringWidth("&020m away")*Renderer.screen.getWidth()/1000, y+height/5*1, Renderer.screen.getWidth()/1000)
+ renderLibs.drawStringCenteredVertically("&010m away", x+3-Renderer.getStringWidth("&010m away")*Renderer.screen.getWidth()/1000, y+height/5*3, Renderer.screen.getWidth()/1000)
+ renderLibs.drawStringCenteredVertically("&00m away", x+3-Renderer.getStringWidth("&00m away")*Renderer.screen.getWidth()/1000, y+height-6, Renderer.screen.getWidth()/1000)
+
+ if(moved){
+ selected = undefined
+ selectedDist = undefined
+ }
+ let lastUuids = []
+ this.nearPlayerData.forEach((data, i)=>{
+ let newLastUuids = []
+ Object.keys(data).forEach(uuid=>{
+ newLastUuids.push(uuid)
+ lastUuids = lastUuids.filter(a=>a!==uuid)
+
+ let dist = data[uuid].distance
+ let oldDist = this.nearPlayerData[i-1]?.[uuid]?.distance || 25
+
+ let thisX = x+(i)/100*width
+ let thisY = y+height-(dist/25)*height
+
+ if(moved && (thisX-mouseX)**2 + (thisY-mouseY)**2 < 3){
+ selected = uuid
+ selectedDist = dist
+ }
+
+ if(i !== 0){
+ Renderer.drawLine(Renderer.color(0, 0, 0), x+(i-1)/100*width, y+height-(oldDist/25)*height, thisX, thisY, 1)
+ }
+
+ Renderer.drawRect(Renderer.color(0, 0, 0), thisX-1, thisY-1, 3, 3)
+ })
+
+ lastUuids.forEach(uuid=>{
+ let dist = 25
+ let oldDist = this.nearPlayerData[i-1]?.[uuid]?.distance || 25
+
+ let thisX = x+(i)/100*width
+ let thisY = y+height-(dist/25)*height
+
+ if(i !== 0){
+ Renderer.drawLine(Renderer.color(0, 0, 0), x+(i-1)/100*width, y+height-(oldDist/25)*height, thisX, thisY, 1)
+ }
+ })
+
+ lastUuids = newLastUuids
+ })
+
+ if(selected){
+ width = this.nearPlayersRenderElement.location.getWidthExact()
+ height = this.nearPlayersRenderElement.location.getHeightExact()
+
+ width*=0.75
+ height*=0.75
+
+ renderLibs.scizzorFast(0,0,Renderer.screen.getWidth(), Renderer.screen.getHeight())
+ let selectedIgn = undefined
+ let lastRenderedSelected = false
+ this.nearPlayerData.forEach((data, i)=>{
+ if(data[selected]){
+ lastRenderedSelected = true
+
+ selectedIgn = data[selected].name
+
+ let dist = data[selected].distance
+ let oldDist = this.nearPlayerData[i-1]?.[selected]?.distance || 25
+
+ let thisX = x+(i)/100*width
+ let thisY = y+height-(dist/25)*height
+
+ if(i !== 0){
+ Renderer.drawLine(Renderer.color(255, 0, 0), x+(i-1)/100*width, y+height-(oldDist/25)*height, thisX, thisY, 3)
+ }
+ }else{
+ if(lastRenderedSelected){
+ lastRenderedSelected = false
+
+ let dist = 25
+ let oldDist = this.nearPlayerData[i-1]?.[selected]?.distance || 25
+
+ let thisX = x+(i)/100*width
+ let thisY = y+height-(dist/25)*height
+
+ if(i !== 0){
+ Renderer.drawLine(Renderer.color(255, 0, 0), x+(i-1)/100*width, y+height-(oldDist/25)*height, thisX, thisY, 3)
+ }
+ }
+ }
+ })
+
+ let width = Math.max(Renderer.getStringWidth(selectedIgn)*2, Renderer.getStringWidth("Distance: " + selectedDist.toFixed(1)))+8
+ let height = 32
+ renderLibs.drawBox([255, 255, 255], mouseX+10, mouseY-height/2, width,height, 3)
+ renderLibs.drawString("&0" + selectedIgn, mouseX+14, mouseY-height/2+3, 2)
+ renderLibs.drawString("&0Distance: &7" + selectedDist.toFixed(1) , mouseX+14, mouseY-height/2+21, 1)
+ }
+
+ }))
+ }
+
+ step(){
+ if(!this.recordNearestPlayers.getValue()) return
+
+ let thisSecondPlayerData = {}
+ World.getAllPlayers().forEach(p=>{
+ let distSq = (p.getX()-Player.getX())**2+(p.getY()-Player.getY())**2+(p.getZ()-Player.getZ())**2
+
+ if(distSq < 25*25 && distSq !== 0){
+ thisSecondPlayerData[p.getUUID().toString()] = {name: p.getName(), distance: Math.sqrt(distSq)}
+ }
+ })
+
+ this.nearPlayerData.push(thisSecondPlayerData)
+
+ if(this.nearPlayerData.length > 100) this.nearPlayerData.shift()
+ }
+
+ initVariables(){
+ this.recordNearestPlayers = undefined
+ this.nearPlayerData = undefined
+ this.nearPlayersGui = undefined
+ this.nearPlayersGuiBox = undefined
+ this.nearPlayersRenderElement = undefined
+ }
+
+ onDisable(){
+ this.nearPlayersGui.delete()
+
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new AgentThings()
+} \ No newline at end of file
diff --git a/features/agentlaiThings/metadata.json b/features/agentlaiThings/metadata.json
new file mode 100644
index 0000000..e44e8fa
--- /dev/null
+++ b/features/agentlaiThings/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Agentlai Settings",
+ "description": "Settings only avalible to agentlai",
+ "isHidden": "hiddenRequirement.js",
+ "isTogglable": true,
+ "defaultEnabled": false,
+ "sortA": 0
+} \ No newline at end of file
diff --git a/features/betterGuis/index.js b/features/betterGuis/index.js
new file mode 100644
index 0000000..8600499
--- /dev/null
+++ b/features/betterGuis/index.js
@@ -0,0 +1,197 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import { drawBoxAtBlockNotVisThruWalls } from "../../utils/renderUtils";
+import ToggleSetting from "../settings/settingThings/toggle";
+
+class BetterGuis extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.replaceSbMenuClicks = new ToggleSetting("Improve Clicks on SBMENU", "This will change clicks to middle clicks, AND use commands where possible (eg /pets)", true, "sbmenu_clicks", this)
+ this.reliableSbMenuClicks = {getValue: ()=>false}//removed because hypixel fixed may add back later //new ToggleSetting("Make SBMENU clicks reliable", "This will delay clicks on sbmenu to time them so they dont get canceled", true, "sbmenu_time", this)
+
+ this.lastWindowId = 0
+ this.shouldHold = 10
+ this.clickSlot = -1
+ this.clickSlotTime = 0
+
+ this.middleClickGuis = [
+ "Your SkyBlock Profile",
+ "Your Skills",
+ "Farming Skill",
+ "Mining Skill",
+ "Heart of the Mountain",
+ "Combat Skill",
+ "Foraging Skill",
+ "Fishing Skill",
+ "Enchanting Skill",
+ "Alchemy Skill",
+ "Carpentry Skill",
+ "Runecrafting Skill",
+ "Social Skill",
+ "Taming Skill",
+ "Dungeoneering",
+ "Your Essence",
+ "Healer Class Perks",
+ "Mage Class Perks",
+ "Beserk Class Perks",
+ "Archer Class Perks",
+ "Tank Class Perks",
+ "Recipe Book",
+ "Trades",
+ "Quest Log",
+ "Quest Log (Completed)",
+ "Fairt Souls Guide",
+ "Dungeon Journals",
+ "Calendar and Events",
+ "Booster Cookie",
+ "Island Management",
+ "Toggle Potion Effects",
+ "Bank",
+ "Bank Account Upgrades",
+ "Co-op Bank Account",
+ "Bank Deposit",
+ "Bank Withdrawal",
+ "Personal Bank Account",
+ "Bazaar Orders",
+ "Co-op Bazaar Orders"
+ ]
+ this.middleClickStartsWith = [
+ "Bestiary",
+ "Private Island",
+ "Hub",
+ "Spiders Den",
+ "Blazing Fortress",
+ "The End",
+ "Deep Caverns",
+ "The Park",
+ "Spooky Festival",
+ "Catacombs",
+ "The Catacombs",
+ "Settings",
+ "Bazaar",
+ "Farming",
+ "Mining",
+ "Woods & Fishes",
+ "Oddities"
+ ]
+ this.middleClickEndsWith = [
+ "Recipe",
+ "Recipes",
+ "Pets",
+ "Collection",
+ "Active Effects"
+ ]
+
+ this.registerEvent("guiMouseClick", this.guiClicked)
+ this.registerStep(true, 10, this.step)
+ }
+
+ guiClicked(mouseX, mouseY, button, gui, event){
+ if(gui.class.toString()==="class net.minecraft.client.gui.inventory.GuiChest" && button===0 && this.replaceSbMenuClicks.getValue()){
+
+ let hoveredSlot = gui.getSlotUnderMouse()
+ if(!hoveredSlot) return
+
+ let hoveredSlotId = hoveredSlot.field_75222_d
+ // console.log(hoveredSlotId)
+ if(this.guiSlotClicked(ChatLib.removeFormatting(Player.getOpenedInventory().getName()), hoveredSlotId)){
+ cancel(event)
+ }
+ }
+ }
+
+ step(){
+ if(this.replaceSbMenuClicks.getValue()){
+ if(Player.getOpenedInventory().getName()==="SkyBlock Menu"){
+ if(this.lastWindowId === 0){
+ this.lastWindowId = Player.getOpenedInventory().getWindowId()
+ return;
+ }
+ if(Player.getOpenedInventory().getWindowId()!==this.lastWindowId){
+ this.lastWindowId = Player.getOpenedInventory().getWindowId()
+ this.shouldHold+= 10
+ if(Date.now()-this.clickSlotTime >1000){
+ this.clickSlot = -1
+ }
+ if(this.clickSlot && this.clickSlot != -1){
+ Player.getOpenedInventory().click(this.clickSlot, false, "MIDDLE")
+ this.clickSlot = -1
+ }
+ }else{
+ this.shouldHold--
+ }
+ }else{
+ this.lastWindowId =0
+ }
+ }
+ }
+
+ guiSlotClicked(inventoryName, slotId){
+ switch(inventoryName){
+ case "SkyBlock Menu":
+ switch(slotId){
+ case 30:
+ ChatLib.command("pets")
+ break
+ case 25:
+ ChatLib.command("storage")
+ break
+ default:
+ if(this.shouldHold>0 && this.reliableSbMenuClicks.getValue()){
+ this.clickSlot = slotId
+ this.clickSlotTime = Date.now()
+ }else{
+ Player.getOpenedInventory().click(slotId, false, "MIDDLE")
+ }
+ break;
+ }
+ return true
+ break
+ default:
+ if(this.middleClickGuis.includes(inventoryName)){
+ Player.getOpenedInventory().click(slotId, false, "MIDDLE")
+ return true
+ }
+ for(let thing of this.middleClickStartsWith){
+ if(inventoryName.startsWith(thing)){
+ Player.getOpenedInventory().click(slotId, false, "MIDDLE")
+ return true
+ }
+ }
+ for(let thing of this.middleClickEndsWith){
+ if(inventoryName.endsWith(thing)){
+ Player.getOpenedInventory().click(slotId, false, "MIDDLE")
+ return true
+ }
+ }
+ return false
+ break
+ }
+ }
+
+ initVariables(){
+ this.replaceSbMenuClicks = undefined
+ this.lastWindowId = undefined
+ this.shouldHold = undefined
+ this.clickSlot = undefined
+ this.clickSlotTime = undefined
+ this.reliableSbMenuClicks = undefined
+ this.middleClickGuis = undefined
+ this.middleClickStartsWith = undefined
+ this.middleClickEndsWith = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new BetterGuis()
+} \ No newline at end of file
diff --git a/features/betterGuis/metadata.json b/features/betterGuis/metadata.json
new file mode 100644
index 0000000..6aeed27
--- /dev/null
+++ b/features/betterGuis/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Better guis",
+ "description": "Edits to a bunch of guis to make them nicer to use",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/changeLogGUI/index.js b/features/changeLogGUI/index.js
new file mode 100644
index 0000000..f6595b0
--- /dev/null
+++ b/features/changeLogGUI/index.js
@@ -0,0 +1,210 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import SoopyGuiElement from "../../../guimanager/GuiElement/SoopyGuiElement";
+import SoopyTextElement from "../../../guimanager/GuiElement/SoopyTextElement";
+import Feature from "../../featureClass/class";
+import GuiPage from "../soopyGui/GuiPage";
+import SoopyMarkdownElement from '../../../guimanager/GuiElement/SoopyMarkdownElement.js';
+import metadata from "../../metadata";
+import ButtonWithArrow from "../../../guimanager/GuiElement/ButtonWithArrow";
+import SoopyMouseClickEvent from "../../../guimanager/EventListener/SoopyMouseClickEvent";
+import ProgressBar from "../../../guimanager/GuiElement/ProgressBar"
+import SoopyRenderEvent from "../../../guimanager/EventListener/SoopyRenderEvent"
+const File = Java.type("java.io.File")
+const URL = Java.type("java.net.URL");
+const PrintStream = Java.type("java.io.PrintStream");
+const Byte = Java.type("java.lang.Byte");
+
+class ChangeLogGui extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.ChangelogPage = new ChangelogPage()
+ }
+
+ initVariables(){
+ this.ChangelogPage = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+
+class ChangelogPage extends GuiPage {
+ constructor(){
+ super(9)
+
+ this.name = "Changelog"
+
+ this.pages = [this.newPage()]
+
+ this.changelogData = []
+ this.downloadableVersion = 0
+
+ let changelogTitle = new SoopyTextElement().setText("ยง0Changelog").setMaxTextScale(3).setLocation(0.1, 0.05, 0.8, 0.1)
+ this.pages[0].addChild(changelogTitle)
+
+ this.changelogArea = new SoopyGuiElement().setLocation(0.1, 0.2, 0.8, 0.8).setScrollable(true)
+ this.pages[0].addChild(this.changelogArea)
+
+ //Update confirmation page
+ this.updatingSidebar =new SoopyGuiElement().setLocation(0,0,1,1)
+ this.updatingSidebarConfirmPage = new SoopyGuiElement().setLocation(0,0,1,1)
+ this.updatingSidebar.addChild(this.updatingSidebarConfirmPage)
+
+ this.updateTitle = new SoopyTextElement().setText("ยง0Update to SoopyAddons ").setMaxTextScale(3).setLocation(0.1, 0.05, 0.8, 0.1)
+ this.updatingSidebarConfirmPage.addChild(this.updateTitle)
+
+ this.warningMessage = new SoopyMarkdownElement().setLocation(0.1, 0.2, 0.8, 0.8)
+ this.warningMessage.setText(`# NOTE
+Updating SoopyAddons through this method is downloading the code from _ยงcmy server_
+This means that there is _ยงcno_ third party that is double checking the code to ensure there is no virus in it.
+This is fine if you trust me to not put a virus in it, but if you dont you should instead wait for the update to be checked and verified by the chattriggers people.`)
+ this.updatingSidebarConfirmPage.addChild(this.warningMessage)
+
+ this.updateButton = new ButtonWithArrow().setText("ยง0Update").setLocation(0.3, 0.3+this.warningMessage.getHeight(), 0.4, 0.2)
+ this.updatingSidebarConfirmPage.addChild(this.updateButton)
+
+ this.updatingSidebarConfirmPage.setScrollable(true)
+
+ this.updateButton.addEvent(new SoopyRenderEvent().setHandler(()=>{
+ this.updateButton.location.location.y.set(0.3+this.warningMessage.getHeight(),0)
+ }))
+ this.updateButton.addEvent(new SoopyMouseClickEvent().setHandler(()=>{
+ this.downloadUpdate()
+ }))
+
+ this.updatingSidebarLoadingPage = new SoopyGuiElement().setLocation(1,0,1,1)
+ this.updatingSidebar.addChild(this.updatingSidebarLoadingPage)
+
+ let updatingTitle = new SoopyTextElement().setText("ยง0Updating...").setMaxTextScale(3).setLocation(0.1, 0.05, 0.8, 0.1)
+ this.updatingSidebarLoadingPage.addChild(updatingTitle)
+
+ this.progressBar = new ProgressBar().setLocation(0.1, 0.2, 0.8, 0.1)
+ this.updatingSidebarLoadingPage.addChild(this.progressBar)
+
+ this.currVersionId = metadata.versionId
+
+ this.finaliseLoading()
+ }
+
+ onOpen(){
+ new Thread(()=>{
+ let data = JSON.parse(FileLib.getUrlContent("http://soopymc.my.to/api/soopyv2/changelog.json"))
+
+ this.changelogData = data.changelog.reverse()
+
+ this.downloadableVersion = data.downloadableVersion
+
+ this.updateText()
+ }).start()
+ }
+
+ showConfirmUpdatePage(){
+ let version = ""
+ this.changelogData.forEach(data=>{
+
+ if(this.downloadableVersion === data.versionId && this.downloadableVersion > this.currVersionId){
+ //add button to download this version
+ version = data.version
+ }
+ })
+ this.updateTitle.setText("ยง0Update to SoopyAddons " + version)
+
+ this.updateButton.location.location.y.set(0.3+this.warningMessage.getHeight(),0)
+
+ this.openSidebarPage(this.updatingSidebar)
+ }
+
+ downloadUpdate(){
+ new Thread(()=>{
+ this.updatingSidebarConfirmPage.location.location.x.set(-1,500)
+ this.updatingSidebarLoadingPage.location.location.x.set(0,500)
+
+ new File("./config/ChatTriggers/modules/SoopyAddonsTempDownload").mkdir()
+
+ this.progressBar.setProgress(0.1)
+
+ this.urlToFile("http://soopymc.my.to/api/soopyv2/downloadLatest.zip", "./config/ChatTriggers/modules/SoopyAddonsTempDownload/SoopyAddons.zip", 10000, 20000)
+
+ this.progressBar.setProgress(0.5)
+
+ FileLib.unzip("./config/ChatTriggers/modules/SoopyAddonsTempDownload/SoopyAddons.zip", "./config/ChatTriggers/modules/SoopyAddonsTempDownload/SoopyAddons/")
+
+ this.progressBar.setProgress(0.75)
+
+ FileLib.deleteDirectory(new File("./config/ChatTriggers/modules/SoopyV2"))
+
+ this.progressBar.setProgress(0.9)
+
+ new File("./config/ChatTriggers/modules/SoopyAddonsTempDownload/SoopyAddons/SoopyV2").renameTo(new File("./config/ChatTriggers/modules/SoopyV2"))
+
+ FileLib.deleteDirectory(new File("./config/ChatTriggers/modules/SoopyAddonsTempDownload"))
+
+ this.progressBar.setProgress(1)
+
+ Client.currentGui.close()
+
+ ChatLib.command("ct load", true)
+ }).start()
+ }
+
+ urlToFile(url, destination, connecttimeout, readtimeout) {
+ const d = new File(destination);
+ d.getParentFile().mkdirs();
+ const connection = new URL(url).openConnection();
+ connection.setDoOutput(true);
+ connection.setConnectTimeout(connecttimeout);
+ connection.setReadTimeout(readtimeout);
+ const IS = connection.getInputStream();
+ const FilePS = new PrintStream(destination);
+ let buf = new Packages.java.lang.reflect.Array.newInstance(Byte.TYPE, 65536);
+ let len;
+ while ((len = IS.read(buf)) > 0) {
+ FilePS.write(buf, 0, len);
+ }
+ IS.close();
+ FilePS.close();
+ }
+
+ updateText(){
+ this.changelogArea.children = []
+
+ let height = 0
+
+ this.changelogData.forEach(data=>{
+
+ if(this.downloadableVersion === data.versionId && this.downloadableVersion > this.currVersionId){
+ //add button to download this version
+ let button = new ButtonWithArrow().setText("ยง0Download this version").setLocation(0.7, height, 0.3, 0.1)
+ this.changelogArea.addChild(button)
+
+ button.addEvent(new SoopyMouseClickEvent().setHandler(()=>{
+ this.showConfirmUpdatePage()
+ }))
+
+ height += 0.05
+ }
+
+ let changes = new SoopyMarkdownElement().setLocation(0,height, 1, 0)
+
+ this.changelogArea.addChild(changes)
+
+ changes.setText("# __" + data.version + "__" + (data.versionId === this.currVersionId?" ยง7Current" : "") + "\n" + data.description)
+
+ height += changes.getHeight()
+
+ height += 0.1
+ })
+ }
+}
+
+module.exports = {
+ class: new ChangeLogGui()
+} \ No newline at end of file
diff --git a/features/changeLogGUI/metadata.json b/features/changeLogGUI/metadata.json
new file mode 100644
index 0000000..8dfbe4b
--- /dev/null
+++ b/features/changeLogGUI/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "ChangeLog gui",
+ "description": "Gui for changelog",
+ "isHidden": true,
+ "isTogglable": false,
+ "defaultEnabled": true,
+ "sortA": 0
+} \ No newline at end of file
diff --git a/features/dataLoader/index.js b/features/dataLoader/index.js
new file mode 100644
index 0000000..41f0c61
--- /dev/null
+++ b/features/dataLoader/index.js
@@ -0,0 +1,128 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+
+class DataLoader extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.stats = {}
+
+ this.area = undefined
+
+ this.isInSkyblock = false
+
+ this.registerStep(true, 2, this.step)
+
+ this.registerEvent("worldLoad", this.worldLoad)
+
+ this.api_loaded_event = this.createCustomEvent("apiLoad")
+
+ this.lastApiData = {
+ "skyblock": undefined,
+ "player": undefined,
+ "skyblock_raw": undefined, //the _raw is loaded from hypixel api instead of soopy api
+ "player_raw": undefined
+ }
+
+ this.loadApi()
+ }
+
+ worldLoad(){
+ this.area = undefined
+ }
+
+ loadApi(){
+ let data = JSON.parse(FileLib.getUrlContent("http://soopymc.my.to/api/v2/player_skyblock/" + Player.getUUID().replace(/-/g, "")))
+
+ if(!data.success) return
+
+ this.api_loaded_event.trigger(data, "skyblock", true, true)
+ this.lastApiData.skyblock = data
+ }
+
+ loadApiData(type, soopyServer){
+ while(this.FeatureManager.features["globalSettings"] === undefined){
+ Thread.sleep(100)
+ }
+ let key = this.FeatureManager.features["globalSettings"].class.apiKeySetting.getValue()
+ if(!key) return
+
+ if(soopyServer){
+
+ }else{
+ if(type === "skyblock"){
+ let data = JSON.parse(FileLib.getUrlContent("https://api.hypixel.net/skyblock/profiles?key=" + key + "&uuid=" + Player.getUUID().replace(/-/g, "")))
+
+ if(!data.success) return
+
+ this.api_loaded_event.trigger(data, "skyblock", false, true)
+ this.lastApiData.skyblock_raw = data
+ }
+ }
+ }
+
+ step(){ //2fps
+ this.stats["Area"] = undefined
+ this.stats["Dungeon"] = undefined
+ TabList.getNames().forEach(n=>{
+ n = ChatLib.removeFormatting(n)
+ if(n.includes(": ")){
+ this.stats[n.split(": ")[0].trim()] = n.split(": ")[1].trim()
+ }
+ })
+ if(this.stats["Dungeon"]){
+ this.stats["Area"] = this.stats["Dungeon"]
+ this.isInDungeon = true
+ }else{
+ this.isInDungeon = false
+ }
+
+ this.dungeonFloor = undefined
+ Scoreboard.getLines().forEach(line=>{
+ let name = ChatLib.removeFormatting(line.getName()).replace(/[^A-z0-9 \:\(\)\.]/g, "")
+ if(this.isInDungeon){
+ if(name.includes("The Catacombs (")){
+ this.dungeonFloor = name.split("(")[1].split(")")[0].toUpperCase()
+ }
+ }
+ if(ChatLib.removeFormatting(line).startsWith(" โฃ ")){
+ this.areaFine = ChatLib.removeFormatting(line).split(" โฃ ")[1].replace(/[^A-z0-9 \:\(\)\.\-]/g, "")
+ }
+ if(name.startsWith("Purse: ")){
+ this.purse = parseInt(name.split("Purse: ")[1].split(" ")[0])
+ }
+ if(name.startsWith("Bits: ")){
+ this.bits = parseInt(name.split("Bits: ")[1].split(" ")[0])
+ }
+ })
+
+ this.isInSkyblock = Scoreboard.getTitle()?.removeFormatting().endsWith("SKYBLOCK")
+ this.area = this.stats["Area"]
+ }
+
+ initVariables(){
+ this.stats = undefined
+ this.isInDungeon = false
+
+ this.dungeonFloor = undefined
+ this.area = undefined
+ this.areaFine = undefined
+ this.bits = undefined
+ this.purse = undefined
+ this.lastApiData = undefined
+ this.isInSkyblock = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new DataLoader()
+} \ No newline at end of file
diff --git a/features/dataLoader/metadata.json b/features/dataLoader/metadata.json
new file mode 100644
index 0000000..4953f99
--- /dev/null
+++ b/features/dataLoader/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Data loader",
+ "description": "Loads data",
+ "isHidden": true,
+ "isTogglable": false,
+ "defaultEnabled": true,
+ "sortA": 0
+} \ No newline at end of file
diff --git a/features/dungeonMap/index.js b/features/dungeonMap/index.js
new file mode 100644
index 0000000..12bd471
--- /dev/null
+++ b/features/dungeonMap/index.js
@@ -0,0 +1,299 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+const BufferedImage = Java.type("java.awt.image.BufferedImage")
+
+class DungeonMap extends Feature {
+ constructor() {
+ super()
+ }
+
+ isInDungeon(){
+ return this.FeatureManager.features["dataLoader"].class.isInDungeon
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.defaultPlayerImage = new Image("skull-steve","https://cravatar.eu/avatar/dc8c39647b294e03ae9ed13ebd65dd29")
+ this.playerImages = {}
+ this.mapDataPlayers = {}
+ this.offset = []
+ this.mapScale = 1
+ this.puzzles = []
+ this.puzzlesTab = []
+ this.newPuzzlesTab = []
+
+ // this.registerEvent("tick", this.tick)
+ this.registerStep(true, 5, this.step)
+ this.registerEvent("renderOverlay", this.renderOverlay)
+ this.registerEvent("worldLoad", this.worldLoad)
+ }
+
+ worldLoad(){
+ this.mortLocation = undefined
+ this.playerImages = {}
+ this.mapDataPlayers = {}
+ this.offset = []
+ this.mapScale = 1
+ this.puzzles = []
+ this.puzzlesTab = []
+ this.newPuzzlesTab = []
+ }
+
+ renderOverlay(){
+ if(this.isInDungeon()){
+ if(this.mapImage){
+ this.mapImage.draw(50,50)
+
+ this.drawPlayersLocations()
+ }
+ }
+ }
+
+ drawPlayersLocations(){
+
+ let uuidToPlayer = {}
+ World.getAllPlayers().forEach(player=>{
+ uuidToPlayer[player.getUUID().toString()] = player
+ })
+
+ Object.keys(this.mapDataPlayers).forEach((uuid)=>{
+ let player = uuidToPlayer[uuid]
+ if(player){
+ this.mapDataPlayers[uuid] = {
+ x: player.getX()/this.mapScale,
+ y: player.getZ()/this.mapScale,
+ rot: player.getYaw()+180
+ }
+ }
+
+ Renderer.translate(this.mapDataPlayers[uuid].x+50+this.offset[0], this.mapDataPlayers[uuid].y+50+this.offset[1])
+ Renderer.rotate(this.mapDataPlayers[uuid].rot)
+ this.getImageForPlayer(uuid).draw(-5,-5, 10, 10)
+ })
+ }
+
+ step(){
+ TabList.getNames().forEach(name=>{
+ name = ChatLib.removeFormatting(name).split()
+ if(name[1].trim() === "[โœฆ]" && !name[0].includes("???") && !this.puzzlesTab.includes(name[0].trim())){
+ this.newPuzzlesTab.push(name[0].trim())
+ this.puzzlesTab.push(name[0].trim())
+ }
+ })
+
+ if(this.isInDungeon()){
+ this.updateMapImage()
+ }else{
+ this.mapImage = undefined
+ }
+ }
+
+ updateMapImage(){
+
+ World.getAllPlayers().forEach(player=>{
+ this.mapDataPlayers[Player.getUUID().toString()] = {
+ x: player.getX()/this.mapScale,
+ y: player.getZ()/this.mapScale,
+ rot: player.getYaw()+180
+ }
+ })
+ if(!this.mortLocation){
+ World.getAllEntities().forEach(entity=>{
+ if(ChatLib.removeFormatting(entity.getName()) === ("Mort")){
+ this.mortLocation = [
+ entity.getX(),
+ entity.getZ()
+ ]
+ }
+ })
+ }
+
+ let IMAGE_SIZE = 128
+ let newImage = new BufferedImage(IMAGE_SIZE,IMAGE_SIZE, BufferedImage.TYPE_INT_RGB)
+ let graphics = newImage.getGraphics()
+
+ graphics.fillRect(0,0,IMAGE_SIZE,IMAGE_SIZE)
+
+ let mapImage = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB)
+ let mapData
+ try {
+ let item = Player.getInventory().getStackInSlot(8)
+ mapData = item.getItem().func_77873_a(item.getItemStack(), World.getWorld()); // ItemStack.getItem().getMapData()
+ } catch (error) {
+ }
+ if(mapData){
+ mapData.field_76203_h.forEach((icon, vec4b) => {
+ let x = vec4b.func_176112_b()
+ let y = vec4b.func_176113_c()
+ let rot = vec4b.func_176111_d()
+
+ //wtf is this
+
+ //vec4b.func_176110_a()
+
+ let closestP = undefined
+ let closestDistance = Infinity
+ Object.keys(this.mapDataPlayers).forEach((uuid)=>{
+ if((x-this.mapDataPlayers[uuid].x)**2+(y-this.mapDataPlayers[uuid].y)**2 < closestDistance){
+ closestDistance = (x-this.mapDataPlayers[uuid].x)**2+(y-this.mapDataPlayers[uuid].y)**2
+ closestP = uuid
+ }
+ })
+
+ this.mapDataPlayers[closestP].x = x
+ this.mapDataPlayers[closestP].y = y
+ this.mapDataPlayers[closestP].rot = rot
+ });
+
+ // console.log("has map data poggies")
+ let bytes = mapData.field_76198_e
+
+ let x = 0
+ let y = 0
+ for(let i = 0; i < bytes.length; i++){
+ // console.log(bytes[i]/4)
+
+ if(bytes[i] !== 0){
+ let j = bytes[i]&255
+ let color = net.minecraft.block.material.MapColor.field_76281_a[j>>2].func_151643_b(j & 3);
+ mapImage.setRGB(x, y, color)
+ newImage.setRGB(x, y, color)
+ }
+ x++
+ if(x >= 128){
+ x=0
+ y++
+
+ if(y>128) break
+ }
+
+ // mapImage.getRGB()
+ }
+
+ // newImage.setRGB(0,0,128,128, ints, 0, 1)
+
+ // graphics. ()
+ //room size is 18
+ //4 inbetween
+
+ //finding room offsets
+ let roomOffsets
+ let mortLocationOnMap = undefined
+ let roomWidth = 0
+ for(let x = 0;x<128;x++){
+ for(let y = 0;y<128;y++){
+ if(bytes[x+y*128] === 30 && bytes[(x-1)+(y-1)*128] === 0){
+ roomWidth++
+ }
+ }
+ if(roomWidth > 0) break;
+ }
+
+ roomWidth = Math.floor(roomWidth*5/4)
+ this.mapScale = 32/roomWidth
+ for(let x = 0;x<128;x++){
+ for(let y = 0;y<128;y++){
+ if(bytes[x+y*128] === 30 && bytes[(x-1)+(y-1)*128] === 0){
+ if(roomOffsets) break;
+ roomOffsets = [x%roomWidth-3, y%roomWidth-3]
+
+ let dir = roomWidth/2-5/this.mapScale
+
+ //top
+ for(let i = 0;i<roomWidth;i++){
+ if(bytes[(i+x-3)+(y-3)*128] !== 0){
+ mortLocationOnMap = [x-2+roomWidth/2, y-2+roomWidth/2-dir]
+ break
+ }
+ }
+ if(mortLocationOnMap) break
+ //bottom
+ for(let i = 0;i<roomWidth;i++){
+ if(bytes[(i+x-3)+(y+roomWidth-3)*128] !== 0){
+ mortLocationOnMap = [x-2+roomWidth/2, y-2+roomWidth/2+dir]
+ break
+ }
+ }
+ //left
+ for(let i = 0;i<roomWidth;i++){
+ if(bytes[(x-3)+(i+y-3)*128] !== 0){
+ mortLocationOnMap = [x-2+roomWidth/2-dir, y-2+roomWidth/2]
+ break
+ }
+ }
+ //right
+ for(let i = 0;i<roomWidth;i++){
+ if(bytes[(x+roomWidth-3)+(i+y-3)*128] !== 0){
+ mortLocationOnMap = [x-2+roomWidth/2+dir, y-2+roomWidth/2]
+ }
+ }
+
+ break
+ }
+ if(bytes[x+y*128] === 66 && bytes[(x-1)+(y)*128] === 0 && bytes[(x)+(y-1)*128] === 0){
+ if(!this.puzzles[x+y*128]){
+ this.puzzles[x+y*128] = this.newPuzzlesTab.shift()
+ }
+ }
+ }
+
+ }
+
+ if(roomOffsets){
+ for(let x = 0;x<128;x++){
+ for(let y = 0;y<128;y++){
+ if((x-roomOffsets[0])%roomWidth===0 || (y-roomOffsets[1])%roomWidth===0){
+ newImage.setRGB(x, y, 0)
+ }
+ }
+ }
+
+ if(mortLocationOnMap && this.mortLocation){
+ this.offset = [mortLocationOnMap[0]-this.mortLocation[0]/this.mapScale, mortLocationOnMap[1]-this.mortLocation[1]/this.mapScale]
+ newImage.setRGB(mortLocationOnMap[0], mortLocationOnMap[1], Renderer.color(255, 0, 0))
+ }
+ }
+
+ // console.log(bytes[Math.floor(Player.getX()/this.mapScale+this.offset[0])+Math.floor(Player.getZ()/this.mapScale + this.offset[1])*128])
+
+ }
+
+
+
+ this.mapImage = new Image(newImage)
+ }
+
+ getImageForPlayer(uuid){
+ uuid = uuid.replace(/-/g, "")
+ if(this.playerImages[uuid] === "Loading") return this.defaultPlayerImage
+ if(this.playerImages[uuid]) return this.playerImages[uuid]
+
+ this.playerImages[uuid]= "Loading"
+ new Thread(()=>{
+ this.playerImages[uuid] = new Image("skull-" + uuid,"https://cravatar.eu/helmavatar/" + uuid)
+ }).start()
+ return this.defaultPlayerImage
+ }
+
+ initVariables(){
+ this.mapImage = undefined
+ this.defaultPlayerImage = undefined
+ this.mortLocation = undefined
+ this.playerImages = undefined
+ this.offset = undefined
+ this.puzzles = undefined
+ this.puzzlesTab = undefined
+ this.mapScale = undefined
+ this.newPuzzlesTab = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new DungeonMap()
+} \ No newline at end of file
diff --git a/features/dungeonMap/metadata.json b/features/dungeonMap/metadata.json
new file mode 100644
index 0000000..2d0df77
--- /dev/null
+++ b/features/dungeonMap/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Dungeon Map",
+ "description": "A really good dungeon map",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/dungeonSolvers/index.js b/features/dungeonSolvers/index.js
new file mode 100644
index 0000000..5ca1ed5
--- /dev/null
+++ b/features/dungeonSolvers/index.js
@@ -0,0 +1,153 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import * as renderUtils from "../../utils/renderUtils";
+import HudTextElement from "../hud/HudTextElement";
+import LocationSetting from "../settings/settingThings/location";
+import ToggleSetting from "../settings/settingThings/toggle";
+
+class DungeonSolvers extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.lividData = {}
+ this.lividData.lividColor = {
+ "Vendetta": "&f",
+ "Crossed": "&d",
+ "Hockey": "&c",
+ "Doctor": "&7",
+ "Frog": "&2",
+ "Smile": "&a",
+ "Scream": "&1",
+ "Purple": "&5",
+ "Arcade": "&e"
+ }
+ this.onWorldLoad()
+
+ this.lividFindEnabled = new ToggleSetting("Correct livid finder", "Finds the real livid to kill in the f5 boss fight", true, "livid_find_enabled", this)
+ this.lividFindHud = new ToggleSetting("Show Livid Hp", "Shows the nametag of the correct livid", true, "livid_hud_enabled", this).requires(this.lividFindEnabled)
+ this.lividHpElement = new HudTextElement()
+ .setToggleSetting(this.lividFindHud)
+ .setLocationSetting(new LocationSetting("Correct Livid Hp Location", "Allows you to edit the location of the correct livid hp text", "livid_hp_location", this, [10, 50, 1, 1])
+ .requires(this.lividFindHud)
+ .editTempText("ยงrยงe๏ดพ ยงcยงlLividยงr ยงa7Mยงcโค ยงe๏ดฟยงr"))
+
+ this.lividFindChat = new ToggleSetting("Say correct livid in chat", "Sends the correct livid in chat", false, "livid_chat_enabled", this).requires(this.lividFindEnabled)
+ this.lividFindBox = new ToggleSetting("Put a box around the correct livid", "This helps to locate it in the group", true, "livid_box_enabled", this).requires(this.lividFindEnabled)
+ this.lividFindNametags = new ToggleSetting("Hide the nametags of incorrect livids", "This helps to locate it in the group", true, "livid_nametags_enabled", this).requires(this.lividFindEnabled)
+
+ this.hudElements.push(this.lividHpElement)
+
+ this.registerStep(true, 2, this.step)
+ this.registerEvent("worldLoad", this.onWorldLoad)
+
+ this.registerEvent("renderOverlay", this.renderHud)
+ this.registerEvent("renderWorld", this.renderWorld)
+ this.registerEvent("renderEntity", this.renderEntity)
+ }
+
+ renderWorld(ticks){
+ if(this.lividFindBox.getValue()){
+ if(this.lividData.correctLividEntity){
+ renderUtils.drawBoxAtEntity(this.lividData.correctLividEntity, 255, 0, 0, 0.75, -2, ticks)
+ }
+ }
+ }
+
+ renderEntity(entity, position, ticks, event){
+ if(this.lividFindNametags.getValue()){
+ if(this.lividData.correctLividEntity){
+ if(entity.getName().includes("Livid") && entity.getName().includes("โค") && entity.getUUID() !== this.lividData.correctLividEntity.getUUID()){
+ cancel(event)
+ }
+ }
+ }
+ }
+
+ renderHud(){
+ for(let element of this.hudElements){
+ element.render()
+ }
+ }
+
+ onWorldLoad(){
+ this.lividData.correctLividColor = undefined
+ this.lividData.correctLividColorHP = undefined
+ this.lividData.sayLividColors = []
+ this.lividData.sayLividColors2 = []
+ this.lividData.correctLividEntity = undefined
+ this.lividHpElement && this.lividHpElement.setText("")
+ }
+
+ step(){ //2fps
+ if(this.lividFindEnabled.getValue() && (this.FeatureManager.features["dataLoader"].class.dungeonFloor === "F5")){ //TODO: fix on M5
+ World.getAllEntities().forEach(entity => {
+ let entityName = entity.getName()
+ if (/(?:Vendetta|Crossed|Hockey|Doctor|Frog|Smile|Scream|Purple|Arcade) Livid/g.test(entityName)) {
+ let lividName = entityName.replace(" Livid", "")
+
+ if (!this.lividData.sayLividColors2.includes(lividName)) {
+ this.lividData.sayLividColors2.push(lividName)
+ if (this.lividData.sayLividColors2.length === 1) {
+ this.lividData.correctLividColor = lividName
+ }
+ if (this.lividData.sayLividColors2.length === 9) {
+ if(this.lividFindChat.getValue()){
+ ChatLib.chat("Correct livid is: " + this.lividData.lividColor[lividName] + lividName)
+ }
+ this.lividData.correctLividColor = lividName
+ }
+ }
+ return;
+ }
+ if (entityName.includes("Livid") && entityName.includes("โค")) {
+ if (!this.lividData.sayLividColors.includes(entityName.substr(0, 3))) {
+ this.lividData.sayLividColors.push(entityName.substr(0, 3))
+ if (this.lividData.sayLividColors.length === 9) {
+ this.lividData.correctLividColorHP = entityName.substr(0, 3)
+ }
+ if (this.lividData.sayLividColors.length === 1) {
+ this.lividData.correctLividColorHP = entityName.substr(0, 3)
+ }
+ }
+
+ if (this.lividData.sayLividColors.length === 1) {
+ if (entityName.includes("Livid") && entityName.includes("โค")) {
+ this.lividHpElement.setText(entityName)
+ }
+ } else {
+ if (this.lividData.correctLividColorHP !== undefined) {
+ // if (this.lividData.correctLividColor === "Arcade") {
+ // this.lividHpElement.setText("Unknown Health (Yellow Livid)")
+ // } else {
+ if (entityName.includes(this.lividData.correctLividColorHP)) {
+ this.lividHpElement.setText(entityName)
+ this.lividData.correctLividEntity = entity
+ }
+ // }
+ }
+ }
+
+ }
+ })
+ }
+ }
+
+ initVariables(){
+ this.lividFindEnabled = undefined
+ this.lividData = undefined
+ this.hudElements = []
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new DungeonSolvers()
+} \ No newline at end of file
diff --git a/features/dungeonSolvers/metadata.json b/features/dungeonSolvers/metadata.json
new file mode 100644
index 0000000..6467a3a
--- /dev/null
+++ b/features/dungeonSolvers/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Dungeon Solvers",
+ "description": "Solvers for dungeon things",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/events/index.js b/features/events/index.js
new file mode 100644
index 0000000..24615e4
--- /dev/null
+++ b/features/events/index.js
@@ -0,0 +1,463 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import { drawBoxAtBlock, drawBoxAtBlockNotVisThruWalls, drawLine } from "../../utils/renderUtils";
+import { calculateDistance, calculateDistanceQuick, fastestPathThrough } from "../../utils/utils";
+import HudTextElement from "../hud/HudTextElement";
+import LocationSetting from "../settings/settingThings/location";
+import ToggleSetting from "../settings/settingThings/toggle";
+
+class Events extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.burrialData = {
+ points: [],
+ locations: [],
+ historicalLocations: []
+ }
+ this.lastRequestTime = 0
+ this.nextUpdateApprox = -1
+ this.lastRequest = 0
+ this.potentialParticleLocs = {}
+ this.showingWaypoints = false
+ this.lastPath = []
+ this.updatingPath = false
+ this.hudElements = []
+ this.lastPathCords = undefined
+
+ this.burrialWaypointsEnabled = new ToggleSetting("Burrial waypoints", "Show waypoints for burrials during the diana event", true, "burrial_waypoints", this)
+ this.burrialWaypointsPath = new ToggleSetting("Pathfind waypoints", "Calculate a path thru all the waypoints", true, "burrial_waypoints_path", this).requires(this.burrialWaypointsEnabled)
+ this.burrialWaypointsNearest = new ToggleSetting("Show nearest using pathfinding", "Use pathfinding to highlight the next burrial instead of disance", true, "burrial_nearest_path", this).requires(this.burrialWaypointsEnabled)
+
+ this.updateTimerEnabled = new ToggleSetting("Show API update timer", "Shows a countdown till the burrial waypoints will be next updated", true, "burrial_timer", this).requires(this.burrialWaypointsEnabled)
+ this.updateTimer = new HudTextElement()
+ .setToggleSetting(this.updateTimerEnabled)
+ .setLocationSetting(new LocationSetting("Timer Location", "Allows you to edit the location of the timer", "burrial_timer_location", this, [10, 50, 1, 1])
+ .requires(this.burrialWaypointsEnabled)
+ .editTempText("&6Update&7> &f100s"))
+ this.hudElements.push(this.updateTimer)
+
+ this.registerEvent("worldLoad", this.worldLoad)
+ this.registerEvent("spawnParticle", this.spawnParticle)
+ this.registerEvent("renderWorld", this.renderWorld)
+ this.registerEvent("renderOverlay", this.renderOverlay)
+ this.registerStep(true, 2, this.step)
+ this.registerStep(false, 5, this.step_5s)
+ this.registerSoopy("apiLoad", this.apiLoad)
+ }
+
+ renderOverlay(){
+ for(let element of this.hudElements){
+ element.render()
+ }
+ }
+
+ renderWorld(ticks){
+ if(this.showingWaypoints && this.lastPathCords && this.burrialWaypointsPath.getValue()){
+ let startPoint = [Player.getPlayer().field_70142_S+Player.getPlayer().field_70159_w*ticks,
+ Player.getPlayer().field_70137_T+Player.getPlayer().field_70181_x*ticks,
+ Player.getPlayer().field_70136_U+Player.getPlayer().field_70179_y*ticks]
+
+ let lastPoint = startPoint || [0,0,0]
+
+ this.lastPathCords.forEach((point)=>{
+ drawLine(...lastPoint,...point,255,255,0)
+
+ lastPoint = point
+ })
+ }
+ if(this.showingWaypoints){
+ let sorted = [...this.burrialData.locations]
+ sorted.sort((a,b)=>{
+ let aDist = calculateDistanceQuick([Player.getX(),Player.getY(),Player.getZ()],[a.x+0.5,a.y+2.5,a.z+0.5])
+ let bDist = calculateDistanceQuick([Player.getX(),Player.getY(),Player.getZ()],[b.x+0.5,b.y+2.5,b.z+0.5])
+
+ return bDist-aDist
+ })
+ sorted.forEach((loc,i)=>{
+
+ let typeReplace = [
+ "Start",
+ "Mob",
+ "Treasure",
+ "Finish",
+ "Unknown"
+ ]
+ if(!loc.clicked){
+ blue = false
+ if(loc.lastPing && Date.now()-loc.lastPing < 500){
+ blue = true
+ }
+ drawBoxAtBlock(loc.x, loc.y,loc.z,0,blue?100:255,blue?255:0)
+ }
+ if(loc.fromApi){
+ Tessellator.drawString(
+ "(" + (loc.chain+1) + "/4) " + typeReplace[loc.type] + " BURRIAL (" + Math.round(calculateDistance([Player.getX(),Player.getY(),Player.getZ()],[loc.x+0.5,loc.y+2.5,loc.z+0.5])) + "m)",
+ loc.x+0.5,
+ loc.y+1.5,
+ loc.z+0.5,
+ loc.clicked? 65280:(loc.nearest?16711680:6579300), true, loc.clicked? 0.04:(loc.nearest?1:0.5), !loc.clicked
+ );
+ }else{
+ Tessellator.drawString(
+ typeReplace[loc.type] + " BURRIAL (" + Math.round(calculateDistance([Player.getX(),Player.getY(),Player.getZ()],[loc.x+0.5,loc.y+2.5,loc.z+0.5])) + "m)",
+ loc.x+0.5,
+ loc.y+1.5,
+ loc.z+0.5,
+ loc.clicked? 65280:(loc.nearest?16711680:6579300), true, loc.clicked? 0.04:(loc.nearest?1:0.5), !loc.clicked
+ );
+ }
+ })
+ }
+ }
+
+ step(){
+
+ hasDianaShovle = false
+ let slots = [0, 1, 2, 3, 4, 5, 6, 7, 8]
+
+ slots.forEach(a=>{
+ item = Player.getInventory().getStackInSlot(a)
+ if(ChatLib.removeFormatting(item.getName()) === "Ancestral Spade"){
+ hasDianaShovle = true
+ }
+ })
+
+ let showingWaypointsNew = hasDianaShovle && this.FeatureManager.features["dataLoader"].class.area === "Hub" && this.burrialWaypointsEnabled.getValue()
+
+ if(!this.showingWaypoints && showingWaypointsNew){
+ this.loadApi()
+ }else{
+ if(Date.now()-this.nextUpdateApprox > 0 && this.nextUpdateApprox > 0 && Date.now()-this.lastRequest>5000){
+ this.nextUpdateApprox = -2
+ this.loadApi()
+ }
+ }
+
+ this.showingWaypoints = showingWaypointsNew
+
+ if(this.showingWaypoints){
+
+ this.updateTimer.setText("&6Update&7> &f" + (this.nextUpdateApprox===-1?"Updating...":(this.nextUpdateApprox===-2?"Loading...":Math.ceil((this.nextUpdateApprox-Date.now())/1000) + "s")))
+ }else{
+ this.updateTimer.setText("")
+ }
+
+ }
+ step_5s(){
+ if(this.showingWaypoints){
+ if(this.burrialWaypointsPath.getValue() || this.burrialWaypointsNearest.getValue()){
+ new Thread(()=>{
+ this.updateBurrialPath()
+ }).start()
+ }
+ }
+ }
+
+ worldLoad(){
+ this.burrialData.points = []
+ this.burrialData.locations = []
+ this.burrialData.historicalLocations = []
+
+ this.showingWaypoints = false
+ }
+
+ loadApi(){
+ new Thread(()=>{
+ this.FeatureManager.features["dataLoader"].class.loadApiData("skyblock", false)
+ }).start()
+ }
+
+ apiLoad(data, dataType, isSoopyServer, isLatest){
+ if(!this.showingWaypoints) return;
+ if(isSoopyServer || dataType !== "skyblock" || !isLatest) return
+ this.lastRequest = Date.now()
+
+ let profileData = data.profiles[0].members[Player.getUUID().toString().replace(/-/g,"")]
+
+ data.profiles.forEach((prof)=>{
+ if((prof.members[Player.getUUID().toString().replace(/-/g,"")].last_save || 0) > (profileData.last_save || 0)){
+ profileData = prof.members[Player.getUUID().toString().replace(/-/g,"")]
+ }
+ })
+ this.nextUpdateApprox = profileData.last_save+173000
+ this.nextUpdateApprox += 2000 //incase ur pc time is behind
+ if(profileData.last_save === this.lastRequestTime){
+ return
+ }
+ this.lastRequestTime = profileData.last_save
+
+ let locsAccessed = []
+ let newLocs = []
+ profileData.griffin.burrows.forEach((burrow)=>{
+ let pushed = false
+ this.burrialData.locations.forEach((loc, i)=>{
+ if((loc.fromApi || loc.clicked) && loc.x + "," + loc.y + "," + loc.z === burrow.x + "," + burrow.y + "," + burrow.z){
+ newLocs.push(loc)
+ pushed = true
+ locsAccessed.push(i)
+ }
+ })
+ this.burrialData.historicalLocations.forEach((loc)=>{
+ if(loc.x + "," + loc.y + "," + loc.z === burrow.x + "," + burrow.y + "," + burrow.z){
+ pushed = true
+ }
+ })
+ if(!pushed){
+ burrow.clicked = false
+ burrow.fromApi = true
+ newLocs.push(burrow)
+ }
+ })
+
+ this.burrialData.locations.forEach((loc)=>{
+ if(!loc.fromApi){
+ let found = false
+ newLocs.forEach((burrow)=>{
+ if(loc.x + "," + loc.y + "," + loc.z === burrow.x + "," + burrow.y + "," + burrow.z){
+ found = true
+ }
+ })
+
+ if(!found){
+ newLocs.push(loc)
+ }
+ }
+ })
+
+ this.burrialData.locations = newLocs
+ this.updateBurrialPath()
+ }
+
+
+ spawnParticle(particle, type, event){
+ if(this.showingWaypoints){
+ let foundEnchant = false
+ let foundCrit = false
+ let foundStep = false
+ let isMob = undefined
+
+ if(particle.toString().startsWith('EntityEnchantmentTableParticleFX, ')){
+ foundEnchant = true
+ }
+ else if(particle.toString().startsWith('EntityCrit2FX, ')){
+ foundCrit = true
+
+ isMob = particle.getUnderlyingEntity().func_70534_d() > 0.5 //mob)
+ }
+ else if(particle.toString().startsWith('EntityFootStepFX, ')){
+ foundStep = true
+ }
+ else if(particle.toString().startsWith('EntityCritFX, ')){
+
+ let locstr = Math.floor(particle.getX()) + "," + Math.floor(particle.getY()-1) + "," + Math.floor(particle.getZ())
+
+ let removed = false
+ this.burrialData.locations.filter((loc, i)=>{
+ if(!loc.clicked && loc.x + "," + loc.y + "," + loc.z === locstr){
+ loc.clicked = true
+ removed = true
+
+ this.lastPathCords.shift()
+ }
+ })
+ if(!removed) return;
+ this.burrialData.locations = this.burrialData.locations.filter(a=>{
+ if(!a.clicked) return true
+ if(calculateDistanceQuick([a.x,a.y,a.z],[Player.getX(),Player.getY(),Player.getZ()]) < 15*15) return true;
+
+ this.burrialData.historicalLocations.unshift(a)
+
+ return false
+ })
+ if(this.burrialData.historicalLocations.length > 10) this.burrialData.historicalLocations.pop()
+
+ return;
+ }
+
+ if(!foundEnchant && !foundCrit && !foundStep) return;
+
+ let locstr = Math.floor(particle.getX()) + "," + Math.floor(particle.getY()-1) + "," + Math.floor(particle.getZ())
+ let locarr = [Math.floor(particle.getX()), Math.floor(particle.getY()-1), Math.floor(particle.getZ())]
+
+ let found = false
+
+ this.burrialData.locations.forEach((loc)=>{
+ if(loc.x + "," + loc.y + "," + loc.z === locstr){
+ found = true
+ loc.lastPing = Date.now()
+ }
+ if((loc.x+1) + "," + loc.y + "," + loc.z === locstr){
+ found = true
+ loc.lastPing = Date.now()
+ }
+ if((loc.x-1) + "," + loc.y + "," + loc.z === locstr){
+ found = true
+ loc.lastPing = Date.now()
+ }
+ if(loc.x + "," + loc.y + "," + (loc.z+1) === locstr){
+ found = true
+ loc.lastPing = Date.now()
+ }
+ if(loc.x + "," + loc.y + "," + (loc.z-1) === locstr){
+ found = true
+ loc.lastPing = Date.now()
+ }
+ })
+ if(this.burrialData.historicalLocations){
+ this.burrialData.historicalLocations.forEach((loc)=>{
+ if(loc.x + "," + loc.y + "," + loc.z === locstr){
+ found = true
+ }
+ })
+ }
+
+ if(found) return;
+
+
+ if (!this.potentialParticleLocs[locstr])this.potentialParticleLocs[locstr] = {enchant: 0, crit: 0, step: 0, isMob: 0, timestamp: Date.now()}
+
+ if(foundEnchant) this.potentialParticleLocs[locstr].enchant++
+ if(foundCrit) this.potentialParticleLocs[locstr].crit++
+ if(foundStep) this.potentialParticleLocs[locstr].step++
+ if(foundCrit && isMob) this.potentialParticleLocs[locstr].isMob++
+ if(foundCrit && !isMob) this.potentialParticleLocs[locstr].isMob--
+
+ this.potentialParticleLocs[locstr].timestamp = Date.now()
+
+ if(this.potentialParticleLocs[locstr].enchant > 2 && this.potentialParticleLocs[locstr].step > 5){
+ this.burrialData.locations.push({
+ "x": locarr[0],
+ "y": locarr[1],
+ "z": locarr[2],
+ "type": this.potentialParticleLocs[locstr].isMob > 1? 1:(this.potentialParticleLocs[locstr].crit > this.potentialParticleLocs[locstr].enchant/20?0:2),
+ "tier": -1,
+ "chain": -1,
+ "fromApi": false
+ })
+ new Thread(()=>{
+ this.updateBurrialPath()
+ }).start()
+ }
+ }
+ }
+
+ burrialClicked(){
+ if(!this.showingWaypoints) return
+
+ let nearestBurriali = undefined
+ let nearestBurrialDist = Infinity
+
+ this.burrialData.locations.forEach((loc, i)=>{
+ let dist = calculateDistanceQuick([loc.x, loc.y, loc.z], [Player.getX(), Player.getY(), Player.getZ()])
+ if(dist < nearestBurrialDist){
+ nearestBurrialDist = dist
+ nearestBurriali = i
+ }
+ })
+
+ if(nearestBurriali === undefined) return;
+ this.burrialData.locations[nearestBurriali].clicked = true
+
+ this.burrialData.locations = this.burrialData.locations.filter(a=>{
+ if(!a.clicked) return true
+ if(calculateDistanceQuick([a.x,a.y,a.z],[Player.getX(),Player.getY(),Player.getZ()]) < 15*15) return true;
+
+ this.burrialData.historicalLocations.unshift(a)
+
+ return false
+ })
+ if(this.burrialData.historicalLocations.length > 10) this.burrialData.historicalLocations.pop()
+ this.lastPathCords.shift()
+ new Thread(()=>{
+ this.updateBurrialPath()
+ }).start()
+ }
+ updateBurrialPath(){
+ if(this.burrialWaypointsPath.getValue() || this.burrialWaypointsNearest.getValue()){
+ let startPoint = [Player.getX(),Player.getY(),Player.getZ()]
+
+ let points = this.burrialData.locations.filter((a)=>{return !a.clicked}).map((a)=>{return [a.x+0.5,a.y+1.3,a.z+0.5]})
+
+ if(points.length !== 0){
+
+ if(points.length >= 10){
+ this.lastPath = undefined
+ this.lastPathCords = undefined
+ }else{
+ this.lastPath = fastestPathThrough(startPoint,points)
+
+ this.lastPathCords = []
+ this.lastPath.forEach(a=>{
+ this.lastPathCords.push(points[a])
+ })
+ }
+
+ if(this.lastPath.length === 0){
+ this.lastPath = undefined
+ this.lastPathCords = undefined
+ }
+
+ }else{
+ this.lastPath = undefined
+ this.lastPathCords = undefined
+ }
+ }
+
+
+ if(this.showingWaypoints){
+ if(this.burrialWaypointsNearest.getValue()){
+ let trueI = 0
+ this.burrialData.locations.forEach((loc,i)=>{
+ if(!loc.clicked && trueI === this.lastPath[0]){
+ this.burrialData.locations[i].nearest = true
+ }else{
+ this.burrialData.locations[i].nearest = false
+ }
+
+ if(!loc.clicked) trueI++
+ })
+ }else{
+ let closestBurrialI = 0
+ let closestBurrialDist = Infinity
+
+ this.burrialData.locations.forEach((loc,i)=>{
+ let dist = calculateDistanceQuick([loc.x,loc.y,loc.z],[Player.getX(),Player.getY(),Player.getZ()])
+ if(dist < closestBurrialDist){
+ closestBurrialDist = dist
+ closestBurrialI = i
+ }
+ this.burrialData.locations[i].nearest = false
+ })
+
+ this.burrialData.locations[closestBurrialI].nearest = true
+ }
+ }
+ }
+
+ initVariables(){
+ this.burrialData = undefined
+ this.lastRequestTime = undefined
+ this.nextUpdateApprox = undefined
+ this.lastRequest = undefined
+ this.potentialParticleLocs = undefined
+ this.showingWaypoints = undefined
+ this.lastPath = undefined
+ this.updatingPath = undefined
+ this.hudElements = undefined
+ this.lastPathCords = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new Events()
+} \ No newline at end of file
diff --git a/features/events/metadata.json b/features/events/metadata.json
new file mode 100644
index 0000000..363625f
--- /dev/null
+++ b/features/events/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Events",
+ "description": "Features for various events (eg diana)",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/featureBase/index.js b/features/featureBase/index.js
new file mode 100644
index 0000000..7a11945
--- /dev/null
+++ b/features/featureBase/index.js
@@ -0,0 +1,24 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+
+class FeatureBase extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+ }
+
+ initVariables(){
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new FeatureBase()
+} \ No newline at end of file
diff --git a/features/featureBase/metadata.json b/features/featureBase/metadata.json
new file mode 100644
index 0000000..a6b411d
--- /dev/null
+++ b/features/featureBase/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Feature Base",
+ "description": "Copy paste and edit this to create a new feature",
+ "isHidden": true,
+ "isTogglable": false,
+ "defaultEnabled": false,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/fragBot/index.js b/features/fragBot/index.js
new file mode 100644
index 0000000..67bb9a3
--- /dev/null
+++ b/features/fragBot/index.js
@@ -0,0 +1,84 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import SettingBase from "../settings/settingThings/settingBase";
+import ToggleSetting from "../settings/settingThings/toggle";
+
+class FragBot extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.hostingFragBot = false
+ this.fragBotQueue = []
+ this.commandQueue = []
+
+ new SettingBase("To host a fragbot use /fragbot", "", undefined, "host_fragbot_info", this)
+
+ this.uploadToWebsite = new ToggleSetting("Advertise fragbot status", "Will show up as a fragbot in other peoples fragbot lists", true, "advertise_fragbot", this)
+
+ this.registerCommand("fragbot", this.fragbotCommand)
+
+ this.registerStep(false, 5, this.step)
+ this.registerStep(true, 2, this.step2)
+
+ this.registerChat("&9&m-----------------------------&r&9\n&r${player} &r&ehas invited you to join their party!\n&r&eYou have &r&c60 &r&eseconds to accept. &r&6Click here to join!&r&9\n&r&9&m-----------------------------&r", this.recievedPartyInvite)
+ }
+
+ step(){
+ if(!this.hostingFragBot) return
+
+ if(this.fragBotQueue.length > 0){
+ let player = this.fragBotQueue.shift()
+ if(player){
+ this.commandQueue.push("/party leave")
+
+ this.commandQueue.push("/party accept " + player)
+ }
+ }
+ }
+ step2(){
+ if(!this.hostingFragBot) return
+
+ if(this.commandQueue.length > 0){
+ let command = this.commandQueue.shift()
+ if(command){
+ ChatLib.say(command)
+ }
+ }
+ }
+ recievedPartyInvite(player){
+ if(!this.hostingFragBot) return
+
+ player = ChatLib.removeFormatting(player).split(" ").pop()
+
+ this.fragBotQueue.push(player)
+ }
+
+ fragbotCommand(...args){
+ if(this.hostingFragBot){
+ this.hostingFragBot = false
+ ChatLib.chat("&aFragbot has been disabled")
+ }else{
+ this.hostingFragBot = true
+ ChatLib.chat("&aNow acting as a fragbot, run /fragbot again to disable")
+ }
+ }
+
+ initVariables(){
+ this.hostingFragBot = undefined
+ this.fragBotQueue = undefined
+ this.commandQueue = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new FragBot()
+} \ No newline at end of file
diff --git a/features/fragBot/metadata.json b/features/fragBot/metadata.json
new file mode 100644
index 0000000..5aaa6a9
--- /dev/null
+++ b/features/fragBot/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Frag Bot",
+ "description": "Features to assist using fragbots, or to host one",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/friendsGUI/index.js b/features/friendsGUI/index.js
new file mode 100644
index 0000000..c7a370e
--- /dev/null
+++ b/features/friendsGUI/index.js
@@ -0,0 +1,154 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import { SoopyRenderEvent } from "../../../guimanager";
+import ButtonWithArrowAndDescription from "../../../guimanager/GuiElement/ButtonWithArrowAndDescription";
+import SoopyGuiElement from "../../../guimanager/GuiElement/SoopyGuiElement";
+import SoopyTextElement from "../../../guimanager/GuiElement/SoopyTextElement";
+import BoxWithLoading from "../../../guimanager/GuiElement/BoxWithLoading";
+import Feature from "../../featureClass/class";
+import GuiPage from "../soopyGui/GuiPage";
+
+class FriendsGui extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.GuiPage = new SettingPage()
+
+ this.registerChat("&9-----------------------------------------------------&r&9${*}&r&9 ${*} &6Friends (Page ${pagenum} of ${maxpages})${friendslist}&r&9-----------------------------------------------------&r", (...args)=>{this.GuiPage.friendListMessageEvent.call(this.GuiPage, ...args)})
+ this.registerStep(true, 5, ()=>{this.GuiPage.regenGuiElements.call(this.GuiPage)})
+ }
+
+ initVariables(){
+ this.GuiPage = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+
+class SettingPage extends GuiPage {
+ constructor(){
+ super(7)
+
+ this.name = "Friends"
+
+ this.pages = [this.newPage()]
+
+ this.lastRender = 0
+
+ this.pages[0].addEvent(new SoopyRenderEvent().setHandler(()=>{this.lastRender = Date.now()}))
+
+
+ let friendsTitle = new SoopyTextElement().setText("ยง0Friends").setMaxTextScale(3).setLocation(0.1, 0.05, 0.8, 0.1)
+ this.pages[0].addChild(friendsTitle)
+
+ this.friendsArea = new SoopyGuiElement().setLocation(0.1, 0.2, 0.8, 0.8).setScrollable(true)
+ this.pages[0].addChild(this.friendsArea)
+
+ this.loadedFriends = {}
+
+ this.pageCount = undefined
+
+ this.heightPerFriend = 0.2
+ this.loadingElement = new BoxWithLoading().setLocation(0,0, 1, 0.175)
+ this.lastLoadedPage = 0
+ this.maxLoadedPage = 0
+
+ this.loadingElement.addEvent(new SoopyRenderEvent().setHandler(()=>{
+ if(Date.now()-this.lastLoadedPage > 1000){
+ this.loadFriendPage(this.maxLoadedPage+1)
+ }
+ }))
+
+
+ this.finaliseLoading()
+ }
+
+ friendListMessageEvent(pagenum, maxpages, friendslist, event){
+ if(Date.now()-this.lastRender < 1000){
+ cancel(event)
+ // console.log("Canceling")
+ }else{
+ return
+ }
+
+ this.maxLoadedPage = parseInt(pagenum)
+
+ this.pageCount = parseInt(maxpages)
+
+ friendslist = friendslist.replace(/\-\>newLine\<\-/g,"\n").split("\n")
+ friendslist.shift()
+
+ friendslist.pop()
+
+ friendslist.forEach((line, i) => {
+ let [name, location] = line.split(" is ")
+
+ if(location){
+ if(this.loadedFriends[ChatLib.removeFormatting(name)]){
+ this.loadedFriends[ChatLib.removeFormatting(name)].nameWithFormatting = name
+ this.loadedFriends[ChatLib.removeFormatting(name)].currentGame = location.replace("in ", "").replace("currently offline", "&cCurrently offline")
+ this.loadedFriends[ChatLib.removeFormatting(name)].element.setText(name).setDesc("&7"+location.replace("in ", "").replace("currently offline", "&cCurrently offline"))
+ }else{
+ this.loadedFriends[ChatLib.removeFormatting(name)] = {
+ nameWithFormatting: name,
+ currentGame: location.replace("in ", "").replace("currently offline", "&cCurrently offline"),
+ element: new ButtonWithArrowAndDescription().setLocation(0, this.heightPerFriend*Object.keys(this.loadedFriends).length+1, 1, this.heightPerFriend*0.875).setText(name).setDesc("&7"+location.replace("in ", "").replace("currently offline", "&cCurrently offline"))
+ }
+ this.loadedFriends[ChatLib.removeFormatting(name)].element.location.location.y.set(this.heightPerFriend*Object.keys(this.loadedFriends).length-this.heightPerFriend, 500+100*i)
+ }
+ }
+ });
+
+ this.regenGuiElements()
+ }
+
+ regenGuiElements(){
+ if(Date.now()-this.lastRender < 1000){
+ this.friendsArea.children = []
+
+ let scroll = this.friendsArea.location.scroll.y.get()/this.friendsArea.location.getHeightExact()
+
+ let minY = -scroll-this.heightPerFriend*0.875-0.5
+
+ let maxY = -scroll+1.5
+
+ Object.keys(this.loadedFriends).forEach((ign, i)=>{
+ if(this.loadedFriends[ign].element.location.location.y.get() > minY && this.loadedFriends[ign].element.location.location.y.get() < maxY){
+ this.friendsArea.addChild(this.loadedFriends[ign].element)
+ }
+ })
+
+ if(this.maxLoadedPage !== this.pageCount){
+ this.loadingElement.location.location.y.set(this.heightPerFriend*Object.keys(this.loadedFriends).length, 500)
+ this.friendsArea.addChild(this.loadingElement)
+ }
+ }
+ }
+
+ loadFriendPage(page){
+ ChatLib.command("friends list " + page)
+ this.lastLoadedPage = Date.now()
+ }
+
+ onOpen(){
+ this.loadedFriends = {}
+ this.pageCount = undefined
+ this.lastLoadedPage = 0
+ this.maxLoadedPage = 0
+
+ this.regenGuiElements()
+
+ this.loadFriendPage(1)
+ }
+}
+
+module.exports = {
+ class: new FriendsGui()
+} \ No newline at end of file
diff --git a/features/friendsGUI/metadata.json b/features/friendsGUI/metadata.json
new file mode 100644
index 0000000..b7b1f6f
--- /dev/null
+++ b/features/friendsGUI/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Friends gui",
+ "description": "Gui for friends",
+ "isHidden": true,
+ "isTogglable": false,
+ "defaultEnabled": true,
+ "sortA": 0
+} \ No newline at end of file
diff --git a/features/globalSettings/index.js b/features/globalSettings/index.js
new file mode 100644
index 0000000..5b459f9
--- /dev/null
+++ b/features/globalSettings/index.js
@@ -0,0 +1,51 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import ButtonSetting from "../settings/settingThings/button";
+import TextSetting from "../settings/settingThings/textSetting";
+
+class Hud extends Feature {
+ constructor() {
+ super()
+
+ this.initVariables()
+ }
+
+ initVariables(){
+
+ this.apiKeySetting = undefined
+ }
+
+ onEnable(){
+ this.apiKeySetting = new TextSetting("Api Key", "Your hypixel api key", "", "api_key", this, "Run /api new to load", true)
+ this.verifyApiKey = new ButtonSetting("Verify api key", "Click this to make sure the api key is working", "verify_key", this, "Click!", this.verifyKey, undefined)
+
+ this.registerChat("&aYour new API key is &r&b${key}&r", this.newKey)
+ }
+
+ verifyKey(){
+ if(this.module.apiKeySetting.getValue() == ""){
+ ChatLib.chat("&c[SOOPY V2] You need to set an api key first!")
+ return
+ }
+
+ var url = "https://api.hypixel.net/key?key=" + this.module.apiKeySetting.getValue()
+
+ ChatLib.chat("&c[SOOPY V2] The rest of checking is yet to be coded!")
+ }
+
+ newKey(key){
+ ChatLib.chat("&c[SOOPY V2] Copied api key!")
+ this.apiKeySetting.setValue(key)
+ }
+
+ onDisable(){
+ this.fpsEnabledSetting.delete()
+
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new Hud()
+} \ No newline at end of file
diff --git a/features/globalSettings/metadata.json b/features/globalSettings/metadata.json
new file mode 100644
index 0000000..801716f
--- /dev/null
+++ b/features/globalSettings/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Global Settings",
+ "description": "Settings that are applied globally",
+ "isHidden": false,
+ "isTogglable": false,
+ "defaultEnabled": true,
+ "sortA": 5
+} \ No newline at end of file
diff --git a/features/hud/HudTextElement.js b/features/hud/HudTextElement.js
new file mode 100644
index 0000000..582b778
--- /dev/null
+++ b/features/hud/HudTextElement.js
@@ -0,0 +1,77 @@
+class HudTextElement{
+ constructor(){
+ this.text = ""
+
+ this.toggleSetting = undefined
+ this.locationSetting = undefined
+
+ this.blackText = "&0" + ChatLib.removeFormatting(this.text)
+
+ this.editTempTimeV = 0
+ this.editTempTextV = undefined
+
+ this.tempDisableTime = 0
+ }
+
+ setText(text){
+ this.text = text
+
+ if(this.locationSetting.shadowType === 2){
+ this.blackText = "&0" + ChatLib.removeFormatting(text)
+ }
+ return this
+ }
+ setToggleSetting(setting){
+ this.toggleSetting = setting
+ return this
+ }
+ setLocationSetting(setting){
+ this.locationSetting = setting
+ setting.setParent(this)
+ return this
+ }
+
+ render(){
+ if(this.toggleSetting && !this.toggleSetting.getValue() || !this.locationSetting) return
+ if(Date.now()-this.tempDisableTime < 100) return
+
+ this.renderRaw()
+ }
+
+ getText(){
+ let text = this.text
+ let blackText = this.blackText
+ if(this.editTempTextV && Date.now()-this.editTempTimeV < 100){
+ text = this.editTempTextV
+ blackText = "&0" + ChatLib.removeFormatting(text)
+ }
+ return [text.split("\n"), blackText.split("\n")]
+ }
+
+ renderRaw(){
+ let [text, blackText] = this.getText()
+
+ text.forEach((line, i)=>{
+ Renderer.scale(this.locationSetting.scale, this.locationSetting.scale)
+ switch(this.locationSetting.shadowType){
+ case 0:
+ Renderer.drawString(line, this.locationSetting.x/this.locationSetting.scale, this.locationSetting.y/this.locationSetting.scale +9*i)
+ break;
+ case 1:
+ Renderer.drawStringWithShadow(line, this.locationSetting.x/this.locationSetting.scale, this.locationSetting.y/this.locationSetting.scale +9*i)
+ break;
+ case 2:
+ Renderer.drawString(blackText[i], (this.locationSetting.x+1)/this.locationSetting.scale, this.locationSetting.y/this.locationSetting.scale +9*i)
+ Renderer.drawString(blackText[i], (this.locationSetting.x-1)/this.locationSetting.scale, this.locationSetting.y/this.locationSetting.scale +9*i)
+ Renderer.drawString(blackText[i], this.locationSetting.x/this.locationSetting.scale, (this.locationSetting.y+1)/this.locationSetting.scale +9*i)
+ Renderer.drawString(blackText[i], this.locationSetting.x/this.locationSetting.scale, (this.locationSetting.y-1)/this.locationSetting.scale +9*i)
+
+ Renderer.drawString(line, this.locationSetting.x/this.locationSetting.scale, this.locationSetting.y/this.locationSetting.scale +9*i)
+ break;
+ }
+ })
+ Renderer.scale(1, 1)
+ }
+}
+
+export default HudTextElement \ No newline at end of file
diff --git a/features/hud/index.js b/features/hud/index.js
new file mode 100644
index 0000000..b6db8c6
--- /dev/null
+++ b/features/hud/index.js
@@ -0,0 +1,315 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import SoopyNumber from "../../../guimanager/Classes/SoopyNumber";
+import Feature from "../../featureClass/class";
+import LocationSetting from "../settings/settingThings/location";
+import ToggleSetting from "../settings/settingThings/toggle";
+import HudTextElement from "./HudTextElement";
+
+class Hud extends Feature {
+ constructor() {
+ super()
+
+ this.initVariables()
+ }
+
+ initVariables(){
+
+ this.hudElements = []
+
+ this.fpsElement = undefined
+ this.cpsElement = undefined
+ this.soulflowElement = undefined
+ this.petElement = undefined
+ this.fpsEnabledSetting = undefined
+ this.cpsEnabledSetting = undefined
+ this.soulflowEnabledSetting = undefined
+ this.soulflowShowWarningSetting=undefined
+ this.soulflowShowWhen0Setting=undefined
+ this.petEnabledSetting=undefined
+ this.fpsFastSetting = undefined
+ this.fpsLowSetting = undefined
+ this.cpsSeperate =undefined
+ this.cpsIncludeRight =undefined
+
+ this.petLevels = undefined
+
+ this.lastTickTime = undefined
+ this.framesSince = undefined
+ this.lastframe = undefined
+ this.Instant = undefined
+
+ this.lastFrameRates = undefined
+
+ this.fps = undefined
+ this.lowFps = undefined
+
+ this.slowestFrameTime = undefined
+ this.lastFrameRatesS = undefined
+ this.numberUtils = undefined
+
+ this.petText = undefined
+ }
+
+ onEnable(){
+
+ this.numberUtils = require("../../utils/numberUtils.js")
+
+ this.fpsEnabledSetting = new ToggleSetting("Fps enabled", "Whether the fps is rendered onto the screen", true, "fps_enabled", this)
+ this.fpsFastSetting = new ToggleSetting("Fast fps update", "Whether the fps is updated fast instead of once per second", true, "fps_fast", this).requires(this.fpsEnabledSetting)
+ this.fpsLowSetting = new ToggleSetting("Low fps display", "Display the minumum frame time next to fps (usefull for finding framedrops)", true, "fps_low", this).requires(this.fpsFastSetting)
+
+ this.fpsElement = new HudTextElement()
+ .setToggleSetting(this.fpsEnabledSetting)
+ .setLocationSetting(new LocationSetting("Show FPS", "Allows you to edit the location of the fps text", "fps_location", this, [10, 10, 1, 1])
+ .requires(this.fpsEnabledSetting))
+ this.hudElements.push(this.fpsElement)
+
+ this.cpsEnabledSetting = new ToggleSetting("Show CPS", "Whether the cps is rendered onto the screen", true, "cps_enabled", this)
+ this.cpsIncludeRight = new ToggleSetting("CPS include right click", "Whether right clicks are shown in the CPS", true, "cps_right", this).requires(this.cpsEnabledSetting)
+ this.cpsSeperate = new ToggleSetting("CPS seperate right", "Seperates right clicks from left clicks", true, "cps_seperate", this).requires(this.cpsIncludeRight)
+
+ this.cpsElement = new HudTextElement()
+ .setToggleSetting(this.cpsEnabledSetting)
+ .setLocationSetting(new LocationSetting("Cps Location", "Allows you to edit the location of the cps text", "cps_location", this, [10, 20, 1, 1])
+ .requires(this.cpsEnabledSetting))
+ this.hudElements.push(this.cpsElement)
+
+ this.petEnabledSetting = new ToggleSetting("Show Current Pet", "Whether the current pet is rendered onto the screen", true, "pet_enabled", this)
+ this.petElement = new HudTextElement()
+ .setToggleSetting(this.petEnabledSetting)
+ .setLocationSetting(new LocationSetting("Pet Location", "Allows you to edit the location of the pet text", "pet_location", this, [10, 30, 1, 1])
+ .requires(this.petEnabledSetting))
+ this.hudElements.push(this.petElement)
+
+ this.soulflowEnabledSetting = new ToggleSetting("Show Soulflow", "Whether the soulflow count is rendered onto the screen", true, "soulflow_enabled", this)
+ this.soulflowShowWarningSetting = new ToggleSetting("Show no Talisman Warning", "Shows a warning if you dont have a soulflow talis in ur inv", true, "soulflow_notalis_warning", this).requires(this.soulflowEnabledSetting)
+ this.soulflowShowWhen0Setting = new ToggleSetting("Show When 0 Soulflow", "If this is off it wont render when you have 0 soulflow", true, "soulflow_showwhen_0", this).requires(this.soulflowEnabledSetting)
+ this.soulflowElement = new HudTextElement()
+ .setToggleSetting(this.soulflowEnabledSetting)
+ .setLocationSetting(new LocationSetting("Soulflow Location", "Allows you to edit the location of the soulflow text", "soulflow_location", this, [10, 40, 1, 1])
+ .requires(this.soulflowEnabledSetting))
+ this.hudElements.push(this.soulflowElement)
+
+ // this.showDragonDamages = new ToggleSetting("Show dragon damages", "This will render the top 3 damages + your damage during a dragon fight", true, "dragon_dmg_enable", this).requires(this.soulflowEnabledSetting)
+ // this.dragonDamageElement = new HudTextElement()
+ // .setToggleSetting(this.showDragonDamages)
+ // .setLocationSetting(new LocationSetting("Damage Location", "Allows you to edit the location of the damage leaderboard", "dragon_dmg_location", this, [50, 40, 1, 1])
+ // .requires(this.showDragonDamages)
+ // .editTempText("Test Line 1\nTest line 2\nTest line 3\nTest line 4 (longer KEKW)"))
+ // this.hudElements.push(this.dragonDamageElement)
+
+ this.step_5second()
+
+ this.lastTickTime = 0
+ this.framesSince = 0
+ this.lastframe = 0
+ this.slowestFrameTime = 0
+
+ this.lastFrameRates = [0,0,0,0,0,0,0,0,0,0]
+ this.lastFrameRatesS = [0,0,0,0,0,0,0,0,0,0]
+
+ this.Instant = Java.type("java.time.Instant");
+
+ this.fps = new SoopyNumber(0)
+ this.lowFps = new SoopyNumber(0)
+
+ this.registerEvent("renderOverlay", this.renderHud)
+ this.registerStep(true, 5, this.step)
+ this.registerStep(false, 5, this.step_5second)
+ this.registerEvent("renderWorld", this.renderWorld)
+
+ this.petLevels = {}
+ this.petText = ""
+ this.registerChat("&cAutopet &eequipped your ${pet}&e! &a&lVIEW RULE&r", (pet)=>{
+ this.petElement.setText("&6Pet&7> "+pet)
+ this.petText = "&6Pet&7> "+pet
+ })
+ this.registerChat("&r&aYou summoned your &r${pet}&r&a!&r", (pet)=>{
+ this.petElement.setText("&6Pet&7> &7[Lvl " + this.petLevels[pet.replace("&", "ยง")] +"] "+pet)
+ this.petText = "&6Pet&7> &7[Lvl " + this.petLevels[pet.replace("&", "ยง")] +"] "+pet
+ })
+ this.registerChat("&r&aYou despawned your &r${*}&r&a!&r", ()=>{
+ this.petElement.setText("&6Pet&7> &cNone")
+ this.petText = "&6Pet&7> &cNone"
+ })
+ this.registerChat("&r&aYour &r${pet} &r&alevelled up to level &r&9${level}&r&a!&r", (pet, level)=>{
+ this.petElement.setText("&6Pet&7> &7[Lvl " + level +"] "+pet)
+ this.petText = "&6Pet&7> &7[Lvl " + level +"] "+pet
+ })
+
+ this.registerSoopy("apiLoad", this.apiLoad)
+ if(this.FeatureManager.features["dataLoader"].class.lastApiData.skyblock){
+ this.apiLoad(this.FeatureManager.features["dataLoader"].class.lastApiData.skyblock, "skyblock", true, true)
+ }
+ }
+
+ onDisable(){
+ this.fpsEnabledSetting.delete()
+ this.fpsFastSetting.delete()
+ this.cpsEnabledSetting.delete()
+
+ this.initVariables()
+ }
+
+ renderHud(){
+
+ if(this.fpsFastSetting.getValue()){
+ if(this.fpsLowSetting.getValue()){
+ this.fpsElement.setText("&6Fps&7> &f" + Math.round(this.fps.get()) + "&7/" + Math.round(this.lowFps.get()))
+ }else{
+ this.fpsElement.setText("&6Fps&7> &f" + Math.round(this.fps.get()))
+ }
+ }
+
+ for(let element of this.hudElements){
+ element.render()
+ }
+ }
+
+ renderWorld(){
+ if(!this.fpsEnabledSetting.getValue() ||!this.fpsFastSetting.getValue()) return
+ this.framesSince++
+
+ let instant = this.Instant.now()
+ let time = instant.getEpochSecond() + (instant.getNano() / 1000000000);
+
+ let thisframeTime = time - this.lastFrame
+
+ if(thisframeTime > this.slowestFrameTime){
+ this.slowestFrameTime = thisframeTime
+ }
+
+ this.lastFrame = time
+ }
+
+ step(){
+ let fps = 0
+
+ if(this.fpsEnabledSetting.getValue() && this.fpsFastSetting.getValue()){
+ //set fps to fast fps
+ // console.log(`${this.framesSince} ${this.lastFrame-this.lastTickTime}`)
+ fps = this.framesSince/(this.lastFrame-this.lastTickTime)
+ if(this.lastFrame===this.lastTickTime) fps = 0
+ this.lastTickTime = this.lastFrame
+ this.framesSince = 0
+
+ this.lastFrameRates.push(fps)
+ this.lastFrameRates.shift()
+
+ if(this.slowestFrameTime > 0){
+ this.lastFrameRatesS.push(1/this.slowestFrameTime)
+ }else{
+ this.lastFrameRatesS.push(0)
+ }
+ this.lastFrameRatesS.shift()
+ this.slowestFrameTime = 0
+
+ fps = this.lastFrameRates.reduce((a,b) => a+b, 0) / this.lastFrameRates.length
+ this.fps.set(fps, 200)
+ if(this.fpsLowSetting.getValue()) this.lowFps.set(this.lastFrameRatesS.reduce((a,b) => a+b, 0) / this.lastFrameRatesS.length, 200)
+ }else{
+ fps = Client.getFPS()
+ this.fpsElement.setText("&6Fps&7> &f" + fps)
+ }
+
+ let cpsText = CPS.getLeftClicksAverage()
+
+ if(this.cpsIncludeRight.getValue()){
+ if(this.cpsSeperate.getValue()){
+ cpsText += "&7 | &f" + CPS.getRightClicksAverage()
+ }else{
+ cpsText += CPS.getRightClicksAverage()
+ }
+ }
+ this.cpsElement.setText("&6Cps&7> &f" + cpsText)
+
+ //Scan opened inventory for all pet levels
+ if(Player && Player.getOpenedInventory().getName().includes(") Pets")){
+ let inv = Player.getOpenedInventory().getItems()
+ for(let i = 0; i < inv.length; i++){
+ if(inv[i]!=null && inv[i].getName().includes("[Lvl ")){
+ let level = inv[i].getName().split(" ")[1].replace("]", "")
+ if(!this.petLevels[inv[i].getName().split("] ")[1]] || this.petLevels[inv[i].getName().split("] ")[1]] < level)this.petLevels[inv[i].getName().split("] ")[1]] = level
+ }
+ }
+ }
+ }
+
+ step_5second(){
+ if(!this.soulflowEnabledSetting.getValue()) return
+ if(!Player.getPlayer()) return
+ if(!Player.getInventory()) return
+
+ if(!this.FeatureManager.features["dataLoader"].class.isInSkyblock){
+ this.soulflowElement.setText("")
+ this.petElement.setText("")
+ return
+ }else{
+ this.petElement.setText(this.petText)
+ }
+
+ let soulflowCount = 0
+ let hasSoulflowItem = false
+ Player.getInventory().getItems().forEach(i=>{
+
+ let id;
+ try{
+ id = i.getNBT().getCompoundTag("tag").getCompoundTag("ExtraAttributes").getString("id")
+ }catch(e){}
+
+ if(id === "SOULFLOW_PILE" || id=== "SOULFLOW_BATTERY" || id === "SOULFLOW_SUPERCELL"){
+ //soulflowCount
+ i.getLore().forEach(line=>{
+ if(line.startsWith("ยง5ยงoยง7Internalized:")){
+ hasSoulflowItem = true
+ soulflowCount = parseInt(ChatLib.removeFormatting(line).substr("Internalized: ".length).split("โธŽ")[0].replace(/,/g,""))
+ }
+ })
+ }
+ })
+ if(!hasSoulflowItem){
+ if(this.soulflowShowWarningSetting.getValue()){
+ this.soulflowElement.setText("&6Soulflow&7> &cNO TALISMAN")
+ }else{
+ this.soulflowElement.setText("")
+ }
+ return;
+ }
+ if(soulflowCount > 0 && !this.soulflowShowWhen0Setting.getValue()){
+ this.soulflowElement.setText("")
+ return;
+ }
+
+ this.soulflowElement.setText("&6Soulflow&7> &f" + this.numberUtils.numberWithCommas(soulflowCount))
+ }
+
+ apiLoad(data, dataType, isSoopyServer, isLatest){
+ if(!isSoopyServer || !isLatest) return
+ if(dataType !== "skyblock") return
+
+ let pet = data.data.profiles[data.data.stats.currentProfileId].members[Player.getUUID().replace(/-/g, "")].selectedPet
+
+ if(!pet){
+ this.petElement.setText("&6Pet&7> &cNone")
+ this.petText = "&6Pet&7> &cNone"
+ return;
+ }
+
+ let petTierColor = {
+ "COMMON": "&f",
+ "UNCOMMON": "&a",
+ "RARE": "&9",
+ "EPIC": "&5",
+ "LEGENDARY": "&6",
+ "MYTHIC": "&d"
+ }
+
+ this.petElement.setText("&6Pet&7> &7[Lvl " + pet.level.level + "] " + petTierColor[pet.tier] + pet.name)
+ this.petText = "&6Pet&7> &7[Lvl " + pet.level.level + "] " + petTierColor[pet.tier] + pet.name
+ }
+}
+
+module.exports = {
+ class: new Hud()
+} \ No newline at end of file
diff --git a/features/hud/metadata.json b/features/hud/metadata.json
new file mode 100644
index 0000000..67f5671
--- /dev/null
+++ b/features/hud/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Hud",
+ "description": "Adds a HUD with custom gui elements like fps, cps, equipped pet ect",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 2
+} \ No newline at end of file
diff --git a/features/improvements/index.js b/features/improvements/index.js
new file mode 100644
index 0000000..4d003f3
--- /dev/null
+++ b/features/improvements/index.js
@@ -0,0 +1,36 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import ToggleSetting from "../settings/settingThings/toggle";
+
+class Improvements extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.betterLineBreaks = new ToggleSetting("Better line breaks", "Changes all dashed lines (-------) in chat into solid lines", true, "better_line_breaks", this)
+
+ this.registerChat("${color}-----------------------------------------------------&r", (color, event)=>{
+ if(this.betterLineBreaks.getValue()){
+ cancel(event)
+ ChatLib.chat(color + "&m" + ChatLib.getChatBreak(" ") + "&r");
+ }
+ }).trigger.triggerIfCanceled(false)
+ }
+
+
+ initVariables(){
+ this.betterLineBreaks = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new Improvements()
+} \ No newline at end of file
diff --git a/features/improvements/metadata.json b/features/improvements/metadata.json
new file mode 100644
index 0000000..4e147f6
--- /dev/null
+++ b/features/improvements/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Improvements",
+ "description": "A bunch of random features that improve aspects of the game",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/mining/index.js b/features/mining/index.js
new file mode 100644
index 0000000..457ab84
--- /dev/null
+++ b/features/mining/index.js
@@ -0,0 +1,201 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import { drawBoxAtBlockNotVisThruWalls } from "../../utils/renderUtils";
+import * as stringUtils from "../../utils/stringUtils";
+import * as utils from "../../utils/utils";
+import HudTextElement from "../hud/HudTextElement";
+import LocationSetting from "../settings/settingThings/location";
+import ToggleSetting from "../settings/settingThings/toggle";
+
+class Mining extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.hudElements = []
+
+ this.guessBalHp = new ToggleSetting("Show bal hp", "This will attempt to show remaining hp on bal (guessed)", true, "bal_hp", this)
+ this.balRespawnHud = new ToggleSetting("Show bal hp and respawn timer HUD", "This will add a HUD element with bal's hp and respawn timer", true, "bal_hud", this)
+ this.balHudElement = new HudTextElement()
+ .setToggleSetting(this.balRespawnHud)
+ .setLocationSetting(new LocationSetting("HUD Location", "Allows you to edit the location of the bal info", "bal_hud_location", this, [10, 50, 1, 1])
+ .requires(this.balRespawnHud)
+ .editTempText("&6Bal&7> &f172/250"))
+ this.hudElements.push(this.balHudElement)
+ this.balPetAlert = new ToggleSetting("Bal pet alert", "Give a notification on screen + sound when you drop a bal pet", true, "bal_alert", this)
+
+ this.showUnlockedGemstoneSlots = new ToggleSetting("Show unlocked gemstone slots", "This will show the unlocked gemstone slots of an item.", true, "unlocked_gemstones", this)
+ this.showContainedGemstoneSlots = new ToggleSetting("Show contained gemstones", "This will show the gemstones currently on an item.", true, "contained_gemstones", this)
+
+ this.seenBalDamages = []
+ this.balHP = 250
+ this.lastBalAlive = 0
+ this.balDespawnDebounce = 0
+ this.armourstandClass = Java.type("net.minecraft.entity.item.EntityArmorStand").class
+
+ this.registerEvent("renderOverlay", this.renderOverlay)
+ this.registerEvent("tick", this.tick)
+ this.registerEvent("itemTooltip", this.itemTooltipEvent)
+ this.registerEvent("renderWorld", this.renderWorld)
+
+ this.registerChat("&r&c&o&r&6&lRARE DROP! &r&eA Bal Pet dropped!&r", ()=>{
+ if(this.balPetAlert.getValue()){
+ World.playSound("random.orb", 1, 1)
+ Client.showTitle("ยงrยงcยงoยงrยง6ยงlRARE DROP! ยงrยงeA Bal Pet dropped!ยงr", "", 20, 50, 20)
+ }
+ })
+ this.registerChat("&r&c&oThe bosses outer shell looks to be weakening!&r", ()=>{
+ this.balHP = 200
+ })
+ this.registerChat("&r&c&oHalf way there! The boss is starting to become weaker!&r", ()=>{
+ this.balHP = 125
+ })
+ this.registerChat("&r&c&oNearly there! The boss is shaking it can't last much longer!&r", ()=>{
+ this.balHP = 75
+ })
+ this.registerChat("&r&c&oThe boss looks weak and tired and retreats into the lava...&r", ()=>{
+ this.balHP = 0
+ })
+ }
+
+ itemTooltipEvent(lore, item, event){
+ this.addLore(item)
+ }
+
+ /**
+ * @param {Item} item
+ */
+ addLore(item){
+ if(this.showUnlockedGemstoneSlots.getValue()){
+ let gems = item.getNBT().getCompoundTag("tag").getCompoundTag("ExtraAttributes").getCompoundTag("gems")
+ if(gems){
+ let unlockedGems = gems.getTagMap().get("unlocked_slots")
+
+ if(unlockedGems){
+
+ if(unlockedGems.func_74745_c() === 0){
+ utils.addLore(item, ChatLib.addColor("&d&lGemstones Unlocked: &f"), ChatLib.addColor("&cNone!"))
+ }else{
+ let gemstoneString = ""
+
+ for(let i = 0; i < unlockedGems.func_74745_c(); i++){
+ let gem = String(unlockedGems.func_150307_f(i)).split("_")
+
+ let name = stringUtils.firstLetterCapital(gem[0].toLowerCase())
+
+ gemstoneString += (gemstoneString===""?"":"&7, &a")+name
+ }
+ utils.addLore(item, ChatLib.addColor("&d&lGemstones Unlocked: &f"), ChatLib.addColor("&a"+gemstoneString))
+ }
+ }
+ }
+ }
+ if(this.showContainedGemstoneSlots.getValue()){
+ let gems = item.getNBT().getCompoundTag("tag").getCompoundTag("ExtraAttributes").getCompoundTag("gems")
+ if(gems){
+ let unlockedGems = gems.getTagMap()
+
+ let gemStr = ""
+
+ unlockedGems.keySet().forEach(gem => {
+ if(gem !== "unlocked_slots" && !gem.endsWith("_gem")){
+ gem = gem.split("_")
+
+ let gemName = stringUtils.firstLetterCapital(gems.getString(gem.join("_") + "_gem").toLowerCase()) ||stringUtils.firstLetterCapital(gem[0].toLowerCase())
+
+ let name = stringUtils.firstLetterCapital(gems.getString(gem.join("_")).toLowerCase()) + " " + gemName
+
+
+ gemStr += (gemStr===""?"":"&7, &a")+name
+ }
+ });
+
+ if(gemStr !== ""){
+ utils.addLore(item, ChatLib.addColor("&d&lGemstones: &f"), ChatLib.addColor("&a"+gemStr))
+ }
+ }
+ }
+ }
+
+ renderWorld(){
+ if(this.guessBalHp.getValue()){
+ if(this.balEntity) Tessellator.drawString(this.balHP + "/250" , this.balEntity.getX(), this.balEntity.getY()+12, this.balEntity.getZ())
+ }
+ }
+
+ tick(){
+ if(this.guessBalHp.getValue() || this.balRespawnHud.getValue()){
+ if(this.FeatureManager.features["dataLoader"].class.area === "Crystal Hollows" && this.FeatureManager.features["dataLoader"].class.areaFine === "Khazad-dm"){
+
+ this.balEntity = undefined
+ World.getAllEntities().filter(a=>a.getName()==="Magma Cube").filter(a=>a.getEntity().func_70809_q() > 10).forEach((bal)=>{
+ //Bal found
+ this.balEntity = bal
+ })
+ if(this.balEntity){
+ this.balDespawnDebounce=0
+ if(this.lastBalAlive !== 0){
+ this.lastBalAlive = 0
+ }
+ World.getAllEntitiesOfType(this.armourstandClass).forEach(e=>{
+ if(Math.abs(e.getX()-this.balEntity.getX())<=5 && Math.abs(e.getZ()-this.balEntity.getZ())<=5 && Math.abs(e.getY()-(this.balEntity.getY()+12))<=5){
+ if(!this.seenBalDamages.includes(e.getUUID())){
+ this.balHP--
+ this.seenBalDamages.push(e.getUUID())
+ }
+ }
+ })
+ }else{
+ this.balDespawnDebounce++
+ if(this.balDespawnDebounce > 10){
+ this.seenBalDamages = []
+ this.balHP = 250
+ if(this.lastBalAlive === 0) this.lastBalAlive = Date.now()
+ }
+ }
+ }
+ }
+
+ if(this.balRespawnHud.getValue() && this.FeatureManager.features["dataLoader"].class.area === "Crystal Hollows" && this.FeatureManager.features["dataLoader"].class.areaFine === "Khazad-dm"){
+ if(this.balEntity){
+ this.balHudElement.setText("&6Bal&7> &f" + this.balHP + "/250")
+ }else{
+ this.balHudElement.setText("&6Bal&7> &f" + Math.max(0, Math.floor((290000-(Date.now()-this.lastBalAlive))/1000)) + "s")
+ }
+ }else{
+ this.balHudElement.setText("")
+ }
+ }
+
+ renderOverlay(){
+ for(let element of this.hudElements){
+ element.render()
+ }
+ }
+
+ initVariables(){
+ this.hudElements = undefined
+ this.guessBalHp = undefined
+ this.balRespawnHud = undefined
+ this.balHudElement = undefined
+ this.balEntity = undefined
+ this.balDespawnDebounce = undefined
+ this.lastBalAlive = undefined
+ this.balHP = undefined
+ this.seenBalDamages = undefined
+ this.armourstandClass = undefined
+ this.balPetAlert = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new Mining()
+} \ No newline at end of file
diff --git a/features/mining/metadata.json b/features/mining/metadata.json
new file mode 100644
index 0000000..ce9ed40
--- /dev/null
+++ b/features/mining/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Mining",
+ "description": "A bunch of features to assist with mining",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/settings/index.js b/features/settings/index.js
new file mode 100644
index 0000000..6d41754
--- /dev/null
+++ b/features/settings/index.js
@@ -0,0 +1,249 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import * as GuiManager from "../../../guimanager/index.js"
+import SoopyGuiElement from "../../../guimanager/GuiElement/SoopyGuiElement";
+import SoopyTextElement from "../../../guimanager/GuiElement/SoopyTextElement";
+import SoopyBoxElement from "../../../guimanager/GuiElement/SoopyBoxElement";
+import TextWithArrow from "../../../guimanager/GuiElement/TextWithArrow";
+import ButtonWithArrow from "../../../guimanager/GuiElement/ButtonWithArrow";
+import BoxWithToggleAndDescription from "../../../guimanager/GuiElement/BoxWithToggleAndDescription";
+import ButtonWithArrowAndDescription from "../../../guimanager/GuiElement/ButtonWithArrowAndDescription";
+import SoopyMouseClickEvent from "../../../guimanager/EventListener/SoopyMouseClickEvent";
+import SoopyContentChangeEvent from "../../../guimanager/EventListener/SoopyContentChangeEvent";
+import SoopyOpenGuiEvent from "../../../guimanager/EventListener/SoopyOpenGuiEvent";
+import settingsCommunicator from "./settingsCommunicator";
+import GuiPage from "../soopyGui/GuiPage"
+
+
+class SettingsRenderer extends Feature {
+ constructor() {
+ super()
+
+ this.gui = undefined
+
+ this.pages = []
+ this.currentPage = 0
+ this.backButton = undefined
+ this.settingsCategoryArea = undefined
+ this.settingsTitle = undefined
+ this.settingsArea = undefined
+ this.modifyingFeature = false
+ this.featureLoadedTextBox = undefined
+
+ this.SettingPage = undefined
+ }
+
+ onEnable(){
+
+ this.SettingPage = new SettingPage()
+ this.SettingPage.FeatureManager = this.FeatureManager
+
+ return;
+ }
+
+ onDisable(){
+ this.SettingPage = undefined
+ return;
+ this.gui.delete()
+
+ this.pages = []
+ this.currentPage = 0
+ this.backButton = undefined
+ this.settingsArea = undefined
+ this.settingsTitle = undefined
+ this.settingsCategoryArea = undefined
+ this.modifyingFeature = false
+ this.featureLoadedTextBox = undefined
+ }
+
+ clickedOpenSettings(){
+
+
+ this.goToPage(1)
+ }
+
+ clickedBackButton(){
+ this.goToPage(this.currentPage-1)
+ }
+
+ goToPage(pageNum, animate=true){
+ pageNum = Math.max(0, Math.min(pageNum, this.pages.length-1))
+
+ if(pageNum == this.currentPage){
+ return
+ }
+
+ this.currentPage = pageNum
+
+ this.pages.forEach((p, i)=>{
+ p.forEach(e=>{
+ e.location.location.x.set(i-pageNum, animate?1000:0)
+ })
+ })
+
+ this.backButton.location.location.y.set(pageNum === 0?-0.2:0, animate?1000:0)
+ }
+}
+
+class SettingPage extends GuiPage {
+ constructor(){
+ super(10)
+
+ this.name = "Settings"
+
+ this.pages = [this.newPage(), this.newPage()]
+
+ this.settingsCategoryArea = undefined
+ this.settingsTitle = undefined
+ this.settingsArea = undefined
+ this.modifyingFeature = false
+ this.featureLoadedTextBox = undefined
+
+ this.SettingPage = undefined
+
+ this.pages[0].addEvent(new GuiManager.SoopyRenderEvent().setHandler(()=>{this.updateLocs()}))
+
+ //###############################################################################################
+ // Settings Category Page
+ //###############################################################################################
+
+
+
+ let settingsCategoryTitle = new SoopyTextElement().setText("ยง0Settings").setMaxTextScale(3).setLocation(0.1, 0.05, 0.8, 0.1)
+ this.pages[0].addChild(settingsCategoryTitle)
+
+ this.settingsCategoryArea = new SoopyGuiElement().setLocation(0.1, 0.2, 0.8, 0.8).setScrollable(true)
+ this.pages[0].addChild(this.settingsCategoryArea)
+
+
+ //###############################################################################################
+ // Settings Page
+ //###############################################################################################
+
+ this.settingsTitle = new SoopyTextElement().setMaxTextScale(3).setLocation(0.1, 0.05, 0.8, 0.1)
+ this.pages[1].addChild(this.settingsTitle)
+
+ this.settingsArea = new SoopyGuiElement().setLocation(0.1, 0.2, 0.8, 0.8).setScrollable(true)
+ this.pages[1].addChild(this.settingsArea)
+
+
+ this.finaliseLoading()
+ }
+
+ onOpen(){
+ this.updateSettingCategories()
+ this.settingsCategoryArea.location.scroll.x.set(0, 0)
+ this.settingsCategoryArea.location.scroll.y.set(0, 0)
+ }
+
+ updateSettingCategories(){
+ this.settingsCategoryArea.children = []
+
+ let i = 0
+
+ Object.keys(this.FeatureManager.featureMetas).sort((a, b)=>{return a.sortA-b.sortA}).forEach((f)=>{
+ let meta = this.FeatureManager.featureMetas[f]
+
+ let isHidden = meta.isHidden
+ if(typeof isHidden === "string"){
+ isHidden = require("../" + f + "/" + isHidden).default()
+ }
+ if(isHidden) return
+
+ let category = new ButtonWithArrowAndDescription().setText("&0" + meta.name).setDesc("&0" +meta.description).setLocation(0, 0.225*i, 1, 0.2)
+ category.addEvent(new SoopyMouseClickEvent().setHandler(()=>{this.clickedOpenCategory(f)}))
+
+ this.settingsCategoryArea.addChild(category)
+
+ i++
+ })
+ // this.FeatureManager.features = {}; enabled features
+ }
+
+ updateSettings(category){
+ let meta = this.FeatureManager.featureMetas[category]
+
+ this.settingsArea.children = []
+
+ this.settingsTitle.setText("&0" + this.FeatureManager.featureMetas[category].name)
+
+ let height = 0
+
+ if(meta.isTogglable){
+ let toggle = new BoxWithToggleAndDescription().setLocation(0, height, 1, 0.2).setText("&0Main toggle").setDesc("&0This is the main toggle for the whole category")
+ this.settingsArea.addChild(toggle)
+
+ toggle.toggle.setValue(this.FeatureManager.isFeatureLoaded(category), 0)
+
+ toggle.toggle.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ //dont allow editing if currenately loading/unloading it
+ if(this.modifyingFeature){
+ resetFun()
+ return
+ }
+
+ //toggle the feature
+ this.modifyingFeature = true
+ new Thread(()=>{
+ this.FeatureManager.featureSettingsData[category].enabled = newVal
+ this.FeatureManager.featureSettingsDataLastUpdated = true
+
+ if(!newVal && this.FeatureManager.isFeatureLoaded(category)){
+ this.FeatureManager.unloadFeature(category)
+ }
+ if(newVal && !this.FeatureManager.isFeatureLoaded(category)){
+ this.FeatureManager.loadFeature(category)
+ }
+
+ Thread.sleep(100)
+
+ this.modifyingFeature = false
+
+ this.updateSettings(category)
+ }).start()
+ }))
+ height += toggle.location.size.y.get()+0.045
+ }
+
+ if(!this.FeatureManager.isFeatureLoaded(category)){
+
+ //only show if feature issnt loaded
+
+ let textBox = new SoopyBoxElement().setLocation(0, height, 1, 0.2)
+ .addChild(new SoopyTextElement().setText("&0Feature not loaded").setLocation(0, 0, 1, 0.5))
+ .addChild(new SoopyTextElement().setText("&0Load feature to access settings").setLocation(0, 0.5, 1, 0.5))
+ this.settingsArea.addChild(textBox)
+ return;
+ }
+
+ settingsCommunicator.getModuleSettings(category).forEach(setting=>{
+ setting.location.location.y.set(height, 0)
+ this.settingsArea.addChild(setting)
+
+ height += 0.045+setting.location.size.y.get()
+ })
+ }
+
+ updateLocs(){
+ let totalHeight = 0
+
+ this.settingsArea.children.forEach(e=>{
+ e.location.location.y.set(totalHeight, 0)
+
+ totalHeight += e.location.size.y.get()+Math.min(0.045,e.location.size.y.get())
+ })
+ }
+
+ clickedOpenCategory(category){
+ this.updateSettings(category)
+ this.goToPage(2)
+
+ this.settingsArea.location.scroll.x.set(0, 0)
+ this.settingsArea.location.scroll.y.set(0, 0)
+ }
+}
+
+module.exports = {
+ class: new SettingsRenderer()
+} \ No newline at end of file
diff --git a/features/settings/metadata.json b/features/settings/metadata.json
new file mode 100644
index 0000000..8f2ace4
--- /dev/null
+++ b/features/settings/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Settings",
+ "description": "Adds a settings gui with toggles for all the modules, also renders each individual module's settings",
+ "isHidden": true,
+ "isTogglable": false,
+ "defaultEnabled": true,
+ "sortA": 0
+} \ No newline at end of file
diff --git a/features/settings/settingThings/button.js b/features/settings/settingThings/button.js
new file mode 100644
index 0000000..77ba4b8
--- /dev/null
+++ b/features/settings/settingThings/button.js
@@ -0,0 +1,18 @@
+import SoopyMouseClickEvent from "../../../../guimanager/EventListener/SoopyMouseClickEvent";
+import ButtonWithArrow from "../../../../guimanager/GuiElement/ButtonWithArrow";
+import SettingBase from "./settingBase";
+
+class ButtonSetting extends SettingBase {
+ constructor(name, description, settingId, module, buttonText, buttonFunction, defaultValue=null){
+ super(name, description, defaultValue, settingId, module)
+
+ this.buttonObject = new ButtonWithArrow().setLocation(0, 0.3, 0.8, 0.4).setText("ยง0"+buttonText)
+ this.settingObject.addChild(this.buttonObject)
+
+ this.buttonObject.addEvent(new SoopyMouseClickEvent().setHandler(()=>{
+ buttonFunction.call(this)
+ }))
+ }
+}
+
+export default ButtonSetting \ No newline at end of file
diff --git a/features/settings/settingThings/location.js b/features/settings/settingThings/location.js
new file mode 100644
index 0000000..69d1a43
--- /dev/null
+++ b/features/settings/settingThings/location.js
@@ -0,0 +1,332 @@
+import { SoopyGui } from "../../../../guimanager"
+import SoopyBoxElement from "../../../../guimanager/GuiElement/SoopyBoxElement"
+import SoopyTextElement from "../../../../guimanager/GuiElement/SoopyTextElement"
+import ButtonSetting from "./button"
+import BoxWithText from "../../../../guimanager/GuiElement/BoxWithText"
+import ButtonWithArrow from "../../../../guimanager/GuiElement/ButtonWithArrow"
+import SoopyMouseClickEvent from "../../../../guimanager/EventListener/SoopyMouseClickEvent"
+import NumberTextBox from "../../../../guimanager/GuiElement/NumberTextBox"
+import SoopyContentChangeEvent from "../../../../guimanager/EventListener/SoopyContentChangeEvent"
+
+let allLocations = []
+
+class LocationSetting extends ButtonSetting {
+ constructor(name, description, settingId, module, defaultLocation){
+ super(name, description, settingId, module, "Edit Position", this.editPosition, defaultLocation)
+
+ this.x = this.getValue()[0]
+ this.y = this.getValue()[1]
+ this.scale = this.getValue()[2]
+ this.shadowType = this.getValue()[3] //0-none, 1-vanilla, 2-border
+
+ this.editTempTextV = undefined
+
+ this.dragging = false
+ this.dragOffset = [0, 0]
+ this.resisizing = false
+ this.resizePoint = 0
+ this.resizeInitialPos=[0, 0, 0, 0, 0, 0]
+
+ this.parent = undefined
+
+ this.soopyGui = new SoopyGui()
+ this.soopyGui._renderBackground = ()=>{} //remove background darkening
+
+ this.elmSettings = new SoopyBoxElement().setLocation(0, 0, 0.25, 0.25)
+
+ let scaleText = new SoopyTextElement().setText("&0Scale: ").setLocation(3, 0.025, 1, 0.15)
+ scaleText.location.location.setRelative(false, true)
+ scaleText.centeredX = false
+ this.elmSettings.addChild(scaleText)
+
+ this.scaleInput = new NumberTextBox().setText(this.scale.toFixed(2)).setLocation(Renderer.getStringWidth("Scale: ")+3, 0.025, -(Renderer.getStringWidth("Scale: ")+6), 0.15)
+ this.scaleInput.location.location.setRelative(false, true)
+ this.scaleInput.location.size.setRelative(false, true)
+ this.elmSettings.addChild(this.scaleInput)
+ this.scaleInput.text.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ try{
+ newVal = parseFloat(newVal)
+ if(!isNaN(newVal)){
+ this.scale = newVal
+ this._updateValue()
+ }
+ }catch(e){}
+ }))
+ this.lastScaleRender = this.scale.toFixed(2)
+
+ let xText = new SoopyTextElement().setText("&0X: ").setLocation(3, 0.225, 1, 0.15)
+ xText.location.location.setRelative(false, true)
+ xText.centeredX = false
+ this.elmSettings.addChild(xText)
+ this.xInput = new NumberTextBox().setText(this.x.toFixed(0)).setLocation(Renderer.getStringWidth("X: ")+3, 0.225, -(Renderer.getStringWidth("X: ")+6), 0.15)
+ this.xInput.location.location.setRelative(false, true)
+ this.xInput.location.size.setRelative(false, true)
+ this.elmSettings.addChild(this.xInput)
+ this.xInput.text.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ try{
+ newVal = parseFloat(newVal)
+ if(!isNaN(newVal)){
+ this.x = newVal
+ this._updateValue()
+ }
+ }catch(e){}
+ }))
+ this.lastXRender = this.x.toFixed(0)
+
+ let yText = new SoopyTextElement().setText("&0Y: ").setLocation(3, 0.425, 1, 0.15)
+ yText.location.location.setRelative(false, true)
+ yText.centeredX = false
+ this.elmSettings.addChild(yText)
+ this.yInput = new NumberTextBox().setText(this.y.toFixed(0)).setLocation(Renderer.getStringWidth("Y: ")+3, 0.425, -(Renderer.getStringWidth("Y: ")+6), 0.15)
+ this.yInput.location.location.setRelative(false, true)
+ this.yInput.location.size.setRelative(false, true)
+ this.elmSettings.addChild(this.yInput)
+ this.yInput.text.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ try{
+ newVal = parseFloat(newVal)
+ if(!isNaN(newVal)){
+ this.y = newVal
+ this._updateValue()
+ }
+ }catch(e){}
+ }))
+ this.lastYRender = this.y.toFixed(0)
+
+ this.soopyGui.element.addChild(this.elmSettings)
+
+ let button = new ButtonWithArrow().setText("&0Reset").setLocation(0.125, 0.625, 0.75, 0.15)
+ button.addEvent(new SoopyMouseClickEvent().setHandler(()=>{
+ this.x = defaultLocation[0]
+ this.y = defaultLocation[1]
+ this.scale = defaultLocation[2]
+ this.shadowType = defaultLocation[3]
+ }))
+ this.elmSettings.addChild(button)
+
+ let button2 = new ButtonWithArrow().setText("&0Back").setLocation(0.125, 0.825, 0.75, 0.15)
+ button2.addEvent(new SoopyMouseClickEvent().setHandler(()=>{
+ this.soopyGui.close()
+ this.guiObject.main.ctGui.open()
+ }))
+ this.elmSettings.addChild(button2)
+
+ this.editGui = this.soopyGui.ctGui
+
+ this.editGui.registerDraw((mouseX, mouseY, partialTicks)=>{
+ this.renderGui(mouseX, mouseY)
+ this.soopyGui._render(mouseX, mouseY, partialTicks)
+ })
+ this.editGui.registerClicked((mouseX, mouseY, button)=>{
+ this.clicked(mouseX, mouseY)
+ this.soopyGui._onClick(mouseX, mouseY, button)
+ })
+ this.editGui.registerMouseReleased((mouseX, mouseY)=>{
+ this.released(mouseX, mouseY)
+ })
+
+ allLocations.push(this)
+ }
+
+ requires(toggleSetting){
+ this.requiresO = toggleSetting
+
+ toggleSetting.toggleObject.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ if(newVal){
+ this.guiObject.location.size.y.set(0.2, 500)
+ }else{
+ this.guiObject.location.size.y.set(0, 500)
+ }
+ }))
+ let newVal = this.requiresO.getValue()
+ if(!newVal){
+ this.guiObject.location.size.y.set(0, 0)
+ }
+
+ return this
+ }
+
+ _updateValue(){
+ this.setValue([this.x, this.y, this.scale, this.shadowType])
+ }
+
+ editTempText(text){
+ this.editTempTextV = text
+ return this;
+ }
+
+ setParent(parent){
+ this.parent = parent
+ }
+
+ editPosition(){
+ this.guiObject.main.ctGui.close()
+
+ this.soopyGui.open()
+ }
+
+ clicked(mouseX, mouseY){
+ let width = this.getWidth()
+ let height = 9*this.parent.getText()[0].length
+
+ let locations = [[this.x, this.y], [this.x+width*this.scale, this.y], [this.x, this.y+height*this.scale], [this.x+width*this.scale, this.y+height*this.scale]]
+
+ locations.forEach((loc, i)=>{
+ if(mouseX >= loc[0]-1*Math.max(1, this.scale) && mouseX <= loc[0]+1*Math.max(1, this.scale)
+ && mouseY >= loc[1]-1*Math.max(1, this.scale) && mouseY <= loc[1]+1*Math.max(1, this.scale)){
+ this.resisizing= true
+ this.resizePoint = i
+ this.resizeInitialPos = [mouseX, mouseY, this.x, this.y, width*this.scale, height*this.scale]
+ }
+ })
+ if(this.resisizing) return;
+
+ if(mouseX > this.x && mouseX < this.x+width*this.scale
+ && mouseY>this.y && mouseY<this.y+height*this.scale){
+ this.dragging = true;
+ this.dragOffset = [this.x-mouseX, this.y-mouseY]
+ }
+ }
+ released(mouseX, mouseY){
+ this.updateLocation(mouseX, mouseY)
+ this.dragging = false
+ this.resisizing = false
+
+ this._updateValue()
+ }
+
+ getWidth(){
+ return Math.max(...(this.parent.getText()[0].map(a=>Renderer.getStringWidth(ChatLib.removeFormatting(a)))))
+ }
+
+ updateLocation(mouseX, mouseY, drawCollidingBox){
+ let width = this.getWidth()
+ let height = 9*this.parent.getText()[0].length
+
+ if(this.dragging){
+ this.x = mouseX+this.dragOffset[0]
+ this.y = mouseY+this.dragOffset[1]
+
+ let snapPoints = []
+ allLocations.forEach(loc=>{
+ if(loc === this) return;
+ snapPoints.push([loc.x, loc.y])
+ snapPoints.push([loc.x+loc.getWidth()*loc.scale, loc.y])
+ snapPoints.push([loc.x+loc.getWidth()*loc.scale, loc.y+height*loc.scale])
+ snapPoints.push([loc.x, loc.y+height*loc.scale])
+ })
+
+ snapPoints.forEach(point=>{
+ //top left
+ if(Math.abs(this.x-point[0]) < 5 && Math.abs(this.y-point[1]) < 5){
+ this.x = point[0]
+ this.y = point[1]
+ }
+
+ //top right
+ if(Math.abs(this.x+width*this.scale-point[0]) < 5 && Math.abs(this.y-point[1]) < 5){
+ this.x = point[0]-width*this.scale
+ this.y = point[1]
+ }
+
+ //bottom left
+ if(Math.abs(this.x-point[0]) < 5 && Math.abs(this.y+height*this.scale-point[1]) < 5){
+ this.x = point[0]
+ this.y = point[1]-height*this.scale
+ }
+
+ //bottom right
+ if(Math.abs(this.x+width*this.scale-point[0]) < 5 && Math.abs(this.y+height*this.scale-point[1]) < 5){
+ this.x = point[0]-width*this.scale
+ this.y = point[1]-height*this.scale
+ }
+ })
+ }
+ if(this.resisizing){
+ if(this.resizePoint === 3){
+ this.scale=(mouseX-this.x)/width
+ this.scale = Math.max(0.25, this.scale)
+ }
+ if(this.resizePoint === 1){
+
+ let [oMouseX, oMouseY, oX, oY, ow, oh] = this.resizeInitialPos
+
+ this.scale=(mouseX-this.x)/width
+ this.scale = Math.max(0.25, this.scale)
+ this.y=oY+oh-height*this.scale
+ }
+ if(this.resizePoint===0){
+ let [oMouseX, oMouseY, oX, oY, ow, oh] = this.resizeInitialPos
+
+ this.scale=(oX+ow-mouseX)/width
+ this.scale = Math.max(0.25, this.scale)
+ this.x=oX+ow-width*this.scale
+ this.y=oY+oh-height*this.scale
+ }
+ if(this.resizePoint === 2){
+ let [oMouseX, oMouseY, oX, oY, ow, oh] = this.resizeInitialPos
+
+ this.scale=(oX+ow-mouseX)/width
+ this.scale = Math.max(0.25, this.scale)
+ this.x=oX+ow-width*this.scale
+ }
+ }
+ }
+
+ renderGui(mouseX, mouseY){
+
+ if(this.parent){
+ this.parent.editTempTextV = this.editTempTextV
+ this.parent.editTempTimeV = Date.now()
+
+ this.parent.tempDisableTime = Date.now()
+ this.parent.renderRaw()
+ }
+
+ let width = this.getWidth()
+ let height = 9*this.parent.getText()[0].length
+
+ this.updateLocation(mouseX, mouseY, true)
+
+ this.renderBox(true)
+
+ if(this.x+width*this.scale/2 > Renderer.screen.getWidth()/2){
+ this.elmSettings.location.location.x.set(Math.min(Math.max(this.x/Renderer.screen.getWidth()-0.25-0.03, 0.02), 0.73))
+ }else{
+ this.elmSettings.location.location.x.set(Math.min(Math.max((this.x+width*this.scale)/Renderer.screen.getWidth()+0.03, 0.02), 0.73))
+ }
+ this.elmSettings.location.location.y.set(Math.min(Math.max((this.y+height*this.scale/2)/Renderer.screen.getHeight() - this.elmSettings.location.size.y.get()/2, 0.02), 0.73))
+
+
+ if(this.lastScaleRender !== this.scale.toFixed(2) && !this.scaleInput.text.selected){
+ this.lastScaleRender = this.scale.toFixed(2)
+ this.scaleInput.setText(this.scale.toFixed(2))
+ }
+ if(this.lastXRender !== this.x.toFixed(0) && !this.xInput.text.selected){
+ this.lastXRender = this.x.toFixed(0)
+ this.xInput.setText(this.x.toFixed(0))
+ }
+ if(this.lastYRender !== this.y.toFixed(0) && !this.yInput.text.selected){
+ this.lastYRender = this.y.toFixed(0)
+ this.yInput.setText(this.y.toFixed(0))
+ }
+ }
+
+ renderBox(drawHandles){
+ let width = this.getWidth()
+ let height = 9*this.parent.getText()[0].length
+
+ Renderer.drawRect(Renderer.color(255, 255, 255), this.x, this.y, width*this.scale, 1)
+ Renderer.drawRect(Renderer.color(255, 255, 255), this.x, this.y, 1, height*this.scale)
+ Renderer.drawRect(Renderer.color(255, 255, 255), this.x, this.y+height*this.scale, width*this.scale, 1)
+ Renderer.drawRect(Renderer.color(255, 255, 255), this.x+width*this.scale, this.y, 1, height*this.scale)
+
+ if(drawHandles){
+ Renderer.drawRect(Renderer.color(255, 255, 255), this.x-1*Math.max(1, this.scale)+width*this.scale, this.y-1*Math.max(1, this.scale), 2*Math.max(1, this.scale)+1, 2*Math.max(1, this.scale)+1)
+ Renderer.drawRect(Renderer.color(255, 255, 255), this.x-1*Math.max(1, this.scale), this.y-1*Math.max(1, this.scale)+height*this.scale, 2*Math.max(1, this.scale)+1, 2*Math.max(1, this.scale)+1)
+ Renderer.drawRect(Renderer.color(255, 255, 255), this.x-1*Math.max(1, this.scale)+width*this.scale, this.y-1*Math.max(1, this.scale)+height*this.scale, 2*Math.max(1, this.scale)+1, 2*Math.max(1, this.scale)+1)
+ Renderer.drawRect(Renderer.color(255, 255, 255), this.x-1*Math.max(1, this.scale), this.y-1*Math.max(1, this.scale), 2*Math.max(1, this.scale)+1, 2*Math.max(1, this.scale)+1)
+ }
+ }
+}
+
+export default LocationSetting \ No newline at end of file
diff --git a/features/settings/settingThings/settingBase.js b/features/settings/settingThings/settingBase.js
new file mode 100644
index 0000000..fbee5bf
--- /dev/null
+++ b/features/settings/settingThings/settingBase.js
@@ -0,0 +1,106 @@
+import SoopyContentChangeEvent from "../../../../guimanager/EventListener/SoopyContentChangeEvent";
+import BoxWithTextAndDescription from "../../../../guimanager/GuiElement/BoxWithTextAndDescription"
+import SoopyGuiElement from "../../../../guimanager/GuiElement/SoopyGuiElement";
+import settingsCommunicator from "../settingsCommunicator";
+
+class SettingBase {
+ constructor(name, description, defaultVal, settingId, module){
+ this.name = name;
+ this.description = description;
+ this.defaultVal = defaultVal;
+ this.settingId = settingId
+ this.module = module
+ this.moduleId = module.getId()
+
+ this.val = defaultVal;
+
+ this.guiObject = new BoxWithTextAndDescription().setDesc("ยง0"+this.description).setText("ยง0"+this.name).setLocation(0, 0, 1, 0.175)
+
+ this.settingObject = new SoopyGuiElement().setLocation(0.8, 0, 0.2, 1)
+
+ this.guiObject.addChild(this.settingObject)
+
+ settingsCommunicator.addSetting(this.moduleId, settingId, this.getGuiObject())
+
+ if(!module.FeatureManager.featureSettingsData[this.moduleId]){
+ module.FeatureManager.featureSettingsData[this.moduleId] = {}
+ }
+ if(!module.FeatureManager.featureSettingsData[this.moduleId].subSettings)module.FeatureManager.featureSettingsData[this.moduleId].subSettings = {}
+ if(!module.FeatureManager.featureSettingsData[this.moduleId].subSettings[settingId]){
+ module.FeatureManager.featureSettingsData[this.moduleId].subSettings[settingId] = {
+ value: this.getDefaultValue(),
+ temp_val: this.getDefaultValue()
+ }
+
+ module.FeatureManager.featureSettingsDataLastUpdated = true
+ }
+ let temp_val_temp =module.FeatureManager.featureSettingsData[this.moduleId].subSettings[settingId].temp_val
+ this.setValue(module.FeatureManager.featureSettingsData[this.moduleId].subSettings[settingId].value)
+ this.temp_val = temp_val_temp
+
+ this.requiresO = undefined
+ }
+
+ getValue(){
+ return this.val;
+ }
+
+ setValue(val){
+ this.val = val;
+
+ if(!this.requiresO || this.requiresO.getValue()){
+ this.temp_val = val
+ }
+
+ if(this.module.FeatureManager.featureSettingsData[this.moduleId].subSettings[this.settingId].value !== val){
+ this.module.FeatureManager.featureSettingsData[this.moduleId].subSettings[this.settingId].value = val
+
+ this.module.FeatureManager.featureSettingsDataLastUpdated = true
+ }
+ if(this.module.FeatureManager.featureSettingsData[this.moduleId].subSettings[this.settingId].temp_val !== this.temp_val){
+ this.module.FeatureManager.featureSettingsData[this.moduleId].subSettings[this.settingId].temp_val = this.temp_val
+
+ this.module.FeatureManager.featureSettingsDataLastUpdated = true
+ }
+ }
+
+ getName(){
+ return this.name;
+ }
+
+ getDescription(){
+ return this.description;
+ }
+
+ getDefaultValue(){
+ return this.defaultVal;
+ }
+
+ getGuiObject(){
+ return this.guiObject;
+ }
+
+ requires(toggleSetting){
+ this.requiresO = toggleSetting
+
+ toggleSetting.toggleObject.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ if(newVal){
+ this.guiObject.location.size.y.set(0.2, 500)
+ }else{
+ this.guiObject.location.size.y.set(0, 500)
+ }
+ }))
+ let newVal = this.requiresO.getValue()
+ if(!newVal){
+ this.guiObject.location.size.y.set(0, 0)
+ }
+
+ return this
+ }
+
+ delete(){
+ settingsCommunicator.removeSetting(this.module, this.settingId)
+ }
+}
+
+export default SettingBase \ No newline at end of file
diff --git a/features/settings/settingThings/textSetting.js b/features/settings/settingThings/textSetting.js
new file mode 100644
index 0000000..4960461
--- /dev/null
+++ b/features/settings/settingThings/textSetting.js
@@ -0,0 +1,33 @@
+
+import SoopyContentChangeEvent from "../../../../guimanager/EventListener/SoopyContentChangeEvent";
+import TextBox from "../../../../guimanager/GuiElement/TextBox";
+import PasswordInput from "../../../../guimanager/GuiElement/PasswordInput"
+import SettingBase from "./settingBase";
+
+class TextSetting extends SettingBase {
+ constructor(name, description, defaultVal, settingId, module, placeholder, isSecret){
+ super(name, description, defaultVal, settingId, module)
+
+ this.textObject = (isSecret?new PasswordInput():new TextBox()).setLocation(0, 0.2, 0.9, 0.6).setText(this.getValue()).setPlaceholder(placeholder)
+ this.settingObject.addChild(this.textObject)
+
+ this.settingObject.setLocation(0.6, 0, 0.4, 1)
+ this.guiObject.text.setLocation(0, 0, 0.6, 0.6)
+ this.guiObject.description.setLocation(0, 0.6, 0.55, 0.4)
+
+ this.textObject.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ this.setValue(newVal)
+ }))
+
+ }
+
+ setValue(newVal){
+ super.setValue(newVal)
+
+ this.textObject.setText(newVal)
+
+ return this
+ }
+}
+
+export default TextSetting \ No newline at end of file
diff --git a/features/settings/settingThings/toggle.js b/features/settings/settingThings/toggle.js
new file mode 100644
index 0000000..d839b8f
--- /dev/null
+++ b/features/settings/settingThings/toggle.js
@@ -0,0 +1,58 @@
+import Enum from "../../../../guimanager/Enum";
+import SoopyContentChangeEvent from "../../../../guimanager/EventListener/SoopyContentChangeEvent";
+import Toggle from "../../../../guimanager/GuiElement/Toggle";
+import SettingBase from "./settingBase";
+
+class ToggleSetting extends SettingBase {
+ constructor(name, description, defaultVal, settingId, module){
+ super(name, description, defaultVal, settingId, module)
+
+ this.toggleObject = new Toggle().setLocation(0, 0.3, 0.8, 0.4).setValue(this.getValue())
+ this.settingObject.addChild(this.toggleObject)
+
+ this.toggleObject.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ this.setValue(newVal)
+ }))
+
+ }
+
+ setValue(newVal){
+ super.setValue(newVal)
+
+ this.toggleObject.setValue(newVal)
+
+ return this
+ }
+
+ requires(toggleSetting){
+ this.requiresO = toggleSetting
+
+ toggleSetting.toggleObject.addEvent(new SoopyContentChangeEvent().setHandler((newVal, oldVal, resetFun)=>{
+ if(newVal){
+ this.setValue(this.temp_val)
+
+ this.toggleObject.triggerEvent(Enum.EVENT.CONTENT_CHANGE, [this.temp_val, false, ()=>{}])
+
+ this.guiObject.location.size.y.set(0.2, 500)
+ }else{
+ this.temp_val = this.getValue()
+ this.setValue(false)
+
+ this.toggleObject.triggerEvent(Enum.EVENT.CONTENT_CHANGE, [false, this.temp_val, ()=>{}])
+
+ this.guiObject.location.size.y.set(0, 500)
+ }
+ }))
+ let newVal = this.requiresO.getValue()
+ if(!newVal){
+ let temp_val = this.temp_val
+ this.setValue(false)
+ this.temp_val = temp_val
+ this.guiObject.location.size.y.set(0, 0)
+ }
+
+ return this
+ }
+}
+
+export default ToggleSetting \ No newline at end of file
diff --git a/features/settings/settingsCommunicator.js b/features/settings/settingsCommunicator.js
new file mode 100644
index 0000000..b54b1c1
--- /dev/null
+++ b/features/settings/settingsCommunicator.js
@@ -0,0 +1,29 @@
+//So features can add settings by adding to this class, then the gui will load data from this class
+//this makes it so i can add settings before the settings gui is loaded
+//and so that settings gui can still be dynamicly reloaded and not break things
+
+class SettingsCommunicator {
+ constructor(){
+ this.settings = {}
+ }
+
+ addSetting(module, settingID, settingObject){
+ if(!this.settings[module]) this.settings[module] = {}
+
+ this.settings[module][settingID] = settingObject
+ }
+ removeSetting(module, settingID){
+ if(!this.settings[module]) return;
+ delete this.settings[module][settingID]
+ }
+ getSetting(module, settingID){
+ return this.settings[module][settingID]
+ }
+ getModuleSettings(module){
+ return Object.values(this.settings[module] || [])
+ }
+}
+
+const settingsCommunicator = new SettingsCommunicator()
+
+export default settingsCommunicator \ No newline at end of file
diff --git a/features/slayers/index.js b/features/slayers/index.js
new file mode 100644
index 0000000..3599996
--- /dev/null
+++ b/features/slayers/index.js
@@ -0,0 +1,359 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import { numberWithCommas } from "../../utils/numberUtils";
+import { drawBoxAtBlock, drawBoxAtEntity, drawFilledBox, drawLine } from "../../utils/renderUtils";
+import HudTextElement from "../hud/HudTextElement";
+import LocationSetting from "../settings/settingThings/location";
+import ToggleSetting from "../settings/settingThings/toggle";
+
+class Slayers extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.expOnKill = new ToggleSetting("Show slayer exp on boss kill", "Says your slayer exp in chat when you kill a boss", true, "slayer_xp", this)
+ this.slainAlert = new ToggleSetting("Show boss slain alert", "This helps you to not kill mobs for ages with an inactive quest", true, "boss_slain_alert", this)
+ this.spawnAlert = new ToggleSetting("Show boss spawned alert", "This helps you to not miss your boss when you spawn it", true, "boss_spawn_alert", this)
+
+
+ this.boxAroundEmanBoss = new ToggleSetting("Box around enderman slayer boss", "This helps to know what boss it yours", true, "eman_box", this)
+ this.boxToEmanBeacon = new ToggleSetting("Box and line to the enderman beacon", "This will help to find the beacon when the boss throws it", true, "eman_beacon", this)
+ this.emanBeaconDinkDonk = new ToggleSetting("DinkDonk when beacon is spawned", "This will help to notice when the beacon is spawned", true, "eman_beacon_dinkdink", this)
+ this.emanEyeThings = new ToggleSetting("Put box around the enderman eye things", "This will help to find them", true, "eman_eye_thing", this)
+ this.emanHpGuiElement = new ToggleSetting("Render the enderman hp on your screen", "This will help you to know what stage u are in ect", true, "eman_hp", this)
+
+ this.emanHpElement = new HudTextElement()
+ .setToggleSetting(this.emanHpGuiElement)
+ .setLocationSetting(new LocationSetting("Eman Hp Location", "Allows you to edit the location of the enderman hp", "eman_location", this, [10, 50, 1, 1])
+ .requires(this.emanHpGuiElement)
+ .editTempText("&6Enderman&7> &f&l30 Hits"))
+ this.hudElements.push(this.emanHpElement)
+
+ this.slayerExp = {}
+ this.slayerExpLoaded = false
+
+ this.lastSlayerType = ""
+ this.lastSlayerExp = 0
+ this.registerChat("&r &r&a&lSLAYER QUEST COMPLETE!&r",(e)=>{
+ this.slayerExp[this.lastSlayerType] = this.lastSlayerExp + (this.slayerExp[this.lastSlayerType] || 0)
+ if(this.expOnKill.getValue()){
+ cancel(e)
+ ChatLib.chat("&r &r&a&lSLAYER QUEST COMPLETE!&r")
+ ChatLib.chat("&r &r&aYou have &d" + numberWithCommas(this.slayerExp[this.lastSlayerType]) + " " + this.lastSlayerType + " XP&r&7!&r")
+ ChatLib.chat("&r &r&aYou have &d" + numberWithCommas(Object.values(this.slayerExp).reduce((a, t)=>t+a, 0)) + " total XP&r&7!&r")
+ }
+ })
+
+ this.bossSlainMessage = false
+ this.bossSpawnedMessage = false
+ this.lastBossNotSpawnedTime = 0
+
+ this.registerEvent("renderOverlay", this.renderOverlay)
+
+
+ this.registerSoopy("apiLoad", this.apiLoad)
+ if(this.FeatureManager.features["dataLoader"].class.lastApiData.skyblock){
+ this.apiLoad(this.FeatureManager.features["dataLoader"].class.lastApiData.skyblock, "skyblock", true, true)
+ }
+
+ this.todoE = []
+ this.beaconPoints = {}
+ this.beaconE = []
+ this.deadE = []
+ this.beaconLocations = {}
+ this.eyeE = []
+ this.todoE2 = []
+ this.emanBoss = undefined
+ this.nextIsBoss = 0
+
+ this.registerForge(net.minecraftforge.event.entity.EntityJoinWorldEvent, this.entityJoinWorldEvent)
+ this.registerForge(net.minecraftforge.event.entity.living.LivingAttackEvent, this.entityAttackEvent) //TODO: Use CT event when ct 2.0 because they fixed
+ this.registerEvent("tick", this.tick)
+ this.registerEvent("renderWorld", this.renderWorld)
+ this.registerEvent("worldLoad", this.worldLoad)
+ this.registerEvent("renderOverlay", this.renderHud)
+ }
+
+ renderHud(){
+ for(let element of this.hudElements){
+ element.render()
+ }
+ }
+
+ worldLoad(){
+ this.todoE = []
+ this.beaconPoints = {}
+ this.beaconE = []
+ this.deadE = []
+ this.todoE2 = []
+ this.beaconLocations = {}
+ this.eyeE = []
+ this.emanBoss = undefined
+ }
+
+ entityAttackEvent(event){
+ if(event.source.func_76346_g() === Player.getPlayer()){
+ if(event.entity instanceof net.minecraft.entity.monster.EntityEnderman){
+ World.getAllEntitiesOfType(net.minecraft.entity.item.EntityArmorStand).forEach(e=>{
+ if(e.getName().includes("Voidgloom Seraph")){
+ //if distance from e to event.entity < 5
+ if((e.getX() - event.entity.field_70165_t)**2 + (e.getY() - event.entity.field_70163_u)**2 + (e.getZ() - event.entity.field_70161_v)**2 < 25){
+ this.emanBoss = e
+ }
+ }
+ })
+ }
+ }
+ }
+
+ renderWorld(ticks){
+ Object.values(this.beaconPoints).forEach(line=>{
+ let lastPoint = undefined
+ line.forEach(p=>{
+ if(lastPoint){
+ drawLine(lastPoint[0], lastPoint[1], lastPoint[2], p[0], p[1], p[2], 0, 0, 255, 3)
+ }
+ lastPoint = p
+ })
+ })
+
+ this.eyeE.forEach(e=>{
+ let x = e.getX() + ((e.getX()-e.getLastX())*ticks)
+ let y = e.getY() + ((e.getY()-e.getLastY())*ticks)
+ let z = e.getZ() + ((e.getZ()-e.getLastZ())*ticks)
+
+ drawBoxAtBlock(x-0.5, y+0.7, z-0.5, 255, 0, 0)
+ })
+
+ if(this.emanBoss) drawBoxAtEntity(this.emanBoss, 0, 255, 0, 1, -3, ticks, 4, false)
+
+ Object.values(this.beaconLocations).forEach(loc=>{
+ drawFilledBox(loc[0]+0.5, loc[1], loc[2]+0.5, 1.01, 1.01, 0, 0, 1, 1, true)
+ })
+ }
+
+ entityJoinWorldEvent(event){
+ this.todoE2.push(event.entity)
+ }
+
+ tick(){
+ this.bossSlainMessage = false
+ let dis1 = false
+ Scoreboard.getLines().forEach((line, i) => {
+ if(ChatLib.removeFormatting(line.getName()).includes("Slayer Quest")){
+ let slayerInfo = ChatLib.removeFormatting(Scoreboard.getLines()[i-1].getName().replace(/ยง/g,"&"))
+ let levelString = slayerInfo.split(" ").pop().trim()
+ let slayerLevelToExp = {
+ "I": 5,
+ "II": 25,
+ "III": 100,
+ "IV": 500,
+ "V": 1500
+ }
+ this.lastSlayerExp = slayerLevelToExp[levelString]
+ let slayerStrToType = {
+ "revenant": "zombie",
+ "tarantula": "spider",
+ "sven": "wolf",
+ "voidgloom":"enderman"
+ }
+ this.lastSlayerType = slayerStrToType[slayerInfo.split(" ")[0].toLowerCase()]
+ //slayerExp[lastSlayerType] += lastSlayerExp
+ }
+ if (line.getName().includes('Boss slain!')) {
+ this.bossSlainMessage = true
+ }
+
+ if (line.getName().includes('Slay the boss!')) {
+
+ if(!this.bossSpawnedMessage && !this.emanBoss){
+ this.nextIsBoss = Date.now()
+ }
+
+ dis1 = true
+ this.bossSpawnedMessage = true
+ }
+ })
+ if (!dis1) {
+ this.lastBossNotSpawnedTime = Date.now()
+ this.bossSpawnedMessage = false
+ }
+
+
+ this.todoE.forEach(e=>{
+ try{
+ if(e instanceof net.minecraft.entity.item.EntityArmorStand && e.func_71124_b(4)){
+ if(e.func_71124_b(4).func_82833_r() === "Beacon"){
+
+ let closestEIsGaming = false
+ let closestDist = Infinity
+ World.getAllEntitiesOfType(net.minecraft.entity.item.EntityArmorStand).forEach(e2=>{
+ if(e2.getName().includes("Voidgloom Seraph")){
+ if((e2.getX() - e.field_70165_t)**2 + (e2.getY() - e.field_70163_u)**2 + (e2.getZ() - e.field_70161_v)**2 < closestDist){
+ closestDist = (e2.getX() - e.field_70165_t)**2 + (e2.getY() - e.field_70163_u)**2 + (e2.getZ() - e.field_70161_v)**2
+ closestEIsGaming = this.emanBoss?e2.getUUID().toString()===this.emanBoss.getUUID().toString():false
+ }
+ }
+ })
+ if(closestEIsGaming){
+ this.beaconE.push(e)
+ }
+ }
+ if(e.func_71124_b(4).func_82833_r().startsWith("ยงa")){
+
+ let closestEIsGaming = false
+ let closestDist = Infinity
+ World.getAllEntitiesOfType(net.minecraft.entity.item.EntityArmorStand).forEach(e2=>{
+ if(e2.getName().includes("Voidgloom Seraph")){
+ if((e2.getX() - e.field_70165_t)**2 + (e2.getY() - e.field_70163_u)**2 + (e2.getZ() - e.field_70161_v)**2 < closestDist){
+ closestDist = (e2.getX() - e.field_70165_t)**2 + (e2.getY() - e.field_70163_u)**2 + (e2.getZ() - e.field_70161_v)**2
+ closestEIsGaming = this.emanBoss?e2.getUUID().toString()===this.emanBoss.getUUID().toString():false
+ }
+ }
+ })
+
+ if(closestEIsGaming && new Item(e.func_71124_b(4)).getNBT().getCompoundTag("tag").getCompoundTag("SkullOwner").getCompoundTag("Properties").getRawNBT().func_150295_c("textures", 10).func_150305_b(0).func_74779_i("Value") === "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWIwNzU5NGUyZGYyNzM5MjFhNzdjMTAxZDBiZmRmYTExMTVhYmVkNWI5YjIwMjllYjQ5NmNlYmE5YmRiYjRiMyJ9fX0="){
+ this.eyeE.push(new Entity(e))
+ }
+ // console.log(":" + new Item(e.func_71124_b(4)).getNBT().getCompoundTag("tag").getCompoundTag("SkullOwner").getCompoundTag("Properties").getRawNBT().func_150295_c("textures", 10).func_150305_b(0).func_74779_i("Value"))
+ }
+ }
+
+ if(e.func_95999_t() && e.func_95999_t().includes("Voidgloom Seraph")){
+ if(Date.now()-this.nextIsBoss < 3000){
+ this.emanBoss = new Entity(e)
+ this.nextIsBoss = false
+ }
+ }
+ }catch(_){console.log(JSON.stringify(_, undefined, 2))}
+ })
+ this.todoE = this.todoE2
+ this.todoE2 = []
+
+ if(this.emanBoss && this.emanBoss.getEntity().field_70128_L) this.emanBoss = undefined
+ this.eyeE = this.eyeE.filter(e=>!e.getEntity().field_70128_L)
+ this.beaconE = this.beaconE.filter((e)=>{
+ if(e.field_70128_L){
+ this.deadE.push([Date.now(), e.func_110124_au().toString()])
+
+ let pos = [e.field_70165_t+0.5, e.field_70163_u+0.7, e.field_70161_v+0.5]
+ //check for a beacon block within 5 blocks of pos
+ for(let x = pos[0] - 5; x <= pos[0] + 5; x++){
+ for(let y = pos[1] - 5; y <= pos[1] + 5; y++){
+ for(let z = pos[2] - 5; z <= pos[2] + 5; z++){
+ if(World.getBlockAt(Math.floor(x), Math.floor(y), Math.floor(z)).getID() === 138){
+ this.beaconLocations[e.func_110124_au().toString()] = [Math.floor(x), Math.floor(y), Math.floor(z)]
+ }
+ }
+ }
+ }
+
+ // if(!this.beaconLocations[e.func_110124_au().toString()]){
+ // console.log("Diddnt find beacon wtf?????")
+ // }
+
+ return false
+ }
+ return true
+ })
+
+ this.beaconE.forEach((e)=>{
+ if(!this.beaconPoints[e.func_110124_au().toString()])this.beaconPoints[e.func_110124_au().toString()] = []
+
+ this.beaconPoints[e.func_110124_au().toString()].push([e.field_70165_t+0.5, e.field_70163_u+0.7, e.field_70161_v+0.5])//x, y, z
+ })
+
+ this.deadE = this.deadE.filter(e=>{
+ if(Date.now()-e[0] > 5000){
+ delete this.beaconPoints[e[1]]
+ delete this.beaconLocations[e[1]]
+ return false
+ }
+
+ let location = this.beaconLocations[e[1]]
+ if(!location){
+ delete this.beaconPoints[e[1]]
+ delete this.beaconLocations[e[1]]
+ return false
+ }
+
+ if(World.getBlockAt(location[0], location[1], location[2]).getID() === 138){
+ Client.showTitle("&cGO TO BEACON!","&c" + (Math.max(0,5000-(Date.now()-e[0]))/1000).toFixed(1) + "s",0,20,10)
+ World.playSound("note.pling",1,1)
+ }else{
+ delete this.beaconPoints[e[1]]
+ delete this.beaconLocations[e[1]]
+ return false
+ }
+ return true
+ })
+
+ if(this.emanBoss){
+ this.emanHpElement.setText("&6Enderman&7> " + this.emanBoss.getName().split("Voidgloom Seraph")[1].trim())
+ }else{
+ this.emanHpElement.setText("")
+ }
+ }
+
+ apiLoad(data, dataType, isSoopyServer, isLatest){
+ if(!isSoopyServer || !isLatest) return
+ if(dataType !== "skyblock") return
+
+ this.slayerExp.zombie = data.data.profiles[data.data.stats.currentProfileId].members[Player.getUUID().replace(/-/g, "")].slayer.zombie.xp
+ this.slayerExp.spider = data.data.profiles[data.data.stats.currentProfileId].members[Player.getUUID().replace(/-/g, "")].slayer.spider.xp
+ this.slayerExp.wolf = data.data.profiles[data.data.stats.currentProfileId].members[Player.getUUID().replace(/-/g, "")].slayer.wolf.xp
+ this.slayerExp.enderman = data.data.profiles[data.data.stats.currentProfileId].members[Player.getUUID().replace(/-/g, "")].slayer.enderman.xp
+ }
+
+ renderOverlay(){
+ if(this.slainAlert.getValue() && this.bossSlainMessage){
+ let scale = Renderer.getStringWidth(ChatLib.removeFormatting("BOSS SLAIN"))/(Renderer.screen.getWidth()*0.75)
+
+ Renderer.scale(1/scale, 1/scale)
+ Renderer.drawString("&4BOSS SLAIN", (Renderer.screen.getWidth()*0.125)*scale, (Renderer.screen.getHeight()/2-9/scale)*scale)
+ Renderer.scale(1, 1)
+ }
+ if(this.spawnAlert.getValue() && this.bossSpawnedMessage && Date.now()-this.lastBossNotSpawnedTime<3000){
+ let scale = Renderer.getStringWidth(ChatLib.removeFormatting("BOSS SPAWNED"))/(Renderer.screen.getWidth()*0.75)
+
+ Renderer.scale(1/scale, 1/scale)
+ Renderer.drawString("&4BOSS SPAWNED", (Renderer.screen.getWidth()*0.125)*scale, (Renderer.screen.getHeight()/2-9/scale)*scale)
+ Renderer.scale(1, 1)
+ }
+ }
+
+ initVariables(){
+ this.expOnKill = undefined
+ this.slainAlert = undefined
+ this.spawnAlert = undefined
+ this.slayerExp = undefined
+ this.slayerExpLoaded = undefined
+ this.lastSlayerType = undefined
+ this.lastSlayerExp = undefined
+ this.bossSpawnedMessage = undefined
+ this.lastBossNotSpawnedTime = undefined
+ this.bossSlainMessage = undefined
+ this.todoE = undefined
+ this.beaconPoints = undefined
+ this.beaconE = undefined
+ this.deadE = undefined
+ this.beaconLocations = undefined
+ this.emanBoss = undefined
+ this.eyeE = undefined
+ this.nextIsBoss = undefined
+ this.hudElements = []
+ this.todoE2 = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+module.exports = {
+ class: new Slayers()
+} \ No newline at end of file
diff --git a/features/slayers/metadata.json b/features/slayers/metadata.json
new file mode 100644
index 0000000..b186b29
--- /dev/null
+++ b/features/slayers/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Slayer",
+ "description": "Slayer features",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/soopyGui/GuiPage.js b/features/soopyGui/GuiPage.js
new file mode 100644
index 0000000..09142d0
--- /dev/null
+++ b/features/soopyGui/GuiPage.js
@@ -0,0 +1,46 @@
+import SoopyGuiElement from '../../../guimanager/GuiElement/SoopyGuiElement.js';
+
+class GuiPage{
+ constructor(priority){
+ this.currentPageId = 0;
+ this.priority = priority
+
+ this.soopyGui = require('./index.js').class;
+ this.name = ""
+
+ this.pages = {}
+ }
+
+ finaliseLoading(){
+ this.soopyGui.addCategory(this);
+ }
+
+ newPage(){
+ this.currentPageId++
+ let page = new SoopyGuiElement().setLocation(1*this.currentPageId,0,1,1)
+
+ page._soopyAddonsPageId = this.currentPageId
+
+ this.pages[this.currentPageId] = page
+
+ return page
+ }
+
+ goToPage(page, anim){
+ this.soopyGui.goToPageNum(page, anim)
+ }
+
+ openSidebarPage(child){
+ this.soopyGui.openSidebarPage(child)
+ }
+ closeSidebarPage(){
+ this.soopyGui.closeSidebarPage()
+ }
+
+ //Override me :D
+ onOpen(){
+
+ }
+}
+
+export default GuiPage; \ No newline at end of file
diff --git a/features/soopyGui/index.js b/features/soopyGui/index.js
new file mode 100644
index 0000000..428bbf1
--- /dev/null
+++ b/features/soopyGui/index.js
@@ -0,0 +1,192 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import * as GuiManager from "../../../guimanager/index.js"
+import SoopyGuiElement from "../../../guimanager/GuiElement/SoopyGuiElement";
+import SoopyTextElement from "../../../guimanager/GuiElement/SoopyTextElement";
+import SoopyBoxElement from "../../../guimanager/GuiElement/SoopyBoxElement";
+import TextWithArrow from "../../../guimanager/GuiElement/TextWithArrow";
+import ButtonWithArrow from "../../../guimanager/GuiElement/ButtonWithArrow";
+import SoopyMouseClickEvent from "../../../guimanager/EventListener/SoopyMouseClickEvent";
+import SoopyOpenGuiEvent from "../../../guimanager/EventListener/SoopyOpenGuiEvent";
+
+
+class SoopyGui extends Feature {
+ constructor() {
+ super()
+
+ this.gui = undefined
+
+ this.pages = []
+ this.currentPage = 0
+ this.backButton = undefined
+
+ this.categoryPage = undefined
+ this.currCategory = undefined
+ this.activePages = []
+ this.lastClickedOpen = undefined
+ }
+
+ onEnable(){
+ this.gui = new GuiManager.SoopyGui()
+
+ // this.gui.isDebugEnabled = true
+
+ this.gui.setOpenCommand("soopyv2")
+
+ this.mainWindowElement = new SoopyBoxElement().setLocation(0.25, 0.2, 0.5, 0.6)
+
+ this.mainWindowElement.addEvent(new SoopyOpenGuiEvent().setHandler(()=>{this.goToPageNum(0, false)}))
+
+ //###############################################################################################
+ // Category Page
+ //###############################################################################################
+
+ this.categoryPage = new SoopyGuiElement().setLocation(0, 0, 1, 1)
+
+ let title = new SoopyTextElement().setText("ยง0Soopy Addons!").setMaxTextScale(3).setLocation(0.1, 0.05, 0.8, 0.1)
+ this.categoryPage.addChild(title)
+
+ this.buttonListElm = new SoopyGuiElement().setLocation(0.1, 0.2, 0.8, 0.8).setScrollable(true)
+ this.categoryPage.addChild(this.buttonListElm)
+
+ //###############################################################################################
+ // Back button for all second pages
+ //###############################################################################################
+
+ this.backButton = new TextWithArrow().setText("ยง0Back").setLocation(0.01, -0.2, 0.1, 0.1).setDirectionRight(false)
+ let backButtonEvent = new SoopyMouseClickEvent().setHandler(()=>{this.clickedBackButton()})
+ this.backButton.addEvent(backButtonEvent)
+
+ this.mainWindowElement.addChild(this.categoryPage)
+ this.mainWindowElement.addChild(this.backButton)
+
+ this.sidebarPage = new SoopyBoxElement().setLocation(0.3, 0.2, 0.3, 0.6)
+ // this.sidebarPage.visable = false
+
+ this.gui.element.addChild(this.sidebarPage)
+ this.gui.element.addChild(this.mainWindowElement)
+
+ this.updateButtons()
+ }
+
+ addCategory(category){
+ this.pages.push(category)
+ this.sortPages()
+
+ this.updateButtons()
+ }
+
+ updateButtons(){
+ if(!this.buttonListElm) return;
+
+ this.buttonListElm.children = []
+
+ this.pages.forEach((p, i)=>{
+ let settingsButton = new ButtonWithArrow().setText("ยง0" + p.name).setLocation(0, 0.25*i, 1, 0.2)
+ settingsButton.addEvent(new SoopyMouseClickEvent().setHandler(()=>{this.clickedOpen(p)}))
+ this.buttonListElm.addChild(settingsButton)
+ })
+ }
+
+ clickedOpen(category){
+ if(!this.lastClickedOpen)this.lastClickedOpen = 0
+ if(Date.now()-this.lastClickedOpen < 100) return //Stopping infinite loop where button getting reset causes click event to get fired again
+ this.lastClickedOpen = Date.now()
+
+ let theParent = this.mainWindowElement.innerObjectPaddingThing || this.mainWindowElement
+ theParent.children = []
+
+ this.mainWindowElement.addChild(this.categoryPage)
+ this.mainWindowElement.addChild(this.backButton)
+
+ this.activePages = category.pages
+
+ Object.values(this.activePages).forEach(p=>{
+ this.mainWindowElement.addChild(p)
+ })
+
+ this.goToPageNum(1, true)
+
+ category.onOpen()
+ }
+
+ sortPages(){
+ this.pages = this.pages.sort((a, b)=>{
+ return b.priority - a.priority
+ })
+ }
+
+ onDisable(){
+ this.gui.delete()
+
+ this.gui = undefined
+
+ this.pages = []
+ this.currentPage = 0
+ this.backButton = undefined
+ this.activePages = []
+ this.currCategory = undefined
+ this.lastClickedOpen = undefined
+ }
+
+ clickedBackButton(){
+ this.goToPageNum(this.currentPage-1)
+ }
+
+ goToPage(page, animate=true){
+ let pageNum = page._soopyAddonsPageId
+
+ if(pageNum == this.currentPage){
+ return
+ }
+
+ this.currentPage = pageNum
+
+ this.pages.forEach((p)=>{
+ Object.values(p.pages).forEach((e, i)=>{
+ e.location.location.x.set(i-pageNum+1, animate?1000:0)
+ })
+ })
+ this.categoryPage.location.location.x.set(-pageNum, animate?1000:0)
+
+ this.backButton.location.location.y.set(pageNum === 0?-0.2:0, animate?1000:0)
+ }
+ goToPageNum(pageNum, animate=true){
+ if(pageNum<0) return;
+
+ this.currentPage = pageNum
+ if(pageNum===0){
+ this.currCategory = undefined
+ this.closeSidebarPage()
+ }
+
+ this.pages.forEach((p)=>{
+ Object.values(p.pages).forEach((e, i)=>{
+ e.location.location.x.set(i-pageNum+1, animate?1000:0)
+ })
+ })
+ this.categoryPage.location.location.x.set(-pageNum, animate?1000:0)
+
+ this.backButton.location.location.y.set(pageNum === 0?-0.2:0, animate?1000:0)
+ }
+ openSidebarPage(child){
+ this.sidebarPage.location.location.x.set(0.625, 500)
+ this.mainWindowElement.location.location.x.set(0.075, 500)
+
+ // this.sidebarPage.visable = true
+ this.sidebarPage.addChild(child)
+ }
+
+ closeSidebarPage(){
+ this.sidebarPage.location.location.x.set(0.3, 500)
+ this.mainWindowElement.location.location.x.set(0.25, 500)
+
+ this.sidebarPage.clearChildren()
+ // this.sidebarPage.visable = false
+ }
+}
+
+module.exports = {
+ class: new SoopyGui()
+} \ No newline at end of file
diff --git a/features/soopyGui/metadata.json b/features/soopyGui/metadata.json
new file mode 100644
index 0000000..8f2ace4
--- /dev/null
+++ b/features/soopyGui/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Settings",
+ "description": "Adds a settings gui with toggles for all the modules, also renders each individual module's settings",
+ "isHidden": true,
+ "isTogglable": false,
+ "defaultEnabled": true,
+ "sortA": 0
+} \ No newline at end of file
diff --git a/features/spamHider/index.js b/features/spamHider/index.js
new file mode 100644
index 0000000..37c964f
--- /dev/null
+++ b/features/spamHider/index.js
@@ -0,0 +1,175 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+import { newSideMessage, setLocation } from "../../../soopyApis";
+
+class SpamHider extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ this.initVariables()
+
+ this.hideMessages = []
+ this.hideMessagesRexex = []
+ this.moveMessages = []
+ this.moveMessagesRexex = []
+
+ this.SpamHiderMessagesRenderer = new SpamHiderMessagesRenderer()
+
+ this.loadSpamMessages()
+
+ this.registerChat("${*}", this.onChat)
+
+ this.registerEvent("renderOverlay", this.renderOverlay)
+ }
+
+ onChat(e){
+ let msg = ChatLib.getChatMessage(e, true).replace(/ยง/g, "&").replace(/(?:^&r)|(?:&r$)/g, "")
+
+ this.hideMessagesRexex.forEach(regex => {
+ if(regex.test(msg)){
+ cancel(e)
+ return
+ }
+ })
+
+ this.moveMessagesRexex.forEach(regex => {
+ if(regex.test(msg)){
+ this.SpamHiderMessagesRenderer.addMessage(msg)
+ cancel(e)
+ return
+ }
+ })
+ }
+ renderOverlay(){
+ this.SpamHiderMessagesRenderer.render(100,100,1, 1)
+ }
+
+ loadSpamMessages(){
+ let messages = JSON.parse(FileLib.getUrlContent("http://soopymc.my.to/api/soopyv2/spamHiderMessages.json"))
+ this.hideMessages = messages.hideMessages
+ this.moveMessages = messages.moveMessages
+
+ this.hideMessagesRexex = []
+ this.hideMessages.forEach(message=>{
+ let regex = new RegExp(message.replace(/[\\^$*+?.()|[\]{}]/g, '\$&')
+ .replace(/\$\{\*\}/g, "(?:.+)"))
+ this.hideMessagesRexex.push(regex)
+ })
+
+ this.moveMessagesRexex = []
+ this.moveMessages.forEach(message=>{
+ let regex = new RegExp(message.replace(/[\\^$*+?.()|[\]{}]/g, '\$&')
+ .replace(/\$\{\*\}/g, "(?:.+)"))
+
+ this.moveMessagesRexex.push(regex)
+ })
+ }
+
+ initVariables(){
+ this.hideMessages = undefined
+ this.hideMessagesRexex = undefined
+ this.moveMessages = undefined
+ this.moveMessagesRexex = undefined
+ this.SpamHiderMessagesRenderer = undefined
+ }
+
+ onDisable(){
+ this.initVariables()
+ }
+}
+
+class SpamHiderMessagesRenderer{
+ constructor(){
+ this.messages = []
+ this.x = 0 //offset from corner, not absolute location
+ this.y = 0 //offset from corner, not absolute location
+ this.scale = 1
+ this.corner = 2
+
+ this.lastRender = 0
+ }
+
+ addMessage(str){
+ this.messages.push([str, Date.now(), this.y])
+ }
+
+ render(){
+ Renderer.drawString("", -100, -100)//Fixes skytils issue //idk if this is still needed, it was in old code and imma just leave it ig
+
+ let now = Date.now()
+ let animDiv = (now-this.lastRender) / 1000
+ this.lastRender = now
+ let swidth = Renderer.screen.getWidth()
+ let sheight = Renderer.screen.getHeight()
+
+ //loop over all messages backwards
+ for(let i = this.messages.length - 1; i >= 0; i--){
+ let message = this.messages[i]
+
+ let [str, time, height] = message
+
+ time = now-time
+
+ let messageWidth = Renderer.getStringWidth(ChatLib.removeFormatting(str))
+
+ let x = 0;
+ let y = 0;
+ if (this.corner === 0) { //top left
+ x = 20
+ this.messages[i][2] = height + (((this.messages.length-i) * -10) - height) * (animDiv * 5)
+ }
+ if (this.corner === 1) { //top right
+ x = swidth - 20 - messageWidth
+ this.messages[i][2] = height + (((this.messages.length-i) * -10) - height) * (animDiv * 5)
+ }
+ if (this.corner === 2) { //bottom right
+ x = swidth - 20 - messageWidth
+ this.messages[i][2] = height + (((this.messages.length-i) * 10) - height) * (animDiv * 5)
+ }
+
+ let animOnOff = 0
+ if (time < 500) {
+ animOnOff = 1 - (time / 500)
+ }
+ if (time > 3500) {
+ animOnOff = ((time - 3500) / 500)
+ }
+
+ animOnOff *= 90
+ animOnOff += 90
+
+ animOnOff = animOnOff * Math.PI / 180;
+
+ animOnOff = Math.sin(animOnOff)
+
+ animOnOff *= -1
+ animOnOff += 1
+
+ if (this.corner === 0) { //top left
+ x += ((animOnOff * -1) * (messageWidth + 30))
+ y = 30 - (height)
+ }
+ if (this.corner === 1) { //top right
+ x += (animOnOff * (messageWidth + 30))
+ y = 30 - (height)
+ }
+ if (this.corner === 2) { //bottom right
+ x += (animOnOff * (messageWidth + 30))
+ y = sheight - 30 - (height)
+ }
+
+ Renderer.drawString(str, x + this.x, y + this.y);
+
+ if (time > 4000) {
+ this.messages.shift()
+ }
+ }
+ }
+}
+
+module.exports = {
+ class: new SpamHider()
+} \ No newline at end of file
diff --git a/features/spamHider/metadata.json b/features/spamHider/metadata.json
new file mode 100644
index 0000000..7e8e2b1
--- /dev/null
+++ b/features/spamHider/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Spam Hider",
+ "description": "A couple of features to help clean up your chat",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 1
+} \ No newline at end of file
diff --git a/features/stat_next_to_name/index.js b/features/stat_next_to_name/index.js
new file mode 100644
index 0000000..b7addb9
--- /dev/null
+++ b/features/stat_next_to_name/index.js
@@ -0,0 +1,19 @@
+/// <reference types="../../../CTAutocomplete" />
+/// <reference lib="es2015" />
+import Feature from "../../featureClass/class";
+
+class StatNextToName extends Feature {
+ constructor() {
+ super()
+ }
+
+ onEnable(){
+ }
+
+ onDisable(){
+ }
+}
+
+module.exports = {
+ class: new StatNextToName()
+} \ No newline at end of file
diff --git a/features/stat_next_to_name/metadata.json b/features/stat_next_to_name/metadata.json
new file mode 100644
index 0000000..cd2f79c
--- /dev/null
+++ b/features/stat_next_to_name/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "Stat next to name",
+ "description": "Shows a players skyblock stat next to their name in the world",
+ "isHidden": false,
+ "isTogglable": true,
+ "defaultEnabled": true,
+ "sortA": 0
+} \ No newline at end of file
diff --git a/forgeEvents.js b/forgeEvents.js
new file mode 100644
index 0000000..e852c49
--- /dev/null
+++ b/forgeEvents.js
@@ -0,0 +1,93 @@
+importClass(net.minecraftforge.common.MinecraftForge) //i would have used the ct module but it is broken (line 78) (this is fixed verison)
+importPackage(net.minecraftforge.fml.common.eventhandler)
+importPackage(org.objectweb.asm)
+importClass(java.lang.ClassLoader)
+importClass(org.apache.commons.lang3.RandomStringUtils)
+importClass(java.util.function.Consumer)
+
+const L = s => `L${s};`
+
+const LoadedInsts = []
+
+function defineClassBytes(name, bytes) {
+ const classLoader = Packages.com.chattriggers.ctjs.CTJS.class.getClassLoader()
+
+ const defClass = ClassLoader.class.getDeclaredMethods()[23] // defineClass()
+
+ defClass.setAccessible(true)
+
+ const n = new java.lang.String(name)
+ const o = new java.lang.Integer(0)
+ const s = new java.lang.Integer(bytes.length)
+ return defClass.invoke(classLoader, n, bytes, o, s)
+}
+
+const registerForge = (e, cb) => {
+ const cw = new ClassWriter(0)
+
+ const event = Type.getType(e.class).internalName
+ const name = RandomStringUtils.randomAlphabetic(7)
+
+ const consumer = Type.getType(Consumer.class).internalName
+ const mcForge = Type.getType(MinecraftForge.class).internalName
+ const eventBus = Type.getType(EventBus.class).internalName
+ const subscribeEvent = Type.getType(SubscribeEvent.class).internalName
+ const obj = Type.getType(java.lang.Object.class).internalName
+
+ cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null, obj, null)
+ // cw.visitInnerClass("net/minecraftforge/event/entity/player/PlayerEvent$BreakSpeed", "net/minecraftforge/event/entity/player/PlayerEvent", "BreakSpeed", ACC_PUBLIC + ACC_STATIC);
+ {
+ cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, "callback", L(consumer), L(consumer + "<" + L(event) + ">"), null).visitEnd()
+ }
+ {
+ const con = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(" + L(consumer) + ")V", "(" + L(consumer + "<" + L(event) + ">") + ")V", null)
+ con.visitCode()
+ con.visitVarInsn(Opcodes.ALOAD, 0)
+ con.visitMethodInsn(Opcodes.INVOKESPECIAL, obj, "<init>", "()V", false)
+
+ con.visitVarInsn(Opcodes.ALOAD, 0)
+ con.visitVarInsn(Opcodes.ALOAD, 1)
+ con.visitFieldInsn(Opcodes.PUTFIELD, name, "callback", L(consumer))
+ con.visitFieldInsn(Opcodes.GETSTATIC, mcForge, "EVENT_BUS", L(eventBus))
+ con.visitVarInsn(Opcodes.ALOAD, 0)
+ con.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventBus, "register", "(" + L(obj) + ")V", false)
+ con.visitInsn(Opcodes.RETURN)
+ con.visitMaxs(2, 2)
+ con.visitEnd()
+ }
+ {
+ const mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "on", "(" + L(event) + ")V", null, null)
+ {
+ const av = mv.visitAnnotation(L(subscribeEvent), true)
+ av.visitEnd()
+ }
+ mv.visitCode()
+ mv.visitVarInsn(Opcodes.ALOAD, 0)
+ mv.visitFieldInsn(Opcodes.GETFIELD, name, "callback", L(consumer))
+ mv.visitVarInsn(Opcodes.ALOAD, 1)
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, consumer, "accept", "(" + L(obj) + ")V", true)
+ mv.visitInsn(Opcodes.RETURN)
+ mv.visitMaxs(2, 2)
+ mv.visitEnd()
+ }
+ cw.visitEnd()
+
+ const inst = defineClassBytes(name, cw.toByteArray())
+ .getDeclaredConstructor(Consumer.class)
+ .newInstance(new java.util.function.Consumer({
+ accept: function (t) { cb(t) }
+ }))
+ LoadedInsts.push(inst)
+ return inst;
+}
+
+const unregisterForge = inst => {
+ MinecraftForge.EVENT_BUS.unregister(inst)
+}
+
+register("gameUnload", () => {
+ LoadedInsts.forEach(unregisterForge)
+ LoadedInsts.length = 0
+})
+
+export { registerForge, unregisterForge }
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..6e08fb6
--- /dev/null
+++ b/index.js
@@ -0,0 +1,12 @@
+/// <reference types="../CTAutocomplete" />
+/// <reference lib="es2015" />
+
+class SoopyAddons {
+ constructor(){
+ this.FeatureManager = require("./featureClass/featureManager.js")
+
+ this.FeatureManager.parent = this
+ }
+}
+
+soopyAddons = new SoopyAddons() \ No newline at end of file
diff --git a/logger.js b/logger.js
new file mode 100644
index 0000000..ae44ae1
--- /dev/null
+++ b/logger.js
@@ -0,0 +1,28 @@
+/// <reference types="../CTAutocomplete" />
+/// <reference lib="es2015" />
+
+class Logger{
+ constructor(){
+ this.loglevel = 4 //0=none, 1=error, 2=warn, 3=info, 4=debug
+ this.logToMcChat = false
+ this.logPrefixes = [
+ "[SOOPYADDONS] ",
+ "[SOOPYADDONS:ERROR] ",
+ "[SOOPYADDONS:WARN] ",
+ "[SOOPYADDONS:INFO] ",
+ "[SOOPYADDONS:DEBUG] "
+ ]
+ this.logMessage("Logger initialised", 3)
+ }
+
+ logMessage(message, level){
+ if(level <= this.loglevel){
+ console.log(this.logPrefixes[level] + message)
+ if(this.logToMcChat){
+ ChatLib.chat(this.logPrefixes[level] + message)
+ }
+ }
+ }
+}
+
+export default new Logger() \ No newline at end of file
diff --git a/metadata.js b/metadata.js
new file mode 100644
index 0000000..aa492b2
--- /dev/null
+++ b/metadata.js
@@ -0,0 +1,5 @@
+if(!global.soopyaddonsv2metathing){
+ global.soopyaddonsv2metathing = JSON.parse(FileLib.read("SoopyV2", "metadata.json"))
+}
+
+export default global.soopyaddonsv2metathing \ No newline at end of file
diff --git a/metadata.json b/metadata.json
new file mode 100644
index 0000000..739b19b
--- /dev/null
+++ b/metadata.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "../CTAutocomplete/schema.json",
+ "creator": "Soopyboo32",
+ "author": "Soopyboo32",
+ "entry": "index.js",
+ "description": "Soopy addons v2",
+ "name": "SoopyV2",
+ "version": "2.0.1",
+ "versionId": 111,
+ "requires": [
+ "soopyApis",
+ "soopyAddonsData",
+ "CustomTabCompletions",
+ "guimanager"
+ ]
+} \ No newline at end of file
diff --git a/utils/numberUtils.js b/utils/numberUtils.js
new file mode 100644
index 0000000..ccc270e
--- /dev/null
+++ b/utils/numberUtils.js
@@ -0,0 +1,50 @@
+module.exports = {
+ numberWithCommas: function(x){
+ if (x === undefined) { return "" }
+ var parts = x.toString().split(".");
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ return parts.join(".");
+ },
+ addNotation: function(type, value) {
+ let returnVal = value;
+ let notList = [];
+ if (type === "shortScale") {
+ //notation type
+ //do notation stuff here
+ notList = [
+ " Thousand",
+ " Million",
+ " Billion",
+ " Trillion",
+ " Quadrillion",
+ " Quintillion"
+ ];
+ }
+
+ if (type === "oneLetters") {
+ notList = [" K", " M", " B", " T"];
+ }
+
+ let checkNum = 1000;
+
+ if (type !== "none" && type !== "commas") {
+ let notValue = notList[notList.length - 1];
+ for (let u = notList.length; u >= 1; u--) {
+ notValue = notList.shift();
+ for (let o = 3; o >= 1; o--) {
+ if (value >= checkNum) {
+ returnVal = value / (checkNum / 100);
+ returnVal = Math.floor(returnVal);
+ returnVal = (returnVal / Math.pow(10, o)) * 10;
+ returnVal = +returnVal.toFixed(o - 1) + notValue;
+ }
+ checkNum *= 10;
+ }
+ }
+ } else {
+ returnVal = this.numberWithCommas(value.toFixed(0));
+ }
+
+ return returnVal;
+ }
+} \ No newline at end of file
diff --git a/utils/renderLib2d.js b/utils/renderLib2d.js
new file mode 100644
index 0000000..b2defd1
--- /dev/null
+++ b/utils/renderLib2d.js
@@ -0,0 +1,147 @@
+
+//--------------------------------------------------------------------------
+// CODE BY DJtheRedstoner
+// IM COPYING THIS BECAUSE THE UPLOADED VERSION IS FOR
+// CT 2.0.0 ONLY
+//--------------------------------------------------------------------------
+
+//TODO: just require the module when ct 2.0 comes out
+
+
+
+const BufferUtils = org.lwjgl.BufferUtils;
+const Project = org.lwjgl.util.glu.Project;
+
+const modelViewMatrix = BufferUtils.createFloatBuffer(16);
+const projectionMatrix = BufferUtils.createFloatBuffer(16);
+const viewportDims = BufferUtils.createIntBuffer(16);
+
+const ScaledResolution = net.minecraft.client.gui.ScaledResolution;
+
+register('renderWorld', () => {
+ Tessellator.pushMatrix();
+
+ let x = Player.getRenderX();
+ let y = Player.getRenderY();
+ let z = Player.getRenderZ();
+
+ Tessellator.translate(-x, -y, -z);
+
+ GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, modelViewMatrix);
+ GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, projectionMatrix);
+
+ Tessellator.popMatrix();
+
+ GL11.glGetInteger(GL11.GL_VIEWPORT, viewportDims);
+});
+
+export default class RenderLib2D {
+ // Utils
+
+ // Original made by DJtheRedstoner
+ static projectPoint = (posX, posY, posZ) => {
+ const coords = BufferUtils.createFloatBuffer(3);
+ const success = Project.gluProject(
+ posX,
+ posY,
+ posZ,
+ modelViewMatrix,
+ projectionMatrix,
+ viewportDims,
+ coords
+ );
+
+ const z = coords.get(2);
+ if (!success || !(z > 0 && z < 1)) return null;
+
+ const sr = new ScaledResolution(Client.getMinecraft());
+
+ const x = coords.get(0) / sr.func_78325_e(); // getScaleFactor
+ let y = coords.get(1) / sr.func_78325_e(); // getScaleFactor
+ // OpenGL starts at bottom left, mc starts at top left
+ y = sr.func_78328_b() - y; // getScaledHeight
+
+ return { x, y, z };
+ }
+
+ // Original made by DJtheRedstoner
+ static calculateBoundingBox = (box) => {
+ let vertices = RenderLib2D.getVertices(box);
+
+ let x1 = java.lang.Float.MAX_VALUE;
+ let x2 = 0;
+ let y1 = java.lang.Float.MAX_VALUE;
+ let y2 = 0;
+
+ vertices.forEach(vertex => {
+ let vec = RenderLib2D.projectPoint(vertex.x, vertex.y, vertex.z);
+ if (vec == null) return null;
+
+ let x = vec.x;
+ let y = vec.y;
+
+ if (x < x1) x1 = x;
+ if (x > x2) x2 = x;
+ if (y < y1) y1 = y;
+ if (y > y2) y2 = y;
+ });
+
+ return { x1, y1, x2, y2 };
+ }
+
+ static getVertices = (box) => {
+ let list = [];
+
+ list.push({ x: box.field_72340_a, y: box.field_72338_b, z: box.field_72339_c });
+ list.push({ x: box.field_72336_d, y: box.field_72338_b, z: box.field_72339_c });
+ list.push({ x: box.field_72336_d, y: box.field_72337_e, z: box.field_72339_c });
+ list.push({ x: box.field_72340_a, y: box.field_72337_e, z: box.field_72339_c });
+ list.push({ x: box.field_72340_a, y: box.field_72338_b, z: box.field_72334_f });
+ list.push({ x: box.field_72336_d, y: box.field_72338_b, z: box.field_72334_f });
+ list.push({ x: box.field_72336_d, y: box.field_72337_e, z: box.field_72334_f });
+ list.push({ x: box.field_72340_a, y: box.field_72337_e, z: box.field_72334_f });
+
+ return list;
+ }
+
+ // Rendering Functions
+
+ static drawNameTag = (vec, string) => {
+ if (vec === null) return;
+
+ Renderer.drawStringWithShadow(string, vec.x - Renderer.getStringWidth(string) / 2, vec.y);
+ }
+
+ static draw2DESP = (aabb, color, thickness) => {
+ let bb = RenderLib2D.calculateBoundingBox(aabb);
+
+ Renderer.drawLine(color, bb.x1, bb.y1, bb.x1, bb.y2, thickness);
+ Renderer.drawLine(color, bb.x1, bb.y1, bb.x2, bb.y1, thickness);
+ Renderer.drawLine(color, bb.x2, bb.y2, bb.x2, bb.y1, thickness);
+ Renderer.drawLine(color, bb.x2, bb.y2, bb.x1, bb.y2, thickness);
+ }
+
+ static draw3DESP = (aabb, color, thickness) => {
+ let vertices = RenderLib2D.getVertices(aabb);
+ let projected = [];
+
+ vertices.forEach(vertex => {
+ let vec = RenderLib2D.projectPoint(vertex.x, vertex.y, vertex.z);
+ if (vec == null) return null;
+ projected.push(vec);
+ });
+
+ if (projected[0] && projected[1]) Renderer.drawLine(color, projected[0].x, projected[0].y, projected[1].x, projected[1].y, thickness);
+ if (projected[0] && projected[4]) Renderer.drawLine(color, projected[0].x, projected[0].y, projected[4].x, projected[4].y, thickness);
+ if (projected[5] && projected[1]) Renderer.drawLine(color, projected[5].x, projected[5].y, projected[1].x, projected[1].y, thickness);
+ if (projected[5] && projected[4]) Renderer.drawLine(color, projected[5].x, projected[5].y, projected[4].x, projected[4].y, thickness);
+ if (projected[3] && projected[2]) Renderer.drawLine(color, projected[3].x, projected[3].y, projected[2].x, projected[2].y, thickness);
+ if (projected[3] && projected[7]) Renderer.drawLine(color, projected[3].x, projected[3].y, projected[7].x, projected[7].y, thickness);
+ if (projected[6] && projected[2]) Renderer.drawLine(color, projected[6].x, projected[6].y, projected[2].x, projected[2].y, thickness);
+ if (projected[6] && projected[7]) Renderer.drawLine(color, projected[6].x, projected[6].y, projected[7].x, projected[7].y, thickness);
+ if (projected[1] && projected[2]) Renderer.drawLine(color, projected[1].x, projected[1].y, projected[2].x, projected[2].y, thickness);
+ if (projected[0] && projected[3]) Renderer.drawLine(color, projected[0].x, projected[0].y, projected[3].x, projected[3].y, thickness);
+ if (projected[4] && projected[7]) Renderer.drawLine(color, projected[4].x, projected[4].y, projected[7].x, projected[7].y, thickness);
+ if (projected[5] && projected[6]) Renderer.drawLine(color, projected[5].x, projected[5].y, projected[6].x, projected[6].y, thickness);
+ }
+}
diff --git a/utils/renderUtils.js b/utils/renderUtils.js
new file mode 100644
index 0000000..1c91b6a
--- /dev/null
+++ b/utils/renderUtils.js
@@ -0,0 +1,291 @@
+
+const GlStateManager = Java.type("net.minecraft.client.renderer.GlStateManager");
+const GL11 = Java.type("org.lwjgl.opengl.GL11");
+
+module.exports = {
+
+ /* accepts parameters
+ * h Object = {h:x, s:y, v:z}
+ * OR
+ * h, s, v
+ */
+ HSVtoRGB:function (h, s, v) {
+ var r, g, b, i, f, p, q, t;
+ if (arguments.length === 1) {
+ s = h.s, v = h.v, h = h.h;
+ }
+ i = Math.floor(h * 6);
+ f = h * 6 - i;
+ p = v * (1 - s);
+ q = v * (1 - f * s);
+ t = v * (1 - (1 - f) * s);
+ switch (i % 6) {
+ case 0: r = v, g = t, b = p; break;
+ case 1: r = q, g = v, b = p; break;
+ case 2: r = p, g = v, b = t; break;
+ case 3: r = p, g = q, b = v; break;
+ case 4: r = t, g = p, b = v; break;
+ case 5: r = v, g = p, b = q; break;
+ }
+ return {
+ r: r * 255,
+ g: g * 255,
+ b: b * 255
+ };
+ },
+ drawLine:function (x, y, z, x2, y2, z2, r, g, b, thickness=1) {
+
+ GL11.glBlendFunc(770, 771);
+ GL11.glEnable(GL11.GL_BLEND);
+ GL11.glLineWidth(thickness);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glDisable(GL11.GL_DEPTH_TEST);
+ GL11.glDepthMask(false);
+ GlStateManager.func_179094_E();
+
+ Tessellator.begin(3).colorize(r, g, b);
+
+ Tessellator.pos(x, y, z).tex(0, 0);
+ Tessellator.pos(x2, y2, z2).tex(0, 0);
+
+ Tessellator.draw();
+
+
+ GlStateManager.func_179121_F();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GL11.glEnable(GL11.GL_DEPTH_TEST);
+ GL11.glDepthMask(true);
+ GL11.glDisable(GL11.GL_BLEND);
+ },
+ drawLineWithDepth:function (x, y, z, x2, y2, z2, r, g, b,t) {
+
+ GL11.glBlendFunc(770, 771);
+ GL11.glEnable(GL11.GL_BLEND);
+ GL11.glLineWidth(t);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glDepthMask(false);
+ GlStateManager.func_179094_E();
+
+ Tessellator.begin(3).colorize(r, g, b);
+
+ Tessellator.pos(x, y, z).tex(0, 0);
+ Tessellator.pos(x2, y2, z2).tex(0, 0);
+
+ Tessellator.draw();
+
+
+ GlStateManager.func_179121_F();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GL11.glDepthMask(true);
+ GL11.glDisable(GL11.GL_BLEND);
+ },
+ drawLineSmall:function (x, y, z, x2, y2, z2, r, g, b) {
+
+ Tessellator.begin(3).colorize(r, g, b);
+
+ Tessellator.pos(x, y, z).tex(0, 0);
+ Tessellator.pos(x2, y2, z2).tex(0, 0);
+
+ Tessellator.draw();
+ },
+ drawBoxAtBlockNotVisThruWalls:function (x, y, z, colorR, colorG, colorB){
+ GL11.glBlendFunc(770, 771);
+ GL11.glEnable(GL11.GL_BLEND);
+ GL11.glLineWidth(3);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GlStateManager.func_179094_E();
+
+ x -= 0.005
+ y -= 0.005
+ z -= 0.005
+
+ Tessellator.begin(3).colorize(colorR, colorG, colorB);
+
+ Tessellator.pos(x+1.01,y+1.01,z+1.01).tex(0, 0);
+ Tessellator.pos(x+1.01,y+1.01,z).tex(0, 0);
+ Tessellator.pos(x,y+1.01,z).tex(0, 0);
+ Tessellator.pos(x,y+1.01,z+1.01).tex(0, 0);
+ Tessellator.pos(x+1.01,y+1.01,z+1.01).tex(0, 0);
+ Tessellator.pos(x+1.01,y,z+1.01).tex(0, 0);
+ Tessellator.pos(x+1.01,y,z).tex(0, 0);
+ Tessellator.pos(x,y,z).tex(0, 0);
+ Tessellator.pos(x,y,z+1.01).tex(0, 0);
+ Tessellator.pos(x,y,z).tex(0, 0);
+ Tessellator.pos(x,y+1.01,z).tex(0, 0);
+ Tessellator.pos(x,y,z).tex(0, 0);
+ Tessellator.pos(x+1.01,y,z).tex(0, 0);
+ Tessellator.pos(x+1.01,y+1.01,z).tex(0, 0);
+ Tessellator.pos(x+1.01,y,z).tex(0, 0);
+ Tessellator.pos(x+1.01,y,z+1.01).tex(0, 0);
+ Tessellator.pos(x,y,z+1.01).tex(0, 0);
+ Tessellator.pos(x,y+1.01,z+1.01).tex(0, 0);
+ Tessellator.pos(x+1.01,y+1.01,z+1.01).tex(0, 0);
+
+ Tessellator.draw();
+
+ GlStateManager.func_179121_F();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GL11.glDisable(GL11.GL_BLEND);
+ },
+ drawBoxAtBlock:function (x, y, z, colorR, colorG, colorB){
+
+ GL11.glBlendFunc(770, 771);
+ GL11.glEnable(GL11.GL_BLEND);
+ GL11.glLineWidth(3);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glDisable(GL11.GL_DEPTH_TEST);
+ GL11.glDepthMask(false);
+ GlStateManager.func_179094_E();
+
+
+ Tessellator.begin(3).colorize(colorR, colorG, colorB);
+
+ Tessellator.pos(x+1,y+1,z+1).tex(0, 0);
+ Tessellator.pos(x+1,y+1,z).tex(0, 0);
+ Tessellator.pos(x,y+1,z).tex(0, 0);
+ Tessellator.pos(x,y+1,z+1).tex(0, 0);
+ Tessellator.pos(x+1,y+1,z+1).tex(0, 0);
+ Tessellator.pos(x+1,y,z+1).tex(0, 0);
+ Tessellator.pos(x+1,y,z).tex(0, 0);
+ Tessellator.pos(x,y,z).tex(0, 0);
+ Tessellator.pos(x,y,z+1).tex(0, 0);
+ Tessellator.pos(x,y,z).tex(0, 0);
+ Tessellator.pos(x,y+1,z).tex(0, 0);
+ Tessellator.pos(x,y,z).tex(0, 0);
+ Tessellator.pos(x+1,y,z).tex(0, 0);
+ Tessellator.pos(x+1,y+1,z).tex(0, 0);
+ Tessellator.pos(x+1,y,z).tex(0, 0);
+ Tessellator.pos(x+1,y,z+1).tex(0, 0);
+ Tessellator.pos(x,y,z+1).tex(0, 0);
+ Tessellator.pos(x,y+1,z+1).tex(0, 0);
+ Tessellator.pos(x+1,y+1,z+1).tex(0, 0);
+
+ Tessellator.draw();
+
+ GlStateManager.func_179121_F();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GL11.glEnable(GL11.GL_DEPTH_TEST);
+ GL11.glDepthMask(true);
+ GL11.glDisable(GL11.GL_BLEND);
+ },
+ drawBoxAtEntity:function (entity, colorR, colorG, colorB, width, height, partialTicks, lineWidth=2, phase=false){
+ let x = entity.getX() + ((entity.getX()-entity.getLastX())*partialTicks)
+ let y = entity.getY() + ((entity.getY()-entity.getLastY())*partialTicks)
+ let z = entity.getZ() + ((entity.getZ()-entity.getLastZ())*partialTicks)
+
+ if(width === null){
+ width = entity.getWidth()/2
+ height = entity.getHeight()
+ }else{
+ width = width/2
+ }
+
+
+ GL11.glBlendFunc(770, 771);
+ GL11.glEnable(GL11.GL_BLEND);
+ GL11.glLineWidth(lineWidth);
+ if(phase) GL11.glDisable(GL11.GL_DEPTH_TEST);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GlStateManager.func_179094_E();
+
+
+ Tessellator.begin(3).colorize(colorR, colorG, colorB);
+
+ Tessellator.pos(x+width,y+height,z+width).tex(0, 0);
+ Tessellator.pos(x+width,y+height,z-width).tex(0, 0);
+ Tessellator.pos(x-width,y+height,z-width).tex(0, 0);
+ Tessellator.pos(x-width,y+height,z+width).tex(0, 0);
+ Tessellator.pos(x+width,y+height,z+width).tex(0, 0);
+ Tessellator.pos(x+width,y,z+width).tex(0, 0);
+ Tessellator.pos(x+width,y,z-width).tex(0, 0);
+ Tessellator.pos(x-width,y,z-width).tex(0, 0);
+ Tessellator.pos(x-width,y,z+width).tex(0, 0);
+ Tessellator.pos(x-width,y,z-width).tex(0, 0);
+ Tessellator.pos(x-width,y+height,z-width).tex(0, 0);
+ Tessellator.pos(x-width,y,z-width).tex(0, 0);
+ Tessellator.pos(x+width,y,z-width).tex(0, 0);
+ Tessellator.pos(x+width,y+height,z-width).tex(0, 0);
+ Tessellator.pos(x+width,y,z-width).tex(0, 0);
+ Tessellator.pos(x+width,y,z+width).tex(0, 0);
+ Tessellator.pos(x-width,y,z+width).tex(0, 0);
+ Tessellator.pos(x-width,y+height,z+width).tex(0, 0);
+ Tessellator.pos(x+width,y+height,z+width).tex(0, 0);
+
+ Tessellator.draw();
+
+ GlStateManager.func_179121_F();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ if(phase) GL11.glEnable(GL11.GL_DEPTH_TEST);
+ GL11.glDisable(GL11.GL_BLEND);
+ },
+ drawFilledBox: function(x, y, z, w, h, red, green, blue, alpha, phase) { //FROM RENDERUTILS
+ GL11.glDisable(GL11.GL_CULL_FACE);
+ if (phase) {
+ GL11.glBlendFunc(770, 771);
+ GL11.glEnable(GL11.GL_BLEND);
+ GL11.glLineWidth(2.0);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glDisable(GL11.GL_DEPTH_TEST);
+ GL11.glDepthMask(false);
+ GlStateManager.func_179094_E();
+ } else {
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glBlendFunc(770, 771);
+ GL11.glEnable(GL11.GL_BLEND);
+ GL11.glLineWidth(2.0);
+ GL11.glDepthMask(false);
+ GlStateManager.func_179094_E();
+ }
+
+ w /= 2;
+
+ Tessellator.begin(GL11.GL_QUADS, false);
+ Tessellator.colorize(red, green, blue, alpha);
+
+ Tessellator.translate(x, y, z)
+ .pos(w, 0, w)
+ .pos(w, 0, -w)
+ .pos(-w, 0, -w)
+ .pos(-w, 0, w)
+
+ .pos(w, h, w)
+ .pos(w, h, -w)
+ .pos(-w, h, -w)
+ .pos(-w, h, w)
+
+ .pos(-w, h, w)
+ .pos(-w, h, -w)
+ .pos(-w, 0, -w)
+ .pos(-w, 0, w)
+
+ .pos(w, h, w)
+ .pos(w, h, -w)
+ .pos(w, 0, -w)
+ .pos(w, 0, w)
+
+ .pos(w, h, -w)
+ .pos(-w, h, -w)
+ .pos(-w, 0, -w)
+ .pos(w, 0, -w)
+
+ .pos(-w, h, w)
+ .pos(w, h, w)
+ .pos(w, 0, w)
+ .pos(-w, 0, w)
+ .draw();
+
+ GL11.glEnable(GL11.GL_CULL_FACE);
+ if (phase) {
+ GlStateManager.func_179121_F();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GL11.glEnable(GL11.GL_DEPTH_TEST);
+ GL11.glDepthMask(true);
+ GL11.glDisable(GL11.GL_BLEND);
+ } else {
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GlStateManager.func_179121_F();
+ GL11.glDepthMask(true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ }
+} \ No newline at end of file
diff --git a/utils/stringUtils.js b/utils/stringUtils.js
new file mode 100644
index 0000000..920dd50
--- /dev/null
+++ b/utils/stringUtils.js
@@ -0,0 +1,11 @@
+module.exports = {
+
+ firstLetterCapital: function (string) {
+ return string.substr(0, 1).toUpperCase() + string.substr(1)
+ },
+
+ firstLetterWordCapital: function (string) {
+ return string.split(" ").map(firstLetterCapital).join(" ")
+ }
+
+} \ No newline at end of file
diff --git a/utils/utils.js b/utils/utils.js
new file mode 100644
index 0000000..b8569d9
--- /dev/null
+++ b/utils/utils.js
@@ -0,0 +1,116 @@
+const NBTTagList = Java.type('net.minecraft.nbt.NBTTagList');
+const NBTTagString = Java.type('net.minecraft.nbt.NBTTagString');
+
+let functions = {
+ addLore: function(item, prefix, value){
+
+ const list = item
+ .getNBT()
+ .getCompoundTag("tag")
+ .getCompoundTag("display")
+ .getTagMap()
+ .get("Lore")
+
+ let done = false
+ // Gets the current lore lines
+ for (let i = 0; i < list.func_74745_c(); i++) {
+ if (String(list.func_150307_f(i)).startsWith(prefix)){
+ list.func_150304_a(i, new NBTTagString(prefix+value));
+ done = true
+ }
+ }
+ if(!done){
+ list.func_74742_a( new NBTTagString(prefix+value))
+ }
+
+ item
+ .getNBT()
+ .getCompoundTag("tag")
+ .getCompoundTag("display")
+ .getRawNBT()
+ .func_74782_a("Lore", list);
+ },
+ calculateDistance: function(p1, p2) {
+ var a = p2[0] - p1[0];
+ var b = p2[1] - p1[1];
+ var c = p2[2] - p1[2];
+
+ let ret = Math.sqrt(a * a + b * b + c * c)
+
+ if(ret<0){
+ ret *= -1
+ }
+ return ret;
+ },
+ calculateDistanceQuick: function(p1, p2) {
+ var a = p2[0] - p1[0];
+ var b = p2[1] - p1[1];
+ var c = p2[2] - p1[2];
+
+ let ret = a * a + b * b + c * c
+
+ if(ret<0){
+ ret *= -1
+ }
+ return ret;
+ },
+ /**
+ * Please try not to use this
+ * it has O(n!)
+ * only use if points < 10 or something
+ * D:
+ * @param {*} startPoint
+ * @param {*} points
+ * @returns
+ */
+ fastestPathThrough:function(startPoint, points){
+ let ret = []
+ while(ret.length<points.length){
+ ret.push(ret.length)
+ }
+
+ let allOrders = functions.permutation(ret)
+
+ let lastOrder = []
+ let lastOrderLength = Infinity
+
+ allOrders.forEach((order)=>{
+ let lastPoint = startPoint
+ let positions = order.map((a)=>{
+ return points[a]
+ })
+ let len = 0
+ positions.forEach((pos)=>{
+ len += functions.calculateDistance(lastPoint,pos)
+ lastPoint = pos
+ })
+
+ if(len < lastOrderLength){
+ lastOrder = order
+ lastOrderLength = len
+ }
+ })
+
+
+ return lastOrder;
+ },
+ permutation:function(array) {
+ function p(array, temp) {
+ var i, x;
+ if (!array.length) {
+ result.push(temp);
+ }
+ for (i = 0; i < array.length; i++) {
+ x = array.splice(i, 1)[0];
+ p(array, temp.concat(x));
+ array.splice(i, 0, x);
+ }
+ }
+
+ var result = [];
+ p(array, []);
+ return result;
+ }
+}
+
+module.exports = functions \ No newline at end of file