Skip to content

Scripting Modifying Logic with Scripts EN

Bogdan edited this page Aug 5, 2026 · 1 revision

Shop Scripting

SDM Shop 2 provides extension points for KubeJS, CraftTweaker, and Java addons. Scripts can add conditions, rewards, modify the final price, and react to shop loading.

Supported JSON components:

  • sdm:condition_script — server-side purchase condition.
  • sdm:reward_script — server-side reward.

Transaction hooks are also available:

  • registerBeforePurchaseEvent — can cancel a purchase before conditions/payment.
  • registerAfterPurchaseEvent — called after a successful purchase, limiter updates, and balance sync.
  • registerPurchaseFailedEvent — called when a purchase is rejected with a reason.

Script Condition

Offer JSON:

{
  "type": "sdm:condition_script",
  "script_id": "my_script_id"
}

If the same offer also has sdm:hide_render, the UI requests the condition result from the server and hides the offer when the script returns false.

KubeJS

SDMShop.register(event => {
    event.registerConditionEvent((player, component, scriptId) => {
        if (scriptId == "my_script_id") {
            return player.stages.has("one");
        }

        // For unknown script_id values, returning true is safer
        // so other script conditions are not broken.
        return true;
    });
});

CraftTweaker

import mods.sdmshop.scripting.ShopScripting;

ShopScripting.registerConditionEvent((player, component, scriptId) => {
    if (scriptId == "my_script_id") {
        return (<item:minecraft:cobblestone> in player.inventory);
    }

    return true;
});

Important: if there are no listeners for script-condition at all, the core returns false. Therefore every used script_id must have a registered handler.

Script Reward

Offer JSON:

{
  "type": "sdm:reward_script",
  "script_id": "give_custom_reward"
}

The reward runs after successful payment. The callback receives amount, the number of purchased offer units.

KubeJS

SDMShop.register(event => {
    event.registerRewardEvent((player, amount, component, scriptId) => {
        if (scriptId == "give_custom_reward") {
            for (let i = 0; i < amount; i++) {
                player.give("minecraft:diamond");
            }
        }
    });
});

CraftTweaker

import mods.sdmshop.scripting.ShopScripting;

ShopScripting.registerRewardEvent((player, amount, component, scriptId) => {
    if (scriptId == "give_custom_reward") {
        for i in 0 .. amount {
            player.give(<item:minecraft:diamond>);
        }
    }
});

Price Modification

The price event runs after normal promo effects and before final price sanitization. The handler receives a map CostComponent -> Double for the selected payment group.

KubeJS

SDMShop.register(event => {
    event.registerPriceEvent((offer, server, chosenGroupId, prices) => {
        if (chosenGroupId == "vip") {
            // prices is a Java Map<CostComponent, Double>.
            // Change values using the method supported by your KubeJS bridge version.
        }
    });
});

CraftTweaker

import mods.sdmshop.scripting.ShopScripting;

ShopScripting.registerPriceEvent((offer, server, chosenGroupId, prices) => {
    if (chosenGroupId == "vip") {
        // Change the map values using the method supported by your CT bridge version.
    }
});

Purchase Hooks

Before Purchase

If the callback returns false, the purchase is cancelled with reason SCRIPT_CANCELLED.

SDMShop.register(event => {
    event.registerBeforePurchaseEvent((offer, player, chosenGroupId, amount) => {
        if (amount > 64) {
            return false;
        }

        return true;
    });
});

After Successful Purchase

SDMShop.register(event => {
    event.registerAfterPurchaseEvent((offer, player, chosenGroupId, amount) => {
        console.info("Player " + player.name.string + " bought " + amount + " item(s)");
    });
});

Purchase Failure

SDMShop.register(event => {
    event.registerPurchaseFailedEvent((offer, player, chosenGroupId, amount, reason) => {
        console.info("Purchase failed: " + reason);
    });
});

Failure reasons:

  • INVALID_INPUT
  • SCRIPT_CANCELLED
  • CONDITION_FAILED
  • LIMIT_FAILED
  • NO_COST_GROUP
  • INVALID_COST
  • CANNOT_PAY
  • PAYMENT_FAILED
  • REWARD_FAILED

Shop Loading

ScriptShopLoadEvent runs when the script manager reloads and lets addons create or modify shops programmatically. On reload, listeners are cleared and the KubeJS event is registered again, so handlers should be declared in the normal script loading location.

Convenient API Methods

These methods are available in KubeJS through the event object inside SDMShop.register and in CraftTweaker through ShopScripting.

Promo

event.triggerGlobalPromo("boss_killed");
event.triggerPlayerPromo(player, "personal_bonus");
event.stopGlobalPromoTrigger("boss_killed");
event.stopPlayerPromoTrigger(player, "personal_bonus");

event.activateGlobalPromo("manual_sale", 60000);
event.activatePlayerPromo(player, "personal_sale", 60000);
event.deactivateGlobalPromo("manual_sale");
event.deactivatePlayerPromo(player, "personal_sale");

event.isGlobalPromoActive("manual_sale");
event.isPlayerPromoActive(player, "personal_sale");

Limits

event.resetOfferLimits("a94d5c7c-efb0-4c7e-a527-9e11b609151d");
event.resetWorldLimits("a94d5c7c-efb0-4c7e-a527-9e11b609151d");
event.resetPlayerLimits(player, "a94d5c7c-efb0-4c7e-a527-9e11b609151d");

event.canPurchase(player, "a94d5c7c-efb0-4c7e-a527-9e11b609151d", 1);
event.getAvailableLimit(player, "a94d5c7c-efb0-4c7e-a527-9e11b609151d");
event.syncLimiterData(player);

Limit methods take the offer UUID as a string. If the UUID is invalid or the offer is not found, methods safely return false/0.

Practical Tips

  • Use unique script_id values, for example my_mod:vip_stage.
  • For conditions, return true for unknown IDs if one listener handles multiple components.
  • Script conditions are server-side: do not put client-only logic into them.
  • For prices, try standard sdm:discount or sdm:price_modifier first; use script price events for logic that cannot be expressed through JSON components.

Clone this wiki locally