├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── src └── main │ ├── resources │ ├── assets │ │ └── ctjs │ │ │ ├── logo.png │ │ │ ├── lang │ │ │ └── en_us.json │ │ │ └── FiraCode-Regular.otf │ ├── ctjs.accesswidener │ ├── fabric.mod.json │ └── ctjs.mixins.json │ ├── java │ └── com │ │ └── chattriggers │ │ └── ctjs │ │ └── internal │ │ ├── Skippable.java │ │ ├── BoundKeyUpdater.java │ │ ├── NameTagOverridable.java │ │ ├── TooltipOverridable.java │ │ ├── CTClientCommandSource.java │ │ └── mixins │ │ ├── sound │ │ ├── SourceAccessor.java │ │ ├── SoundAccessor.java │ │ ├── SoundSystemAccessor.java │ │ ├── SoundManagerAccessor.java │ │ └── SoundSystemMixin.java │ │ ├── ClickableWidgetAccessor.java │ │ ├── PlayerListEntryAccessor.java │ │ ├── ClientChunkManagerAccessor.java │ │ ├── GameOptionsAccessor.java │ │ ├── ChatScreenAccessor.java │ │ ├── ClientWorldAccessor.java │ │ ├── NbtCompoundAccessor.java │ │ ├── HandledScreenAccessor.java │ │ ├── PlayerListHudAccessor.java │ │ ├── AbstractSoundInstanceAccessor.java │ │ ├── MinecraftClientAccessor.java │ │ ├── BookScreenAccessor.java │ │ ├── ChunkAccessor.java │ │ ├── ClientChunkMapAccessor.java │ │ ├── commands │ │ ├── CommandContextAccessor.java │ │ ├── CommandNodeAccessor.java │ │ ├── ClientCommandSourceMixin.java │ │ ├── ClientPlayNetworkHandlerMixin.java │ │ ├── EntitySelectorAccessor.java │ │ └── CommandDispatcherMixin.java │ │ ├── ChatHudAccessor.java │ │ ├── Scoreboard$1Accessor.java │ │ ├── BossBarHudAccessor.java │ │ ├── ClientPlayNetworkHandlerAccessor.java │ │ ├── EntityRenderDispatcherAccessor.java │ │ ├── RenderTickCounterMixin.java │ │ ├── KeyBindingAccessor.java │ │ ├── stdio │ │ ├── BootstrapMixin.java │ │ └── LoggerPrintStreamMixin.java │ │ ├── ScoreboardObjectiveMixin.java │ │ ├── ParticleManagerMixin.java │ │ ├── ClientPlayerEntityMixin.java │ │ ├── ClientPlayerInteractionManagerMixin.java │ │ ├── LivingEntityMixin.java │ │ ├── GameOptionsMixin.java │ │ ├── ScreenHandlerMixin.java │ │ ├── PlayerListHudMixin.java │ │ ├── EntityRenderDispatcherMixin.java │ │ ├── InGameHudMixin.java │ │ ├── BlockEntityRenderDispatcherMixin.java │ │ ├── PlayerScreenHandlerMixin.java │ │ ├── ChatHudMixin.java │ │ ├── ClientConnectionMixin.java │ │ ├── ParticleAccessor.java │ │ ├── SystemDetailsMixin.java │ │ ├── MouseMixin.java │ │ ├── ItemStackMixin.java │ │ ├── PlayerEntityMixin.java │ │ ├── HandledScreenMixin.java │ │ ├── CreativeInventoryScreenMixin.java │ │ └── MinecraftClientMixin.java │ └── kotlin │ └── com │ └── chattriggers │ └── ctjs │ ├── api │ ├── CTWrapper.kt │ ├── commands │ │ └── RootCommand.kt │ ├── triggers │ │ ├── RegularTrigger.kt │ │ ├── CancellableEvent.kt │ │ ├── SoundPlayTrigger.kt │ │ ├── EventTrigger.kt │ │ ├── TriggerType.kt │ │ ├── StepTrigger.kt │ │ ├── Trigger.kt │ │ └── ClassFilterTrigger.kt │ ├── inventory │ │ ├── Slot.kt │ │ ├── action │ │ │ ├── KeyAction.kt │ │ │ ├── DropAction.kt │ │ │ ├── Action.kt │ │ │ ├── DragAction.kt │ │ │ └── ClickAction.kt │ │ ├── ItemType.kt │ │ └── nbt │ │ │ ├── NBTTagList.kt │ │ │ └── NBTBase.kt │ ├── entity │ │ ├── PlayerInteraction.kt │ │ ├── BlockEntity.kt │ │ ├── LivingEntity.kt │ │ └── PlayerMP.kt │ ├── vec │ │ ├── Vec2f.kt │ │ ├── Vec3f.kt │ │ └── Vec3i.kt │ ├── world │ │ ├── PotionEffectType.kt │ │ ├── PotionEffect.kt │ │ ├── Server.kt │ │ ├── block │ │ │ ├── Block.kt │ │ │ ├── BlockType.kt │ │ │ └── BlockPos.kt │ │ └── Chunk.kt │ ├── client │ │ ├── MathLib.kt │ │ └── CPS.kt │ └── render │ │ └── Book.kt │ ├── internal │ ├── launch │ │ ├── MixinDetails.kt │ │ ├── CTJSPreLaunch.kt │ │ ├── generation │ │ │ ├── GenerationContext.kt │ │ │ ├── ModifyConstantGenerator.kt │ │ │ ├── ModifyArgsGenerator.kt │ │ │ ├── ModifyReturnValueInjector.kt │ │ │ ├── DynamicMixinGenerator.kt │ │ │ ├── ModifyArgGenerator.kt │ │ │ ├── ModifyExpressionValueGenerator.kt │ │ │ ├── InjectGenerator.kt │ │ │ ├── ModifyVariableGenerator.kt │ │ │ └── ModifyReceiverGenerator.kt │ │ └── CTMixinPlugin.kt │ ├── compat │ │ └── ModMenuEntry.kt │ ├── utils │ │ ├── CategorySorting.kt │ │ ├── Initializer.kt │ │ └── extensions.kt │ ├── engine │ │ ├── module │ │ │ ├── ModuleMetadata.kt │ │ │ └── Module.kt │ │ ├── JSErrorReporter.kt │ │ └── JSContextFactory.kt │ ├── commands │ │ ├── Command.kt │ │ ├── StaticCommand.kt │ │ └── CommandCollection.kt │ ├── listeners │ │ └── WorldListener.kt │ └── console │ │ ├── TextAreaWriter.kt │ │ └── data.kt │ ├── engine │ ├── MixinCallback.kt │ ├── WrappedThread.kt │ └── Console.kt │ └── typealiases.kt ├── typing-generator ├── src │ └── main │ │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider └── build.gradle.kts ├── .idea └── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── gradle.properties ├── settings.gradle.kts ├── .gitignore ├── LICENSE ├── .github └── workflows │ ├── build.yml │ └── javadocs.yml └── gradlew.bat /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChatTriggers/ctjs/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/assets/ctjs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChatTriggers/ctjs/HEAD/src/main/resources/assets/ctjs/logo.png -------------------------------------------------------------------------------- /src/main/resources/assets/ctjs/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "ctjs.key.binding.console": "Console", 3 | "ctjs.key.category": "ChatTriggers" 4 | } 5 | -------------------------------------------------------------------------------- /src/main/resources/assets/ctjs/FiraCode-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChatTriggers/ctjs/HEAD/src/main/resources/assets/ctjs/FiraCode-Regular.otf -------------------------------------------------------------------------------- /typing-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | com.chattriggers.ctjs.typing.Provider 2 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/Skippable.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal; 2 | 3 | 4 | public interface Skippable { 5 | void ctjs_setShouldSkip(boolean shouldSkip); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/ctjs.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v2 named 2 | accessible class net/minecraft/client/world/ClientChunkManager$ClientChunkMap 3 | accessible class net/minecraft/client/option/GameOptions$Visitor -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx4G 3 | org.gradle.parallel=true 4 | 5 | # Mod Properties 6 | mod_version = 3.0.0-beta 7 | archives_base_name = ctjs 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/CTWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api 2 | 3 | interface CTWrapper { 4 | val mcValue: MCClass 5 | 6 | fun toMC(): MCClass = mcValue 7 | } 8 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | maven("https://maven.fabricmc.net") 6 | } 7 | } 8 | 9 | rootProject.name = "ctjs" 10 | include(":typing-generator") 11 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/BoundKeyUpdater.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal; 2 | 3 | import net.minecraft.client.option.KeyBinding; 4 | 5 | public interface BoundKeyUpdater { 6 | void ctjs_updateBoundKey(KeyBinding keyBinding); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/commands/RootCommand.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.commands 2 | 3 | // This really only exists so that we can hide away the DynamicCommand internals 4 | // in the internals package 5 | interface RootCommand { 6 | fun register() 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/triggers/RegularTrigger.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.triggers 2 | 3 | 4 | class RegularTrigger(method: Any, triggerType: ITriggerType) : Trigger(method, triggerType) { 5 | override fun trigger(args: Array) { 6 | callMethod(args) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /typing-generator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | dependencies { 3 | classpath(libs.gradle.plugin) 4 | } 5 | } 6 | 7 | plugins { 8 | alias(libs.plugins.kotlin) 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation(libs.ksp) 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/NameTagOverridable.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal; 2 | 3 | import com.chattriggers.ctjs.api.message.TextComponent; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | public interface NameTagOverridable { 7 | void ctjs_setOverriddenNametagName(@Nullable TextComponent component); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/TooltipOverridable.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal; 2 | 3 | import net.minecraft.text.Text; 4 | 5 | import java.util.List; 6 | 7 | public interface TooltipOverridable { 8 | void ctjs_setTooltip(List tooltip); 9 | void ctjs_setShouldOverrideTooltip(boolean shouldOverrideTooltip); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/CTClientCommandSource.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal; 2 | 3 | import net.minecraft.command.CommandSource; 4 | 5 | import java.util.HashMap; 6 | 7 | public interface CTClientCommandSource extends CommandSource { 8 | void setContextValue(String key, Object value); 9 | 10 | HashMap getContextValues(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/sound/SourceAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.sound; 2 | 3 | import net.minecraft.client.sound.Source; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Invoker; 6 | 7 | @Mixin(Source.class) 8 | public interface SourceAccessor { 9 | @Invoker 10 | int invokeGetSourceState(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ClickableWidgetAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.gui.widget.ClickableWidget; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(ClickableWidget.class) 8 | public interface ClickableWidgetAccessor { 9 | @Accessor 10 | void setHeight(int height); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/MixinDetails.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch 2 | 3 | import com.chattriggers.ctjs.engine.MixinCallback 4 | 5 | internal data class MixinDetails( 6 | val injectors: MutableList = mutableListOf(), 7 | val fieldWideners: MutableMap = mutableMapOf(), 8 | val methodWideners: MutableMap = mutableMapOf(), 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListEntryAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.network.PlayerListEntry; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Invoker; 6 | 7 | @Mixin(PlayerListEntry.class) 8 | public interface PlayerListEntryAccessor { 9 | @Invoker 10 | void invokeSetLatency(int latency); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ClientChunkManagerAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.world.ClientChunkManager; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(ClientChunkManager.class) 8 | public interface ClientChunkManagerAccessor { 9 | @Accessor 10 | ClientChunkManager.ClientChunkMap getChunks(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/GameOptionsAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.option.GameOptions; 4 | import net.minecraft.client.option.KeyBinding; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(GameOptions.class) 9 | public interface GameOptionsAccessor { 10 | @Accessor 11 | void setAllKeys(KeyBinding[] keys); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ChatScreenAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.gui.screen.ChatScreen; 4 | import net.minecraft.client.gui.widget.TextFieldWidget; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ChatScreen.class) 9 | public interface ChatScreenAccessor { 10 | @Accessor 11 | TextFieldWidget getChatField(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/sound/SoundAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.sound; 2 | 3 | import net.minecraft.client.sound.Sound; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.Mutable; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(Sound.class) 9 | public interface SoundAccessor { 10 | @Accessor 11 | @Mutable 12 | void setAttenuation(int attenuation); 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/* 15 | !.idea/codeStyles 16 | *.iml 17 | *.ipr 18 | *.iws 19 | 20 | # vscode 21 | 22 | .settings/ 23 | .vscode/ 24 | bin/ 25 | .classpath 26 | .project 27 | 28 | # macos 29 | 30 | *.DS_Store 31 | 32 | # fabric 33 | 34 | run/ 35 | 36 | # java 37 | 38 | hs_err_*.log 39 | replay_*.log 40 | *.hprof 41 | *.jfr 42 | 43 | versions/*/api/ 44 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ClientWorldAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.world.ClientChunkManager; 4 | import net.minecraft.client.world.ClientWorld; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ClientWorld.class) 9 | public interface ClientWorldAccessor { 10 | @Accessor 11 | ClientChunkManager getChunkManager(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/NbtCompoundAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.nbt.NbtCompound; 4 | import net.minecraft.nbt.NbtElement; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | import java.util.Map; 9 | 10 | @Mixin(NbtCompound.class) 11 | public interface NbtCompoundAccessor { 12 | @Accessor 13 | Map getEntries(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/HandledScreenAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.gui.screen.ingame.HandledScreen; 4 | import net.minecraft.screen.slot.Slot; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | @Mixin(HandledScreen.class) 9 | public interface HandledScreenAccessor { 10 | @Invoker 11 | Slot invokeGetSlotAt(double x, double y); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListHudAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.gui.hud.PlayerListHud; 4 | import net.minecraft.text.Text; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(PlayerListHud.class) 9 | public interface PlayerListHudAccessor { 10 | @Accessor 11 | Text getHeader(); 12 | 13 | @Accessor 14 | Text getFooter(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/compat/ModMenuEntry.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.compat 2 | 3 | import com.chattriggers.ctjs.api.Config 4 | import com.terraformersmc.modmenu.api.ConfigScreenFactory 5 | import com.terraformersmc.modmenu.api.ModMenuApi 6 | 7 | internal class ModMenuEntry : ModMenuApi { 8 | override fun getModConfigScreenFactory(): ConfigScreenFactory<*> { 9 | return ConfigScreenFactory { 10 | Config.gui() 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/AbstractSoundInstanceAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.sound.AbstractSoundInstance; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(AbstractSoundInstance.class) 8 | public interface AbstractSoundInstanceAccessor { 9 | @Accessor 10 | void setRepeat(boolean repeat); 11 | 12 | @Accessor 13 | void setRepeatDelay(int delay); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; 4 | import net.minecraft.client.MinecraftClient; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(MinecraftClient.class) 9 | public interface MinecraftClientAccessor { 10 | @Accessor 11 | YggdrasilAuthenticationService getAuthenticationService(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/BookScreenAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.gui.screen.ingame.BookScreen; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | @Mixin(BookScreen.class) 9 | public interface BookScreenAccessor { 10 | @Accessor 11 | int getPageIndex(); 12 | 13 | @Invoker 14 | void invokeUpdatePageButtons(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ChunkAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.block.entity.BlockEntity; 4 | import net.minecraft.util.math.BlockPos; 5 | import net.minecraft.world.chunk.Chunk; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | 9 | import java.util.Map; 10 | 11 | @Mixin(Chunk.class) 12 | public interface ChunkAccessor { 13 | @Accessor 14 | Map getBlockEntities(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ClientChunkMapAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.world.ClientChunkManager.ClientChunkMap; 4 | import net.minecraft.world.chunk.WorldChunk; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | import java.util.concurrent.atomic.AtomicReferenceArray; 9 | 10 | @Mixin(ClientChunkMap.class) 11 | public interface ClientChunkMapAccessor { 12 | @Accessor 13 | AtomicReferenceArray getChunks(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/commands/CommandContextAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.commands; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import com.mojang.brigadier.context.ParsedArgument; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | import java.util.Map; 9 | 10 | @Mixin(value = CommandContext.class, remap = false) 11 | public interface CommandContextAccessor { 12 | @Accessor 13 | Map> getArguments(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/triggers/CancellableEvent.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.triggers 2 | 3 | open class CancellableEvent { 4 | private var cancelled = false 5 | 6 | @JvmOverloads 7 | fun setCanceled(newVal: Boolean = true) { 8 | cancelled = newVal 9 | } 10 | 11 | @JvmOverloads 12 | fun setCancelled(newVal: Boolean = true) { 13 | cancelled = newVal 14 | } 15 | 16 | fun isCancelable() = true 17 | fun isCancellable() = true 18 | 19 | fun isCancelled() = cancelled 20 | fun isCanceled() = cancelled 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ChatHudAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.gui.hud.ChatHud; 4 | import net.minecraft.client.gui.hud.ChatHudLine; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | import org.spongepowered.asm.mixin.gen.Invoker; 8 | 9 | import java.util.List; 10 | 11 | @Mixin(ChatHud.class) 12 | public interface ChatHudAccessor { 13 | @Accessor 14 | List getMessages(); 15 | 16 | @Invoker 17 | void invokeRefresh(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/Scoreboard$1Accessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.scoreboard.ScoreHolder; 4 | import net.minecraft.scoreboard.ScoreboardScore; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(targets = "net.minecraft.scoreboard.Scoreboard$1") 9 | public interface Scoreboard$1Accessor { 10 | @Accessor("field_47543") 11 | ScoreboardScore getScore(); 12 | 13 | @Accessor("field_47547") 14 | ScoreHolder getHolder(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/BossBarHudAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.gui.hud.BossBarHud; 4 | import net.minecraft.client.gui.hud.ClientBossBar; 5 | import org.spongepowered.asm.mixin.Final; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | 9 | import java.util.Map; 10 | import java.util.UUID; 11 | 12 | @Mixin(BossBarHud.class) 13 | public interface BossBarHudAccessor { 14 | @Accessor 15 | @Final 16 | Map getBossBars(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ClientPlayNetworkHandlerAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.network.ClientPlayNetworkHandler; 4 | import net.minecraft.client.network.PlayerListEntry; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | import java.util.Collection; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | 12 | @Mixin(ClientPlayNetworkHandler.class) 13 | public interface ClientPlayNetworkHandlerAccessor { 14 | @Accessor 15 | Map getPlayerListEntries(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/commands/CommandNodeAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.commands; 2 | 3 | import com.mojang.brigadier.tree.CommandNode; 4 | import com.mojang.brigadier.tree.LiteralCommandNode; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | import java.util.Map; 9 | 10 | @Mixin(value = CommandNode.class, remap = false) 11 | public interface CommandNodeAccessor { 12 | @Accessor("children") 13 | Map> getChildNodes(); 14 | 15 | @Accessor 16 | Map> getLiterals(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/Slot.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory 2 | 3 | import com.chattriggers.ctjs.api.CTWrapper 4 | import com.chattriggers.ctjs.MCSlot 5 | 6 | class Slot(override val mcValue: MCSlot) : CTWrapper { 7 | val index by mcValue::index 8 | 9 | val displayX by mcValue::x 10 | 11 | val displayY by mcValue::y 12 | 13 | val inventory get() = Inventory(mcValue.inventory) 14 | 15 | val item get(): Item? = Item.fromMC(mcValue.stack) 16 | 17 | val isEnabled get() = mcValue.isEnabled 18 | 19 | override fun toString() = "Slot(inventory=$inventory, index=$index, item=$item)" 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/action/KeyAction.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory.action 2 | 3 | import net.minecraft.screen.slot.SlotActionType 4 | 5 | class KeyAction(slot: Int, windowId: Int) : Action(slot, windowId) { 6 | private var key: Int = -1 7 | 8 | fun getKey(): Int = key 9 | 10 | /** 11 | * Which key to act as if has been clicked (REQUIRED). 12 | * Options currently are 0-8, representing the hotbar keys 13 | * 14 | * @param key which key to "click" 15 | */ 16 | fun setKey(key: Int) = apply { 17 | this.key = key 18 | } 19 | 20 | override fun complete() { 21 | doClick(key, SlotActionType.SWAP) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/EntityRenderDispatcherAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.render.VertexConsumerProvider; 4 | import net.minecraft.client.render.entity.EntityRenderDispatcher; 5 | import net.minecraft.client.util.math.MatrixStack; 6 | import net.minecraft.entity.Entity; 7 | import org.joml.Quaternionf; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.gen.Invoker; 10 | 11 | @Mixin(EntityRenderDispatcher.class) 12 | public interface EntityRenderDispatcherAccessor { 13 | @Invoker 14 | void invokeRenderFire(MatrixStack matrices, VertexConsumerProvider vertexConsumers, Entity entity, Quaternionf rotation); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/entity/PlayerInteraction.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.entity 2 | 3 | import net.minecraft.util.Hand 4 | 5 | sealed class PlayerInteraction(val name: String, val mainHand: Boolean) { 6 | object AttackBlock : PlayerInteraction("AttackBlock", true) 7 | object AttackEntity : PlayerInteraction("AttackEntity", true) 8 | object BreakBlock : PlayerInteraction("BreakBlock", true) 9 | class UseBlock(hand: Hand) : PlayerInteraction("UseBlock", hand == Hand.MAIN_HAND) 10 | class UseEntity(hand: Hand) : PlayerInteraction("UseEntity", hand == Hand.MAIN_HAND) 11 | class UseItem(hand: Hand) : PlayerInteraction("UseItem", hand == Hand.MAIN_HAND) 12 | 13 | override fun toString(): String = name 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/utils/CategorySorting.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.utils 2 | 3 | import gg.essential.vigilance.data.Category 4 | import gg.essential.vigilance.data.SortingBehavior 5 | 6 | internal object CategorySorting : SortingBehavior() { 7 | override fun getCategoryComparator(): Comparator { 8 | return Comparator { o1, o2 -> 9 | val categories = listOf("General", "Console") 10 | 11 | if (o1.name !in categories || o2.name !in categories) { 12 | throw IllegalArgumentException("All categories must be in the list of categories") 13 | } 14 | 15 | categories.indexOf(o1.name) - categories.indexOf(o2.name) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/action/DropAction.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory.action 2 | 3 | import net.minecraft.screen.slot.SlotActionType 4 | 5 | class DropAction(slot: Int, windowId: Int) : Action(slot, windowId) { 6 | private var holdingCtrl = false 7 | 8 | fun getHoldingCtrl(): Boolean = holdingCtrl 9 | 10 | /** 11 | * Whether the click should act as if control is being held (defaults to false) 12 | * 13 | * @param holdingCtrl to hold ctrl or not 14 | */ 15 | fun setHoldingCtrl(holdingCtrl: Boolean) = apply { 16 | this.holdingCtrl = holdingCtrl 17 | } 18 | 19 | override fun complete() { 20 | doClick(if (holdingCtrl) 1 else 0, SlotActionType.THROW) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/sound/SoundSystemAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.sound; 2 | 3 | import net.minecraft.client.sound.Channel; 4 | import net.minecraft.client.sound.SoundInstance; 5 | import net.minecraft.client.sound.SoundSystem; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | import org.spongepowered.asm.mixin.gen.Invoker; 9 | 10 | import java.util.Map; 11 | 12 | @Mixin(SoundSystem.class) 13 | public interface SoundSystemAccessor { 14 | @Accessor 15 | Channel getChannel(); 16 | 17 | @Accessor 18 | Map getSources(); 19 | 20 | @Invoker 21 | void invokeStart(); 22 | 23 | @Invoker 24 | void invokeTick(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/RenderTickCounterMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.engine.CTEvents; 4 | import net.minecraft.client.render.RenderTickCounter; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 9 | 10 | @Mixin(RenderTickCounter.Dynamic.class) 11 | public class RenderTickCounterMixin { 12 | @Inject(method = "beginRenderTick(J)I", at = @At("HEAD")) 13 | private void injectBeginRenderTick(long timeMillis, CallbackInfoReturnable cir) { 14 | CTEvents.RENDER_TICK.invoker().invoke(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/sound/SoundManagerAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.sound; 2 | 3 | import net.minecraft.client.sound.SoundManager; 4 | import net.minecraft.client.sound.SoundSystem; 5 | import net.minecraft.client.sound.WeightedSoundSet; 6 | import net.minecraft.resource.Resource; 7 | import net.minecraft.util.Identifier; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.gen.Accessor; 10 | 11 | import java.util.Map; 12 | 13 | @Mixin(SoundManager.class) 14 | public interface SoundManagerAccessor { 15 | @Accessor 16 | SoundSystem getSoundSystem(); 17 | 18 | @Accessor 19 | Map getSounds(); 20 | 21 | @Accessor 22 | Map getSoundResources(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/engine/MixinCallback.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.engine 2 | 3 | import com.chattriggers.ctjs.internal.launch.IInjector 4 | import java.lang.invoke.MethodHandle 5 | import java.lang.invoke.SwitchPoint 6 | 7 | data class MixinCallback(internal val id: Int, internal val injector: IInjector) { 8 | internal var method: Any? = null 9 | internal var handle: MethodHandle? = null 10 | internal var invalidator = SwitchPoint() 11 | 12 | fun attach(method: Any) { 13 | this.method = method 14 | 15 | // The target method of this mixin has changed, so we need to re-initialize the invokedynamic instruction tied 16 | // to this callback 17 | SwitchPoint.invalidateAll(arrayOf(invalidator)) 18 | invalidator = SwitchPoint() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/triggers/SoundPlayTrigger.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.triggers 2 | 3 | 4 | class SoundPlayTrigger(method: Any) : Trigger(method, TriggerType.SOUND_PLAY) { 5 | private var soundNameCriteria = "" 6 | 7 | /** 8 | * Sets the sound name criteria. 9 | * 10 | * @param soundNameCriteria the sound name 11 | * @return the trigger for method chaining 12 | */ 13 | fun setCriteria(soundNameCriteria: String) = apply { this.soundNameCriteria = soundNameCriteria } 14 | 15 | override fun trigger(args: Array) { 16 | if (args[1] is CharSequence 17 | && soundNameCriteria != "" 18 | && !args[1].toString().equals(soundNameCriteria, ignoreCase = true) 19 | ) 20 | return 21 | 22 | callMethod(args) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/KeyBindingAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.option.KeyBinding; 4 | import net.minecraft.client.util.InputUtil; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | @Mixin(KeyBinding.class) 12 | public interface KeyBindingAccessor { 13 | @Accessor("CATEGORY_ORDER_MAP") 14 | static Map getCategoryMap() { throw new IllegalStateException(); } 15 | 16 | @Accessor("KEY_CATEGORIES") 17 | static Set getKeyCategories() { 18 | throw new IllegalStateException(); 19 | } 20 | 21 | @Accessor 22 | InputUtil.Key getBoundKey(); 23 | 24 | @Accessor 25 | int getTimesPressed(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/stdio/BootstrapMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.stdio; 2 | 3 | import com.chattriggers.ctjs.internal.launch.CTMixinPlugin; 4 | import net.minecraft.Bootstrap; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | @Mixin(Bootstrap.class) 11 | public class BootstrapMixin { 12 | @Inject(method = "setOutputStreams", at = @At("HEAD")) 13 | private static void injectSetOutputStreams(CallbackInfo ci) { 14 | // MC will re-wrap the output streams, so we restore them to their original state 15 | // so they don't end up double-wrapped. 16 | CTMixinPlugin.restoreOutputStreams(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ScoreboardObjectiveMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.world.Scoreboard; 4 | import net.minecraft.scoreboard.ScoreboardObjective; 5 | import net.minecraft.text.Text; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(ScoreboardObjective.class) 12 | public class ScoreboardObjectiveMixin { 13 | @Inject(method = "setDisplayName", at = @At("HEAD"), cancellable = true) 14 | private void chattriggers$keepCustomName(Text name, CallbackInfo ci) { 15 | if (Scoreboard.INSTANCE.getCustomTitle$ctjs()) { 16 | ci.cancel(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ParticleManagerMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.triggers.TriggerType; 4 | import net.minecraft.client.particle.Particle; 5 | import net.minecraft.client.particle.ParticleManager; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(ParticleManager.class) 12 | public class ParticleManagerMixin { 13 | @Inject(method = "addParticle(Lnet/minecraft/client/particle/Particle;)V", at = @At("HEAD"), cancellable = true) 14 | private void injectAddParticle(Particle particle, CallbackInfo ci) { 15 | TriggerType.SPAWN_PARTICLE.triggerAll(new com.chattriggers.ctjs.api.entity.Particle(particle), ci); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/commands/ClientCommandSourceMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.commands; 2 | 3 | import com.chattriggers.ctjs.internal.CTClientCommandSource; 4 | import net.minecraft.client.network.ClientCommandSource; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | 8 | import java.util.HashMap; 9 | 10 | @SuppressWarnings("AddedMixinMembersNamePattern") 11 | @Mixin(ClientCommandSource.class) 12 | public abstract class ClientCommandSourceMixin implements CTClientCommandSource { 13 | @Unique 14 | private final HashMap contextValues = new HashMap<>(); 15 | 16 | @Override 17 | public void setContextValue(String key, Object value) { 18 | contextValues.put(key, value); 19 | } 20 | 21 | @Override 22 | public HashMap getContextValues() { 23 | return contextValues; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/engine/module/ModuleMetadata.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.engine.module 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.Serializable 5 | import kotlinx.serialization.json.JsonNames 6 | 7 | @Serializable 8 | @OptIn(ExperimentalSerializationApi::class) 9 | data class ModuleMetadata( 10 | val name: String? = null, 11 | val version: String? = null, 12 | var entry: String? = null, 13 | var mixinEntry: String? = null, 14 | val tags: ArrayList? = null, 15 | val pictureLink: String? = null, 16 | @JsonNames("author") 17 | val creator: String? = null, 18 | val description: String? = null, 19 | val requires: ArrayList? = null, 20 | val helpMessage: String? = null, 21 | val changelog: String? = null, 22 | val ignored: ArrayList? = null, 23 | var isRequired: Boolean = false 24 | ) 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/CTJSPreLaunch.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch 2 | 3 | import com.chattriggers.ctjs.engine.LogType 4 | import com.chattriggers.ctjs.engine.printToConsole 5 | import com.chattriggers.ctjs.engine.printTraceToConsole 6 | import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint 7 | 8 | class CTJSPreLaunch : PreLaunchEntrypoint { 9 | override fun onPreLaunch() { 10 | val prevHandler = Thread.getDefaultUncaughtExceptionHandler() 11 | Thread.setDefaultUncaughtExceptionHandler { thread, exception -> 12 | "Uncaught exception in thread \"${thread.name}\"".printToConsole(LogType.ERROR) 13 | exception.printTraceToConsole() 14 | prevHandler.uncaughtException(thread, exception) 15 | } 16 | 17 | try { 18 | DynamicMixinManager.applyMixins() 19 | } catch (e: Throwable) { 20 | IllegalStateException("Error generating dynamic mixins", e).printTraceToConsole() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/entity/BlockEntity.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.entity 2 | 3 | import com.chattriggers.ctjs.api.CTWrapper 4 | import com.chattriggers.ctjs.api.world.block.Block 5 | import com.chattriggers.ctjs.api.world.block.BlockPos 6 | import com.chattriggers.ctjs.api.world.block.BlockType 7 | import com.chattriggers.ctjs.MCBlockEntity 8 | import net.minecraft.block.entity.BlockEntityType 9 | 10 | class BlockEntity(override val mcValue: MCBlockEntity) : CTWrapper { 11 | 12 | fun getX(): Int = getBlockPos().x 13 | 14 | fun getY(): Int = getBlockPos().y 15 | 16 | fun getZ(): Int = getBlockPos().z 17 | 18 | fun getBlockType(): BlockType = BlockType(BlockEntityType.getId(mcValue.type)!!.toString()) 19 | 20 | fun getBlockPos(): BlockPos = BlockPos(mcValue.pos) 21 | 22 | fun getBlock(): Block = Block(getBlockType(), getBlockPos()) 23 | 24 | override fun toString(): String { 25 | return "BlockEntity(type=${getBlockType()}, pos=[${getX()}, ${getY()}, ${getZ()}])" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ClientPlayerEntityMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.client.Player; 4 | import com.chattriggers.ctjs.api.inventory.Item; 5 | import com.chattriggers.ctjs.api.triggers.TriggerType; 6 | import net.minecraft.client.network.ClientPlayerEntity; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 11 | 12 | @Mixin(ClientPlayerEntity.class) 13 | public class ClientPlayerEntityMixin { 14 | @Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true) 15 | private void injectDropSelectedItem(boolean entireStack, CallbackInfoReturnable cir) { 16 | // dropping item while not in gui 17 | Item stack = Player.getHeldItem(); 18 | if (stack != null && !stack.getMcValue().isEmpty()) { 19 | TriggerType.DROP_ITEM.triggerAll(stack, entireStack, cir); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ClientPlayerInteractionManagerMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.engine.CTEvents; 4 | import net.minecraft.client.network.ClientPlayerInteractionManager; 5 | import net.minecraft.util.math.BlockPos; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 10 | 11 | @Mixin(ClientPlayerInteractionManager.class) 12 | public class ClientPlayerInteractionManagerMixin { 13 | @Inject( 14 | method = "breakBlock", 15 | at = @At( 16 | value = "INVOKE", 17 | target = "Lnet/minecraft/block/Block;onBroken(Lnet/minecraft/world/WorldAccess;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)V" 18 | ) 19 | ) 20 | private void injectBreakBlock(BlockPos pos, CallbackInfoReturnable cir) { 21 | CTEvents.BREAK_BLOCK.invoker().breakBlock(pos); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ChatTriggers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/vec/Vec2f.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.vec 2 | 3 | import kotlin.math.acos 4 | import kotlin.math.sqrt 5 | 6 | data class Vec2f @JvmOverloads constructor(val x: Float = 0f, val y: Float = 0f) { 7 | fun magnitudeSquared() = x * x + y * y 8 | 9 | fun magnitude() = sqrt(magnitudeSquared()) 10 | 11 | fun translated(dx: Float, dy: Float) = Vec2f(x + dx, y + dy) 12 | 13 | fun scaled(scale: Float) = Vec2f(x * scale, y * scale) 14 | 15 | fun scaled(xScale: Float, yScale: Float) = Vec2f(x * xScale, y * yScale) 16 | 17 | fun dotProduct(other: Vec2f) = x * other.x + y * other.y 18 | 19 | fun angleTo(other: Vec2f): Float { 20 | return acos(dotProduct(other) / (magnitude() * other.magnitude()).coerceIn(-1f, 1f)) 21 | } 22 | 23 | fun normalized() = magnitude().let { 24 | Vec2f(x / it, y / it) 25 | } 26 | 27 | operator fun unaryMinus() = Vec2f(-x, -y) 28 | 29 | operator fun plus(other: Vec2f) = Vec2f(x + other.x, y + other.y) 30 | 31 | operator fun minus(other: Vec2f) = this + (-other) 32 | 33 | override fun toString() = "Vec2f($x, $y)" 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/world/PotionEffectType.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.world 2 | 3 | import com.chattriggers.ctjs.api.message.TextComponent 4 | import net.minecraft.entity.effect.StatusEffect 5 | import net.minecraft.registry.Registries 6 | import java.awt.Color 7 | 8 | class PotionEffectType(val type: StatusEffect) { 9 | /** 10 | * The Int associated with this type 11 | */ 12 | val rawId get() = Registries.STATUS_EFFECT.getRawId(type) 13 | 14 | /** 15 | * Whether this effect is instant (e.g. instant health) 16 | */ 17 | val isInstant get() = type.isInstant 18 | 19 | /** 20 | * The raw key used for this effect type 21 | */ 22 | val translationKey get() = type.translationKey 23 | 24 | /** 25 | * The user-friendly name of this type as a [TextComponent] 26 | */ 27 | val name get() = TextComponent(type.name) 28 | 29 | /** 30 | * The [net.minecraft.entity.effect.StatusEffectCategory] of this type 31 | */ 32 | val category get() = type.category 33 | 34 | /** 35 | * The color of this type 36 | */ 37 | val color get() = Color(type.color) 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/commands/Command.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.commands 2 | 3 | import com.chattriggers.ctjs.internal.mixins.commands.CommandNodeAccessor 4 | import com.chattriggers.ctjs.internal.utils.asMixin 5 | import com.mojang.brigadier.CommandDispatcher 6 | import com.mojang.brigadier.arguments.ArgumentType 7 | import com.mojang.brigadier.builder.LiteralArgumentBuilder 8 | import com.mojang.brigadier.builder.RequiredArgumentBuilder 9 | import net.minecraft.command.CommandSource 10 | 11 | interface Command { 12 | val overrideExisting: Boolean 13 | val name: String 14 | 15 | fun registerImpl(dispatcher: CommandDispatcher) 16 | 17 | fun unregisterImpl(dispatcher: CommandDispatcher) { 18 | dispatcher.root.asMixin().apply { 19 | childNodes.remove(name) 20 | literals.remove(name) 21 | } 22 | } 23 | } 24 | 25 | fun literal(name: String) = LiteralArgumentBuilder.literal(name) 26 | 27 | fun argument(name: String, argument: ArgumentType) = 28 | RequiredArgumentBuilder.argument(name, argument) 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/utils/Initializer.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.utils 2 | 3 | import com.chattriggers.ctjs.api.client.CPS 4 | import com.chattriggers.ctjs.api.client.KeyBind 5 | import com.chattriggers.ctjs.api.commands.DynamicCommands 6 | import com.chattriggers.ctjs.internal.commands.CTCommand 7 | import com.chattriggers.ctjs.internal.commands.StaticCommand 8 | import com.chattriggers.ctjs.internal.console.ConsoleHostProcess 9 | import com.chattriggers.ctjs.internal.engine.module.ModuleUpdater 10 | import com.chattriggers.ctjs.internal.listeners.ClientListener 11 | import com.chattriggers.ctjs.internal.listeners.MouseListener 12 | import com.chattriggers.ctjs.internal.listeners.WorldListener 13 | 14 | internal interface Initializer { 15 | fun init() 16 | 17 | companion object { 18 | internal val initializers = listOf( 19 | ClientListener, 20 | ConsoleHostProcess, 21 | CPS, 22 | CTCommand, 23 | DynamicCommands, 24 | KeyBind, 25 | ModuleUpdater, 26 | MouseListener, 27 | StaticCommand, 28 | WorldListener, 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/triggers/EventTrigger.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.triggers 2 | 3 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo 4 | 5 | class EventTrigger(method: Any, triggerType: ITriggerType) : Trigger(method, triggerType) { 6 | private var triggerIfCanceled = true 7 | 8 | /** 9 | * Sets if this trigger should run if the event has already been canceled. 10 | * True by default. 11 | * 12 | * @param bool Boolean to set 13 | * @return the trigger object for method chaining 14 | */ 15 | fun triggerIfCanceled(bool: Boolean) = apply { triggerIfCanceled = bool } 16 | 17 | override fun trigger(args: Array) { 18 | val isCanceled = when (val event = args.lastOrNull()) { 19 | is CancellableEvent -> event.isCanceled() 20 | is CallbackInfo -> event.isCancelled 21 | else -> throw IllegalArgumentException( 22 | "Expected last argument of ${type.name} trigger to be an Event, got ${event?.javaClass?.name ?: "null"}" 23 | ) 24 | } 25 | 26 | if (triggerIfCanceled || !isCanceled) 27 | callMethod(args) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/stdio/LoggerPrintStreamMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.stdio; 2 | 3 | import net.minecraft.util.logging.LoggerPrintStream; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.Shadow; 6 | 7 | import java.io.PrintStream; 8 | 9 | // Add additional overrides so that org.spongepowered.asm.util.PrettyPrinter 10 | // will output to the log file. 11 | @Mixin(LoggerPrintStream.class) 12 | public class LoggerPrintStreamMixin { 13 | @Shadow 14 | protected void log(String message) { 15 | throw new IllegalStateException(); 16 | } 17 | 18 | public PrintStream printf(String format, Object... args) { 19 | // The incoming format string will have a trailing newline, but this is 20 | // going to a slf4j method, which will add the newline for us. So we strip 21 | // the last newline, if one exists. 22 | String formatted = format.formatted(args); 23 | if (!formatted.isEmpty() && formatted.charAt(formatted.length() - 1) == '\n') 24 | formatted = formatted.substring(0, formatted.length() - 1); 25 | log(formatted); 26 | return (LoggerPrintStream) (Object) this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/LivingEntityMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.triggers.TriggerType; 4 | import net.minecraft.entity.Entity; 5 | import net.minecraft.entity.EntityType; 6 | import net.minecraft.entity.LivingEntity; 7 | import net.minecraft.entity.damage.DamageSource; 8 | import net.minecraft.world.World; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(LivingEntity.class) 16 | public abstract class LivingEntityMixin extends Entity { 17 | public LivingEntityMixin(EntityType type, World world) { 18 | super(type, world); 19 | } 20 | 21 | @Inject(method = "onDeath", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;setPose(Lnet/minecraft/entity/EntityPose;)V")) 22 | private void chattriggers$entityDeath(DamageSource damageSource, CallbackInfo ci) { 23 | if (getWorld().isClient) { 24 | TriggerType.ENTITY_DEATH.triggerAll(this); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/GenerationContext.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import com.chattriggers.ctjs.api.Mappings 4 | import com.chattriggers.ctjs.internal.launch.Descriptor 5 | import com.chattriggers.ctjs.internal.launch.DynamicMixinManager 6 | import com.chattriggers.ctjs.internal.launch.Mixin 7 | import org.spongepowered.asm.mixin.transformer.ClassInfo 8 | 9 | internal data class GenerationContext(val mixin: Mixin) { 10 | val mappedClass = Mappings.getMappedClass(mixin.target) ?: run { 11 | if (mixin.remap == false) { 12 | Mappings.getUnmappedClass(mixin.target) 13 | } else { 14 | error("Unknown class name ${mixin.target}") 15 | } 16 | } 17 | val generatedClassName = "CTMixin_\$${mixin.target.replace('.', '_')}\$_${mixinCounter++}" 18 | val generatedClassFullPath = "${DynamicMixinManager.GENERATED_PACKAGE}/$generatedClassName" 19 | 20 | fun findMethod(method: String): Pair { 21 | val descriptor = Descriptor.Parser(method).parseMethod(full = false) 22 | return Utils.findMethod(mappedClass, descriptor) 23 | } 24 | 25 | companion object { 26 | private var mixinCounter = 0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/GameOptionsMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.BoundKeyUpdater; 4 | import net.minecraft.client.option.GameOptions; 5 | import net.minecraft.client.option.KeyBinding; 6 | import net.minecraft.client.util.InputUtil; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | @Mixin(GameOptions.class) 14 | public class GameOptionsMixin implements BoundKeyUpdater { 15 | @Unique 16 | private GameOptions.Visitor visitor; 17 | 18 | @Inject(method = "accept", at = @At("HEAD")) 19 | private void captureVisitor(GameOptions.Visitor visitor, CallbackInfo ci) { 20 | this.visitor = visitor; 21 | } 22 | 23 | @Override 24 | public void ctjs_updateBoundKey(KeyBinding keyBinding) { 25 | String string = keyBinding.getBoundKeyTranslationKey(); 26 | String string2 = visitor.visitString("key_" + keyBinding.getTranslationKey(), string); 27 | if (!string.equals(string2)) { 28 | keyBinding.setBoundKey(InputUtil.fromTranslationKey(string2)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/commands/ClientPlayNetworkHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.commands; 2 | 3 | import com.chattriggers.ctjs.internal.engine.CTEvents; 4 | import com.mojang.brigadier.CommandDispatcher; 5 | import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; 6 | import net.minecraft.client.network.ClientPlayNetworkHandler; 7 | import net.minecraft.command.CommandSource; 8 | import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(ClientPlayNetworkHandler.class) 16 | public class ClientPlayNetworkHandlerMixin { 17 | @Shadow 18 | private CommandDispatcher commandDispatcher; 19 | 20 | @Inject(method = "onCommandTree", at = @At("TAIL")) 21 | private void injectOnCommandTree(CommandTreeS2CPacket packet, CallbackInfo ci) { 22 | //noinspection unchecked 23 | CTEvents.NETWORK_COMMAND_DISPATCHER_REGISTER.invoker().register( 24 | (CommandDispatcher) (Object) commandDispatcher 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: "ubuntu-latest" 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - uses: actions/setup-java@v4 18 | with: 19 | distribution: temurin 20 | java-version: 21 21 | 22 | - uses: actions/cache@v4 23 | with: 24 | path: | 25 | ~/.gradle/caches 26 | ~/.gradle/wrapper 27 | key: "${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/*.versions.toml') }}" 28 | restore-keys: | 29 | ${{ runner.os }}-gradle- 30 | 31 | - name: Build 32 | run: ./gradlew --no-daemon build -Pfull 33 | 34 | - name: Get Short SHA 35 | id: sha 36 | run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT 37 | 38 | - name: Flatten Output Directory 39 | run: | 40 | mkdir temp-dir 41 | cp build/libs/* temp-dir 42 | cp build/generated/ksp/main/resources/* temp-dir 43 | 44 | - name: Publish Artifacts 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: ChatTriggers-artifacts-${{ steps.sha.outputs.sha_short }} 48 | path: temp-dir 49 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/triggers/TriggerType.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.triggers 2 | 3 | import com.chattriggers.ctjs.internal.engine.JSLoader 4 | 5 | sealed interface ITriggerType { 6 | val name: String 7 | 8 | fun triggerAll(vararg args: Any?) { 9 | JSLoader.exec(this, args) 10 | } 11 | } 12 | 13 | enum class TriggerType : ITriggerType { 14 | // client 15 | CHAT, 16 | ACTION_BAR, 17 | TICK, 18 | STEP, 19 | GAME_UNLOAD, 20 | GAME_LOAD, 21 | CLICKED, 22 | SCROLLED, 23 | DRAGGED, 24 | GUI_OPENED, 25 | MESSAGE_SENT, 26 | ITEM_TOOLTIP, 27 | PLAYER_INTERACT, 28 | GUI_KEY, 29 | GUI_MOUSE_CLICK, 30 | GUI_MOUSE_DRAG, 31 | PACKET_SENT, 32 | PACKET_RECEIVED, 33 | SERVER_CONNECT, 34 | SERVER_DISCONNECT, 35 | GUI_CLOSED, 36 | DROP_ITEM, 37 | 38 | // rendering 39 | PRE_RENDER_WORLD, 40 | POST_RENDER_WORLD, 41 | BLOCK_HIGHLIGHT, 42 | RENDER_OVERLAY, 43 | RENDER_PLAYER_LIST, 44 | RENDER_ENTITY, 45 | RENDER_BLOCK_ENTITY, 46 | GUI_RENDER, 47 | POST_GUI_RENDER, 48 | 49 | // world 50 | SOUND_PLAY, 51 | WORLD_LOAD, 52 | WORLD_UNLOAD, 53 | SPAWN_PARTICLE, 54 | ENTITY_DEATH, 55 | ENTITY_DAMAGE, 56 | 57 | // misc 58 | COMMAND, 59 | OTHER 60 | } 61 | 62 | data class CustomTriggerType(override val name: String) : ITriggerType 63 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/listeners/WorldListener.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.listeners 2 | 3 | import com.chattriggers.ctjs.api.render.Renderer 4 | import com.chattriggers.ctjs.api.triggers.CancellableEvent 5 | import com.chattriggers.ctjs.api.triggers.TriggerType 6 | import com.chattriggers.ctjs.internal.utils.Initializer 7 | import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents 8 | import net.minecraft.util.math.BlockPos 9 | 10 | object WorldListener : Initializer { 11 | override fun init() { 12 | WorldRenderEvents.BLOCK_OUTLINE.register { _, ctx -> 13 | val event = CancellableEvent() 14 | TriggerType.BLOCK_HIGHLIGHT.triggerAll(BlockPos(ctx.blockPos()), event) 15 | !event.isCancelled() 16 | } 17 | 18 | WorldRenderEvents.START.register { ctx -> 19 | val deltaTicks = ctx.tickCounter().getTickDelta(false) 20 | Renderer.withMatrix(ctx.matrixStack(), deltaTicks) { 21 | TriggerType.PRE_RENDER_WORLD.triggerAll(deltaTicks) 22 | } 23 | } 24 | 25 | WorldRenderEvents.LAST.register { ctx -> 26 | val deltaTicks = ctx.tickCounter().getTickDelta(false) 27 | Renderer.withMatrix(ctx.matrixStack(), deltaTicks) { 28 | TriggerType.POST_RENDER_WORLD.triggerAll(deltaTicks) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/engine/WrappedThread.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.engine 2 | 3 | import com.chattriggers.ctjs.engine.printTraceToConsole 4 | import com.chattriggers.ctjs.internal.engine.JSContextFactory 5 | import com.chattriggers.ctjs.internal.engine.JSLoader 6 | import org.mozilla.javascript.Context 7 | import java.util.concurrent.ForkJoinPool 8 | 9 | @Suppress("unused") 10 | class WrappedThread(private val task: Runnable) { 11 | fun start() { 12 | ForkJoinPool.commonPool().execute { 13 | try { 14 | JSContextFactory.enterContext() 15 | task.run() 16 | Context.exit() 17 | } catch (e: Throwable) { 18 | e.printTraceToConsole() 19 | } 20 | } 21 | } 22 | 23 | // Provide the following methods as no-ops to avoid breaking 24 | // changes, as this class use to extend Thread 25 | fun run() {} 26 | fun stop() {} 27 | fun interrupt() {} 28 | fun isInterrupted() = false 29 | fun destroy() {} 30 | fun isAlive() = true 31 | fun suspend() {} 32 | fun resume() {} 33 | fun getId() = 0L 34 | 35 | companion object { 36 | @JvmStatic 37 | @JvmOverloads 38 | fun sleep(millis: Long, nanos: Int = 0) = Thread.sleep(millis, nanos) 39 | 40 | @JvmStatic 41 | fun currentThread(): Thread = Thread.currentThread() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/world/PotionEffect.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.world 2 | 3 | import com.chattriggers.ctjs.api.message.TextComponent 4 | import net.minecraft.entity.effect.StatusEffect 5 | import net.minecraft.entity.effect.StatusEffectInstance 6 | import net.minecraft.registry.Registries 7 | 8 | /** 9 | * Represents a specific instance of a [PotionEffectType] 10 | */ 11 | class PotionEffect(val effect: StatusEffectInstance) { 12 | /** 13 | * The type of this potion 14 | */ 15 | val type get() = PotionEffectType(effect.effectType.value()) 16 | 17 | /** 18 | * Returns the translation key of the potion. 19 | * Ex: "potion.poison" 20 | */ 21 | val name get() = effect.translationKey 22 | 23 | /** 24 | * Returns the localized name of the potion that 25 | * is displayed in the player's inventory. 26 | * Ex: "Poison" 27 | */ 28 | val localizedName get() = TextComponent(effect.effectType.value().name).unformattedText 29 | 30 | val amplifier get() = effect.amplifier 31 | 32 | val duration get() = effect.duration 33 | 34 | val id get() = Registries.STATUS_EFFECT.getRawId(effect.effectType.value()) 35 | 36 | val ambient get() = effect.isAmbient 37 | 38 | val isInfinite get() = effect.isInfinite 39 | 40 | val showsParticles get() = effect.shouldShowParticles() 41 | 42 | override fun toString(): String = effect.toString() 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ScreenHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.triggers.CancellableEvent; 4 | import com.chattriggers.ctjs.api.inventory.Item; 5 | import com.chattriggers.ctjs.api.triggers.TriggerType; 6 | import net.minecraft.entity.player.PlayerEntity; 7 | import net.minecraft.inventory.Inventory; 8 | import net.minecraft.item.ItemStack; 9 | import net.minecraft.screen.ScreenHandler; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(ScreenHandler.class) 16 | public class ScreenHandlerMixin { 17 | @Inject(method = "dropInventory", at = @At("HEAD")) 18 | private void injectDropInventory(PlayerEntity player, Inventory inventory, CallbackInfo ci) { 19 | // dropping items for guis that don't keep items in them while the gui is closed 20 | if (inventory != player.playerScreenHandler.getCraftingInput()) { 21 | for (int i = 0; i < inventory.size(); i++) { 22 | ItemStack stack = inventory.getStack(i); 23 | if (!stack.isEmpty()) { 24 | TriggerType.DROP_ITEM.triggerAll(Item.fromMC(stack), true, new CancellableEvent()); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/sound/SoundSystemMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.sound; 2 | 3 | import com.chattriggers.ctjs.api.triggers.TriggerType; 4 | import com.chattriggers.ctjs.api.vec.Vec3f; 5 | import net.minecraft.client.sound.SoundInstance; 6 | import net.minecraft.client.sound.SoundSystem; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(SoundSystem.class) 13 | public class SoundSystemMixin { 14 | @Inject(method = "play(Lnet/minecraft/client/sound/SoundInstance;)V", at = @At("HEAD"), cancellable = true) 15 | private void injectPlay(SoundInstance sound, CallbackInfo ci) { 16 | float volume = 0f; 17 | float pitch = 0f; 18 | 19 | try { 20 | volume = sound.getVolume(); 21 | } catch (Throwable ignored) { 22 | } 23 | 24 | try { 25 | pitch = sound.getPitch(); 26 | } catch (Throwable ignored) { 27 | } 28 | 29 | TriggerType.SOUND_PLAY.triggerAll( 30 | new Vec3f((float) sound.getX(), (float) sound.getY(), (float) sound.getZ()), 31 | sound.getId().toString(), 32 | volume, 33 | pitch, 34 | sound.getCategory(), 35 | ci 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/commands/EntitySelectorAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.commands; 2 | 3 | import net.minecraft.command.EntitySelector; 4 | import net.minecraft.entity.Entity; 5 | import net.minecraft.predicate.NumberRange; 6 | import net.minecraft.util.TypeFilter; 7 | import net.minecraft.util.math.Box; 8 | import net.minecraft.util.math.Vec3d; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.gen.Accessor; 11 | 12 | import java.util.List; 13 | import java.util.UUID; 14 | import java.util.function.BiConsumer; 15 | import java.util.function.Function; 16 | import java.util.function.Predicate; 17 | 18 | @Mixin(EntitySelector.class) 19 | public interface EntitySelectorAccessor { 20 | @Accessor 21 | int getLimit(); 22 | 23 | @Accessor 24 | boolean getIncludesNonPlayers(); 25 | 26 | @Accessor 27 | List> getPredicates(); 28 | 29 | @Accessor 30 | NumberRange.DoubleRange getDistance(); 31 | 32 | @Accessor 33 | Function getPositionOffset(); 34 | 35 | @Accessor 36 | Box getBox(); 37 | 38 | @Accessor 39 | BiConsumer> getSorter(); 40 | 41 | @Accessor 42 | boolean getSenderOnly(); 43 | 44 | @Accessor 45 | String getPlayerName(); 46 | 47 | @Accessor 48 | UUID getUuid(); 49 | 50 | @Accessor 51 | TypeFilter getEntityFilter(); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/vec/Vec3f.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.vec 2 | 3 | import kotlin.math.acos 4 | import kotlin.math.sqrt 5 | 6 | data class Vec3f @JvmOverloads constructor( 7 | val x: Float = 0f, val y: Float = 0f, val z: Float = 0f, 8 | ) { 9 | fun magnitudeSquared() = x * x + y * y + z * z 10 | 11 | fun magnitude() = sqrt(magnitudeSquared()) 12 | 13 | fun translated(dx: Float, dy: Float, dz: Float) = Vec3f(x + dx, y + dy, z + dz) 14 | 15 | fun scaled(scale: Float) = Vec3f(x * scale, y * scale, z * scale) 16 | 17 | fun scaled(xScale: Float, yScale: Float, zScale: Float) = Vec3f(x * xScale, y * yScale, z * zScale) 18 | 19 | fun crossProduct(other: Vec3f) = Vec3f( 20 | y * other.z - z * other.y, 21 | z * other.x - x * other.z, 22 | x * other.y - y * other.x, 23 | ) 24 | 25 | fun dotProduct(other: Vec3f) = x * other.x + y * other.y + z * other.z 26 | 27 | fun angleTo(other: Vec3f): Float { 28 | return acos(dotProduct(other) / (magnitude() * other.magnitude()).coerceIn(-1f, 1f)) 29 | } 30 | 31 | fun normalized() = magnitude().let { 32 | Vec3f(x / it, y / it, z / it) 33 | } 34 | 35 | operator fun unaryMinus() = Vec3f(-x, -y, -z) 36 | 37 | operator fun plus(other: Vec3f) = Vec3f(x + other.x, y + other.y, z + other.z) 38 | 39 | operator fun minus(other: Vec3f) = this + (-other) 40 | 41 | override fun toString() = "Vec3f($x, $y, $z)" 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/client/MathLib.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.client 2 | 3 | object MathLib { 4 | /** 5 | * Maps a number from one range to another. 6 | * 7 | * @param number the number to map 8 | * @param in_min the original range min 9 | * @param in_max the original range max 10 | * @param out_min the final range min 11 | * @param out_max the final range max 12 | * @return the re-mapped number 13 | */ 14 | @JvmStatic 15 | fun map(number: Float, in_min: Float, in_max: Float, out_min: Float, out_max: Float): Float { 16 | return (number - in_min) * (out_max - out_min) / (in_max - in_min) + out_min 17 | } 18 | 19 | /** 20 | * Clamps a floating number between two values. 21 | * 22 | * @param number the number to clamp 23 | * @param min the minimum 24 | * @param max the maximum 25 | * @return the clamped number 26 | */ 27 | @JvmStatic 28 | fun clampFloat(number: Float, min: Float, max: Float): Float { 29 | return number.coerceIn(min, max) 30 | } 31 | 32 | /** 33 | * Clamps an integer number between two values. 34 | * 35 | * @param number the number to clamp 36 | * @param min the minimum 37 | * @param max the maximum 38 | * @return the clamped number 39 | */ 40 | @JvmStatic 41 | fun clamp(number: Int, min: Int, max: Int): Int { 42 | return number.coerceIn(min, max) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "ctjs", 4 | "name": "ChatTriggers", 5 | "version": "$version", 6 | "description": "A framework for Minecraft that allows for live scripting and client modification using JavaScript", 7 | "authors": ["Ecolsson", "FalseHonesty", "kerbybit"], 8 | "contributors": ["Squagward", "Debuggings", "DJtheRedstoner"], 9 | "icon": "assets/ctjs/logo.png", 10 | "license": ["MIT"], 11 | "contact": { 12 | "homepage": "https://chattriggers.com", 13 | "sources": "https://github.com/ChatTriggers/ChatTriggers", 14 | "issues": "https://github.com/ChatTriggers/ChatTriggers/issues" 15 | }, 16 | "environment": "client", 17 | "entrypoints": { 18 | "preLaunch": [ 19 | "com.chattriggers.ctjs.internal.launch.CTJSPreLaunch" 20 | ], 21 | "client": ["com.chattriggers.ctjs.CTJS"], 22 | "modmenu": [ 23 | "com.chattriggers.ctjs.internal.compat.ModMenuEntry" 24 | ] 25 | }, 26 | "mixins": ["ctjs.mixins.json"], 27 | "depends": { 28 | "fabricloader": ">=$loader_version", 29 | "fabric-api": ">=$fabric_api_version", 30 | "fabric-language-kotlin": ">=$fabric_kotlin_version" 31 | }, 32 | "custom": { 33 | "ctjs:yarn-mappings": "$yarn_mappings", 34 | "modmenu": { 35 | "links": { 36 | "modmenu.discord": "https://discord.gg/chattriggers" 37 | } 38 | } 39 | }, 40 | "accessWidener": "ctjs.accesswidener" 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/ItemType.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory 2 | 3 | import com.chattriggers.ctjs.api.CTWrapper 4 | import com.chattriggers.ctjs.api.message.TextComponent 5 | import com.chattriggers.ctjs.api.world.block.BlockType 6 | import com.chattriggers.ctjs.MCItem 7 | import com.chattriggers.ctjs.internal.utils.toIdentifier 8 | import net.minecraft.item.Items 9 | import net.minecraft.registry.Registries 10 | 11 | class ItemType(override val mcValue: MCItem) : CTWrapper { 12 | init { 13 | require(mcValue !== Items.AIR) { 14 | "Can not wrap air as an ItemType" 15 | } 16 | } 17 | 18 | constructor(itemName: String) : this(Registries.ITEM[itemName.toIdentifier()]) 19 | 20 | constructor(id: Int) : this(Registries.ITEM[id]) 21 | 22 | constructor(blockType: BlockType) : this(blockType.toMC().asItem()) 23 | 24 | fun getName(): String = getNameComponent().formattedText 25 | 26 | fun getNameComponent(): TextComponent = TextComponent(mcValue.name) 27 | 28 | fun getId(): Int = MCItem.getRawId(mcValue) 29 | 30 | fun getTranslationKey(): String = mcValue.translationKey 31 | 32 | fun getRegistryName(): String = Registries.ITEM.getId(mcValue).toString() 33 | 34 | fun asItem(): Item = Item(this) 35 | 36 | companion object { 37 | @JvmStatic 38 | fun fromMC(mcValue: MCItem): ItemType? { 39 | return if (mcValue === Items.AIR) { 40 | null 41 | } else { 42 | ItemType(mcValue) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListHudMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.triggers.TriggerType; 4 | import com.chattriggers.ctjs.api.world.TabList; 5 | import net.minecraft.client.gui.DrawContext; 6 | import net.minecraft.client.gui.hud.PlayerListHud; 7 | import net.minecraft.scoreboard.Scoreboard; 8 | import net.minecraft.scoreboard.ScoreboardObjective; 9 | import net.minecraft.text.Text; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(PlayerListHud.class) 16 | public class PlayerListHudMixin { 17 | @Inject(method = "render", at = @At("HEAD"), cancellable = true) 18 | private void injectRenderPlayerList(DrawContext context, int scaledWindowWidth, Scoreboard scoreboard, ScoreboardObjective objective, CallbackInfo ci) { 19 | TriggerType.RENDER_PLAYER_LIST.triggerAll(ci); 20 | } 21 | 22 | @Inject(method = "setHeader", at = @At("HEAD"), cancellable = true) 23 | private void ctjs$keepCustomHeader(Text header, CallbackInfo ci) { 24 | if (TabList.INSTANCE.getCustomHeader$ctjs()) { 25 | ci.cancel(); 26 | } 27 | } 28 | 29 | @Inject(method = "setFooter", at = @At("HEAD"), cancellable = true) 30 | private void ctjs$keepCustomFooter(Text footer, CallbackInfo ci) { 31 | if (TabList.INSTANCE.getCustomFooter$ctjs()) { 32 | ci.cancel(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/EntityRenderDispatcherMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.engine.CTEvents; 4 | import com.chattriggers.ctjs.api.render.Renderer; 5 | import com.llamalad7.mixinextras.sugar.Local; 6 | import net.minecraft.client.render.VertexConsumerProvider; 7 | import net.minecraft.client.render.entity.EntityRenderDispatcher; 8 | import net.minecraft.client.render.entity.EntityRendererFactory; 9 | import net.minecraft.client.util.math.MatrixStack; 10 | import net.minecraft.entity.Entity; 11 | import net.minecraft.resource.ResourceManager; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | @Mixin(EntityRenderDispatcher.class) 18 | public abstract class EntityRenderDispatcherMixin { 19 | @Inject(method = "reload", at = @At("TAIL")) 20 | private void injectReload(ResourceManager manager, CallbackInfo ci, @Local EntityRendererFactory.Context context) { 21 | Renderer.initializePlayerRenderers$ctjs(context); 22 | } 23 | 24 | @Inject(method = "render", at = @At("HEAD"), cancellable = true) 25 | private void injectRender(Entity entity, double x, double y, double z, float yaw, float tickDelta, 26 | MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, 27 | CallbackInfo ci) { 28 | CTEvents.RENDER_ENTITY.invoker().render(matrices, entity, tickDelta, ci); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/typealiases.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs 2 | 3 | typealias MCSound = net.minecraft.client.sound.Sound 4 | typealias MCBlockPos = net.minecraft.util.math.BlockPos 5 | typealias MCBlock = net.minecraft.block.Block 6 | typealias MCChunk = net.minecraft.world.chunk.Chunk 7 | typealias MCEntity = net.minecraft.entity.Entity 8 | typealias MCLivingEntity = net.minecraft.entity.LivingEntity 9 | typealias MCParticle = net.minecraft.client.particle.Particle 10 | typealias MCTeam = net.minecraft.scoreboard.Team 11 | typealias MCInventory = net.minecraft.inventory.Inventory 12 | typealias MCItem = net.minecraft.item.Item 13 | typealias MCNbtBase = net.minecraft.nbt.NbtElement 14 | typealias MCNbtCompound = net.minecraft.nbt.NbtCompound 15 | typealias MCNbtList = net.minecraft.nbt.NbtList 16 | typealias MCBlockEntity = net.minecraft.block.entity.BlockEntity 17 | typealias MCDifficulty = net.minecraft.world.Difficulty 18 | typealias MCGraphicsMode = net.minecraft.client.option.GraphicsMode 19 | typealias MCSlot = net.minecraft.screen.slot.Slot 20 | typealias MCAttenuationType = net.minecraft.client.sound.SoundInstance.AttenuationType 21 | typealias MCBossBarColor = net.minecraft.entity.boss.BossBar.Color 22 | typealias MCBossBarStyle = net.minecraft.entity.boss.BossBar.Style 23 | typealias MCCloudRenderMode = net.minecraft.client.option.CloudRenderMode 24 | typealias MCParticlesMode = net.minecraft.client.option.ParticlesMode 25 | typealias MCDimensionType = net.minecraft.world.dimension.DimensionType 26 | typealias MCVertexFormat = net.minecraft.client.render.VertexFormat 27 | typealias MCChatVisibility = net.minecraft.network.message.ChatVisibility 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/engine/Console.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.engine 2 | 3 | import com.chattriggers.ctjs.api.Config 4 | import com.chattriggers.ctjs.internal.console.ConsoleHostProcess 5 | import kotlinx.serialization.Serializable 6 | import org.mozilla.javascript.WrappedException 7 | import java.awt.Color 8 | 9 | // A wrapper object so that we can hide away the implementation in the 10 | // internal package 11 | object Console { 12 | fun clear() = ConsoleHostProcess.clear() 13 | fun println(obj: Any, logType: LogType, end: String, customColor: Color?) = 14 | ConsoleHostProcess.println(obj, logType, end, customColor) 15 | fun println(obj: Any, logType: LogType, end: String) = ConsoleHostProcess.println(obj, logType, end, null) 16 | fun println(obj: Any, logType: LogType) = println(obj, logType, "\n") 17 | fun println(obj: Any) = println(obj, LogType.INFO) 18 | 19 | fun printStackTrace(error: Throwable) = ConsoleHostProcess.printStackTrace(error) 20 | fun show() = ConsoleHostProcess.show() 21 | fun close() = ConsoleHostProcess.close() 22 | fun onConsoleSettingsChanged(settings: Config.ConsoleSettings) = 23 | ConsoleHostProcess.onConsoleSettingsChanged(settings) 24 | } 25 | 26 | @Serializable 27 | enum class LogType { 28 | INFO, 29 | WARN, 30 | ERROR, 31 | } 32 | 33 | fun Any.printToConsole(logType: LogType = LogType.INFO) { 34 | Console.println(this, logType) 35 | } 36 | 37 | fun Throwable.printTraceToConsole(): Unit = if (this is WrappedException) { 38 | wrappedException.printTraceToConsole() 39 | } else { 40 | this.printStackTrace() 41 | Console.printStackTrace(this) 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/InGameHudMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.world.Scoreboard; 4 | import com.chattriggers.ctjs.internal.engine.CTEvents; 5 | import net.minecraft.client.gui.DrawContext; 6 | import net.minecraft.client.gui.hud.InGameHud; 7 | import net.minecraft.client.render.RenderTickCounter; 8 | import net.minecraft.scoreboard.ScoreboardObjective; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | 15 | @Mixin(InGameHud.class) 16 | public class InGameHudMixin { 17 | @Inject(method = "renderScoreboardSidebar(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/scoreboard/ScoreboardObjective;)V", at = @At("HEAD"), cancellable = true) 18 | private void injectRenderScoreboard(DrawContext matrices, ScoreboardObjective objective, CallbackInfo ci) { 19 | if (!Scoreboard.getShouldRender()) 20 | ci.cancel(); 21 | } 22 | 23 | @Inject( 24 | method = "render", 25 | at = @At( 26 | value = "INVOKE", 27 | target = "Lnet/minecraft/client/gui/LayeredDrawer;render(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/client/render/RenderTickCounter;)V", 28 | shift = At.Shift.AFTER 29 | ) 30 | ) 31 | private void injectRenderOverlay(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { 32 | CTEvents.RENDER_OVERLAY.invoker().render(context.getMatrices(), tickCounter.getTickDelta(false)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/vec/Vec3i.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.vec 2 | 3 | import java.util.* 4 | import kotlin.math.acos 5 | import kotlin.math.sqrt 6 | 7 | open class Vec3i @JvmOverloads constructor( 8 | val x: Int = 0, val y: Int = 0, val z: Int = 0, 9 | ) { 10 | fun magnitudeSquared() = x * x + y * y + z * z 11 | 12 | fun magnitude() = sqrt(magnitudeSquared().toFloat()) 13 | 14 | open fun translated(dx: Int, dy: Int, dz: Int) = Vec3i(x + dx, y + dy, z + dz) 15 | 16 | open fun scaled(scale: Int) = Vec3i(x * scale, y * scale, z * scale) 17 | 18 | open fun scaled(xScale: Int, yScale: Int, zScale: Int) = Vec3i(x * xScale, y * yScale, z * zScale) 19 | 20 | open fun crossProduct(other: Vec3i) = Vec3i( 21 | y * other.z - z * other.y, 22 | z * other.x - x * other.z, 23 | x * other.y - y * other.x, 24 | ) 25 | 26 | fun dotProduct(other: Vec3i) = x * other.x + y * other.y + z * other.z 27 | 28 | fun angleTo(other: Vec3i): Float { 29 | return acos(dotProduct(other) / (magnitude() * other.magnitude()).coerceIn(-1f, 1f)) 30 | } 31 | 32 | fun normalized() = magnitude().let { 33 | Vec3f(x / it, y / it, z / it) 34 | } 35 | 36 | open operator fun unaryMinus() = Vec3i(-x, -y, -z) 37 | 38 | open operator fun plus(other: Vec3i) = Vec3i(x + other.x, y + other.y, z + other.z) 39 | 40 | open operator fun minus(other: Vec3i) = this + (-other) 41 | 42 | override fun hashCode() = Objects.hash(x, y, z) 43 | 44 | override fun equals(other: Any?) = other is Vec3i && x == other.x && y == other.y && z == other.z 45 | 46 | override fun toString() = "Vec3i($x, $y, $z)" 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/BlockEntityRenderDispatcherMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.engine.CTEvents; 4 | import net.minecraft.block.entity.BlockEntity; 5 | import net.minecraft.client.render.VertexConsumerProvider; 6 | import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher; 7 | import net.minecraft.client.util.math.MatrixStack; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | import java.util.Objects; 14 | 15 | @Mixin(BlockEntityRenderDispatcher.class) 16 | public class BlockEntityRenderDispatcherMixin { 17 | @Inject( 18 | method = "render(Lnet/minecraft/block/entity/BlockEntity;FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;)V", 19 | at = @At( 20 | value = "INVOKE", 21 | target = "Lnet/minecraft/client/render/block/entity/BlockEntityRenderDispatcher;runReported(Lnet/minecraft/block/entity/BlockEntity;Ljava/lang/Runnable;)V" 22 | ), 23 | cancellable = true 24 | ) 25 | private void injectRender(BlockEntity blockEntity, float tickDelta, MatrixStack matrices, 26 | VertexConsumerProvider vertexConsumers, CallbackInfo ci) { 27 | if (blockEntity.hasWorld() && Objects.requireNonNull(blockEntity.getWorld()).isClient) { 28 | CTEvents.RENDER_BLOCK_ENTITY.invoker().render(matrices, blockEntity, tickDelta, ci); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/entity/LivingEntity.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.entity 2 | 3 | import com.chattriggers.ctjs.api.inventory.Item 4 | import com.chattriggers.ctjs.api.world.PotionEffect 5 | import com.chattriggers.ctjs.api.world.PotionEffectType 6 | import com.chattriggers.ctjs.MCEntity 7 | import com.chattriggers.ctjs.MCLivingEntity 8 | import net.minecraft.entity.EquipmentSlot 9 | import net.minecraft.registry.Registries 10 | 11 | open class LivingEntity(override val mcValue: MCLivingEntity) : Entity(mcValue) { 12 | fun getActivePotionEffects(): List { 13 | return mcValue.statusEffects.map(::PotionEffect) 14 | } 15 | 16 | fun canSeeEntity(other: MCEntity) = mcValue.canSee(other) 17 | 18 | fun canSeeEntity(other: Entity) = canSeeEntity(other.toMC()) 19 | 20 | /** 21 | * Gets the item currently in the entity's specified inventory slot. 22 | * 0 for main hand, 1 for offhand, 2-5 for armor 23 | * 24 | * @param slot the slot to access 25 | * @return the item in said slot 26 | */ 27 | fun getStackInSlot(slot: Int): Item? { 28 | return mcValue.getEquippedStack(EquipmentSlot.entries[slot])?.let(Item::fromMC) 29 | } 30 | 31 | fun getHP() = mcValue.health 32 | 33 | fun getMaxHP() = mcValue.maxHealth 34 | 35 | fun getAbsorption() = mcValue.absorptionAmount 36 | 37 | fun getAge() = mcValue.age 38 | 39 | fun getArmorValue() = mcValue.armor 40 | 41 | fun isPotionActive(id: Int) = mcValue.hasStatusEffect(Registries.STATUS_EFFECT.getEntry(id).get()) 42 | 43 | fun isPotionActive(type: PotionEffectType) = mcValue.hasStatusEffect(Registries.STATUS_EFFECT.getEntry(type.type)) 44 | 45 | fun isPotionActive(effect: PotionEffect) = isPotionActive(effect.type) 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/commands/CommandDispatcherMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins.commands; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import com.mojang.brigadier.CommandDispatcher; 5 | import com.mojang.brigadier.ParseResults; 6 | import com.mojang.brigadier.StringReader; 7 | import com.mojang.brigadier.context.CommandContextBuilder; 8 | import com.mojang.brigadier.tree.CommandNode; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | @Mixin(value = CommandDispatcher.class, remap = false) 15 | public class CommandDispatcherMixin { 16 | @Inject( 17 | method = "parseNodes", 18 | at = @At( 19 | value = "INVOKE", 20 | target = "Lcom/mojang/brigadier/context/CommandContextBuilder;withCommand(Lcom/mojang/brigadier/Command;)Lcom/mojang/brigadier/context/CommandContextBuilder;", 21 | shift = At.Shift.AFTER 22 | ) 23 | ) 24 | private void addRedirectCommandToContextIfNecessary( 25 | CommandNode node, 26 | StringReader originalReader, 27 | CommandContextBuilder contextSoFar, 28 | CallbackInfoReturnable> cir, 29 | @Local(ordinal = 1) CommandNode child, 30 | @Local(ordinal = 1) CommandContextBuilder context 31 | ) { 32 | // TODO: If there is a redirect modifier, this fix will ignore it. 33 | 34 | if (context.getCommand() == null && child.getRedirect() != null && child.getRedirect().getCommand() != null) 35 | context.withCommand(child.getRedirect().getCommand()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerScreenHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.inventory.Item; 4 | import com.chattriggers.ctjs.api.triggers.CancellableEvent; 5 | import com.chattriggers.ctjs.api.triggers.TriggerType; 6 | import net.minecraft.entity.player.PlayerEntity; 7 | import net.minecraft.inventory.RecipeInputInventory; 8 | import net.minecraft.item.ItemStack; 9 | import net.minecraft.screen.PlayerScreenHandler; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | @Mixin(PlayerScreenHandler.class) 18 | public class PlayerScreenHandlerMixin { 19 | @Shadow 20 | @Final 21 | private RecipeInputInventory craftingInput; 22 | 23 | @Inject( 24 | method = "onClosed", 25 | at = @At( 26 | value = "INVOKE", 27 | target = "Lnet/minecraft/inventory/CraftingResultInventory;clear()V", 28 | shift = At.Shift.AFTER 29 | ) 30 | ) 31 | private void injectOnClosed(PlayerEntity player, CallbackInfo ci) { 32 | // dropping items for player's crafting slots. needs a whole injection point due to there 33 | // being an extra if to make sure it only calls dropInventory server-side 34 | if (player.getWorld().isClient) { 35 | for (int i = 0; i < craftingInput.size(); i++) { 36 | ItemStack stack = craftingInput.getStack(i); 37 | if (!stack.isEmpty()) { 38 | TriggerType.DROP_ITEM.triggerAll(Item.fromMC(stack), true, new CancellableEvent()); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ChatHudMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.message.ChatLib; 4 | import net.minecraft.client.gui.hud.ChatHud; 5 | import net.minecraft.client.gui.hud.ChatHudLine; 6 | import net.minecraft.client.gui.hud.MessageIndicator; 7 | import net.minecraft.network.message.MessageSignatureData; 8 | import net.minecraft.text.Text; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | import java.util.List; 17 | 18 | @Mixin(ChatHud.class) 19 | public class ChatHudMixin { 20 | @Final 21 | @Shadow 22 | private List messages; 23 | 24 | @Inject(method = "clear", at = @At("TAIL")) 25 | private void injectClear(boolean clearHistory, CallbackInfo ci) { 26 | ChatLib.INSTANCE.onChatHudClearChat$ctjs(); 27 | } 28 | 29 | // TODO: is it this or addVisibleMessage 30 | @Inject( 31 | method = "addMessage(Lnet/minecraft/client/gui/hud/ChatHudLine;)V", 32 | at = @At( 33 | value = "INVOKE", 34 | target = "Ljava/util/List;remove(I)Ljava/lang/Object;", 35 | shift = At.Shift.BEFORE 36 | ) 37 | ) 38 | private void injectMessageRemovedForChatLimit(ChatHudLine message, CallbackInfo ci) { 39 | ChatLib.INSTANCE.onChatHudLineRemoved$ctjs(messages.getLast()); 40 | } 41 | 42 | // Note: ChatHudLine objects are also removed in queueForRemoval, however those are signature based. 43 | // The Message objects that CT sends will always create ChatHudLine objects with null signatures, 44 | // so objects removed in that method will never be in the ChatLib.chatLineIds map 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/action/Action.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory.action 2 | 3 | import com.chattriggers.ctjs.api.client.Client 4 | import com.chattriggers.ctjs.api.client.Player 5 | import com.chattriggers.ctjs.api.inventory.Inventory 6 | import net.minecraft.screen.slot.SlotActionType 7 | 8 | abstract class Action(var slot: Int, var windowId: Int) { 9 | fun setSlot(slot: Int) = apply { 10 | this.slot = slot 11 | } 12 | 13 | fun setWindowId(windowId: Int) = apply { 14 | this.windowId = windowId 15 | } 16 | 17 | internal abstract fun complete() 18 | 19 | protected fun doClick(button: Int, mode: SlotActionType) { 20 | Client.getMinecraft().interactionManager?.clickSlot( 21 | windowId, 22 | slot, 23 | button, 24 | mode, 25 | Player.toMC(), 26 | ) 27 | } 28 | 29 | companion object { 30 | /** 31 | * Creates a new action. 32 | * The Inventory must be a container, see [Inventory.isContainer]. 33 | * The slot can be -999 for outside of the gui 34 | * 35 | * @param inventory the inventory to complete the action on 36 | * @param slot the slot to complete the action on 37 | * @param typeString the type of action to do (CLICK, DRAG, DROP, KEY) 38 | * @return the new action 39 | */ 40 | @JvmStatic 41 | fun of(inventory: Inventory, slot: Int, typeString: String) = 42 | when (Type.valueOf(typeString.uppercase())) { 43 | Type.CLICK -> ClickAction(slot, inventory.getWindowId()) 44 | Type.DRAG -> DragAction(slot, inventory.getWindowId()) 45 | Type.KEY -> KeyAction(slot, inventory.getWindowId()) 46 | Type.DROP -> DropAction(slot, inventory.getWindowId()) 47 | } 48 | } 49 | 50 | enum class Type { 51 | CLICK, DRAG, KEY, DROP 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ClientConnectionMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.engine.CTEvents; 4 | import com.chattriggers.ctjs.api.triggers.TriggerType; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import net.minecraft.network.ClientConnection; 7 | import net.minecraft.network.NetworkSide; 8 | import net.minecraft.network.PacketCallbacks; 9 | import net.minecraft.network.packet.Packet; 10 | import org.jetbrains.annotations.Nullable; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | @Mixin(ClientConnection.class) 18 | public abstract class ClientConnectionMixin { 19 | @Shadow 20 | public abstract NetworkSide getSide(); 21 | 22 | @Inject( 23 | method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/packet/Packet;)V", 24 | at = @At( 25 | value = "INVOKE", 26 | target = "Lnet/minecraft/network/ClientConnection;handlePacket(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;)V", 27 | shift = At.Shift.BEFORE 28 | ), 29 | cancellable = true 30 | ) 31 | private void injectHandlePacket(ChannelHandlerContext channelHandlerContext, Packet packet, CallbackInfo ci) { 32 | if (getSide() == NetworkSide.CLIENTBOUND) 33 | CTEvents.PACKET_RECEIVED.invoker().receive(packet, ci); 34 | } 35 | 36 | @Inject( 37 | method = "send(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/PacketCallbacks;)V", 38 | at = @At("HEAD"), 39 | cancellable = true 40 | ) 41 | private void injectSendPacket(Packet packet, @Nullable PacketCallbacks callbacks, CallbackInfo ci) { 42 | TriggerType.PACKET_SENT.triggerAll(packet, ci); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/triggers/StepTrigger.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.triggers 2 | 3 | import com.chattriggers.ctjs.api.client.Client 4 | 5 | class StepTrigger(method: Any) : Trigger(method, TriggerType.STEP) { 6 | private var fps: Long = 60L 7 | private var delay: Long = -1 8 | private var systemTime: Long = Client.getSystemTime() 9 | private var elapsed: Long = 0L 10 | 11 | /** 12 | * Sets the frames per second that the trigger activates. 13 | * This has a maximum one step per second. 14 | * @param fps the frames per second to set 15 | * @return the trigger for method chaining 16 | */ 17 | fun setFps(fps: Long) = apply { 18 | this.fps = if (fps < 1) 1L else fps 19 | systemTime = Client.getSystemTime() + 1000 / this.fps 20 | } 21 | 22 | /** 23 | * Sets the delay in seconds between the trigger activation. 24 | * This has a minimum of one step every second. This will override [setFps]. 25 | * @param delay The delay in seconds 26 | * @return the trigger for method chaining 27 | */ 28 | fun setDelay(delay: Long) = apply { 29 | this.delay = if (delay < 1) 1L else delay 30 | systemTime = Client.getSystemTime() - this.delay * 1000 31 | } 32 | 33 | override fun register(): Trigger { 34 | systemTime = Client.getSystemTime() 35 | return super.register() 36 | } 37 | 38 | override fun trigger(args: Array) { 39 | if (delay < 0) { 40 | // run trigger based on set fps value (60 per second by default) 41 | while (systemTime < Client.getSystemTime() + 1000 / fps) { 42 | callMethod(arrayOf(++elapsed)) 43 | systemTime += 1000 / fps 44 | } 45 | } else { 46 | // run trigger based on set delay in seconds 47 | while (Client.getSystemTime() > systemTime + delay * 1000) { 48 | callMethod(arrayOf(++elapsed)) 49 | systemTime += delay * 1000 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ParticleAccessor.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import net.minecraft.client.particle.Particle; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(Particle.class) 8 | public interface ParticleAccessor { 9 | @Accessor(value = "x") 10 | double getX(); 11 | 12 | @Accessor(value = "x") 13 | void setX(double value); 14 | 15 | @Accessor(value = "y") 16 | double getY(); 17 | 18 | @Accessor(value = "y") 19 | void setY(double value); 20 | 21 | @Accessor(value = "z") 22 | double getZ(); 23 | 24 | @Accessor(value = "z") 25 | void setZ(double value); 26 | 27 | @Accessor 28 | double getVelocityX(); 29 | 30 | @Accessor 31 | void setVelocityX(double value); 32 | 33 | @Accessor 34 | double getVelocityY(); 35 | 36 | @Accessor 37 | void setVelocityY(double value); 38 | 39 | @Accessor 40 | double getVelocityZ(); 41 | 42 | @Accessor 43 | void setVelocityZ(double value); 44 | 45 | @Accessor 46 | float getRed(); 47 | 48 | @Accessor 49 | void setRed(float value); 50 | 51 | @Accessor 52 | float getGreen(); 53 | 54 | @Accessor 55 | void setGreen(float value); 56 | 57 | @Accessor 58 | float getBlue(); 59 | 60 | @Accessor 61 | void setBlue(float value); 62 | 63 | @Accessor 64 | float getAlpha(); 65 | 66 | @Accessor 67 | void setAlpha(float value); 68 | 69 | @Accessor 70 | int getAge(); 71 | 72 | @Accessor 73 | void setAge(int value); 74 | 75 | @Accessor 76 | double getPrevPosX(); 77 | 78 | @Accessor 79 | void setPrevPosX(double value); 80 | 81 | @Accessor 82 | double getPrevPosY(); 83 | 84 | @Accessor 85 | void setPrevPosY(double value); 86 | 87 | @Accessor 88 | double getPrevPosZ(); 89 | 90 | @Accessor 91 | void setPrevPosZ(double value); 92 | 93 | @Accessor 94 | boolean getDead(); 95 | 96 | @Accessor 97 | void setDead(boolean value); 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/SystemDetailsMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.engine.module.Module; 4 | import com.chattriggers.ctjs.internal.engine.module.ModuleManager; 5 | import com.chattriggers.ctjs.internal.engine.module.ModuleMetadata; 6 | import net.minecraft.util.SystemDetails; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Comparator; 15 | import java.util.List; 16 | import java.util.function.Supplier; 17 | 18 | @Mixin(SystemDetails.class) 19 | public abstract class SystemDetailsMixin { 20 | @Shadow 21 | public abstract void addSection(String string, Supplier supplier); 22 | 23 | @Inject(method = "", at = @At("RETURN")) 24 | private void addModules(CallbackInfo ci) { 25 | addSection("ChatTriggers Modules", () -> { 26 | List modules = new ArrayList<>(ModuleManager.INSTANCE.getCachedModules()); 27 | modules.sort(Comparator.comparing(Module::getName)); 28 | 29 | StringBuilder sb = new StringBuilder(); 30 | 31 | for (Module module : modules) { 32 | sb 33 | .append("\n") 34 | .append("\t\t") 35 | .append(module.getName()) 36 | .append(": "); 37 | 38 | ModuleMetadata metadata = module.getMetadata(); 39 | if (metadata.getVersion() != null) { 40 | sb.append("v") 41 | .append(module.getMetadata().getVersion()); 42 | } else { 43 | sb.append("No module version specified"); 44 | } 45 | } 46 | 47 | return sb.toString(); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/console/TextAreaWriter.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.console 2 | 3 | import com.chattriggers.ctjs.engine.LogType 4 | import java.awt.Color 5 | import java.io.PrintWriter 6 | import java.io.Writer 7 | import javax.swing.JTextPane 8 | import javax.swing.text.AttributeSet 9 | import javax.swing.text.SimpleAttributeSet 10 | import javax.swing.text.StyleConstants 11 | import javax.swing.text.StyleContext 12 | 13 | class TextAreaWriter( 14 | private val textArea: JTextPane, 15 | private val configGetter: () -> ConfigUpdateMessage, 16 | ) : Writer() { 17 | val printWriter = PrintWriter(this) 18 | private var currentLogType: LogType = LogType.INFO 19 | private var customColor: Color? = null 20 | 21 | override fun write(cbuf: CharArray, off: Int, len: Int) { 22 | val s = String(cbuf, off, len) 23 | val sc = StyleContext.getDefaultStyleContext() 24 | val config = configGetter() 25 | 26 | val color = customColor ?: when (currentLogType) { 27 | LogType.INFO -> Color(config.textColor) 28 | LogType.WARN -> Color(config.warningColor) 29 | LogType.ERROR -> Color(config.errorColor) 30 | } 31 | 32 | val attributes: AttributeSet = sc.addAttribute( 33 | SimpleAttributeSet.EMPTY, 34 | StyleConstants.Foreground, 35 | color, 36 | ) 37 | 38 | textArea.document.insertString(textArea.document.length, s, attributes) 39 | textArea.caretPosition = textArea.document.length 40 | currentLogType = LogType.INFO 41 | customColor = null 42 | } 43 | 44 | override fun flush() { 45 | } 46 | 47 | override fun close() { 48 | } 49 | 50 | @JvmOverloads 51 | fun println(s: Any, logType: LogType = LogType.INFO, end: String = "\n", customColor: Color? = null) { 52 | currentLogType = logType 53 | this.customColor = customColor 54 | printWriter.print(s) 55 | printWriter.print(end) 56 | } 57 | 58 | fun clear() { 59 | textArea.text = "" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/engine/JSErrorReporter.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.engine 2 | 3 | import com.chattriggers.ctjs.engine.Console 4 | import org.mozilla.javascript.ErrorReporter 5 | import org.mozilla.javascript.EvaluatorException 6 | 7 | object JSErrorReporter : ErrorReporter { 8 | private const val MESSAGE_PREFIX = "js: " 9 | 10 | override fun warning(message: String?, sourceName: String?, line: Int, lineSource: String?, lineOffset: Int) { 11 | reportErrorMessage(message, sourceName, line, lineSource, lineOffset, isWarning = true) 12 | } 13 | 14 | override fun error(message: String?, sourceName: String?, line: Int, lineSource: String?, lineOffset: Int) { 15 | reportErrorMessage(message, sourceName, line, lineSource, lineOffset, isWarning = false) 16 | } 17 | 18 | private fun reportErrorMessage( 19 | inputMessage: String?, 20 | sourceName: String?, 21 | line: Int, 22 | lineSource: String?, 23 | lineOffset: Int, 24 | isWarning: Boolean, 25 | ) { 26 | var message = if (line > 0) { 27 | if (sourceName == null) { 28 | "line $line: $inputMessage" 29 | } else "\"$sourceName\", line $line: $inputMessage" 30 | } else inputMessage 31 | 32 | if (isWarning) 33 | message = "warning: $message" 34 | 35 | Console.println(MESSAGE_PREFIX + message) 36 | if (lineSource != null) { 37 | Console.println(MESSAGE_PREFIX + lineSource) 38 | Console.println(MESSAGE_PREFIX + buildIndicator(lineOffset)) 39 | } 40 | } 41 | 42 | private fun buildIndicator(offset: Int) = buildString { 43 | repeat(offset) { append('.') } 44 | append('^') 45 | } 46 | 47 | override fun runtimeError( 48 | message: String?, 49 | sourceName: String?, 50 | line: Int, 51 | lineSource: String?, 52 | lineOffset: Int 53 | ): EvaluatorException { 54 | return EvaluatorException(message, sourceName, line, lineSource, lineOffset) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/engine/JSContextFactory.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.engine 2 | 3 | import com.chattriggers.ctjs.CTJS 4 | import com.chattriggers.ctjs.internal.launch.CTJavaObjectMappingProvider 5 | import org.mozilla.javascript.Context 6 | import org.mozilla.javascript.Context.EMIT_DEBUG_OUTPUT 7 | import org.mozilla.javascript.Context.FEATURE_LOCATION_INFORMATION_IN_ERROR 8 | import org.mozilla.javascript.ContextFactory 9 | import org.mozilla.javascript.WrapFactory 10 | import java.io.File 11 | import java.net.URL 12 | import java.net.URLClassLoader 13 | 14 | object JSContextFactory : ContextFactory() { 15 | private val classLoader = ModifiedURLClassLoader() 16 | var optimize = true 17 | 18 | fun addAllURLs(urls: List) = classLoader.addAllURLs(urls) 19 | 20 | override fun onContextCreated(cx: Context) { 21 | super.onContextCreated(cx) 22 | 23 | cx.debugOutputPath = File(".", "DEBUG") 24 | cx.applicationClassLoader = classLoader 25 | cx.optimizationLevel = if (optimize) 9 else 0 26 | cx.languageVersion = Context.VERSION_ES6 27 | cx.errorReporter = JSErrorReporter 28 | 29 | cx.wrapFactory = WrapFactory().apply { 30 | isJavaPrimitiveWrap = false 31 | } 32 | 33 | if (!CTJS.isDevelopment) 34 | cx.javaObjectMappingProvider = CTJavaObjectMappingProvider 35 | } 36 | 37 | override fun hasFeature(cx: Context?, featureIndex: Int): Boolean { 38 | when (featureIndex) { 39 | FEATURE_LOCATION_INFORMATION_IN_ERROR -> return true 40 | EMIT_DEBUG_OUTPUT -> return CTJS.isDevelopment 41 | } 42 | 43 | return super.hasFeature(cx, featureIndex) 44 | } 45 | 46 | private class ModifiedURLClassLoader : URLClassLoader(arrayOf(), javaClass.classLoader) { 47 | val sources = mutableSetOf() 48 | 49 | fun addAllURLs(urls: List) { 50 | (urls - sources).forEach(::addURL) 51 | } 52 | 53 | public override fun addURL(url: URL) { 54 | super.addURL(url) 55 | sources.add(url) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/world/Server.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.world 2 | 3 | import com.chattriggers.ctjs.api.client.Client 4 | import com.chattriggers.ctjs.api.client.Player 5 | import com.chattriggers.ctjs.api.message.TextComponent 6 | 7 | object Server { 8 | @JvmStatic 9 | fun toMC() = Client.getMinecraft().currentServerEntry 10 | 11 | @JvmStatic 12 | fun isSingleplayer(): Boolean = Client.getMinecraft().isInSingleplayer 13 | 14 | /** 15 | * Gets the current server's IP, or "localhost" if the player 16 | * is in a single-player world. 17 | * 18 | * @return The IP of the current server 19 | */ 20 | @JvmStatic 21 | fun getIP(): String { 22 | if (isSingleplayer()) 23 | return "localhost" 24 | 25 | return toMC()?.address ?: "" 26 | } 27 | 28 | /** 29 | * Gets the current server's name, or "SinglePlayer" if the player 30 | * is in a single-player world. 31 | * 32 | * @return The name of the current server 33 | */ 34 | @JvmStatic 35 | fun getName(): String { 36 | if (isSingleplayer()) 37 | return "SinglePlayer" 38 | 39 | return toMC()?.name ?: "" 40 | } 41 | 42 | /** 43 | * Gets the current server's MOTD, or "SinglePlayer" if the player 44 | * is in a single-player world. 45 | * 46 | * @return The MOTD of the current server 47 | */ 48 | @JvmStatic 49 | fun getMOTD(): String { 50 | if (isSingleplayer()) 51 | return "SinglePlayer" 52 | 53 | return toMC()?.label?.let { TextComponent(it) }?.formattedText ?: "" 54 | } 55 | 56 | /** 57 | * Gets the ping to the current server, or 5 if the player 58 | * is in a single-player world. Returns -1 if not in a world 59 | * 60 | * @return The ping to the current server 61 | */ 62 | @JvmStatic 63 | fun getPing(): Long { 64 | if (isSingleplayer()) { 65 | return 5L 66 | } 67 | 68 | val player = Player.toMC() ?: return -1L 69 | 70 | return Client.getConnection()?.getPlayerListEntry(player.uuid)?.latency?.toLong() 71 | ?: toMC()?.ping 72 | ?: -1L 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/action/DragAction.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory.action 2 | 3 | import net.minecraft.screen.slot.SlotActionType 4 | 5 | class DragAction(slot: Int, windowId: Int) : Action(slot, windowId) { 6 | private lateinit var clickType: ClickType 7 | private lateinit var stage: Stage 8 | 9 | fun getClickType(): ClickType = clickType 10 | 11 | /** 12 | * The type of click (REQUIRED) 13 | * 14 | * @param clickType the new click type 15 | */ 16 | fun setClickType(clickType: ClickType) = apply { 17 | this.clickType = clickType 18 | } 19 | 20 | fun getStage(): Stage = stage 21 | 22 | /** 23 | * The stage of this drag (REQUIRED) 24 | * BEGIN is when beginning the drag 25 | * SLOT is for each slot being dragged into 26 | * END is for ending the drag 27 | * 28 | * @param stage the stage 29 | */ 30 | fun setStage(stage: Stage) = apply { 31 | this.stage = stage 32 | } 33 | 34 | /** 35 | * Sets the type of click. 36 | * Possible values are: LEFT, RIGHT, MIDDLE 37 | * 38 | * @param clickType the click type 39 | * @return the current Action for method chaining 40 | */ 41 | fun setClickString(clickType: String) = apply { 42 | this.clickType = ClickType.valueOf(clickType.uppercase()) 43 | } 44 | 45 | /** 46 | * Sets the stage of this drag. 47 | * Possible values are: BEGIN, SLOT, END [stage] 48 | * 49 | * @param stage the stage 50 | * @return the current Action for method chaining 51 | */ 52 | fun setStageString(stage: String) = apply { 53 | this.stage = Stage.valueOf(stage.uppercase()) 54 | } 55 | 56 | override fun complete() { 57 | val button = stage.stage and 3 or (clickType.button and 3) shl 2 58 | 59 | if (stage != Stage.SLOT) { 60 | slot = -999 61 | println("Enforcing slot of -999") 62 | } 63 | 64 | doClick(button, SlotActionType.QUICK_CRAFT) 65 | } 66 | 67 | enum class ClickType(val button: Int) { 68 | LEFT(0), RIGHT(1), MIDDLE(2) 69 | } 70 | 71 | enum class Stage(val stage: Int) { 72 | BEGIN(0), SLOT(1), END(2) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/MouseMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.engine.CTEvents; 4 | import com.chattriggers.ctjs.internal.listeners.MouseListener; 5 | import net.minecraft.client.Mouse; 6 | import net.minecraft.client.gui.screen.Screen; 7 | import org.objectweb.asm.Opcodes; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(Mouse.class) 15 | public class MouseMixin { 16 | @Shadow 17 | private int activeButton; 18 | 19 | @Inject( 20 | method = "onMouseButton", 21 | at = @At( 22 | value = "FIELD", 23 | target = "Lnet/minecraft/client/MinecraftClient;currentScreen:Lnet/minecraft/client/gui/screen/Screen;", 24 | opcode = Opcodes.GETFIELD 25 | ) 26 | ) 27 | private void injectOnMouseButton(long window, int button, int action, int mods, CallbackInfo ci) { 28 | MouseListener.onRawMouseInput(button, action); 29 | } 30 | 31 | @Inject( 32 | method = "onMouseScroll", 33 | at = @At( 34 | value = "FIELD", 35 | target = "Lnet/minecraft/client/MinecraftClient;options:Lnet/minecraft/client/option/GameOptions;", 36 | opcode = Opcodes.GETFIELD 37 | ) 38 | ) 39 | private void injectOnMouseScroll(long window, double horizontal, double vertical, CallbackInfo ci) { 40 | MouseListener.onRawMouseScroll(vertical); 41 | } 42 | 43 | @Inject( 44 | method = "method_55795(Lnet/minecraft/client/gui/screen/Screen;DDDD)V", 45 | at = @At( 46 | value = "INVOKE", 47 | target = "Lnet/minecraft/client/gui/screen/Screen;mouseDragged(DDIDD)Z" 48 | ), 49 | cancellable = true 50 | ) 51 | private void injectOnGuiMouseDrag(Screen screen, double d, double e, double f, double g, CallbackInfo ci) { 52 | if (screen != null) { 53 | CTEvents.GUI_MOUSE_DRAG.invoker().process(f, g, d, e, activeButton, screen, ci); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/world/block/Block.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.world.block 2 | 3 | import com.chattriggers.ctjs.api.client.Player 4 | import com.chattriggers.ctjs.api.inventory.Item 5 | import com.chattriggers.ctjs.api.world.World 6 | 7 | /** 8 | * An immutable reference to a placed block in the world. It 9 | * has a block type, a position, and optionally a specific face. 10 | */ 11 | open class Block( 12 | val type: BlockType, 13 | val pos: BlockPos, 14 | val face: BlockFace? = null, 15 | ) { 16 | val x: Int get() = pos.x 17 | val y: Int get() = pos.y 18 | val z: Int get() = pos.z 19 | 20 | fun withType(type: BlockType) = Block(type, pos, face) 21 | 22 | fun withPos(pos: BlockPos) = Block(type, pos, face) 23 | 24 | /** 25 | * Narrows this block to reference a certain face. Used by 26 | * [Player.lookingAt] to specify the block face 27 | * being looked at. 28 | */ 29 | fun withFace(face: BlockFace) = Block(type, pos, face) 30 | 31 | fun getState() = World.toMC()?.getBlockState(pos.toMC()) 32 | 33 | @JvmOverloads 34 | fun isEmittingPower(face: BlockFace? = null): Boolean { 35 | if (face != null) 36 | return World.toMC()!!.isEmittingRedstonePower(pos.toMC(), face.toMC()) 37 | return BlockFace.entries.any { isEmittingPower(it) } 38 | } 39 | 40 | @JvmOverloads 41 | fun getEmittingPower(face: BlockFace? = null): Int { 42 | if (face != null) 43 | return World.toMC()!!.getEmittedRedstonePower(pos.toMC(), face.toMC()) 44 | return BlockFace.entries.asSequence().map(::getEmittingPower).firstOrNull { it != 0 } ?: 0 45 | } 46 | 47 | fun isReceivingPower() = World.toMC()!!.isReceivingRedstonePower(pos.toMC()) 48 | 49 | fun getReceivingPower() = World.toMC()!!.getReceivedRedstonePower(pos.toMC()) 50 | 51 | /** 52 | * Checks whether the block can be mined with the tool in the player's hand 53 | * 54 | * @return whether the block can be mined 55 | */ 56 | fun canBeHarvested(): Boolean = Player.getHeldItem()?.let(::canBeHarvestedWith) ?: false 57 | 58 | fun canBeHarvestedWith(item: Item): Boolean = item.canHarvest(this) 59 | 60 | override fun toString() = "Block{type=$type, pos=($x, $y, $z), face=$face}" 61 | } 62 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/utils/extensions.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.utils 2 | 3 | import com.chattriggers.ctjs.internal.launch.Descriptor 4 | import com.fasterxml.jackson.core.Version 5 | import net.minecraft.util.Identifier 6 | import net.minecraft.util.math.MathHelper 7 | import org.mozilla.javascript.NativeObject 8 | import org.mozilla.javascript.Scriptable 9 | import java.net.URLEncoder 10 | import java.nio.charset.Charset 11 | import kotlin.reflect.KClass 12 | 13 | fun String.toVersion(): Version { 14 | val (semvar, extra) = if ('-' in this) { 15 | split('-') 16 | } else listOf(this, null) 17 | 18 | val split = semvar!!.split(".").map(String::toInt) 19 | return Version(split.getOrElse(0) { 0 }, split.getOrElse(1) { 0 }, split.getOrElse(2) { 0 }, extra, null, null) 20 | } 21 | 22 | fun String.toIdentifier(): Identifier { 23 | return Identifier.of(if (':' in this) this else "minecraft:$this") 24 | } 25 | 26 | fun String.urlEncode() = URLEncoder.encode(this, Charset.defaultCharset()) 27 | 28 | // A helper function that makes the intent explicit and reduces parens 29 | inline fun Any.asMixin() = this as T 30 | 31 | inline fun NativeObject?.get(key: String): T? { 32 | return this?.get(key) as? T 33 | } 34 | 35 | fun NativeObject?.getOption(key: String, default: Any): String { 36 | return (this?.get(key) ?: default).toString() 37 | } 38 | 39 | // Note: getOrDefault(...).toInt/Double/Float() should be preferred 40 | // over getOrDefault(...), as the exact numeric type 41 | // of numeric properties depends on Rhino internals 42 | inline fun NativeObject?.getOrDefault(key: String, default: T): T { 43 | return this?.get(key) as? T ?: default 44 | } 45 | 46 | fun NativeObject?.getOrNull(key: String): Any? { 47 | return this?.get(key).takeIf { it != Scriptable.NOT_FOUND } 48 | } 49 | 50 | fun Double.toRadians() = this * MathHelper.RADIANS_PER_DEGREE 51 | fun Float.toRadians() = this * MathHelper.RADIANS_PER_DEGREE 52 | fun Double.toDegrees() = this * MathHelper.DEGREES_PER_RADIAN 53 | fun Float.toDegrees() = this * MathHelper.DEGREES_PER_RADIAN 54 | 55 | fun KClass<*>.descriptorString(): String = java.descriptorString() 56 | fun KClass<*>.descriptor() = Descriptor.Object(descriptorString()) 57 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyConstantGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.MethodAssembly 4 | import com.chattriggers.ctjs.internal.launch.ModifyConstant 5 | import com.chattriggers.ctjs.internal.utils.descriptorString 6 | import org.objectweb.asm.tree.MethodNode 7 | import org.spongepowered.asm.mixin.injection.ModifyConstant as SPModifyConstant 8 | 9 | internal class ModifyConstantGenerator( 10 | ctx: GenerationContext, 11 | id: Int, 12 | private val modifyConstant: ModifyConstant, 13 | ) : InjectorGenerator(ctx, id) { 14 | override val type = "modifyConstant" 15 | 16 | override fun getInjectionSignature(): InjectionSignature { 17 | val (mappedMethod, method) = ctx.findMethod(modifyConstant.method) 18 | 19 | val type = modifyConstant.constant.getTypeDescriptor() 20 | val parameters = listOf(Parameter(type)) + modifyConstant.locals?.map(Utils::getParameterFromLocal).orEmpty() 21 | 22 | return InjectionSignature( 23 | mappedMethod, 24 | parameters, 25 | type, 26 | method.isStatic, 27 | ) 28 | } 29 | 30 | override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { 31 | node.visitAnnotation(SPModifyConstant::class.descriptorString(), true).apply { 32 | visit("method", signature.targetMethod.toFullDescriptor()) 33 | if (modifyConstant.slice != null) 34 | visit("slice", modifyConstant.slice.map(Utils::createSliceAnnotation)) 35 | visit("constant", listOf(Utils.createConstantAnnotation(modifyConstant.constant))) 36 | if (modifyConstant.remap != null) 37 | visit("remap", modifyConstant.remap) 38 | if (modifyConstant.require != null) 39 | visit("require", modifyConstant.require) 40 | if (modifyConstant.expect != null) 41 | visit("expect", modifyConstant.expect) 42 | if (modifyConstant.allow != null) 43 | visit("allow", modifyConstant.allow) 44 | if (modifyConstant.constraints != null) 45 | visit("constraints", modifyConstant.constraints) 46 | visitEnd() 47 | } 48 | } 49 | 50 | context(MethodAssembly) 51 | override fun generateNotAttachedBehavior() { 52 | generateParameterLoad(0) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/nbt/NBTTagList.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory.nbt 2 | 3 | import com.chattriggers.ctjs.MCNbtBase 4 | import com.chattriggers.ctjs.MCNbtList 5 | import net.minecraft.nbt.NbtElement 6 | import org.mozilla.javascript.NativeArray 7 | 8 | class NBTTagList(override val mcValue: MCNbtList) : NBTBase(mcValue) { 9 | val tagCount: Int 10 | get() = mcValue.size 11 | 12 | fun appendTag(nbt: NBTBase) = appendTag(nbt.toMC()) 13 | 14 | fun appendTag(nbt: MCNbtBase) = apply { 15 | mcValue.add(nbt) 16 | } 17 | 18 | operator fun set(id: Int, nbt: NBTBase) = set(id, nbt.toMC()) 19 | 20 | operator fun set(id: Int, nbt: MCNbtBase) = apply { 21 | mcValue[id] = nbt 22 | } 23 | 24 | fun insertTag(index: Int, nbt: NBTBase) = insertTag(index, nbt.toMC()) 25 | 26 | fun insertTag(index: Int, nbt: MCNbtBase) = apply { 27 | mcValue.add(index, nbt) 28 | } 29 | 30 | fun removeTag(index: Int) = fromMC(mcValue.removeAt(index)) 31 | 32 | fun getShortAt(index: Int) = mcValue.getShort(index) 33 | 34 | fun getIntAt(index: Int) = mcValue.getInt(index) 35 | 36 | fun getFloatAt(index: Int) = mcValue.getFloat(index) 37 | 38 | fun getDoubleAt(index: Int) = mcValue.getDouble(index) 39 | 40 | fun getStringTagAt(index: Int): String = mcValue.getString(index) 41 | 42 | fun getListAt(index: Int) = NBTTagList(mcValue.getList(index)) 43 | 44 | fun getCompoundTagAt(index: Int) = NBTTagCompound(mcValue.getCompound(index)) 45 | 46 | fun getIntArrayAt(index: Int): IntArray = mcValue.getIntArray(index) 47 | 48 | fun getLongArrayAt(index: Int): LongArray = mcValue.getLongArray(index) 49 | 50 | operator fun get(index: Int): NbtElement = mcValue[index] 51 | 52 | fun get(index: Int, type: Byte): Any = when (type) { 53 | NbtElement.SHORT_TYPE -> getShortAt(index) 54 | NbtElement.INT_TYPE -> getIntAt(index) 55 | NbtElement.FLOAT_TYPE -> getFloatAt(index) 56 | NbtElement.DOUBLE_TYPE -> getDoubleAt(index) 57 | NbtElement.STRING_TYPE -> getStringTagAt(index) 58 | NbtElement.LIST_TYPE -> getListAt(index) 59 | NbtElement.COMPOUND_TYPE -> getCompoundTagAt(index) 60 | NbtElement.INT_ARRAY_TYPE -> getIntArrayAt(index) 61 | NbtElement.LONG_ARRAY_TYPE -> getLongArrayAt(index) 62 | else -> get(index) 63 | } 64 | 65 | fun toArray(): NativeArray = mcValue.toObject() 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/ItemStackMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.internal.Skippable; 4 | import com.chattriggers.ctjs.internal.TooltipOverridable; 5 | import com.llamalad7.mixinextras.sugar.Local; 6 | import net.minecraft.entity.player.PlayerEntity; 7 | import net.minecraft.item.Item; 8 | import net.minecraft.item.ItemStack; 9 | import net.minecraft.item.tooltip.TooltipType; 10 | import net.minecraft.text.Text; 11 | import org.jetbrains.annotations.Nullable; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Unique; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Objects; 21 | 22 | @Mixin(ItemStack.class) 23 | public class ItemStackMixin implements TooltipOverridable, Skippable { 24 | @Unique 25 | private boolean shouldOverrideTooltip = false; 26 | @Unique 27 | private List overriddenTooltip = new ArrayList<>(); 28 | @Unique 29 | private boolean shouldSkipFabricEvent = false; 30 | 31 | @Inject(method = "getTooltip", at = @At("HEAD"), cancellable = true) 32 | private void injectGetTooltip(Item.TooltipContext context, @Nullable PlayerEntity player, TooltipType type, CallbackInfoReturnable> cir) { 33 | if (shouldOverrideTooltip) 34 | cir.setReturnValue(Objects.requireNonNull(overriddenTooltip)); 35 | } 36 | 37 | @Inject(method = "getTooltip", at = @At(value = "RETURN", ordinal = 1, shift = At.Shift.BEFORE), cancellable = true) 38 | private void cancelFabricEvent(Item.TooltipContext context, @Nullable PlayerEntity player, TooltipType type, CallbackInfoReturnable> cir, @Local List list) { 39 | if (shouldSkipFabricEvent) 40 | cir.setReturnValue(list); 41 | } 42 | 43 | @Override 44 | public void ctjs_setTooltip(List tooltip) { 45 | overriddenTooltip = tooltip; 46 | } 47 | 48 | @Override 49 | public void ctjs_setShouldOverrideTooltip(boolean shouldOverrideTooltip) { 50 | this.shouldOverrideTooltip = shouldOverrideTooltip; 51 | } 52 | 53 | @Override 54 | public void ctjs_setShouldSkip(boolean shouldSkip) { 55 | shouldSkipFabricEvent = shouldSkip; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/entity/PlayerMP.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.entity 2 | 3 | import com.chattriggers.ctjs.api.client.Client 4 | import com.chattriggers.ctjs.api.message.TextComponent 5 | import com.chattriggers.ctjs.api.render.Renderer 6 | import com.chattriggers.ctjs.internal.NameTagOverridable 7 | import com.chattriggers.ctjs.MCTeam 8 | import com.chattriggers.ctjs.internal.utils.asMixin 9 | import net.minecraft.client.network.PlayerListEntry 10 | import net.minecraft.entity.player.PlayerEntity 11 | import net.minecraft.text.Text 12 | import org.mozilla.javascript.NativeObject 13 | 14 | class PlayerMP(override val mcValue: PlayerEntity) : LivingEntity(mcValue) { 15 | fun isSpectator() = mcValue.isSpectator 16 | 17 | fun getPing(): Int { 18 | return getPlayerInfo()?.latency ?: -1 19 | } 20 | 21 | fun getTeam(): Team? { 22 | return getPlayerInfo()?.scoreboardTeam?.let(::Team) 23 | } 24 | 25 | /** 26 | * Gets the display name for this player, 27 | * i.e. the name shown in tab list and in the player's nametag. 28 | * @return the display name 29 | */ 30 | fun getDisplayName() = getPlayerName(getPlayerInfo()) 31 | 32 | fun setTabDisplayName(textComponent: TextComponent) { 33 | getPlayerInfo()?.displayName = textComponent 34 | } 35 | 36 | /** 37 | * Sets the name for this player shown above their head, 38 | * in their name tag 39 | * 40 | * @param textComponent the new name to display 41 | */ 42 | fun setNametagName(textComponent: TextComponent) { 43 | mcValue.asMixin().ctjs_setOverriddenNametagName(textComponent) 44 | } 45 | 46 | /** 47 | * Draws the player in the GUI. Takes the same parameters as [Renderer.drawPlayer] 48 | * minus `player`. 49 | * 50 | * @see Renderer.drawPlayer 51 | */ 52 | fun draw(obj: NativeObject) = apply { 53 | obj["player"] = this 54 | Renderer.drawPlayer(obj) 55 | } 56 | 57 | private fun getPlayerName(playerListEntry: PlayerListEntry?): TextComponent { 58 | return playerListEntry?.displayName?.let { TextComponent(it) } 59 | ?: TextComponent( 60 | MCTeam.decorateName( 61 | playerListEntry?.scoreboardTeam, 62 | Text.of(playerListEntry?.profile?.name) 63 | ) 64 | ) 65 | } 66 | 67 | private fun getPlayerInfo() = Client.getConnection()?.getPlayerListEntry(mcValue.uuid) 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/ctjs.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "com.chattriggers.ctjs.internal.mixins", 5 | "compatibilityLevel": "JAVA_21", 6 | "client": [ 7 | "AbstractSoundInstanceAccessor", 8 | "BlockEntityRenderDispatcherMixin", 9 | "BookScreenAccessor", 10 | "BossBarHudAccessor", 11 | "ChatHudAccessor", 12 | "ChatHudMixin", 13 | "ChatScreenAccessor", 14 | "ClickableWidgetAccessor", 15 | "ClientChunkManagerAccessor", 16 | "ClientChunkMapAccessor", 17 | "ClientPlayerEntityMixin", 18 | "ClientPlayerInteractionManagerMixin", 19 | "ClientPlayNetworkHandlerAccessor", 20 | "ClientWorldAccessor", 21 | "CreativeInventoryScreenMixin", 22 | "EntityRenderDispatcherAccessor", 23 | "EntityRenderDispatcherMixin", 24 | "GameOptionsAccessor", 25 | "GameOptionsMixin", 26 | "HandledScreenAccessor", 27 | "HandledScreenMixin", 28 | "InGameHudMixin", 29 | "KeyBindingAccessor", 30 | "MinecraftClientMixin", 31 | "MouseMixin", 32 | "ParticleAccessor", 33 | "ParticleManagerMixin", 34 | "PlayerListEntryAccessor", 35 | "PlayerListHudAccessor", 36 | "PlayerListHudMixin", 37 | "RenderTickCounterMixin", 38 | "commands.ClientCommandSourceMixin", 39 | "commands.ClientPlayNetworkHandlerMixin", 40 | "sound.SoundAccessor", 41 | "sound.SoundManagerAccessor", 42 | "sound.SoundSystemAccessor", 43 | "sound.SoundSystemMixin", 44 | "sound.SourceAccessor" 45 | ], 46 | "injectors": { 47 | "defaultRequire": 1 48 | }, 49 | "plugin": "com.chattriggers.ctjs.internal.launch.CTMixinPlugin", 50 | "mixins": [ 51 | "ChunkAccessor", 52 | "ClientConnectionMixin", 53 | "ItemStackMixin", 54 | "LivingEntityMixin", 55 | "MinecraftClientAccessor", 56 | "NbtCompoundAccessor", 57 | "PlayerEntityMixin", 58 | "PlayerScreenHandlerMixin", 59 | "Scoreboard$1Accessor", 60 | "ScoreboardObjectiveMixin", 61 | "ScreenHandlerMixin", 62 | "SystemDetailsMixin", 63 | "commands.CommandContextAccessor", 64 | "commands.CommandDispatcherMixin", 65 | "commands.CommandNodeAccessor", 66 | "commands.EntitySelectorAccessor", 67 | "stdio.BootstrapMixin", 68 | "stdio.LoggerPrintStreamMixin" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgsGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.MethodAssembly 4 | import codes.som.koffee.insns.jvm.aconst_null 5 | import com.chattriggers.ctjs.internal.launch.Descriptor 6 | import com.chattriggers.ctjs.internal.launch.ModifyArgs 7 | import com.chattriggers.ctjs.internal.utils.descriptor 8 | import com.chattriggers.ctjs.internal.utils.descriptorString 9 | import org.objectweb.asm.tree.MethodNode 10 | import org.spongepowered.asm.mixin.injection.invoke.arg.Args 11 | import org.spongepowered.asm.mixin.injection.ModifyArgs as SPModifyArgs 12 | 13 | internal class ModifyArgsGenerator( 14 | ctx: GenerationContext, 15 | id: Int, 16 | private val modifyArgs: ModifyArgs, 17 | ) : InjectorGenerator(ctx, id) { 18 | override val type = "modifyArgs" 19 | 20 | override fun getInjectionSignature(): InjectionSignature { 21 | val (mappedMethod, method) = ctx.findMethod(modifyArgs.method) 22 | 23 | val parameters = mutableListOf() 24 | parameters.add(Parameter(Args::class.descriptor())) 25 | modifyArgs.locals?.forEach { 26 | parameters.add(Utils.getParameterFromLocal(it)) 27 | } 28 | 29 | return InjectionSignature( 30 | mappedMethod, 31 | parameters, 32 | Descriptor.Primitive.VOID, 33 | method.isStatic, 34 | ) 35 | } 36 | 37 | override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { 38 | node.visitAnnotation(SPModifyArgs::class.descriptorString(), true).apply { 39 | visit("method", listOf(signature.targetMethod.toFullDescriptor())) 40 | if (modifyArgs.slice != null) 41 | visit("slice", modifyArgs.slice) 42 | visit("at", Utils.createAtAnnotation(modifyArgs.at)) 43 | if (modifyArgs.remap != null) 44 | visit("remap", modifyArgs.remap) 45 | if (modifyArgs.expect != null) 46 | visit("expect", modifyArgs.expect) 47 | if (modifyArgs.allow != null) 48 | visit("allow", modifyArgs.allow) 49 | if (modifyArgs.constraints != null) 50 | visit("constraints", modifyArgs.constraints) 51 | } 52 | } 53 | 54 | context(MethodAssembly) 55 | override fun generateNotAttachedBehavior() { 56 | // This method is expected to leave something on the stack 57 | aconst_null 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/triggers/Trigger.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.triggers 2 | 3 | import com.chattriggers.ctjs.CTJS 4 | import com.chattriggers.ctjs.internal.engine.JSLoader 5 | 6 | abstract class Trigger protected constructor( 7 | var method: Any, 8 | var type: ITriggerType, 9 | ) : Comparable { 10 | private var priority: Priority = Priority.NORMAL 11 | 12 | var isRegistered = false 13 | private set 14 | 15 | init { 16 | // See note for register method 17 | @Suppress("LeakingThis") 18 | register() 19 | } 20 | 21 | /** 22 | * Sets a trigger's priority using [Priority]. 23 | * Highest runs first. 24 | * @param priority the priority of the trigger 25 | * @return the trigger for method chaining 26 | */ 27 | fun setPriority(priority: Priority) = apply { 28 | this.priority = priority 29 | 30 | // Re-register so the position in the ConcurrentSkipListSet gets updated 31 | unregister() 32 | register() 33 | } 34 | 35 | /** 36 | * Registers a trigger based on its type. 37 | * This is done automatically with TriggerRegister. 38 | * @return the trigger for method chaining 39 | */ 40 | // NOTE: Class initialization cannot be done in this method. It is called in 41 | // the init block above, and thus the child class version will not be 42 | // run initially 43 | open fun register() = apply { 44 | if (!isRegistered) { 45 | isRegistered = true 46 | JSLoader.addTrigger(this) 47 | } 48 | } 49 | 50 | /** 51 | * Unregisters a trigger. 52 | * @return the trigger for method chaining 53 | */ 54 | open fun unregister() = apply { 55 | if (isRegistered) { 56 | isRegistered = false 57 | JSLoader.removeTrigger(this) 58 | } 59 | } 60 | 61 | protected fun callMethod(args: Array) { 62 | if (CTJS.isLoaded) 63 | JSLoader.trigger(this, method, args) 64 | } 65 | 66 | internal abstract fun trigger(args: Array) 67 | 68 | override fun compareTo(other: Trigger): Int { 69 | val ordCmp = priority.ordinal - other.priority.ordinal 70 | return if (ordCmp == 0) 71 | hashCode() - other.hashCode() 72 | else ordCmp 73 | } 74 | 75 | enum class Priority { 76 | //LOWEST IS RAN LAST 77 | HIGHEST, 78 | HIGH, 79 | NORMAL, 80 | LOW, 81 | LOWEST 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/console/data.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.console 2 | 3 | import com.chattriggers.ctjs.api.Config 4 | import com.chattriggers.ctjs.engine.LogType 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | sealed class H2CMessage 9 | 10 | @Serializable 11 | class ConfigUpdateMessage( 12 | val textColor: Int, 13 | val backgroundColor: Int, 14 | val warningColor: Int, 15 | val errorColor: Int, 16 | val openConsoleOnError: Boolean, 17 | val customTheme: Boolean, 18 | val theme: Int, 19 | val useFiraCode: Boolean, 20 | val fontSize: Int, 21 | ) : H2CMessage() { 22 | companion object { 23 | fun constructFromConfig(settings: Config.ConsoleSettings) = ConfigUpdateMessage( 24 | settings.consoleTextColor.rgb, 25 | settings.consoleBackgroundColor.rgb, 26 | settings.consoleWarningColor.rgb, 27 | settings.consoleErrorColor.rgb, 28 | settings.openConsoleOnError, 29 | settings.customTheme, 30 | settings.consoleTheme, 31 | settings.consoleFiraCodeFont, 32 | settings.consoleFontSize, 33 | ) 34 | } 35 | } 36 | 37 | @Serializable 38 | class InitMessage( 39 | val modVersion: String, 40 | val config: ConfigUpdateMessage, 41 | val firaFontBytes: ByteArray?, 42 | ) : H2CMessage() 43 | 44 | @Serializable 45 | data object OpenMessage : H2CMessage() 46 | 47 | @Serializable 48 | data object TerminateMessage : H2CMessage() 49 | 50 | @Serializable 51 | class EvalResultMessage(val id: Int, val result: String) : H2CMessage() 52 | 53 | @Serializable 54 | class PrintMessage( 55 | val text: String, 56 | val logType: LogType, 57 | val end: String, 58 | val color: Int?, 59 | ) : H2CMessage() 60 | 61 | @Serializable 62 | class PrintErrorMessage(val error: Error) : H2CMessage() { 63 | @Serializable 64 | data class Error(val message: String, val trace: List, val cause: Error?) 65 | } 66 | 67 | @Serializable 68 | data class StackTrace( 69 | val fileName: String?, 70 | val className: String, 71 | val methodName: String, 72 | val lineNumber: Int, 73 | ) 74 | 75 | @Serializable 76 | data object ClearConsoleMessage : H2CMessage() 77 | 78 | @Serializable 79 | sealed class C2HMessage 80 | 81 | @Serializable 82 | class EvalTextMessage(val id: Int, val string: String) : C2HMessage() 83 | 84 | @Serializable 85 | data object ReloadCTMessage : C2HMessage() 86 | 87 | @Serializable 88 | class FontSizeMessage(val delta: Int) : C2HMessage() 89 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/world/block/BlockType.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.world.block 2 | 3 | import com.chattriggers.ctjs.api.CTWrapper 4 | import com.chattriggers.ctjs.api.inventory.Item 5 | import com.chattriggers.ctjs.api.inventory.ItemType 6 | import com.chattriggers.ctjs.api.message.TextComponent 7 | import com.chattriggers.ctjs.MCBlock 8 | import com.chattriggers.ctjs.MCItem 9 | import com.chattriggers.ctjs.internal.utils.toIdentifier 10 | import net.minecraft.registry.Registries 11 | 12 | /** 13 | * An immutable wrapper around Minecraft's Block object. Note 14 | * that this references a block "type", and not an actual block 15 | * in the world. If a reference to a particular block is needed, 16 | * use [Block] 17 | */ 18 | class BlockType(override val mcValue: MCBlock) : CTWrapper { 19 | constructor(block: BlockType) : this(block.mcValue) 20 | 21 | constructor(blockName: String) : this(Registries.BLOCK[blockName.toIdentifier()]) 22 | 23 | constructor(blockID: Int) : this(ItemType(MCItem.byRawId(blockID)).getRegistryName()) 24 | 25 | constructor(item: Item) : this(MCBlock.getBlockFromItem(item.mcValue.item)) 26 | 27 | /** 28 | * Returns a [Block] based on this block and the 29 | * provided BlockPos 30 | * 31 | * @param blockPos the block position 32 | * @return a [Block] object 33 | */ 34 | fun withBlockPos(blockPos: BlockPos) = Block(this, blockPos) 35 | 36 | fun getID(): Int = Registries.BLOCK.indexOf(mcValue) 37 | 38 | /** 39 | * Gets the block's registry name. 40 | * Example: minecraft:oak_planks 41 | * 42 | * @return the block's registry name 43 | */ 44 | fun getRegistryName(): String = Registries.BLOCK.getId(mcValue).toString() 45 | 46 | /** 47 | * Gets the block's translation key. 48 | * Example: block.minecraft.oak_planks 49 | * 50 | * @return the block's translation key 51 | */ 52 | fun getTranslationKey(): String = mcValue.translationKey 53 | 54 | /** 55 | * Gets the block's localized name. 56 | * Example: Wooden Planks 57 | * 58 | * @return the block's localized name 59 | */ 60 | fun getName() = TextComponent(mcValue.name).formattedText 61 | 62 | fun getLightValue(): Int = getDefaultState().luminance 63 | 64 | fun getDefaultState() = mcValue.defaultState 65 | 66 | fun canProvidePower() = getDefaultState().emitsRedstonePower() 67 | 68 | fun isTranslucent() = getDefaultState().hasSidedTransparency() 69 | 70 | override fun toString(): String = "BlockType{${getRegistryName()}}" 71 | } 72 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/world/block/BlockPos.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.world.block 2 | 3 | import com.chattriggers.ctjs.api.CTWrapper 4 | import com.chattriggers.ctjs.api.entity.Entity 5 | import com.chattriggers.ctjs.api.vec.Vec3i 6 | import com.chattriggers.ctjs.MCBlockPos 7 | import net.minecraft.util.math.Vec3d 8 | import kotlin.math.floor 9 | import kotlin.math.sqrt 10 | 11 | class BlockPos(x: Int, y: Int, z: Int) : Vec3i(x, y, z), CTWrapper { 12 | override val mcValue = MCBlockPos(x, y, z) 13 | 14 | constructor(x: Number, y: Number, z: Number) : this( 15 | floor(x.toDouble()).toInt(), 16 | floor(y.toDouble()).toInt(), 17 | floor(z.toDouble()).toInt() 18 | ) 19 | 20 | constructor(pos: Vec3i) : this(pos.x, pos.y, pos.z) 21 | 22 | constructor(pos: MCBlockPos) : this(pos.x, pos.y, pos.z) 23 | 24 | constructor(source: Entity) : this(source.getPos()) 25 | 26 | override fun translated(dx: Int, dy: Int, dz: Int) = BlockPos(super.translated(dx, dy, dz)) 27 | 28 | override fun scaled(scale: Int) = BlockPos(super.scaled(scale)) 29 | 30 | override fun scaled(xScale: Int, yScale: Int, zScale: Int) = BlockPos(super.scaled(xScale, yScale, zScale)) 31 | 32 | override fun crossProduct(other: Vec3i) = BlockPos(super.crossProduct(other)) 33 | 34 | override operator fun unaryMinus() = BlockPos(super.unaryMinus()) 35 | 36 | override operator fun plus(other: Vec3i) = BlockPos(super.plus(other)) 37 | 38 | override operator fun minus(other: Vec3i) = BlockPos(super.minus(other)) 39 | 40 | @JvmOverloads 41 | fun up(n: Int = 1) = offset(BlockFace.UP, n) 42 | 43 | @JvmOverloads 44 | fun down(n: Int = 1) = offset(BlockFace.DOWN, n) 45 | 46 | @JvmOverloads 47 | fun north(n: Int = 1) = offset(BlockFace.NORTH, n) 48 | 49 | @JvmOverloads 50 | fun south(n: Int = 1) = offset(BlockFace.SOUTH, n) 51 | 52 | @JvmOverloads 53 | fun east(n: Int = 1) = offset(BlockFace.EAST, n) 54 | 55 | @JvmOverloads 56 | fun west(n: Int = 1) = offset(BlockFace.WEST, n) 57 | 58 | @JvmOverloads 59 | fun offset(facing: BlockFace, n: Int = 1): BlockPos { 60 | return BlockPos(x + facing.getOffsetX() * n, y + facing.getOffsetY() * n, z + facing.getOffsetZ() * n) 61 | } 62 | 63 | fun distanceTo(other: BlockPos): Double { 64 | val x = (mcValue.x - other.x).toDouble() 65 | val y = (mcValue.y - other.y).toDouble() 66 | val z = (mcValue.z - other.z).toDouble() 67 | return sqrt(x * x + y * y + z * z) 68 | } 69 | 70 | fun toVec3d() = Vec3d(x.toDouble(), y.toDouble(), z.toDouble()) 71 | } 72 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReturnValueInjector.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.MethodAssembly 4 | import com.chattriggers.ctjs.internal.launch.Descriptor 5 | import com.chattriggers.ctjs.internal.launch.ModifyReturnValue 6 | import com.chattriggers.ctjs.internal.utils.descriptorString 7 | import org.objectweb.asm.tree.MethodNode 8 | import org.spongepowered.asm.mixin.injection.Desc 9 | import com.llamalad7.mixinextras.injector.ModifyReturnValue as SPModifyReturnValue 10 | 11 | internal class ModifyReturnValueInjector( 12 | ctx: GenerationContext, 13 | id: Int, 14 | private val modifyReturnValue: ModifyReturnValue 15 | ) : InjectorGenerator(ctx, id) { 16 | override val type = "modifyReturnValue" 17 | 18 | override fun getInjectionSignature(): InjectionSignature { 19 | val (mappedMethod, method) = ctx.findMethod(modifyReturnValue.method) 20 | val returnType = Descriptor.Parser(mappedMethod.returnType.value).parseType(full = true) 21 | check(returnType != Descriptor.Primitive.VOID) { 22 | "ModifyReturnValue mixin cannot target a void method" 23 | } 24 | 25 | val parameters = listOf(Parameter(returnType)) + modifyReturnValue.locals 26 | ?.map(Utils::getParameterFromLocal) 27 | .orEmpty() 28 | 29 | return InjectionSignature( 30 | mappedMethod, 31 | parameters, 32 | returnType, 33 | method.isStatic, 34 | ) 35 | } 36 | 37 | override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { 38 | node.visitAnnotation(SPModifyReturnValue::class.descriptorString(), true).apply { 39 | visit("method", listOf(signature.targetMethod.toFullDescriptor())) 40 | visit("at", Utils.createAtAnnotation(modifyReturnValue.at)) 41 | if (modifyReturnValue.slice != null) 42 | visit("slice", listOf(modifyReturnValue.slice.map(Utils::createSliceAnnotation))) 43 | if (modifyReturnValue.remap != null) 44 | visit("remap", modifyReturnValue.remap) 45 | if (modifyReturnValue.require != null) 46 | visit("require", modifyReturnValue.require) 47 | if (modifyReturnValue.expect != null) 48 | visit("expect", modifyReturnValue.expect) 49 | if (modifyReturnValue.allow != null) 50 | visit("allow", modifyReturnValue.allow) 51 | visitEnd() 52 | } 53 | } 54 | 55 | context(MethodAssembly) 56 | override fun generateNotAttachedBehavior() { 57 | generateParameterLoad(0) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/world/Chunk.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.world 2 | 3 | import com.chattriggers.ctjs.api.CTWrapper 4 | import com.chattriggers.ctjs.api.entity.BlockEntity 5 | import com.chattriggers.ctjs.api.entity.Entity 6 | import com.chattriggers.ctjs.internal.mixins.ChunkAccessor 7 | import com.chattriggers.ctjs.MCBlockPos 8 | import com.chattriggers.ctjs.MCChunk 9 | import com.chattriggers.ctjs.MCEntity 10 | import com.chattriggers.ctjs.internal.utils.asMixin 11 | import net.minecraft.util.math.Box 12 | 13 | // TODO: Add more methods here? 14 | class Chunk(override val mcValue: MCChunk) : CTWrapper { 15 | /** 16 | * Gets the x position of the chunk 17 | */ 18 | fun getX() = mcValue.pos.x 19 | 20 | /** 21 | * Gets the z position of the chunk 22 | */ 23 | fun getZ() = mcValue.pos.z 24 | 25 | /** 26 | * Gets the minimum x coordinate of a block in the chunk 27 | * 28 | * @return the minimum x coordinate 29 | */ 30 | fun getMinBlockX() = getX() * 16 31 | 32 | /** 33 | * Gets the minimum z coordinate of a block in the chunk 34 | * 35 | * @return the minimum z coordinate 36 | */ 37 | fun getMinBlockZ() = getZ() * 16 38 | 39 | /** 40 | * Gets every entity in this chunk 41 | * 42 | * @return the entity list 43 | */ 44 | fun getAllEntities(): List = getAllEntitiesOfType(MCEntity::class.java) 45 | 46 | /** 47 | * Gets every entity in this chunk of a certain class 48 | * 49 | * @param clazz the class to filter for (Use `Java.type().class` to get this) 50 | * @return the entity list 51 | */ 52 | fun getAllEntitiesOfType(clazz: Class): List { 53 | val box = Box( 54 | MCBlockPos(getMinBlockX(), mcValue.bottomY, getMinBlockZ()) 55 | ).stretch(16.0, mcValue.topY.toDouble(), 16.0) 56 | 57 | return World.toMC()?.getEntitiesByClass(clazz, box) { true }?.map(Entity::fromMC) ?: listOf() 58 | } 59 | 60 | /** 61 | * Gets every block entity in this chunk 62 | * 63 | * @return the block entity list 64 | */ 65 | fun getAllBlockEntities(): List { 66 | return mcValue.asMixin().blockEntities.values.map(::BlockEntity) 67 | } 68 | 69 | /** 70 | * Gets every block entity in this chunk of a certain class 71 | * 72 | * @param clazz the class to filter for (Use `Java.type().class` to get this) 73 | * @return the block entity list 74 | */ 75 | fun getAllBlockEntitiesOfType(clazz: Class<*>): List { 76 | return getAllBlockEntities().filter { 77 | clazz.isInstance(it.toMC()) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerEntityMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.triggers.TriggerType; 4 | import com.chattriggers.ctjs.internal.NameTagOverridable; 5 | import com.chattriggers.ctjs.api.message.TextComponent; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.entity.EntityType; 8 | import net.minecraft.entity.LivingEntity; 9 | import net.minecraft.entity.player.PlayerEntity; 10 | import net.minecraft.text.MutableText; 11 | import net.minecraft.world.World; 12 | import org.jetbrains.annotations.Nullable; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Unique; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | 20 | @Mixin(PlayerEntity.class) 21 | public abstract class PlayerEntityMixin extends LivingEntity implements NameTagOverridable { 22 | @Unique 23 | private TextComponent overriddenNametagName; 24 | 25 | protected PlayerEntityMixin(EntityType entityType, World world) { 26 | super(entityType, world); 27 | } 28 | 29 | @ModifyVariable(method = "getDisplayName", at = @At(value = "STORE", ordinal = 0)) 30 | private MutableText injectGetName(MutableText original) { 31 | if (overriddenNametagName != null) 32 | return overriddenNametagName.toMutableText$ctjs(); 33 | return original; 34 | } 35 | 36 | @Inject( 37 | method = "attack", 38 | at = @At( 39 | value = "INVOKE", 40 | target = "Lnet/minecraft/entity/Entity;damage(Lnet/minecraft/entity/damage/DamageSource;F)Z" 41 | ) 42 | ) 43 | private void chattriggers$entityDamage(Entity target, CallbackInfo ci) { 44 | if (getWorld().isClient) { 45 | TriggerType.ENTITY_DAMAGE.triggerAll(com.chattriggers.ctjs.api.entity.Entity.fromMC(target)); 46 | } 47 | } 48 | 49 | @Inject( 50 | method = "attack", 51 | at = @At( 52 | value = "INVOKE", 53 | target = "Lnet/minecraft/entity/LivingEntity;damage(Lnet/minecraft/entity/damage/DamageSource;F)Z" 54 | ) 55 | ) 56 | private void chattriggers$entityDamageSweeping(Entity target, CallbackInfo ci) { 57 | if (getWorld().isClient) { 58 | TriggerType.ENTITY_DAMAGE.triggerAll(com.chattriggers.ctjs.api.entity.Entity.fromMC(target)); 59 | } 60 | } 61 | 62 | @Override 63 | public void ctjs_setOverriddenNametagName(@Nullable TextComponent component) { 64 | overriddenNametagName = component; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/HandledScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.inventory.Item; 4 | import com.chattriggers.ctjs.api.message.TextComponent; 5 | import com.chattriggers.ctjs.api.triggers.TriggerType; 6 | import net.minecraft.client.gui.DrawContext; 7 | import net.minecraft.client.gui.screen.Screen; 8 | import net.minecraft.client.gui.screen.ingame.HandledScreen; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.screen.ScreenHandler; 11 | import net.minecraft.screen.slot.Slot; 12 | import net.minecraft.screen.slot.SlotActionType; 13 | import net.minecraft.text.Text; 14 | import org.spongepowered.asm.mixin.Final; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.Shadow; 17 | import org.spongepowered.asm.mixin.injection.At; 18 | import org.spongepowered.asm.mixin.injection.Inject; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | 21 | import java.util.Objects; 22 | 23 | @Mixin(HandledScreen.class) 24 | public class HandledScreenMixin extends Screen { 25 | @Shadow 26 | protected Slot focusedSlot; 27 | 28 | @Shadow 29 | @Final 30 | protected ScreenHandler handler; 31 | 32 | private HandledScreenMixin(Text title) { 33 | super(title); 34 | } 35 | 36 | @Inject( 37 | method = "drawMouseoverTooltip", 38 | at = @At( 39 | value = "INVOKE", 40 | target = "Lnet/minecraft/client/gui/DrawContext;drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;II)V" 41 | ), 42 | cancellable = true 43 | ) 44 | private void injectDrawMouseoverTooltip(DrawContext context, int x, int y, CallbackInfo ci) { 45 | ItemStack stack = focusedSlot.getStack(); 46 | TriggerType.ITEM_TOOLTIP.triggerAll( 47 | getTooltipFromItem(Objects.requireNonNull(client), stack) 48 | .stream() 49 | .map(TextComponent::new) 50 | .toList(), 51 | Item.fromMC(stack), 52 | ci 53 | ); 54 | } 55 | 56 | @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At("HEAD"), cancellable = true) 57 | private void injectOnMouseClick(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { 58 | if ( 59 | (slotId != -999 && actionType == SlotActionType.THROW) || // dropping item from slot 60 | (slotId == -999 && actionType == SlotActionType.PICKUP) // dropping by clicking outside inventory 61 | ) { 62 | TriggerType.DROP_ITEM.triggerAll(Item.fromMC(handler.getCursorStack()), button == 0, ci); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/action/ClickAction.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory.action 2 | 3 | import com.chattriggers.ctjs.api.client.Player 4 | import net.minecraft.screen.slot.SlotActionType 5 | 6 | class ClickAction(slot: Int, windowId: Int) : Action(slot, windowId) { 7 | private lateinit var clickType: ClickType 8 | private var holdingShift = false 9 | private var itemInHand = Player.getHeldItem() != null 10 | private var pickupAll = false 11 | 12 | fun getClickType(): ClickType = clickType 13 | 14 | /** 15 | * The type of click (REQUIRED) 16 | * 17 | * @param clickType the new click type 18 | */ 19 | fun setClickType(clickType: ClickType) = apply { 20 | this.clickType = clickType 21 | } 22 | 23 | fun getHoldingShift(): Boolean = holdingShift 24 | 25 | /** 26 | * Whether the click should act as if shift is being held (defaults to false) 27 | * 28 | * @param holdingShift to hold shift or not 29 | */ 30 | fun setHoldingShift(holdingShift: Boolean) = apply { 31 | this.holdingShift = holdingShift 32 | } 33 | 34 | fun getItemInHand(): Boolean = itemInHand 35 | 36 | /** 37 | * Whether the click should act as if an item is being held 38 | * (defaults to whether there actually is an item in the hand) 39 | * 40 | * @param itemInHand to be holding an item or not 41 | */ 42 | fun setItemInHand(itemInHand: Boolean) = apply { 43 | this.itemInHand = itemInHand 44 | } 45 | 46 | fun getPickupAll() = pickupAll 47 | 48 | /** 49 | * Whether the click should try to pick up all items of said type in the inventory (essentially double clicking) 50 | * (defaults to whether there actually is an item in the hand) 51 | * 52 | * @param pickupAll to pick up all items of the same type 53 | */ 54 | fun setPickupAll(pickupAll: Boolean) = apply { 55 | this.pickupAll = pickupAll 56 | } 57 | 58 | /** 59 | * Sets the type of click. 60 | * Possible values are: LEFT, RIGHT, MIDDLE 61 | * 62 | * @param clickType the click type 63 | * @return the current Action for method chaining 64 | */ 65 | fun setClickString(clickType: String) = apply { 66 | this.clickType = ClickType.valueOf(clickType.uppercase()) 67 | } 68 | 69 | override fun complete() { 70 | val mode = when { 71 | clickType == ClickType.MIDDLE -> SlotActionType.CLONE 72 | holdingShift -> SlotActionType.QUICK_MOVE 73 | pickupAll -> SlotActionType.PICKUP_ALL 74 | else -> SlotActionType.PICKUP 75 | } 76 | 77 | doClick(clickType.button, mode) 78 | } 79 | 80 | enum class ClickType(val button: Int) { 81 | LEFT(0), RIGHT(1), MIDDLE(2) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/DynamicMixinGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.assembleClass 4 | import codes.som.koffee.modifiers.public 5 | import com.chattriggers.ctjs.CTJS 6 | import com.chattriggers.ctjs.internal.launch.* 7 | import org.objectweb.asm.ClassWriter 8 | import org.objectweb.asm.Opcodes 9 | import java.io.File 10 | import org.spongepowered.asm.mixin.Mixin as SPMixin 11 | 12 | internal class DynamicMixinGenerator(private val ctx: GenerationContext, private val details: MixinDetails) { 13 | fun generate(): ByteArray { 14 | val mixinClassNode = assembleClass(public, ctx.generatedClassFullPath, version = Opcodes.V17) { 15 | for ((id, injector) in details.injectors) { 16 | when (injector) { 17 | is Inject -> InjectGenerator(ctx, id, injector).generate() 18 | is Redirect -> RedirectGenerator(ctx, id, injector).generate() 19 | is ModifyArg -> ModifyArgGenerator(ctx, id, injector).generate() 20 | is ModifyArgs -> ModifyArgsGenerator(ctx, id, injector).generate() 21 | is ModifyConstant -> ModifyConstantGenerator(ctx, id, injector).generate() 22 | is ModifyExpressionValue -> ModifyExpressionValueGenerator(ctx, id, injector).generate() 23 | is ModifyReceiver -> ModifyReceiverGenerator(ctx, id, injector).generate() 24 | is ModifyReturnValue -> ModifyReturnValueInjector(ctx, id, injector).generate() 25 | is ModifyVariable -> ModifyVariableGenerator(ctx, id, injector).generate() 26 | is WrapOperation -> WrapOperationGenerator(ctx, id, injector).generate() 27 | is WrapWithCondition -> WrapWithConditionGenerator(ctx, id, injector).generate() 28 | } 29 | } 30 | } 31 | 32 | val mixinAnnotation = mixinClassNode.visitAnnotation(SPMixin::class.java.descriptorString(), false) 33 | val mixin = ctx.mixin 34 | mixinAnnotation.visit("targets", listOf(ctx.mappedClass.name.value)) 35 | if (mixin.priority != null) 36 | mixinAnnotation.visit("priority", mixin.priority) 37 | if (mixin.remap != null) 38 | mixinAnnotation.visit("remap", mixin.remap) 39 | mixinAnnotation.visitEnd() 40 | 41 | val writer = ClassWriter(ClassWriter.COMPUTE_FRAMES) 42 | mixinClassNode.accept(writer) 43 | val bytes = writer.toByteArray() 44 | 45 | if (CTJS.isDevelopment) { 46 | val dir = File(CTJS.configLocation, "ChatTriggers/mixin-classes") 47 | dir.mkdirs() 48 | File(dir, "${ctx.generatedClassName}.class").writeBytes(bytes) 49 | } 50 | 51 | return bytes 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/triggers/ClassFilterTrigger.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.triggers 2 | 3 | import com.chattriggers.ctjs.api.entity.BlockEntity 4 | import com.chattriggers.ctjs.api.entity.Entity 5 | import com.chattriggers.ctjs.MCBlockEntity 6 | import com.chattriggers.ctjs.MCEntity 7 | import net.minecraft.network.packet.Packet 8 | 9 | sealed class ClassFilterTrigger( 10 | method: Any, 11 | private val triggerType: ITriggerType, 12 | private val wrappedClass: Class, 13 | ) : Trigger(method, triggerType) { 14 | private var triggerClasses: List> = emptyList() 15 | 16 | /** 17 | * Alias for `setFilteredClasses([A.class])` 18 | * 19 | * @param clazz The class for which this trigger should run for 20 | */ 21 | fun setFilteredClass(clazz: Class) = setFilteredClasses(listOf(clazz)) 22 | 23 | /** 24 | * Sets which classes this trigger should run for. If the list is empty, it runs 25 | * for every class. 26 | * 27 | * @param classes The classes for which this trigger should run for 28 | * @return This trigger object for chaining 29 | */ 30 | fun setFilteredClasses(classes: List>) = apply { triggerClasses = classes } 31 | 32 | override fun trigger(args: Array) { 33 | val placeholder = evalTriggerType(args) 34 | if (triggerClasses.isEmpty() || triggerClasses.any { it.isInstance(placeholder) }) 35 | callMethod(args) 36 | } 37 | 38 | private fun evalTriggerType(args: Array): Unwrapped { 39 | val arg = args.getOrNull(0) ?: error("First argument of $triggerType trigger can not be null") 40 | 41 | check(wrappedClass.isInstance(arg)) { 42 | "Expected first argument of $triggerType trigger to be instance of $wrappedClass" 43 | } 44 | 45 | @Suppress("UNCHECKED_CAST") 46 | return unwrap(arg as Wrapped) 47 | } 48 | 49 | protected abstract fun unwrap(wrapped: Wrapped): Unwrapped 50 | } 51 | 52 | class RenderEntityTrigger(method: Any) : ClassFilterTrigger( 53 | method, 54 | TriggerType.RENDER_ENTITY, 55 | Entity::class.java, 56 | ) { 57 | override fun unwrap(wrapped: Entity): MCEntity = wrapped.toMC() 58 | } 59 | 60 | class RenderBlockEntityTrigger(method: Any) : ClassFilterTrigger( 61 | method, 62 | TriggerType.RENDER_BLOCK_ENTITY, 63 | BlockEntity::class.java 64 | ) { 65 | override fun unwrap(wrapped: BlockEntity): MCBlockEntity = wrapped.toMC() 66 | } 67 | 68 | class PacketTrigger(method: Any, triggerType: ITriggerType) : ClassFilterTrigger, Packet<*>>( 69 | method, 70 | triggerType, 71 | Packet::class.java, 72 | ) { 73 | override fun unwrap(wrapped: Packet<*>): Packet<*> = wrapped 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/javadocs.yml: -------------------------------------------------------------------------------- 1 | name: javadocs 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | name: Deploy Javadocs 12 | runs-on: "ubuntu-latest" 13 | if: github.repository == 'ChatTriggers/ctjs' 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: actions/setup-java@v4 19 | with: 20 | distribution: temurin 21 | java-version: 21 22 | 23 | - uses: actions/cache@v4 24 | with: 25 | path: | 26 | ~/.gradle/caches 27 | ~/.gradle/wrapper 28 | key: "${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/*.versions.toml') }}" 29 | restore-keys: | 30 | ${{ runner.os }}-gradle- 31 | 32 | - name: Build Javadocs 33 | run: ./gradlew --no-daemon generateDokkaDocs 34 | 35 | - name: Publish Javadocs 36 | uses: appleboy/scp-action@master 37 | with: 38 | host: ${{ secrets.REMOTE_HOST }} 39 | username: ${{ secrets.ACTIONS_DEPLOYER_USERNAME }} 40 | key: ${{ secrets.ACTIONS_DEPLOYER_SSH_KEY }} 41 | passphrase: ${{ secrets.ACTIONS_DEPLOYER_PASSPHRASE }} 42 | source: ${{ github.workspace }}/build/javadocs/ 43 | # TODO: Remove "new-" when we transition to 3.0.0 44 | target: /srv/www/static/home/new-javadocs 45 | strip_components: 4 46 | rm: true 47 | 48 | - name: Set File Permissions 49 | uses: appleboy/ssh-action@master 50 | with: 51 | host: ${{ secrets.REMOTE_HOST }} 52 | username: ${{ secrets.ACTIONS_DEPLOYER_USERNAME }} 53 | key: ${{ secrets.ACTIONS_DEPLOYER_SSH_KEY }} 54 | passphrase: ${{ secrets.ACTIONS_DEPLOYER_PASSPHRASE }} 55 | script: | 56 | chmod -R g+w /srv/www/static/home/new-javadocs 57 | chmod -R g+w /srv/www/static/home/javadocs-archive 58 | 59 | - name: Create archives zip file 60 | if: startsWith(github.ref, 'refs/tags/') 61 | uses: appleboy/ssh-action@master 62 | with: 63 | host: ${{ secrets.REMOTE_HOST }} 64 | username: ${{ secrets.ACTIONS_DEPLOYER_USERNAME }} 65 | key: ${{ secrets.ACTIONS_DEPLOYER_SSH_KEY }} 66 | passphrase: ${{ secrets.ACTIONS_DEPLOYER_PASSPHRASE }} 67 | script: | 68 | tag=${{ github.ref_name }} 69 | echo "Creating archive, tag=${tag}..." 70 | 71 | # Copy the current javadocs to a new dir, remove the "older" directory, and 72 | # save it as a zip file in the archives folder 73 | cd /srv/www/static/home 74 | cp -r new-javadocs tmp 75 | rm -rf tmp/older 76 | zip -r "${tag}.zip" tmp 77 | mv "${tag}.zip" javadocs-archive 78 | rm -rf tmp 79 | 80 | echo "${tag}" >> javadocs-archive/versions 81 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/client/CPS.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.client 2 | 3 | import com.chattriggers.ctjs.api.world.World 4 | import com.chattriggers.ctjs.internal.engine.CTEvents 5 | import com.chattriggers.ctjs.internal.utils.Initializer 6 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents 7 | import java.util.* 8 | import kotlin.math.max 9 | import kotlin.math.roundToInt 10 | 11 | object CPS : Initializer { 12 | private val leftClicks = ClicksTracker() 13 | private val rightClicks = ClicksTracker() 14 | private val middleClicks = ClicksTracker() 15 | 16 | override fun init() { 17 | ClientTickEvents.START_CLIENT_TICK.register { 18 | leftClicks.tick() 19 | rightClicks.tick() 20 | middleClicks.tick() 21 | } 22 | 23 | CTEvents.MOUSE_CLICKED.register { _, _, button, pressed -> 24 | if (pressed) { 25 | when (button) { 26 | 0 -> leftClicks.click() 27 | 1 -> rightClicks.click() 28 | 2 -> middleClicks.click() 29 | } 30 | } 31 | } 32 | } 33 | 34 | @JvmStatic 35 | fun getLeftClicksMax(): Int = leftClicks.maxClicks 36 | 37 | @JvmStatic 38 | fun getRightClicksMax(): Int = rightClicks.maxClicks 39 | 40 | @JvmStatic 41 | fun getMiddleClicksMax(): Int = middleClicks.maxClicks 42 | 43 | @JvmStatic 44 | fun getLeftClicks(): Int = leftClicks.clicks.size 45 | 46 | @JvmStatic 47 | fun getRightClicks(): Int = rightClicks.clicks.size 48 | 49 | @JvmStatic 50 | fun getMiddleClicks(): Int = middleClicks.clicks.size 51 | 52 | @JvmStatic 53 | fun getLeftClicksAverage(): Int = leftClicks.average() 54 | 55 | @JvmStatic 56 | fun getRightClicksAverage(): Int = rightClicks.average() 57 | 58 | @JvmStatic 59 | fun getMiddleClicksAverage(): Int = middleClicks.average() 60 | 61 | private class ClicksTracker { 62 | val clicks = mutableListOf() 63 | var maxClicks = 0 64 | private val runningAverages = LinkedList() 65 | 66 | fun click() { 67 | clicks.add(World.getTicksPerSecond()) 68 | } 69 | 70 | fun tick() { 71 | // Decrease all existing click values 72 | for (i in clicks.indices) 73 | clicks[i]-- 74 | clicks.removeIf { it <= 0 } 75 | 76 | // Save the current CPS 77 | runningAverages.add(clicks.size) 78 | if (runningAverages.size > 100) 79 | runningAverages.removeAt(0) 80 | 81 | maxClicks = if (runningAverages.lastOrNull() == 0) { 82 | runningAverages.clear() 83 | 0 84 | } else { 85 | max(maxClicks, clicks.size) 86 | } 87 | } 88 | 89 | fun average() = if (runningAverages.isNotEmpty()) { 90 | runningAverages.average().roundToInt() 91 | } else 0 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/commands/StaticCommand.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.commands 2 | 3 | import com.chattriggers.ctjs.api.triggers.CommandTrigger 4 | import com.chattriggers.ctjs.internal.mixins.commands.CommandNodeAccessor 5 | import com.chattriggers.ctjs.internal.utils.asMixin 6 | import com.mojang.brigadier.CommandDispatcher 7 | import com.mojang.brigadier.arguments.StringArgumentType 8 | import net.minecraft.command.CommandSource 9 | 10 | internal class StaticCommand( 11 | val trigger: CommandTrigger, 12 | override val name: String, 13 | private val aliases: Set, 14 | override val overrideExisting: Boolean, 15 | private val staticSuggestions: List, 16 | private val dynamicSuggestions: ((List) -> List)?, 17 | ) : Command { 18 | override fun registerImpl(dispatcher: CommandDispatcher) { 19 | val builder = literal(name) 20 | .then(argument("args", StringArgumentType.greedyString()) 21 | .suggests { ctx, builder -> 22 | val suggestions = if (dynamicSuggestions != null) { 23 | val args = try { 24 | StringArgumentType.getString(ctx, "args").split(" ") 25 | } catch (e: IllegalArgumentException) { 26 | emptyList() 27 | } 28 | 29 | // Kotlin compiler bug: Without this null assert, it complains that the receiver is 30 | // nullable, but with it, it says it's unnecessary. 31 | @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") 32 | dynamicSuggestions!!(args) 33 | } else staticSuggestions 34 | 35 | for (suggestion in suggestions) 36 | builder.suggest(suggestion) 37 | 38 | builder.buildFuture() 39 | } 40 | .onExecute { 41 | trigger.trigger(StringArgumentType.getString(it, "args").split(" ").toTypedArray()) 42 | }) 43 | .onExecute { trigger.trigger(emptyArray()) } 44 | 45 | val node = dispatcher.register(builder) 46 | 47 | // Can't use .redirect() since it doesn't work without arguments 48 | for (alias in aliases) { 49 | val aliasNode = literal(alias) 50 | node.children.forEach { 51 | aliasNode.then(it) 52 | } 53 | aliasNode.executes(node.command) 54 | 55 | dispatcher.register(aliasNode) 56 | } 57 | } 58 | 59 | override fun unregisterImpl(dispatcher: CommandDispatcher) { 60 | super.unregisterImpl(dispatcher) 61 | dispatcher.root.asMixin().apply { 62 | for (alias in aliases) { 63 | childNodes.remove(alias) 64 | literals.remove(alias) 65 | } 66 | } 67 | } 68 | 69 | companion object : CommandCollection() 70 | } 71 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.MethodAssembly 4 | import com.chattriggers.ctjs.internal.launch.At 5 | import com.chattriggers.ctjs.internal.launch.ModifyArg 6 | import com.chattriggers.ctjs.internal.utils.descriptorString 7 | import org.objectweb.asm.tree.MethodNode 8 | import kotlin.math.sign 9 | import org.spongepowered.asm.mixin.injection.ModifyArg as SPModifyArg 10 | 11 | internal class ModifyArgGenerator( 12 | ctx: GenerationContext, 13 | id: Int, 14 | private val modifyArg: ModifyArg, 15 | ) : InjectorGenerator(ctx, id) { 16 | override val type = "modifyArg" 17 | 18 | override fun getInjectionSignature(): InjectionSignature { 19 | val (mappedMethod, method) = ctx.findMethod(modifyArg.method) 20 | 21 | // Resolve the target method 22 | val atTarget = modifyArg.at.atTarget 23 | check(atTarget is At.InvokeTarget) { "ModifyArg expects At.target to be INVOKE" } 24 | val targetDescriptor = atTarget.descriptor 25 | requireNotNull(targetDescriptor.parameters) 26 | 27 | if (modifyArg.index !in targetDescriptor.parameters.indices) 28 | error("ModifyArg received an out-of-bounds index ${modifyArg.index}") 29 | 30 | val parameters = if (modifyArg.captureAllParams == true) { 31 | targetDescriptor.parameters.mapTo(mutableListOf(), ::Parameter) 32 | } else mutableListOf(Parameter(targetDescriptor.parameters[modifyArg.index])) 33 | 34 | val returnType = targetDescriptor.parameters[modifyArg.index] 35 | 36 | modifyArg.locals?.forEach { 37 | parameters.add(Utils.getParameterFromLocal(it)) 38 | } 39 | 40 | return InjectionSignature( 41 | mappedMethod, 42 | parameters, 43 | returnType, 44 | method.isStatic, 45 | ) 46 | } 47 | 48 | override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { 49 | node.visitAnnotation(SPModifyArg::class.descriptorString(), true).apply { 50 | visit("method", listOf(signature.targetMethod.toFullDescriptor())) 51 | if (modifyArg.slice != null) 52 | visit("slice", modifyArg.slice) 53 | visit("at", Utils.createAtAnnotation(modifyArg.at)) 54 | visit("index", modifyArg.index) 55 | if (modifyArg.remap != null) 56 | visit("remap", modifyArg.remap) 57 | if (modifyArg.require != null) 58 | visit("require", modifyArg.require) 59 | if (modifyArg.expect != null) 60 | visit("expect", modifyArg.expect) 61 | if (modifyArg.allow != null) 62 | visit("allow", modifyArg.allow) 63 | if (modifyArg.constraints != null) 64 | visit("constraints", modifyArg.constraints) 65 | } 66 | } 67 | 68 | context(MethodAssembly) 69 | override fun generateNotAttachedBehavior() { 70 | generateParameterLoad(0) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # Library versions 3 | minecraft = "1.21" 4 | yarn = "1.21+build.2" 5 | 6 | loader = "0.15.11" 7 | fabric-api = "0.100.3+1.21" 8 | fabric-kotlin = "1.11.0+kotlin.2.0.0" 9 | 10 | mapping-io = "0.6.1" 11 | rhino = "7c7c509668" 12 | jackson-core = "2.13.2" 13 | textarea = "3.2.0" 14 | serialization = "1.5.1" 15 | koffee = "315bc11234" 16 | 17 | universalcraft = "342" 18 | elementa = "649" 19 | vigilance = "297" 20 | 21 | modmenu = "11.0.1" 22 | devauth = "1.2.1" 23 | dokka = "1.9.20" 24 | 25 | # Plugin Versions 26 | kotlin = "2.0.0" 27 | loom = "1.7-SNAPSHOT" 28 | validator = "0.14.0" 29 | ksp = "2.0.0-1.0.22" 30 | 31 | [libraries] 32 | minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } 33 | yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" } 34 | 35 | fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "loader" } 36 | fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" } 37 | fabric-kotlin = { module = "net.fabricmc:fabric-language-kotlin", version.ref = "fabric-kotlin" } 38 | 39 | mapping-io = { module = "net.fabricmc:mapping-io", version.ref = "mapping-io" } 40 | rhino = { module = "com.github.ChatTriggers:rhino", version.ref = "rhino" } 41 | jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jackson-core" } 42 | textarea = { module = "com.fifesoft:rsyntaxtextarea", version.ref = "textarea" } 43 | serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } 44 | koffee = { module = "com.github.ChatTriggers:koffee", version.ref = "koffee" } 45 | 46 | universalcraft = { module = "gg.essential:universalcraft-1.21-fabric", version.ref = "universalcraft" } 47 | elementa = { module = "gg.essential:elementa-1.18.1-fabric", version.ref = "elementa" } 48 | vigilance = { module = "gg.essential:vigilance-1.18.1-fabric", version.ref = "vigilance" } 49 | 50 | modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" } 51 | devauth = { module = "me.djtheredstoner:DevAuth-fabric", version.ref = "devauth" } 52 | versioning = { module = "org.jetbrains.dokka:versioning-plugin", version.ref = "dokka" } 53 | 54 | ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } 55 | gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 56 | 57 | [bundles] 58 | fabric = ["fabric-loader", "fabric-api", "fabric-kotlin"] 59 | included = ["mapping-io", "rhino", "jackson-core", "textarea", "serialization", "koffee"] 60 | essential = ["universalcraft", "elementa", "vigilance"] 61 | 62 | [plugins] 63 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 64 | serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 65 | loom = { id = "fabric-loom", version.ref = "loom" } 66 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } 67 | validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "validator" } 68 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 69 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyExpressionValueGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.MethodAssembly 4 | import com.chattriggers.ctjs.internal.launch.At 5 | import com.chattriggers.ctjs.internal.launch.Descriptor 6 | import com.chattriggers.ctjs.internal.launch.ModifyExpressionValue 7 | import com.chattriggers.ctjs.internal.utils.descriptorString 8 | import org.objectweb.asm.tree.MethodNode 9 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue as SPModifyExpressionValue 10 | 11 | internal class ModifyExpressionValueGenerator( 12 | ctx: GenerationContext, 13 | id: Int, 14 | private val modifyExpressionValue: ModifyExpressionValue, 15 | ) : InjectorGenerator(ctx, id) { 16 | override val type = "modifyExpressionValue" 17 | 18 | override fun getInjectionSignature(): InjectionSignature { 19 | val (mappedMethod, method) = ctx.findMethod(modifyExpressionValue.method) 20 | 21 | val exprDescriptor = when (val atTarget = modifyExpressionValue.at.atTarget) { 22 | is At.InvokeTarget -> atTarget.descriptor.returnType 23 | is At.FieldTarget -> atTarget.descriptor.type 24 | is At.NewTarget -> atTarget.descriptor.type 25 | is At.ConstantTarget -> atTarget.descriptor 26 | } 27 | 28 | check(exprDescriptor != null && exprDescriptor.isType) 29 | check(exprDescriptor != Descriptor.Primitive.VOID) { 30 | "ModifyExpressionValue mixin cannot target a void method" 31 | } 32 | 33 | val parameters = listOf(Parameter(exprDescriptor)) + modifyExpressionValue.locals 34 | ?.map(Utils::getParameterFromLocal) 35 | .orEmpty() 36 | 37 | return InjectionSignature( 38 | mappedMethod, 39 | parameters, 40 | exprDescriptor, 41 | method.isStatic, 42 | ) 43 | } 44 | 45 | override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { 46 | node.visitAnnotation(SPModifyExpressionValue::class.descriptorString(), true).apply { 47 | visit("method", listOf(signature.targetMethod.toFullDescriptor())) 48 | visit("at", Utils.createAtAnnotation(modifyExpressionValue.at)) 49 | if (modifyExpressionValue.slice != null) 50 | visit("slice", modifyExpressionValue.slice.map(Utils::createSliceAnnotation)) 51 | if (modifyExpressionValue.remap != null) 52 | visit("remap", modifyExpressionValue.remap) 53 | if (modifyExpressionValue.require != null) 54 | visit("require", modifyExpressionValue.require) 55 | if (modifyExpressionValue.expect != null) 56 | visit("expect", modifyExpressionValue.expect) 57 | if (modifyExpressionValue.allow != null) 58 | visit("allow", modifyExpressionValue.allow) 59 | visitEnd() 60 | } 61 | } 62 | 63 | context(MethodAssembly) 64 | override fun generateNotAttachedBehavior() { 65 | generateParameterLoad(0) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.MethodAssembly 4 | import codes.som.koffee.insns.jvm.aconst_null 5 | import codes.som.koffee.insns.jvm.areturn 6 | import codes.som.koffee.insns.jvm.ldc 7 | import com.chattriggers.ctjs.internal.launch.Descriptor 8 | import com.chattriggers.ctjs.internal.launch.Inject 9 | import com.chattriggers.ctjs.internal.utils.descriptor 10 | import org.objectweb.asm.tree.MethodNode 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable 13 | import org.spongepowered.asm.mixin.injection.Inject as SPInject 14 | 15 | internal class InjectGenerator( 16 | ctx: GenerationContext, 17 | id: Int, 18 | private val inject: Inject, 19 | ) : InjectorGenerator(ctx, id) { 20 | override val type = "inject" 21 | 22 | override fun getInjectionSignature(): InjectionSignature { 23 | val (mappedMethod, method) = ctx.findMethod(inject.method) 24 | val parameters = mutableListOf() 25 | 26 | if (mappedMethod.returnType.value == "V") { 27 | parameters.add(Parameter(CallbackInfo::class.descriptor())) 28 | } else { 29 | parameters.add(Parameter(CallbackInfoReturnable::class.descriptor())) 30 | } 31 | 32 | inject.locals?.forEach { 33 | parameters.add(Utils.getParameterFromLocal(it)) 34 | } 35 | 36 | return InjectionSignature( 37 | mappedMethod, 38 | parameters, 39 | Descriptor.Primitive.VOID, 40 | method.isStatic, 41 | ) 42 | } 43 | 44 | override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { 45 | node.visitAnnotation(SPInject::class.java.descriptorString(), true).apply { 46 | if (inject.id != null) 47 | visit("id", inject.id) 48 | visit("method", signature.targetMethod.toFullDescriptor()) 49 | if (inject.slice != null) 50 | visit("slice", inject.slice.map(Utils::createSliceAnnotation)) 51 | if (inject.at != null) 52 | visit("at", inject.at.map(Utils::createAtAnnotation)) 53 | if (inject.cancellable != null) 54 | visit("cancellable", inject.cancellable) 55 | if (inject.remap != null) 56 | visit("remap", inject.remap) 57 | if (inject.require != null) 58 | visit("require", inject.require) 59 | if (inject.expect != null) 60 | visit("expect", inject.expect) 61 | if (inject.allow != null) 62 | visit("allow", inject.allow) 63 | if (inject.constraints != null) 64 | visit("constraints", inject.constraints) 65 | visitEnd() 66 | } 67 | } 68 | 69 | context(MethodAssembly) 70 | override fun generateNotAttachedBehavior() { 71 | // This method is expected to leave something on the stack 72 | aconst_null 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/commands/CommandCollection.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.commands 2 | 3 | import com.chattriggers.ctjs.engine.LogType 4 | import com.chattriggers.ctjs.engine.printToConsole 5 | import com.chattriggers.ctjs.internal.engine.CTEvents 6 | import com.chattriggers.ctjs.internal.utils.Initializer 7 | import com.mojang.brigadier.CommandDispatcher 8 | import com.mojang.brigadier.builder.ArgumentBuilder 9 | import com.mojang.brigadier.context.CommandContext 10 | import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback 11 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents 12 | import net.minecraft.command.CommandSource 13 | 14 | abstract class CommandCollection : Initializer { 15 | private val allCommands = mutableSetOf() 16 | 17 | private var clientDispatcher: CommandDispatcher? = null 18 | private var networkDispatcher: CommandDispatcher? = null 19 | 20 | @Suppress("UNCHECKED_CAST") 21 | override fun init() { 22 | ClientCommandRegistrationCallback.EVENT.register { dispatcher, _ -> 23 | clientDispatcher = dispatcher as CommandDispatcher 24 | allCommands.forEach { it.registerImpl(dispatcher) } 25 | } 26 | 27 | CTEvents.NETWORK_COMMAND_DISPATCHER_REGISTER.register { dispatcher -> 28 | networkDispatcher = dispatcher as CommandDispatcher 29 | allCommands.forEach { it.registerImpl(dispatcher) } 30 | } 31 | 32 | ClientPlayConnectionEvents.DISCONNECT.register { _, _ -> 33 | clientDispatcher = null 34 | networkDispatcher = null 35 | } 36 | } 37 | 38 | fun register(command: Command) { 39 | allCommands.add(command) 40 | if (clientDispatcher.hasConflict(command) || networkDispatcher.hasConflict(command)) { 41 | existingCommandWarning(command.name).printToConsole(LogType.WARN) 42 | } else { 43 | clientDispatcher?.let { command.registerImpl(it) } 44 | networkDispatcher?.let { command.registerImpl(it) } 45 | } 46 | } 47 | 48 | fun unregister(command: Command) { 49 | for (dispatcher in listOfNotNull(clientDispatcher, networkDispatcher)) 50 | command.unregisterImpl(dispatcher) 51 | } 52 | 53 | fun unregisterAll() { 54 | allCommands.forEach(::unregister) 55 | allCommands.clear() 56 | } 57 | 58 | fun > ArgumentBuilder.onExecute(block: (CommandContext) -> Unit): T = 59 | executes { 60 | block(it) 61 | 1 62 | } 63 | 64 | private fun CommandDispatcher<*>?.hasConflict(command: Command) = 65 | !command.overrideExisting && (this?.root?.getChild(command.name) != null) 66 | 67 | private fun existingCommandWarning(name: String) = 68 | """ 69 | Command with name $name already exists! This will not override the 70 | other command with the same name. To override the other command, set the 71 | overrideExisting flag in setName() (the second argument) to true. 72 | """.trimIndent().replace("\n", "") 73 | } 74 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyVariableGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.MethodAssembly 4 | import com.chattriggers.ctjs.internal.launch.Local 5 | import com.chattriggers.ctjs.internal.launch.ModifyVariable 6 | import com.chattriggers.ctjs.internal.utils.descriptorString 7 | import org.objectweb.asm.tree.MethodNode 8 | import org.spongepowered.asm.mixin.injection.ModifyVariable as SPModifyVariable 9 | 10 | internal class ModifyVariableGenerator( 11 | ctx: GenerationContext, 12 | id: Int, 13 | private var modifyVariable: ModifyVariable, 14 | ) : InjectorGenerator(ctx, id) { 15 | override val type = "modifyVariable" 16 | 17 | override fun getInjectionSignature(): InjectionSignature { 18 | val (mappedMethod, method) = ctx.findMethod(modifyVariable.method) 19 | 20 | // Construct a temporary local so we can call Utils.getParameterFromLocal 21 | val tempLocal = Local( 22 | modifyVariable.print, 23 | modifyVariable.index, 24 | modifyVariable.ordinal, 25 | modifyVariable.type, 26 | mutable = false, 27 | ) 28 | 29 | val parameter = Utils.getParameterFromLocal(tempLocal, name = "ModifyVariable") 30 | 31 | // Update our ModifyVariable annotation since we may have changed the index 32 | modifyVariable = modifyVariable.copy( 33 | index = parameter.local?.index ?: modifyVariable.index, 34 | ) 35 | 36 | return InjectionSignature( 37 | mappedMethod, 38 | listOf(parameter.copy(local = null)), 39 | parameter.descriptor, 40 | method.isStatic, 41 | ) 42 | } 43 | 44 | override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { 45 | node.visitAnnotation(SPModifyVariable::class.descriptorString(), true).apply { 46 | visit("method", listOf(signature.targetMethod.toFullDescriptor())) 47 | visit("at", Utils.createAtAnnotation(modifyVariable.at)) 48 | if (modifyVariable.slice != null) 49 | visit("slice", Utils.createSliceAnnotation(modifyVariable.slice!!)) 50 | if (modifyVariable.print != null) 51 | visit("print", modifyVariable.print) 52 | if (modifyVariable.ordinal != null) 53 | visit("ordinal", modifyVariable.ordinal) 54 | if (modifyVariable.index != null) 55 | visit("index", modifyVariable.index) 56 | if (modifyVariable.remap != null) 57 | visit("remap", modifyVariable.remap) 58 | if (modifyVariable.require != null) 59 | visit("require", modifyVariable.require) 60 | if (modifyVariable.expect != null) 61 | visit("expect", modifyVariable.expect) 62 | if (modifyVariable.allow != null) 63 | visit("allow", modifyVariable.allow) 64 | if (modifyVariable.constraints != null) 65 | visit("constraints", modifyVariable.constraints) 66 | visitEnd() 67 | } 68 | } 69 | 70 | context(MethodAssembly) 71 | override fun generateNotAttachedBehavior() { 72 | generateParameterLoad(0) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/engine/module/Module.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.engine.module 2 | 3 | import com.chattriggers.ctjs.api.message.ChatLib 4 | import com.chattriggers.ctjs.api.render.Renderer 5 | import com.chattriggers.ctjs.api.render.Text 6 | import com.fasterxml.jackson.core.Version 7 | import java.io.File 8 | 9 | class Module(val name: String, var metadata: ModuleMetadata, val folder: File) { 10 | var targetModVersion: Version? = null 11 | var requiredBy = mutableSetOf() 12 | 13 | private val gui = object { 14 | var collapsed = true 15 | var x = 0f 16 | var y = 0f 17 | var description = Text(metadata.description ?: "No description provided in the metadata") 18 | } 19 | 20 | fun draw(x: Float, y: Float, width: Float): Float { 21 | gui.x = x 22 | gui.y = y 23 | 24 | Renderer.pushMatrix() 25 | Renderer.drawRect( 26 | 0xaa000000, 27 | x, y, width, 13f 28 | ) 29 | Renderer.drawStringWithShadow( 30 | metadata.name ?: name, 31 | x + 3, y + 3 32 | ) 33 | 34 | return if (gui.collapsed) { 35 | Renderer.translate(x + width - 5, y + 8) 36 | Renderer.rotate(180f) 37 | Renderer.drawString("^", 0f, 0f) 38 | 39 | Renderer.popMatrix() 40 | 15f 41 | } else { 42 | gui.description.setMaxWidth(width.toInt() - 5) 43 | 44 | Renderer.drawRect(0x50000000, x, y + 13, width, gui.description.getHeight() + 12) 45 | Renderer.drawString("^", x + width - 10, y + 5) 46 | 47 | gui.description.draw(x + 3, y + 15) 48 | 49 | if (metadata.version != null) { 50 | Renderer.drawStringWithShadow( 51 | ChatLib.addColor("&8v${metadata.version}"), 52 | x + width - Renderer.getStringWidth(ChatLib.addColor("&8v${metadata.version}")), 53 | y + gui.description.getHeight() + 15 54 | ) 55 | } 56 | 57 | Renderer.drawStringWithShadow( 58 | ChatLib.addColor( 59 | if (metadata.isRequired && requiredBy.isNotEmpty()) { 60 | "&8required by $requiredBy" 61 | } else { 62 | "&4[delete]" 63 | } 64 | ), 65 | x + 3, y + gui.description.getHeight() + 15 66 | ) 67 | 68 | Renderer.popMatrix() 69 | gui.description.getHeight() + 27 70 | } 71 | } 72 | 73 | fun click(x: Double, y: Double, width: Float) { 74 | if (x > gui.x && x < gui.x + width 75 | && y > gui.y && y < gui.y + 13 76 | ) { 77 | gui.collapsed = !gui.collapsed 78 | return 79 | } 80 | 81 | if (gui.collapsed || (metadata.isRequired && requiredBy.isNotEmpty())) return 82 | 83 | if (x > gui.x && x < gui.x + 45 84 | && y > gui.y + gui.description.getHeight() + 15 && y < gui.y + gui.description.getHeight() + 25 85 | ) { 86 | ModuleManager.deleteModule(name) 87 | } 88 | } 89 | 90 | override fun toString() = "Module{name=$name,version=${metadata.version}}" 91 | } 92 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/render/Book.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.render 2 | 3 | import com.chattriggers.ctjs.api.client.Client 4 | import com.chattriggers.ctjs.api.message.TextComponent 5 | import com.chattriggers.ctjs.internal.mixins.BookScreenAccessor 6 | import com.chattriggers.ctjs.internal.utils.asMixin 7 | import net.minecraft.client.gui.screen.ingame.BookScreen 8 | import net.minecraft.text.StringVisitable 9 | 10 | class Book { 11 | private var screen: BookScreen? = null 12 | private val customContents = BookScreen.Contents(emptyList()) 13 | 14 | /** 15 | * Add a page to the book. 16 | * 17 | * @param contents the entire message for what the page should be 18 | * @return the current book to allow method chaining 19 | */ 20 | fun addPage(contents: TextComponent) = apply { 21 | customContents.pages.add(contents) 22 | } 23 | 24 | /** 25 | * Overloaded method for adding a simple page to the book. 26 | * 27 | * @param message a simple string to make the page 28 | * @return the current book to allow method chaining 29 | */ 30 | fun addPage(message: String) = apply { 31 | addPage(TextComponent(message)) 32 | } 33 | 34 | /** 35 | * Inserts a page at the specified index of the book 36 | * 37 | * @param pageIndex the index of the page to set 38 | * @param message the message to set the page to 39 | * @return the current book to allow method chaining 40 | */ 41 | fun insertPage(pageIndex: Int, message: TextComponent) = apply { 42 | require(pageIndex in customContents.pages.indices) { 43 | println("Invalid index $pageIndex for Book with ${customContents.pageCount} pages") 44 | } 45 | 46 | customContents.pages.add(pageIndex, message) 47 | screen?.asMixin()?.invokeUpdatePageButtons() 48 | } 49 | 50 | fun insertPage(pageIndex: Int, message: String) = insertPage(pageIndex, TextComponent(message)) 51 | 52 | /** 53 | * Sets a page of the book to the specified message. 54 | * 55 | * @param pageIndex the index of the page to set 56 | * @param message the message to set the page to 57 | * @return the current book to allow method chaining 58 | */ 59 | fun setPage(pageIndex: Int, message: TextComponent) = apply { 60 | require(pageIndex in customContents.pages.indices) { 61 | println("Invalid index $pageIndex for Book with ${customContents.pageCount} pages") 62 | } 63 | 64 | customContents.pages[pageIndex] = message 65 | screen?.asMixin()?.invokeUpdatePageButtons() 66 | } 67 | 68 | fun setPage(pageIndex: Int, message: String) = setPage(pageIndex, TextComponent(message)) 69 | 70 | @JvmOverloads 71 | fun display(pageIndex: Int = 0) { 72 | screen = BookScreen(customContents) 73 | Client.scheduleTask { 74 | Client.getMinecraft().setScreen(screen) 75 | screen!!.setPage(pageIndex) 76 | } 77 | } 78 | 79 | fun isOpen(): Boolean { 80 | return Client.currentGui.get() === screen 81 | } 82 | 83 | fun getCurrentPage(): Int { 84 | return if (!isOpen() || screen == null) -1 else screen!!.asMixin().pageIndex 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReceiverGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch.generation 2 | 3 | import codes.som.koffee.MethodAssembly 4 | import codes.som.koffee.insns.jvm.getfield 5 | import codes.som.koffee.insns.jvm.invokevirtual 6 | import codes.som.koffee.insns.jvm.putfield 7 | import com.chattriggers.ctjs.internal.launch.At 8 | import com.chattriggers.ctjs.internal.launch.ModifyReceiver 9 | import com.chattriggers.ctjs.internal.utils.descriptorString 10 | import org.objectweb.asm.tree.MethodNode 11 | import com.llamalad7.mixinextras.injector.ModifyReceiver as SPModifyReceiver 12 | 13 | internal class ModifyReceiverGenerator( 14 | ctx: GenerationContext, 15 | id: Int, 16 | private val modifyReceiver: ModifyReceiver, 17 | ) : InjectorGenerator(ctx, id) { 18 | override val type = "modifyReceiver" 19 | 20 | override fun getInjectionSignature(): InjectionSignature { 21 | val (mappedMethod, method) = ctx.findMethod(modifyReceiver.method) 22 | 23 | val (owner, extraParams) = when (val atTarget = modifyReceiver.at.atTarget) { 24 | is At.InvokeTarget -> atTarget.descriptor.owner to atTarget.descriptor.parameters 25 | is At.FieldTarget -> { 26 | check(atTarget.isStatic != null && atTarget.isGet != null) { 27 | "ModifyReceiver targeting FIELD expects an opcode value" 28 | } 29 | check(!atTarget.isStatic) { "ModifyReceiver targeting FIELD expects a non-static field access" } 30 | if (atTarget.isGet) { 31 | atTarget.descriptor.owner to emptyList() 32 | } else atTarget.descriptor.owner to listOf(atTarget.descriptor.type!!) 33 | } 34 | else -> error("Unsupported At.value for ModifyReceiver: ${atTarget.targetName}") 35 | } 36 | 37 | val params = listOf(Parameter(owner!!)) + 38 | extraParams!!.map(::Parameter) + 39 | modifyReceiver.locals?.map(Utils::getParameterFromLocal).orEmpty() 40 | 41 | return InjectionSignature( 42 | mappedMethod, 43 | params, 44 | owner, 45 | method.isStatic, 46 | ) 47 | } 48 | 49 | override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { 50 | node.visitAnnotation(SPModifyReceiver::class.descriptorString(), true).apply { 51 | visit("method", listOf(signature.targetMethod.toFullDescriptor())) 52 | visit("at", Utils.createAtAnnotation(modifyReceiver.at)) 53 | if (modifyReceiver.slice != null) 54 | visit("slice", listOf(modifyReceiver.slice.map(Utils::createSliceAnnotation))) 55 | if (modifyReceiver.remap != null) 56 | visit("remap", modifyReceiver.remap) 57 | if (modifyReceiver.require != null) 58 | visit("require", modifyReceiver.require) 59 | if (modifyReceiver.expect != null) 60 | visit("expect", modifyReceiver.expect) 61 | if (modifyReceiver.allow != null) 62 | visit("allow", modifyReceiver.allow) 63 | visitEnd() 64 | } 65 | } 66 | 67 | context(MethodAssembly) 68 | override fun generateNotAttachedBehavior() { 69 | generateParameterLoad(0) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/internal/launch/CTMixinPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.launch 2 | 3 | import com.chattriggers.ctjs.api.Mappings 4 | import com.chattriggers.ctjs.engine.printTraceToConsole 5 | import com.chattriggers.ctjs.internal.engine.module.ModuleManager 6 | import com.llamalad7.mixinextras.MixinExtrasBootstrap 7 | import org.objectweb.asm.tree.ClassNode 8 | import org.slf4j.LoggerFactory 9 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin 10 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo 11 | import java.io.OutputStream 12 | import java.io.PrintStream 13 | 14 | class CTMixinPlugin : IMixinConfigPlugin { 15 | override fun onLoad(mixinPackage: String?) { 16 | redirectIO() 17 | 18 | Mappings.initialize() 19 | ModuleManager.setup() 20 | MixinExtrasBootstrap.init() 21 | 22 | try { 23 | DynamicMixinManager.initialize() 24 | DynamicMixinManager.applyAccessWideners() 25 | } catch (e: Throwable) { 26 | IllegalStateException("Error generating dynamic mixins", e).printTraceToConsole() 27 | } 28 | } 29 | 30 | override fun getRefMapperConfig(): String? = null 31 | 32 | override fun shouldApplyMixin(targetClassName: String?, mixinClassName: String?): Boolean = true 33 | 34 | override fun acceptTargets(myTargets: MutableSet?, otherTargets: MutableSet?) { 35 | } 36 | 37 | override fun getMixins(): MutableList? = null 38 | 39 | override fun preApply( 40 | targetClassName: String?, 41 | targetClass: ClassNode?, 42 | mixinClassName: String?, 43 | mixinInfo: IMixinInfo? 44 | ) { 45 | } 46 | 47 | override fun postApply( 48 | targetClassName: String?, 49 | targetClass: ClassNode?, 50 | mixinClassName: String?, 51 | mixinInfo: IMixinInfo? 52 | ) { 53 | } 54 | 55 | private fun redirectIO() { 56 | // This mimics what net.minecraft.Bootstrap does with the output streams, but 57 | // this happens earlier and allows stderr to show up in latest.log (which is 58 | // required for @Local(print = true)). Note that we copy the relevant classes 59 | // to avoid class-loading anything in the MC package. 60 | System.setErr(LoggerPrintStream("STDERR", System.err)) 61 | System.setOut(LoggerPrintStream("STDOUT", STDOUT)) 62 | } 63 | 64 | private open class LoggerPrintStream(protected val name: String, out: OutputStream) : PrintStream(out) { 65 | override fun println(message: String?) { 66 | log(message) 67 | } 68 | 69 | override fun println(obj: Any?) { 70 | log(obj.toString()) 71 | } 72 | 73 | override fun printf(format: String, vararg args: Any?): PrintStream = apply { 74 | log(format.format(args)) 75 | } 76 | 77 | protected open fun log(message: String?) { 78 | LOGGER.info("[{}]: {}", name, message) 79 | } 80 | } 81 | 82 | companion object { 83 | private val STDOUT = System.out 84 | private val STDERR = System.out 85 | private val LOGGER = LoggerFactory.getLogger(CTMixinPlugin::class.java) 86 | 87 | @JvmStatic 88 | fun restoreOutputStreams() { 89 | System.setErr(STDERR) 90 | System.setOut(STDOUT) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/kotlin/com/chattriggers/ctjs/api/inventory/nbt/NBTBase.kt: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.api.inventory.nbt 2 | 3 | import com.chattriggers.ctjs.api.CTWrapper 4 | import com.chattriggers.ctjs.MCNbtBase 5 | import com.chattriggers.ctjs.MCNbtCompound 6 | import com.chattriggers.ctjs.MCNbtList 7 | import net.minecraft.nbt.* 8 | import org.mozilla.javascript.NativeArray 9 | import org.mozilla.javascript.NativeObject 10 | 11 | open class NBTBase(override val mcValue: MCNbtBase) : CTWrapper { 12 | /** 13 | * Gets the type byte for the tag. 14 | */ 15 | val id: Byte 16 | get() = mcValue.type 17 | 18 | /** 19 | * Creates a clone of the tag. 20 | */ 21 | fun copy() = mcValue.copy() 22 | 23 | /** 24 | * Return whether this compound has no tags. 25 | */ 26 | fun hasNoTags() = when (this) { 27 | is NBTTagCompound -> tagMap.isEmpty() 28 | is NBTTagList -> mcValue.isEmpty() 29 | else -> false 30 | } 31 | 32 | fun hasTags() = !hasNoTags() 33 | 34 | override fun equals(other: Any?) = mcValue == other 35 | 36 | override fun hashCode() = mcValue.hashCode() 37 | 38 | override fun toString() = mcValue.toString() 39 | 40 | companion object { 41 | @JvmStatic 42 | fun fromMC(nbt: MCNbtBase): NBTBase = when (nbt) { 43 | is MCNbtCompound -> NBTTagCompound(nbt) 44 | is MCNbtList -> NBTTagList(nbt) 45 | else -> NBTBase(nbt) 46 | } 47 | 48 | fun MCNbtBase.toObject(): Any? { 49 | return when (this) { 50 | is NbtString -> asString() 51 | is NbtByte -> byteValue() 52 | is NbtShort -> shortValue() 53 | is NbtInt -> intValue() 54 | is NbtLong -> longValue() 55 | is NbtFloat -> floatValue() 56 | is NbtDouble -> doubleValue() 57 | is MCNbtCompound -> toObject() 58 | is MCNbtList -> toObject() 59 | is NbtByteArray -> NativeArray(byteArray.toTypedArray()).expose() 60 | is NbtIntArray -> NativeArray(intArray.toTypedArray()).expose() 61 | else -> error("Unknown tag type $javaClass") 62 | } 63 | } 64 | 65 | fun MCNbtCompound.toObject(): NativeObject { 66 | val o = NativeObject() 67 | o.expose() 68 | 69 | for (key in keys) { 70 | val value = this[key] 71 | if (value != null) { 72 | o.put(key, o, value.toObject()) 73 | } 74 | } 75 | 76 | return o 77 | } 78 | 79 | fun MCNbtList.toObject(): NativeArray { 80 | val tags = mutableListOf() 81 | for (i in 0 until count()) { 82 | tags.add(get(i).toObject()) 83 | } 84 | val array = NativeArray(tags.toTypedArray()) 85 | array.expose() 86 | return array 87 | } 88 | 89 | private fun NativeArray.expose() = apply { 90 | // Taken from the private NativeArray#init method 91 | exportAsJSClass(32, this, false) 92 | } 93 | 94 | private fun NativeObject.expose() = apply { 95 | // Taken from the private NativeObject#init method 96 | exportAsJSClass(12, this, false) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/CreativeInventoryScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.triggers.CancellableEvent; 4 | import com.chattriggers.ctjs.api.inventory.Item; 5 | import com.chattriggers.ctjs.api.triggers.TriggerType; 6 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 7 | import com.llamalad7.mixinextras.sugar.Local; 8 | import net.minecraft.client.gui.screen.ingame.AbstractInventoryScreen; 9 | import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; 10 | import net.minecraft.entity.player.PlayerInventory; 11 | import net.minecraft.screen.slot.Slot; 12 | import net.minecraft.screen.slot.SlotActionType; 13 | import net.minecraft.text.Text; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.Slice; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | 21 | @Mixin(CreativeInventoryScreen.class) 22 | public abstract class CreativeInventoryScreenMixin extends AbstractInventoryScreen { 23 | private CreativeInventoryScreenMixin(CreativeInventoryScreen.CreativeScreenHandler screenHandler, PlayerInventory playerInventory, Text text) { 24 | super(screenHandler, playerInventory, text); 25 | } 26 | 27 | @ModifyExpressionValue( 28 | method = "onMouseClick", 29 | at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screen/ingame/CreativeInventoryScreen;lastClickOutsideBounds:Z") 30 | ) 31 | private boolean injectOnMouseClick(boolean original, @Local(ordinal = 1) int button) { 32 | // dropping by clicking outside creative tab 33 | CancellableEvent event = new CancellableEvent(); 34 | if (original) { 35 | TriggerType.DROP_ITEM.triggerAll(Item.fromMC(handler.getCursorStack()), button == 0, event); 36 | } 37 | 38 | return original && !event.isCanceled(); 39 | } 40 | 41 | @ModifyExpressionValue( 42 | method = "onMouseClick", 43 | slice = @Slice( 44 | from = @At( 45 | value = "FIELD", 46 | target = "Lnet/minecraft/client/gui/screen/ingame/CreativeInventoryScreen;deleteItemSlot:Lnet/minecraft/screen/slot/Slot;", 47 | ordinal = 0 48 | ) 49 | ), 50 | at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;isEmpty()Z", ordinal = 0) 51 | ) 52 | private boolean injectOnMouseClick1(boolean original, @Local(ordinal = 1) int button) { 53 | // dropping by clicking outside creative inventory 54 | CancellableEvent event = new CancellableEvent(); 55 | if (!original) { 56 | TriggerType.DROP_ITEM.triggerAll(Item.fromMC(handler.getCursorStack()), button == 0, event); 57 | } 58 | 59 | // !(original || eventCanceled) => !original && !canceled 60 | return original || event.isCanceled(); 61 | } 62 | 63 | @Inject(method = "onMouseClick", at = @At(value = "INVOKE", target = "Lnet/minecraft/screen/slot/Slot;takeStack(I)Lnet/minecraft/item/ItemStack;"), cancellable = true) 64 | private void injectOnMouseClick2(@NotNull Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { 65 | // dropping item from slot in creative inventory 66 | TriggerType.DROP_ITEM.triggerAll(Item.fromMC(slot.getStack()), button == 0, ci); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java: -------------------------------------------------------------------------------- 1 | package com.chattriggers.ctjs.internal.mixins; 2 | 3 | import com.chattriggers.ctjs.api.world.Scoreboard; 4 | import com.chattriggers.ctjs.api.world.TabList; 5 | import com.chattriggers.ctjs.internal.engine.CTEvents; 6 | import com.chattriggers.ctjs.api.triggers.TriggerType; 7 | import com.chattriggers.ctjs.internal.engine.module.ModuleManager; 8 | import net.minecraft.client.MinecraftClient; 9 | import net.minecraft.client.gui.screen.DownloadingTerrainScreen; 10 | import net.minecraft.client.gui.screen.Screen; 11 | import net.minecraft.client.network.ServerInfo; 12 | import net.minecraft.client.world.ClientWorld; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Shadow; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | 20 | @Mixin(MinecraftClient.class) 21 | public abstract class MinecraftClientMixin { 22 | @Shadow @Nullable public ClientWorld world; 23 | 24 | @Shadow public abstract ServerInfo getCurrentServerEntry(); 25 | @Shadow public abstract boolean isIntegratedServerRunning(); 26 | 27 | @Inject(method = "joinWorld", at = @At("HEAD")) 28 | private void injectWorldUnload(ClientWorld world, DownloadingTerrainScreen.WorldEntryReason worldEntryReason, CallbackInfo ci) { 29 | if (this.world == null && world != null) { 30 | TriggerType.SERVER_CONNECT.triggerAll(); 31 | } else if (this.world != null && world == null) { 32 | TriggerType.SERVER_DISCONNECT.triggerAll(); 33 | } 34 | 35 | if (this.world != null) { 36 | TriggerType.WORLD_UNLOAD.triggerAll(); 37 | Scoreboard.INSTANCE.clearCustom$ctjs(); 38 | TabList.INSTANCE.clearCustom$ctjs(); 39 | } 40 | } 41 | 42 | @Inject(method = "joinWorld", at = @At("TAIL")) 43 | private void injectWorldLoad(ClientWorld world, DownloadingTerrainScreen.WorldEntryReason worldEntryReason, CallbackInfo ci) { 44 | if (world != null) 45 | TriggerType.WORLD_LOAD.triggerAll(); 46 | } 47 | 48 | @Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;Z)V", at = @At("HEAD")) 49 | private void injectDisconnect(Screen disconnectionScreen, boolean transferring, CallbackInfo ci) { 50 | // disconnect() is also called when connecting, so we check that there is 51 | // an existing server 52 | if (this.isIntegratedServerRunning() || this.getCurrentServerEntry() != null) { 53 | TriggerType.WORLD_UNLOAD.triggerAll(); 54 | TriggerType.SERVER_DISCONNECT.triggerAll(); 55 | Scoreboard.INSTANCE.clearCustom$ctjs(); 56 | TabList.INSTANCE.clearCustom$ctjs(); 57 | } 58 | } 59 | 60 | @Inject(method = "setScreen", at = @At("HEAD")) 61 | private void injectScreenOpened(Screen screen, CallbackInfo ci) { 62 | if (screen != null) 63 | TriggerType.GUI_OPENED.triggerAll(screen, ci); 64 | } 65 | 66 | @Inject(method = "run", at = @At("HEAD")) 67 | private void injectRun(CallbackInfo ci) { 68 | new Thread(() -> { 69 | ModuleManager.INSTANCE.entryPass(); 70 | TriggerType.GAME_LOAD.triggerAll(); 71 | }).start(); 72 | } 73 | 74 | @Inject(method = "render", at = @At("HEAD")) 75 | private void injectRender(boolean tick, CallbackInfo ci) { 76 | CTEvents.RENDER_GAME.invoker().run(); 77 | } 78 | } 79 | --------------------------------------------------------------------------------