-
Notifications
You must be signed in to change notification settings - Fork 1
Scripting Modifying Logic with Scripts EN
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.
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.
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;
});
});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.
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.
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");
}
}
});
});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>);
}
}
});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.
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.
}
});
});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.
}
});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;
});
});SDMShop.register(event => {
event.registerAfterPurchaseEvent((offer, player, chosenGroupId, amount) => {
console.info("Player " + player.name.string + " bought " + amount + " item(s)");
});
});SDMShop.register(event => {
event.registerPurchaseFailedEvent((offer, player, chosenGroupId, amount, reason) => {
console.info("Purchase failed: " + reason);
});
});Failure reasons:
INVALID_INPUTSCRIPT_CANCELLEDCONDITION_FAILEDLIMIT_FAILEDNO_COST_GROUPINVALID_COSTCANNOT_PAYPAYMENT_FAILEDREWARD_FAILED
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.
These methods are available in KubeJS through the event object inside SDMShop.register and in CraftTweaker through ShopScripting.
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");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.
- Use unique
script_idvalues, for examplemy_mod:vip_stage. - For conditions, return
truefor 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:discountorsdm:price_modifierfirst; use script price events for logic that cannot be expressed through JSON components.