Skip to content

Handling super calls

Lukas22041 edited this page Aug 29, 2026 · 3 revisions

When doing broad subtype-matched patches, you may eventually run into an issue related to super calls. To visualize this issue, let's start with a patch that modifies the tooltip of all BaseSpecialItemPlugin classes.

The createTooltip method exists to create the tooltip of a special item when hovered over, so let's make a patch for it.

@Patch(target = @ClassMatch(subtype = BaseSpecialItemPlugin.class))
public class ShipBPPatch {

    @After(target = @MethodMatch(methodName = "createTooltip"))
    public static void afterCreateTooltip(AfterContext context) {
        TooltipMakerAPI tooltip = (TooltipMakerAPI) context.getArg(0);

        SpecialItemPlugin self = (SpecialItemPlugin) context.getSelf();

        tooltip.addSpacer(10f);
        tooltip.addPara("Added tooltip to " + self.getName(),
                0f, Misc.getHighlightColor(), Misc.getHighlightColor());
    }
}

However, if we then inspect the result of our patch in-game, we get a bizarre result. For some reason, the tooltip was added three times instead of just once, each time in a different location.

Screenshot

Finding the cause

To understand why this occurs, we can take a look at the code we are patching. The in-game example we inspected is a ship blueprint, so we should view its code.

//From ShipBlueprintItemPlugin.java
@Override
public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, CargoTransferHandlerAPI transferHandler, Object stackSource) {
    super.createTooltip(tooltip, expanded, transferHandler, stackSource);

    float pad = 3f;
    float opad = 10f;
    float small = 5f;

    //... Rest of method
}

What jumps out is that the method is calling super.createTooltip(tooltip, expanded, transferHandler, stackSource); at the start.

This is relevant because when doing a subtype targeting patch, you patch the inheritor and the original method. This means that the super call will also call the patched inherited method.

To solve this issue, you have to make use of the isMostDerivedCall() method that is available from the context. It returns true only when the executed method is the highest-level override available on its instance's type.

@Patch(target = @ClassMatch(subtype = BaseSpecialItemPlugin.class))
public class ShipBPPatch {

    @After(target = @MethodMatch(methodName = "createTooltip"))
    public static void afterCreateTooltip(AfterContext context) {
        TooltipMakerAPI tooltip = (TooltipMakerAPI) context.getArg(0);

        SpecialItemPlugin self = (SpecialItemPlugin) context.getSelf();

        //Only run for the "highest level" call of the method.
        if (context.isMostDerivedCall()) {
            tooltip.addSpacer(10f);
            tooltip.addPara("Added tooltip to " + self.getName(),
                    0f, Misc.getHighlightColor(), Misc.getHighlightColor());
        }
    }
}

Now we can check the result in-game. This shows us that we partially solved the issue, but there is still a second tooltip there.

Screenshot

Secondary issue

This issue comes from the fact that there are two methods within the inherited class with the name createTooltip. Our current patch will target both.

//From BaseSpecialItemPlugin.java

public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, CargoTransferHandlerAPI transferHandler, Object stackSource) {
    // ... Start of method
    createTooltip(tooltip, expanded, transferHandler, stackSource, useGray);
}
public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, CargoTransferHandlerAPI transferHandler, Object stackSource, boolean useGray) {
    // ... Rest of Method
}

The first createTooltip method is the one that we wanted to patch. isMostDerivedCall will return false here, since we are overriding it a layer above. This is not the case for the second declaration of the method, which is not overridden in ShipBlueprintItemPlugin. As such, isMostDerivedCall will actually return true.

The fix for this issue is simple: we just need to narrow it down to one of the two methods. For our case, we only want to patch the first one, so the quickest fix is to add the parameterCount condition to our @MethodMatch.

@Patch(target = @ClassMatch(subtype = BaseSpecialItemPlugin.class))
public class ShipBPPatch {
                                                               //Add the parameter condition
    @After(target = @MethodMatch(methodName = "createTooltip", parameterCount = 4))
    public static void afterCreateTooltip(AfterContext context) {
        TooltipMakerAPI tooltip = (TooltipMakerAPI) context.getArg(0);

        SpecialItemPlugin self = (SpecialItemPlugin) context.getSelf();

        if (context.isMostDerivedCall()) {
            tooltip.addSpacer(10f);
            tooltip.addPara("Added tooltip to " + self.getName(),
                    0f, Misc.getHighlightColor(), Misc.getHighlightColor());
        }
    }
}

Screenshot

Clone this wiki locally