Skip to content

Commit 6dd14e7

Browse files
committed
feat: firmament api
1 parent 3f33928 commit 6dd14e7

File tree

19 files changed

+357
-50
lines changed

19 files changed

+357
-50
lines changed

build.gradle.kts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import com.google.gson.JsonObject
1414
import moe.nea.licenseextractificator.LicenseDiscoveryTask
1515
import moe.nea.mcautotranslations.gradle.CollectTranslations
1616
import net.fabricmc.loom.LoomGradleExtension
17+
import net.fabricmc.loom.task.RemapJarTask
18+
import net.fabricmc.loom.task.RemapSourcesJarTask
1719
import net.fabricmc.loom.task.RunGameTask
1820
import org.apache.tools.ant.taskdefs.condition.Os
1921
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
@@ -88,7 +90,13 @@ val shadowJar = tasks.register("shadowJar", ShadowJar::class)
8890
val mergedSourceSetsJar = tasks.register("mergedSourceSetsJar", ShadowJar::class)
8991

9092
val compatSourceSets: MutableSet<SourceSet> = mutableSetOf()
91-
fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabled: Boolean = true): SourceSet {
93+
fun createIsolatedSourceSet(
94+
name: String,
95+
path: String = "compat/$name",
96+
isEnabled: Boolean = true,
97+
inheritsFromMain: Boolean = true,
98+
enableKsp: Boolean = true,
99+
): SourceSet {
92100
val ss = sourceSets.create(name) {
93101
this.java.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
94102
this.kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
@@ -100,6 +108,8 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl
100108
this.commandLineArgumentProviders.add { // TODO: update https://github.com/google/ksp/issues/2075
101109
listOf("firmament.sourceset=${ss.name}")
102110
}
111+
if (!enableKsp)
112+
this.enabled = false
103113
}
104114
tasks.named("compile${upperName}Kotlin", KotlinCompile::class) {
105115
this.enabled = isEnabled
@@ -116,7 +126,8 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl
116126
}
117127
configurations {
118128
(ss.implementationConfigurationName) {
119-
extendsFrom(getByName(mainSS.compileClasspathConfigurationName))
129+
if (inheritsFromMain)
130+
extendsFrom(getByName(mainSS.compileClasspathConfigurationName))
120131
}
121132
(ss.annotationProcessorConfigurationName) {
122133
extendsFrom(getByName(mainSS.annotationProcessorConfigurationName))
@@ -125,15 +136,18 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl
125136
if (isEnabled)
126137
extendsFrom(getByName(ss.runtimeClasspathConfigurationName))
127138
}
128-
("ksp$upperName") {
129-
extendsFrom(ksp.get())
130-
}
139+
if (enableKsp)
140+
("ksp$upperName") {
141+
extendsFrom(ksp.get())
142+
}
131143
}
132144
dependencies {
133145
if (isEnabled)
134146
runtimeOnly(ss.output)
135-
(ss.implementationConfigurationName)(project.files(tasks.compileKotlin.map { it.destinationDirectory }))
136-
(ss.implementationConfigurationName)(project.files(tasks.compileJava.map { it.destinationDirectory }))
147+
if (inheritsFromMain) {
148+
(ss.implementationConfigurationName)(project.files(tasks.compileKotlin.map { it.destinationDirectory }))
149+
(ss.implementationConfigurationName)(project.files(tasks.compileJava.map { it.destinationDirectory }))
150+
}
137151
}
138152
mergedSourceSetsJar.configure {
139153
from(ss.output)
@@ -205,6 +219,7 @@ val reiSourceSet = createIsolatedSourceSet("rei", isEnabled = false)
205219
val moulconfigSourceSet = createIsolatedSourceSet("moulconfig")
206220
val irisSourceSet = createIsolatedSourceSet("iris")
207221
val customTexturesSourceSet = createIsolatedSourceSet("texturePacks", "texturePacks")
222+
val apiSourceSet = createIsolatedSourceSet("api", "api", inheritsFromMain = false, enableKsp = false)
208223

209224
dependencies {
210225
// Minecraft dependencies
@@ -223,6 +238,11 @@ dependencies {
223238
modImplementation(libs.moulconfig)
224239
modImplementation(libs.manninghamMills)
225240
modImplementation(libs.basicMath)
241+
implementation(apiSourceSet.output)
242+
(apiSourceSet.implementationConfigurationName)(loom.namedMinecraftJars)
243+
(apiSourceSet.implementationConfigurationName)(libs.jspecify)
244+
(apiSourceSet.implementationConfigurationName)(libs.jbAnnotations)
245+
// configurations.forEach { println(it.name) }
226246
include(libs.basicMath)
227247
(modmenuSourceSet.modImplementationConfigurationName)(libs.modmenu)
228248
modImplementation(libs.hypixelmodapi)
@@ -389,10 +409,36 @@ tasks.withType<JavaCompile> {
389409
}
390410
}
391411

412+
fun AbstractArchiveTask.badjar() = destinationDirectory.set(layout.buildDirectory.dir("badjars"))
392413
tasks.jar {
393-
destinationDirectory.set(layout.buildDirectory.dir("badjars"))
414+
badjar()
394415
archiveClassifier.set("slim")
395416
}
417+
val apiJarNamed = tasks.register("apiJarNamed", Jar::class) {
418+
badjar()
419+
archiveClassifier = "api-named"
420+
from(apiSourceSet.output)
421+
}
422+
val apiJar = tasks.register("apiJar", RemapJarTask::class) {
423+
inputFile = apiJarNamed.flatMap { it.archiveFile }
424+
addNestedDependencies = false
425+
archiveClassifier = "api"
426+
}
427+
val apiSourcesJarNamed = tasks.register("apiSourcesJarNamed", Jar::class) {
428+
badjar()
429+
from(apiSourceSet.allSource)
430+
archiveClassifier = "api-sources-named"
431+
}
432+
val apiSourcesJar = tasks.register("apiSourceSJar", RemapSourcesJarTask::class) {
433+
inputFile = apiSourcesJarNamed.flatMap { it.archiveFile }
434+
archiveClassifier = "api-sources"
435+
}
436+
437+
tasks.assemble {
438+
dependsOn(apiJar)
439+
dependsOn(apiSourcesJar)
440+
}
441+
396442
mergedSourceSetsJar.configure {
397443
from(zipTree(tasks.jar.flatMap { it.archiveFile }))
398444
destinationDirectory.set(layout.buildDirectory.dir("badjars"))

gradle/libs.versions.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ rei_fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref =
102102
rei_dev_api = { module = "com.github.shedaniel.roughlyenoughitems:RoughlyEnoughItems-api", version.ref = "reidev" }
103103
rei_dev_fabric = { module = "com.github.shedaniel.roughlyenoughitems:RoughlyEnoughItems-fabric", version.ref = "reidev" }
104104

105+
jspecify = { module = "org.jspecify:jspecify", version = "1.0.0" }
106+
jbAnnotations = { module = "org.jetbrains:annotations", version = "26.0.2" }
107+
105108
moulconfig = { module = "org.notenoughupdates.moulconfig:modern-1.21.10", version.ref = "moulconfig" }
106109
repoparser = { module = "moe.nea:neurepoparser", version.ref = "neurepoparser" }
107110
mixinextras = { module = "io.github.llamalad7:mixinextras-fabric", version.ref = "mixinextras" }
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package moe.nea.firmament.api.v1;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
import org.jspecify.annotations.Nullable;
5+
6+
import java.util.List;
7+
import java.util.Optional;
8+
9+
/**
10+
* Methods you can call to get information about firmaments current state.
11+
*/
12+
@ApiStatus.NonExtendable
13+
public abstract class FirmamentAPI {
14+
private static @Nullable FirmamentAPI INSTANCE;
15+
16+
/**
17+
* @return the canonical instance of the {@link FirmamentAPI}.
18+
*/
19+
public static FirmamentAPI getInstance() {
20+
if (INSTANCE != null)
21+
return INSTANCE;
22+
try {
23+
return INSTANCE = (FirmamentAPI) Class.forName("moe.nea.firmament.impl.v1.FirmamentAPIImpl")
24+
.getField("INSTANCE")
25+
.get(null);
26+
} catch (IllegalAccessException | NoSuchFieldException | ClassCastException e) {
27+
throw new RuntimeException("Firmament API implementation class found, but could not load api instance.", e);
28+
} catch (ClassNotFoundException e) {
29+
throw new RuntimeException("Could not find Firmament API, check FabricLoader.getInstance().isModLoaded(\"firmament\") first.");
30+
}
31+
}
32+
33+
/**
34+
* @return list-view of registered extensions
35+
*/
36+
public abstract List<? extends FirmamentExtension> getExtensions();
37+
38+
/**
39+
* Obtain a reference to the currently hovered item widget, which may be either in the item list or placed in a UI.
40+
* This widget may or may not also be present in the Widgets on the current screen.
41+
*
42+
* @return the currently hovered firmament item widget.
43+
*/
44+
public abstract Optional<FirmamentItemWidget> getHoveredItemWidget();
45+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package moe.nea.firmament.api.v1;
2+
3+
import net.minecraft.client.gui.navigation.ScreenRectangle;
4+
import net.minecraft.client.gui.screens.Screen;
5+
6+
import java.util.Collection;
7+
import java.util.List;
8+
9+
public interface FirmamentExtension {
10+
11+
/**
12+
* This method gets called during client initialization, if firmament is installed. Can be used as an alternative to
13+
* checking {@code FabricLoader.getInstance().isModLoaded("firmament")}.
14+
*/
15+
default void onLoad() {}
16+
17+
/**
18+
* @param screen the current active screen
19+
* @return whether inventory buttons should be hidden on the current screen.
20+
*/
21+
default boolean shouldHideInventoryButtons(Screen screen) {
22+
return false;
23+
}
24+
25+
/**
26+
* @param screen the current active screen
27+
* @return a list of zones which contain content rendered by other mods, which should therefore hide the items in those areas
28+
*/
29+
default Collection<? extends ScreenRectangle> getExclusionZones(Screen screen) {
30+
return List.of();
31+
}
32+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package moe.nea.firmament.api.v1;
2+
3+
import net.minecraft.world.item.ItemStack;
4+
import org.jetbrains.annotations.ApiStatus;
5+
6+
@ApiStatus.NonExtendable
7+
public interface FirmamentItemWidget {
8+
/**
9+
* non-exhaustive enum for the placement position of an item widget
10+
*/
11+
@ApiStatus.NonExtendable
12+
interface Placement {
13+
String name();
14+
15+
Placement ITEM_LIST = () -> "ITEM_LIST";
16+
Placement RECIPE_SCREEN = () -> "RECIPE_SCREEN";
17+
}
18+
19+
/**
20+
* @return where in the UI the item widget is placed
21+
*/
22+
Placement getPlacement();
23+
24+
/**
25+
* get the currently displayed {@link ItemStack}. this empty stack might be empty. care should be taken not to
26+
* mutate the item stack instance, without {@link ItemStack#copy copying} it first.
27+
*
28+
* @return the currently displayed {@link ItemStack}, may be empty.
29+
*/
30+
ItemStack getItemStack();
31+
32+
/**
33+
* @return a SkyBlock id, potentially processed to reflect more details about the item stack, such as which specific
34+
* {@code PET} it is. this is sometimes referred to as the "neu id".
35+
*/
36+
String getSkyBlockId();
37+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@NullMarked
2+
package moe.nea.firmament.api.v1;
3+
4+
import org.jspecify.annotations.NullMarked;

src/main/java/moe/nea/firmament/init/HandledScreenRiser.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
package moe.nea.firmament.init;
33

44
import me.shedaniel.mm.api.ClassTinkerers;
5+
import net.minecraft.client.gui.components.events.AbstractContainerEventHandler;
56
import net.minecraft.client.gui.components.events.GuiEventListener;
67
import net.minecraft.client.gui.screens.Screen;
78
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
89
import net.minecraft.client.input.CharacterEvent;
910
import net.minecraft.client.input.KeyEvent;
11+
import net.minecraft.client.input.MouseButtonEvent;
1012
import org.objectweb.asm.Opcodes;
1113
import org.objectweb.asm.Type;
1214
import org.objectweb.asm.tree.ClassNode;
@@ -27,6 +29,8 @@ public class HandledScreenRiser extends RiserUtils {
2729
Intermediary.InterClass KeyInput = Intermediary.<KeyEvent>intermediaryClass();
2830
Intermediary.InterClass CharInput = Intermediary.<CharacterEvent>intermediaryClass();
2931
Intermediary.InterClass HandledScreen = Intermediary.<AbstractContainerScreen>intermediaryClass();
32+
Intermediary.InterClass AbstractContainerEventHandler = Intermediary.<AbstractContainerEventHandler>intermediaryClass();
33+
Intermediary.InterClass MouseButtonEvent = Intermediary.<MouseButtonEvent>intermediaryClass();
3034
Intermediary.InterMethod mouseScrolled = Intermediary.intermediaryMethod(
3135
GuiEventListener::mouseScrolled,
3236
Intermediary.ofClass(boolean.class),
@@ -35,6 +39,14 @@ public class HandledScreenRiser extends RiserUtils {
3539
Intermediary.ofClass(double.class),
3640
Intermediary.ofClass(double.class)
3741
);
42+
Intermediary.InterMethod mouseClickedScreen = Intermediary.intermediaryMethod(
43+
//onMouseClicked$firmament
44+
GuiEventListener::mouseClicked,
45+
Intermediary.ofClass(boolean.class),
46+
MouseButtonEvent,
47+
Intermediary.ofClass(boolean.class)
48+
);
49+
;
3850
Intermediary.InterMethod keyReleased = Intermediary.intermediaryMethod(
3951
GuiEventListener::keyReleased,
4052
Intermediary.ofClass(boolean.class),
@@ -47,12 +59,12 @@ public class HandledScreenRiser extends RiserUtils {
4759
);
4860

4961

50-
5162
@Override
5263
public void addTinkerers() {
5364
addTransformation(HandledScreen, this::addMouseScroll, true);
5465
addTransformation(HandledScreen, this::addKeyReleased, true);
5566
addTransformation(HandledScreen, this::addCharTyped, true);
67+
addTransformation(Screen, this::addMouseClicked, true);
5668
}
5769

5870
/**
@@ -86,7 +98,7 @@ void insertTrueHandler(MethodNode node,
8698

8799
void addKeyReleased(ClassNode classNode) {
88100
addSuperInjector(
89-
classNode, keyReleased.mapped(), keyReleased.mappedDesc(), "keyReleased_firmament",
101+
classNode, keyReleased.mapped(), keyReleased.mappedDesc(), HandledScreen, Screen, "keyReleased_firmament",
90102
insns -> {
91103
// ALOAD 0, load this
92104
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
@@ -95,9 +107,25 @@ void addKeyReleased(ClassNode classNode) {
95107
});
96108
}
97109

110+
void addMouseClicked(ClassNode classNode) {
111+
addSuperInjector(
112+
classNode, mouseClickedScreen.mapped(), mouseClickedScreen.mappedDesc(),
113+
Screen, AbstractContainerEventHandler, "onMouseClicked$firmament",
114+
insns -> {
115+
// load this
116+
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
117+
// load mouse event
118+
insns.add(new VarInsnNode(Opcodes.ALOAD, 1));
119+
// load doubled
120+
insns.add(new VarInsnNode(Opcodes.ILOAD, 2));
121+
}
122+
);
123+
}
124+
98125
void addCharTyped(ClassNode classNode) {
99126
addSuperInjector(
100-
classNode, charTyped.mapped(), charTyped.mappedDesc(), "charTyped_firmament",
127+
classNode, charTyped.mapped(), charTyped.mappedDesc(),
128+
HandledScreen, Screen, "charTyped_firmament",
101129
insns -> {
102130
// ALOAD 0, load this
103131
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
@@ -110,6 +138,8 @@ void addSuperInjector(
110138
ClassNode classNode,
111139
String name,
112140
Type desc,
141+
Intermediary.InterClass currentClass,
142+
Intermediary.InterClass parentClass,
113143
String firmamentName,
114144
Consumer<InsnList> loadArgs
115145
) {
@@ -125,7 +155,7 @@ void addSuperInjector(
125155
var insns = keyReleasedNode.instructions;
126156
loadArgs.accept(insns);
127157
// INVOKESPECIAL call super method
128-
insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Screen.mapped().getInternalName(),
158+
insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, parentClass.mapped().getInternalName(),
129159
name, desc.getDescriptor()));
130160
// IRETURN return int on stack (booleans are int at runtime)
131161
insns.add(new InsnNode(Opcodes.IRETURN));
@@ -134,7 +164,7 @@ void addSuperInjector(
134164
insertTrueHandler(keyReleasedNode, loadArgs, insns -> {
135165
// INVOKEVIRTUAL call custom handler
136166
insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
137-
HandledScreen.mapped().getInternalName(),
167+
currentClass.mapped().getInternalName(),
138168
firmamentName,
139169
desc.getDescriptor()));
140170
});
@@ -143,7 +173,7 @@ void addSuperInjector(
143173

144174
void addMouseScroll(ClassNode classNode) {
145175
addSuperInjector(
146-
classNode, mouseScrolled.mapped(), mouseScrolled.mappedDesc(), "mouseScrolled_firmament",
176+
classNode, mouseScrolled.mapped(), mouseScrolled.mappedDesc(), HandledScreen, Screen, "mouseScrolled_firmament",
147177
insns -> {
148178
// ALOAD 0, load this
149179
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));

0 commit comments

Comments
 (0)