For Mod Developers

Custom Scopes

Configure a dedicated Shogi scope for mod-specific rules and effects.

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 yourmod first, before falling back to shogi

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 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 scope
  • scope.intValue(...) registers on your custom scope

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_owner or is_warp_plate to 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 defaultNamespaces order 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 you created the value with scope.intValue(...) / scope.booleanValue(...) instead of Shogi.intValue(...) / Shogi.booleanValue(...).

Next guides