Custom Scopes
Goal
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.
1. Create a dedicated scope
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:
- creates a separate scope identified as
yourmod:rules - keeps your custom effects out of Shogi's global default scope
- allows unqualified names to resolve from
yourmodfirst, before falling back toshogi
If you do not call setDefaultNamespaces(...), the scope will only default to its own namespace.
2. Register custom effects on that scope
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"), ...)
3. Define values on the custom scope
Once you have a scope, create values from the scope itself:
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);
}
Any override for action_cost will now be parsed against your scope, so your custom effects are available there.
4. Tell users where rules go
Rule file locations are derived from the scope identifier.
For a value yourmod:action_cost on scope yourmod:rules:
- config file:
config/yourmod.rules.json - datapack file:
data/yourmod/yourmod/rules/action_cost.json
If your scope path contains slashes, config files replace them with dots:
- scope
yourmod:teleports/rules - config file
config/yourmod.teleports.rules.json - datapack prefix
data/<propertyNamespace>/yourmod/teleports/rules/
5. What users can write
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.
When to use a custom scope
Use a custom scope when:
- your mod needs effect names that only make sense for its own context
- you want shorthand names like
is_owneroris_warp_plateto resolve cleanly - values for one integration should not accidentally expose effects from another integration
- you want to future-proof and avoid breaking changes should you need to expand in the future
Troubleshooting
- Unknown function/effect: the effect was not registered on the same scope as the value being resolved.
- Effect only works with full namespace: your
defaultNamespacesorder does not include the namespace you expect. - Rules are not loading: check the scope id and make sure the file path matches
config/<scopeNamespace>.<scopePath>.json. - Value resolves but custom rules never apply: make sure the value was created from the same scope that owns the rule file.