├── 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 |
4 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
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 extends LivingEntity> 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 |
--------------------------------------------------------------------------------