Use a custom ShogiScope when your mod needs its own rule vocabulary instead of exposing everything on Shogi's global default scope.
This is the pattern used by mods like Waystones: values are registered on a dedicated scope, and that scope exposes extra effects such as is_inventory_button, is_owner, or is_warp_stone.
Create the scope once, usually in the same class that declares your Shogi values:
public class YourModRules {
public static final ShogiScope scope = Shogi.scope(id("rules"), it -> {
it.setDefaultNamespaces(List.of("yourmod", "shogi"));
});
}
What this does:
yourmod:rulesyourmod first, before falling back to shogiIf you do not call setDefaultNamespaces(...), the scope will only default to its own namespace.
Simple no-argument effects can be added with registerSimpleEffect(...):
public static final ShogiScope scope = Shogi.scope(id("rules"), it -> {
it.setDefaultNamespaces(List.of("yourmod", "shogi"));
it.registerSimpleEffect(id("is_special_mode"), context ->
context.level().dimension().location().getPath().equals("the_end"));
it.registerSimpleEffect(id("is_owner"), context ->
context.entity() instanceof ServerPlayer player && isOwner(player, context));
});
For parameterized effects, register a codec and optional positional argument names:
public static final ShogiScope scope = Shogi.scope(id("rules"), it -> {
it.setDefaultNamespaces(List.of("yourmod", "shogi"));
it.registerEffect(IsWithinCharge.IDENTIFIER, IsWithinCharge.MAP_CODEC, List.of("charge"));
it.registerEffect(ApplyCost.IDENTIFIER, ApplyCost.mapCodec(it), List.of("amount"));
});
The positional names are what let users write concise expressions like:
is_within_charge(200) -> apply_cost(3)
That is the same shape Waystones uses for registrations such as:
it.registerEffect(IsWithinDistance.IDENTIFIER, IsWithinDistance.MAP_CODEC, List.of("distance"))it.registerSimpleEffect(id("is_inventory_button"), ...)Once you have a scope, create values from the scope itself instead of from Shogi.*Value(...):
public class YourModRules {
public static final ShogiScope scope = Shogi.scope(id("rules"), it -> {
it.setDefaultNamespaces(List.of("yourmod", "shogi"));
it.registerSimpleEffect(id("is_special_mode"), context -> isSpecialMode(context));
});
public static final ShogiValue<YourContext, Integer> actionCost =
scope.intValue(id("action_cost"), context -> 5);
public static final ShogiValue<YourContext, Boolean> allowTeleport =
scope.booleanValue(id("allow_teleport"), context -> true);
}
This is the important difference:
Shogi.intValue(...) registers on Shogi's default scopescope.intValue(...) registers on your custom scopeAny override for action_cost will now be parsed against your scope, so your custom effects are available there.
Rule file locations are derived from the scope identifier.
For a value yourmod:action_cost on scope yourmod:rules:
config/yourmod.rules.jsondata/yourmod/yourmod/rules/action_cost.jsonIf your scope path contains slashes, config files replace them with dots:
yourmod:teleports/rulesconfig/yourmod.teleports.rules.jsondata/<propertyNamespace>/yourmod/teleports/rules/Because the scope can default to both your namespace and shogi when you set them with setDefaultNamespaces, users can mix your effects with built-in Shogi ones without writing full namespaces every time.
Example expressions for a Waystones-style scope:
is_inventory_button -> cooldown_cost('inventory_button', '300s')
is_warp_stone -> damage_item(80)
is_owner + is_global -> xp_points_cost(0)
These work because the scope recognizes mod-specific names like is_inventory_button and Shogi names like cooldown_cost in the same rule set.
Use a custom scope when:
is_owner or is_warp_plate to resolve cleanlydefaultNamespaces order does not include the namespace you expect.config/<scopeNamespace>.<scopePath>.json.scope.intValue(...) / scope.booleanValue(...) instead of Shogi.intValue(...) / Shogi.booleanValue(...).