-
Notifications
You must be signed in to change notification settings - Fork 0
Extension API for Modders
CobbleCompanion: Basis exposes a small, stable, client-only API that lets another mod add its own tab to the companion window — this is exactly the mechanism CobbleCompanion's own official extension modules (CobbleDollars' Wallet tab, etc.) use, so it's a real, exercised code path, not a theoretical hook.
Stability promise: everything in package com.cobblecompanion.api is the only supported way
to extend CompanionScreen. Internal classes under com.cobblecompanion.client.screens (and
elsewhere) are not part of the API contract and can change at any time without notice — don't
reflect into them.
| Class | Role |
|---|---|
CompanionExtensions |
Static registry. Call once at startup to plug your tab in. |
CompanionTabExtension |
Interface you implement — your tab's content and input handling. |
CompanionTabContext |
Facade passed into every callback — window geometry + the shared mini-GUI toolkit. |
Source: src/main/java/com/cobblecompanion/api/ in the
CobbleCompanion repo.
- CobbleCompanion reserves a fixed set of tab slots (
CompanionScreen.TAB_*constants). Most are used by Basis itself;TAB_WALLET(index6) is the one currently free for extensions — it's what CobbleCompanion: CobbleDollars' Wallet tab uses. - If nothing is registered for a tab index, CompanionScreen just renders its existing "not installed" placeholder there — no crash, no compile-time dependency in either direction.
- Your mod depends on CobbleCompanion
compileOnly(see Building against it below) — at runtime, if a player doesn't have CobbleCompanion installed at all, your extension mod simply never callsregisterTab, becauseCompanionExtensionsitself never gets classloaded on the server / non-companion clients. If a player does have both installed, your tab lights up automatically.
| Constant | Index | Used by |
|---|---|---|
TAB_POKEDEX |
0 | Basis |
TAB_LIVINGDEX |
1 | Basis |
TAB_TODO |
2 | Basis |
TAB_WHONEEDS |
3 | Basis |
TAB_TYPES |
4 | Basis |
TAB_TEAMBUILDER |
5 | Basis |
TAB_WALLET |
6 | Free — CobbleDollars' Wallet tab uses it today, the intended extension slot |
TAB_SEARCH |
7 | Basis |
TAB_HOME |
8 | Basis |
TAB_FRIENDS |
9 | Basis |
TAB_PROFESSOR |
10 | Basis (admin-only) |
TAB_SETTINGS |
11 | Basis |
If you need an additional slot beyond TAB_WALLET, open an issue on the
CobbleCompanion repo — new tab indices are added
on request.
public final class CompanionExtensions {
public static void registerTab(int tabIndex, CompanionTabExtension extension);
public static CompanionTabExtension getTab(int tabIndex);
public static boolean hasTab(int tabIndex);
}Call registerTab once, in your mod's client-side constructor/init (same place
CobbleCompanion's own extensions do it) — see the worked example below.
The interface you implement. Only isAvailable, render, and mouseClicked are required —
everything else has a sensible default (no-op / false):
public interface CompanionTabExtension {
boolean isAvailable(CompanionTabContext ctx);
void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick, CompanionTabContext ctx);
boolean mouseClicked(double mouseX, double mouseY, int button, CompanionTabContext ctx);
default void renderTopLayerContent(GuiGraphics graphics, int mouseX, int mouseY, CompanionTabContext ctx) {}
default void mouseReleased(double mouseX, double mouseY, int button, CompanionTabContext ctx) {}
default void mouseDragged(double mouseX, double mouseY, CompanionTabContext ctx) {}
default boolean isDraggingScrollbar() { return false; }
default boolean mouseScrolled(double mouseX, double mouseY, double scrollY, CompanionTabContext ctx) { return false; }
default boolean keyPressed(int keyCode, int scanCode, int modifiers, CompanionTabContext ctx) { return false; }
default boolean charTyped(char chr, int modifiers, CompanionTabContext ctx) { return false; }
default boolean isCapturingTextInput() { return false; }
default void onTabOpened(CompanionTabContext ctx) {}
// Optional blocking yes/no confirmation overlay (used by the Wallet tab's transfer confirm dialog)
default boolean hasBlockingOverlay() { return false; }
default void renderBlockingOverlay(GuiGraphics graphics, int mouseX, int mouseY, CompanionTabContext ctx) {}
default boolean blockingOverlayMouseClicked(double mouseX, double mouseY, CompanionTabContext ctx) { return false; }
}Notes:
-
No lifecycle state is handed to you. Your implementation holds its own state (text fields,
scroll position, ...) exactly like CobbleCompanion's own built-in tabs do — a fresh
CompanionScreeninstance is created every time the player opens the window. -
isAvailableis checked by CompanionScreen to decide whether to show your tab as active or as the "not installed" placeholder — typically wired to a server-sent flag ("does the server have my companion mod loaded too?"). -
renderTopLayerContentruns after everything else inCompanionScreen.render()(same as Basis' own search-suggestion rendering) — use it for anything that must draw above other widgets, e.g. a suggestion dropdown under a search field.
Passed into every callback — gives you window geometry plus the shared mini-GUI toolkit
(CompanionScreen implements this interface itself and passes this):
public interface CompanionTabContext {
int guiLeft(); int guiTop(); int guiWidth(); int guiHeight();
int screenWidth(); int screenHeight();
void drawSmallLabel(GuiGraphics graphics, String text, int x, int y, float scale, int color, boolean bold, boolean uniformFont);
void drawScaledBoldText(GuiGraphics graphics, String text, int x, int y, float scale, int color);
int smallLabelWidth(String text, float scale, boolean bold, boolean uniformFont);
List<String> wrapText(String text, float scale, boolean bold, boolean uniformFont, int maxWidth);
void renderConfirmButton(GuiGraphics graphics, int x, int y, int w, int h, String label, int color, int mouseX, int mouseY);
void renderScrollbar(GuiGraphics graphics, int barX, int barWidth, int trackTop, int trackHeight, double scrollAmount, int maxScroll);
boolean isMouseOverScrollbar(double mouseX, double mouseY, int barX, int barWidth, int trackTop, int trackHeight);
double scrollAmountFromMouseY(double mouseY, int trackTop, int trackHeight, int maxScroll);
boolean isInRect(double mouseX, double mouseY, int x, int y, int w, int h);
void renderSearchSuggestions(GuiGraphics graphics, int mouseX, int mouseY, CobblemonSearchBox box, List<String> suggestions);
boolean handleSearchSuggestionClick(double mouseX, double mouseY, CobblemonSearchBox box, List<String> suggestions);
default void sendToServer(CustomPacketPayload payload) { /* channel-guarded, see below */ }
default String tr(String key) { /* I18n.get */ }
default String tr(String key, Object... args) { /* I18n.get */ }
}Use ctx.sendToServer(...), not PacketDistributor.sendToServer(...) directly. It's
guarded against sending a payload the connected server never negotiated (mixed-version /
vanilla-only servers) — a raw PacketDistributor call there throws
UnsupportedOperationException and crashes the client. This is the same guard CobbleCompanion's
own code uses everywhere internally.
Layout constants (padding, colors, etc.) are deliberately not part of this interface — bring your own, exactly like every built-in tab already does for its own content.
Add CobbleCompanion as a compileOnly dependency (you don't ship its classes, you only compile
against its public API — the same pattern CobbleCompanion's own split repos use against
each other, see their READMEs' "Building" sections):
dependencies {
compileOnly fileTree(dir: 'libs', include: ['CobbleCompanion-Basis*.jar'])
// ... your other dependencies
}Build CobbleCompanion-Basis-*.jar yourself from the
CobbleCompanion repo (./gradlew jar) and drop
the result into your own libs/ folder.
This mirrors exactly how CobbleCompanion: CobbleDollars registers its real Wallet tab.
package com.example.mycompanionaddon.client;
import com.cobblecompanion.api.CompanionTabContext;
import com.cobblecompanion.api.CompanionTabExtension;
import net.minecraft.client.gui.GuiGraphics;
public class MyTabExtension implements CompanionTabExtension {
@Override
public boolean isAvailable(CompanionTabContext ctx) {
return true; // or: some server-synced "my mod is loaded server-side too" flag
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick, CompanionTabContext ctx) {
ctx.drawScaledBoldText(graphics, "Hello from my addon!", ctx.guiLeft() + 10, ctx.guiTop() + 10, 1f, 0xFFFFFFFF);
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button, CompanionTabContext ctx) {
return false; // return true if you consumed the click
}
}Registration, in your mod's client constructor (guard it so a server-only install of your mod never touches this class — same as CobbleCompanion's own extensions):
// client-side init only, e.g. inside FMLClientSetupEvent or your mod's client constructor
CompanionExtensions.registerTab(CompanionScreen.TAB_WALLET, new MyTabExtension());That's it — no mixins, no reflection, no compile-time dependency from CobbleCompanion back to your mod.
Open an issue on the CobbleCompanion repo.