Compare commits

..

11 Commits

Author SHA1 Message Date
Matt 01e1e4a502 pack: correct Numismatics coin IDs (5 denominations, not 4)
The 0.18.0 economy_policy.js named 'numismatics:sundial' which doesn't
exist -- the real registry IDs are spur, bevel, cog, crown, sun. The
KubeJS warning surfaced this at startup:

  Failed to read ingredient from numismatics:sundial:
  Item with ID numismatics:sundial does not exist!

Updates both economy_policy.js (recipe removal) and jei_hides.js (JEI
hide) to use the correct 5-denomination list. Bumps to 0.18.1.
2026-05-23 14:18:11 +00:00
Matt e53b4cc868 Merge pull request 'Economy MVP: Numismatics+Patchouli+welcome flow' (#15) from feature/economy-mvp into main 2026-05-23 14:16:45 +00:00
Matt 2ac4b2f2cd pack: economy MVP -- Numismatics + Patchouli + welcome flow, bump to 0.18.0
Adds the currency layer and the first-join experience.

Mods:
- Create: Numismatics 1.0.20  (currency, vendor blocks, piggy banks)
- Patchouli 1.21.1-93         (kept in pack for future rich manual book)

KubeJS scripts (server_scripts/):
- economy_policy.js  remove ALL Waystones recipes; remove coin recipes
                     only (piggy banks etc. stay craftable for now)
- welcome.js         first-login grant: 1 waystone + vanilla written-book
                     "Brass & Sigil Manual v1" (5 pages: welcome,
                     waystones, money, spawn plots, rules). Per-player
                     flag bnsWelcomeGranted in persistentData gates it.

KubeJS scripts (client_scripts/):
- jei_hides.js       hide all Waystones items + 4 coin denominations from
                     the JEI ingredient list. Combined with the recipe
                     removal players never encounter these items via
                     normal play -- only via the welcome grant or the
                     bank exchange.

The written-book manual is the MVP placeholder. When more content lands
(plot system, dynamic exchange, etc.) it'll be replaced by a proper
Patchouli book authored as a tweak jar -- Patchouli's already in the
lockfile so no mod-add will be needed at that point.
2026-05-23 14:16:45 +00:00
Matt d60c0176dd Merge pull request 'Waystones policy KubeJS (1/player + ban share/portstones)' (#14) from feature/waystones-policy-kubejs into main 2026-05-23 13:40:30 +00:00
Matt b064a9f515 pack: KubeJS Waystones policy (1-per-player + ban share/portstones)
Adds pack/overrides/kubejs/server_scripts/waystones_policy.js which:

- Caps regular waystones at 1 per player. Per-player count is stored in
  player.persistentData and decremented on break (works correctly because
  restrictedWaystones = ["PLAYER"] means only owners can break).
- Bans all 16 sharestone variants. They defeat the "build transport"
  intent (solo pair-teleport) and can be placed just outside a claim
  for stealth proximity access.
- Bans all 16 portstone variants pending separate design review.
- Leaves warp plates and warp scrolls untouched.

Bumps to 0.17.0 so launcher re-syncs.
2026-05-23 13:40:29 +00:00
Matt c29dd2d690 Merge pull request 'Fix gitignore + track Waystones override' (#13) from fix/gitignore-overrides into main 2026-05-23 12:57:42 +00:00
Matt 0bdc48c86f gitignore: scope overrides ignore to mods/, track defaultconfigs/
The previous rule 'pack/overrides/' ignored the whole directory, which
made it impossible to re-include subpaths via ! exception (git's rule).

Narrow the ignore to 'pack/overrides/mods/' -- the only path that
Build-Tweaks.ps1 actually writes -- and drop the broken whitelist
block. Other subdirs under pack/overrides/ (defaultconfigs/, etc.) now
live in git like normal source.

Adds the Waystones override that 0.16.1 attempted (spawnInVillages =
DISABLED). Bumps to 0.16.3 so launcher caches re-sync.
2026-05-23 12:57:41 +00:00
Matt 02e50f2fe5 Merge pull request 'Track pack/overrides/defaultconfigs/ + Waystones override' (#12) from feature/track-defaultconfigs into main 2026-05-23 12:56:13 +00:00
Matt 50007e1109 pack: track pack/overrides/defaultconfigs/ + ship Waystones override
.gitignore previously ignored all of pack/overrides/ because that dir is
the build output of Build-Tweaks.ps1. But defaultconfigs/ inside it holds
hand-written source-of-truth configs that should be in git (otherwise
they don't survive cross-machine deploys). Whitelisting that subdir.

Adds pack/overrides/defaultconfigs/waystones-common.toml -- the same
file the empty 0.16.1 commit was supposed to ship but couldn't because
of the .gitignore rule. Bumps to 0.16.2.

The override changes just one key:
- spawnInVillages: REGULAR -> DISABLED

All other Waystones settings stay at mod default (ACTIVATION visibility,
PLAYER restriction, empty allowedVisibilities) which already provides
the base-intrusion protections we discussed.
2026-05-23 12:56:13 +00:00
Matt d4dfcb1e11 Merge pull request 'Tune Waystones config + ship via defaultconfigs' (#11) from feature/waystones-config into main 2026-05-23 12:55:19 +00:00
Matt 3dc0767a24 pack: tune Waystones config + bump to 0.16.1
Ships an override of waystones-common.toml under pack/overrides/
defaultconfigs/ so first-run installs (clients + fresh servers) get the
intended defaults. NeoForge auto-copies defaultconfigs/<file> into
config/<file> on first run only -- player-tweaked configs after that
are preserved across pack updates.

The only override vs mod-default in this commit:
- spawnInVillages: REGULAR -> DISABLED

Everything else stays at mod default, which already gives the security
posture we want:
- defaultVisibility = ACTIVATION  -> each player must physically visit
  a waystone before they can teleport TO it. Solves the "player B
  teleports into player A's base" concern without a hard per-player
  placement cap (which v21.x dropped).
- restrictedWaystones = [PLAYER]  -> only owners can edit their own
  waystones.
- allowedVisibilities = []        -> no player UI can create global
  waystones; OPs use the /waystone command for server-public ones.

Wild waystones (in the open world, not villages) stay on -- they're
public-good fast travel, not tied to bases.
2026-05-23 12:55:18 +00:00
7 changed files with 337 additions and 2 deletions
+2 -1
View File
@@ -55,8 +55,9 @@ CLAUDE.md
# ─── Build artifacts that get regenerated ───────────────────────────────── # ─── Build artifacts that get regenerated ─────────────────────────────────
# Tweak jars rebuilt by scripts/Build-Tweaks.ps1 # Tweak jars rebuilt by scripts/Build-Tweaks.ps1
pack/overrides/ pack/overrides/mods/
# manifest.json regenerated by scripts/Build-Pack.ps1 -- produced at scripts/ # manifest.json regenerated by scripts/Build-Pack.ps1 -- produced at scripts/
# (default OutputPath) and copied to the deploy share by Deploy-Brass.ps1 # (default OutputPath) and copied to the deploy share by Deploy-Brass.ps1
scripts/manifest.json scripts/manifest.json
pack/manifest.json pack/manifest.json
@@ -0,0 +1,87 @@
[general]
#List of waystone origins that should prevent others from editing. PLAYER is special in that it allows only edits by the owner of the waystone.
restrictedWaystones = ["PLAYER"]
#Set to "GLOBAL" to have newly placed or found waystones be global by default.
#Allowed Values: ACTIVATION, GLOBAL, SHARD_ONLY, ORANGE_SHARESTONE, MAGENTA_SHARESTONE, LIGHT_BLUE_SHARESTONE, YELLOW_SHARESTONE, LIME_SHARESTONE, PINK_SHARESTONE, GRAY_SHARESTONE, LIGHT_GRAY_SHARESTONE, CYAN_SHARESTONE, PURPLE_SHARESTONE, BLUE_SHARESTONE, BROWN_SHARESTONE, GREEN_SHARESTONE, RED_SHARESTONE, BLACK_SHARESTONE
defaultVisibility = "ACTIVATION"
#Add "GLOBAL" to allow every player to create global waystones.
allowedVisibilities = []
#The time in ticks that it takes to use a warp stone. This is the charge-up time when holding right-click.
warpStoneUseTime = 32
#The time in ticks that it takes to use a warp plate. This is the time the player has to stand on top for.
warpPlateUseTime = 15
#The time in ticks it takes to use a scroll. This is the charge-up time when holding right-click.
scrollUseTime = 32
[inventoryButton]
#Set to 'NONE' for no inventory button. Set to 'NEAREST' for an inventory button that teleports to the nearest waystone. Set to 'ANY' for an inventory button that opens the waystone selection menu. Set to a waystone name for an inventory button that teleports to a specifically named waystone.
inventoryButton = ""
#The x position of the inventory button in the inventory.
inventoryButtonX = 58
#The y position of the inventory button in the inventory.
inventoryButtonY = 60
#The y position of the inventory button in the creative menu.
creativeInventoryButtonX = 88
#The y position of the inventory button in the creative menu.
creativeInventoryButtonY = 33
[worldGen]
#Set to 'DEFAULT' to only generate the normally textured waystones. Set to 'MOSSY' or 'SANDY' to generate all as that variant. Set to 'BIOME' to make the style depend on the biome it is generated in.
#Allowed Values: DEFAULT, MOSSY, SANDY, BLACKSTONE, DEEPSLATE, END_STONE, BIOME
wildWaystoneStyle = "BIOME"
#Approximate chunk distance between wild waystones being generated. Set to 0 to disable generation.
chunksBetweenWildWaystones = 25
#List of dimensions that wild waystones are allowed to spawn in. If left empty, all dimensions except those in wildWaystonesDimensionDenyList will be able to spawn waystones, provided the biomes are in the `has_structure/waystone` tag.
wildWaystonesDimensionAllowList = ["minecraft:the_nether", "minecraft:overworld", "minecraft:the_end"]
#List of dimensions that wild waystones are not allowed to spawn in, even if the biome is in the `has_structure/waystone` tag. Only used if wildWaystonesDimensionAllowList is empty.
wildWaystonesDimensionDenyList = []
#Set to 'PRESET_FIRST' to first use names from the nameGenerationPresets. Set to 'PRESET_ONLY' to use only those custom names. Set to 'MIXED' to have some waystones use custom names, and others random names.
#Allowed Values: PRESET_FIRST, RANDOM_ONLY, PRESET_ONLY, MIXED
nameGenerationMode = "PRESET_FIRST"
#The template to use when generating new names. Supported placeholders are {Biome} (english biome name) and {MrPork} (the default name generator).
nameGenerationTemplate = "{MrPork}"
#These names will be used for the PRESET name generation mode. See the nameGenerationMode option for more info.
nameGenerationPresets = []
#Set to REGULAR to have waystones spawn in some villages. Set to FREQUENT to have waystones spawn in most villages. Set to DISABLED to disable waystone generation in villages. Waystones will only spawn in vanilla or supported villages.
#Allowed Values: DISABLED, REGULAR, FREQUENT
spawnInVillages = "DISABLED"
[teleports]
#Set to false to simply disable all xp costs. See warpRequirements for more fine-grained control.
enableCosts = true
#Set to false to simply disable all cooldowns. See warpRequirements for more fine-grained control.
enableCooldowns = true
#List of warp requirements with comma-separated parameters in parentheses. Conditions can be defined as comma-separated list in square brackets. Will be applied in order.
warpRequirements = ["[is_not_interdimensional] scaled_add_xp_cost(distance, 0.01)", "[is_interdimensional] add_xp_cost(27)", "[source_is_warp_plate] multiply_xp_cost(0)", "[target_is_global] multiply_xp_cost(0)", "min_xp_cost(0)", "max_xp_cost(27)", "[source_is_inventory_button] add_cooldown(inventory_button, 300)"]
#Set to ENABLED to have nearby pets teleport with you. Set to SAME_DIMENSION to have nearby pets teleport with you only if you're not changing dimensions. Set to DISABLED to disable.
#Allowed Values: ENABLED, SAME_DIMENSION, DISABLED
transportPets = "DISABLED"
#Set to ENABLED to have leashed mobs teleport with you. Set to SAME_DIMENSION to have leashed mobs teleport with you only if you're not changing dimensions. Set to DISABLED to disable.
#Allowed Values: ENABLED, SAME_DIMENSION, DISABLED
transportLeashed = "ENABLED"
#List of entities that cannot be teleported, either as pet, leashed, or on warp plates.
entityDenyList = ["minecraft:wither"]
#Set to true to enable warp modifier items for applying status effects on teleports.
enableModifiers = true
[client]
#If enabled, the text overlay on waystones will no longer always render at full brightness.
disableTextGlow = false
[compatibility]
#If enabled, JourneyMap waypoints will be created for each activated waystone.
journeyMap = true
#If enabled, JourneyMap waypoints will only be created if the mod 'JourneyMap Integration' is not installed
preferJourneyMapIntegrationMod = true
#If enabled, Waystones will add markers for waystones and sharestones to Dynmap.
dynmap = true
[blueMap]
#Controls whether Waystones' BlueMap integration should be loaded
enabled = true
#If enabled, waystones will be tracked as markers on BlueMap
includeWaystones = true
#If enabled, sharestones will be tracked as markers on BlueMap.
includeSharestones = true
#If enabled, undiscovered waystones will be tracked as markers on BlueMap.
includeUndiscoveredWaystones = false
@@ -0,0 +1,53 @@
// Brass & Sigil — JEI hides
//
// Hides all Waystones items + all Numismatics coin items from the JEI
// item list. Combined with the server-side recipe removal in
// economy_policy.js, players don't see crafting recipes OR the item
// icons in the JEI ingredient list -- they only ever encounter these
// items through the welcome grant or the bank exchange.
const COLORS = [
'white', 'orange', 'magenta', 'light_blue', 'yellow', 'lime',
'pink', 'gray', 'light_gray', 'cyan', 'purple', 'blue',
'brown', 'green', 'red', 'black',
];
const WAYSTONE_ITEMS = [
// regular waystone variants
'waystones:waystone',
'waystones:mossy_waystone',
'waystones:sandy_waystone',
'waystones:blackstone_waystone',
'waystones:deepslate_waystone',
'waystones:end_stone_waystone',
// handheld + plate
'waystones:warp_stone',
'waystones:warp_scroll',
'waystones:bound_scroll',
'waystones:return_scroll',
'waystones:warp_plate',
// sharestones (16 colors) + shards
'waystones:sharestone', // base id if registered
];
COLORS.forEach(c => {
WAYSTONE_ITEMS.push(`waystones:${c}_sharestone`);
WAYSTONE_ITEMS.push(`waystones:${c}_portstone`);
});
const COIN_ITEMS = [
'numismatics:spur',
'numismatics:bevel',
'numismatics:cog',
'numismatics:crown',
'numismatics:sun',
];
JEIEvents.hideItems(event => {
[...WAYSTONE_ITEMS, ...COIN_ITEMS].forEach(id => {
try { event.hide(id); } catch (e) {
// Some IDs may not exist (e.g. base 'waystones:sharestone' isn't
// registered if only colored variants are). Swallow per-item
// failures so one bad id doesn't break the whole pass.
}
});
});
@@ -0,0 +1,26 @@
// Brass & Sigil — economy policy (recipe removal)
//
// Players can't craft Waystones items (they're distributed by the welcome
// grant -- see welcome.js -- and any replacements come from the bank
// vendor in the future). Players also can't manufacture currency: coins
// only enter circulation through the bank exchange (item-in / coin-out).
//
// Other Numismatics blocks (piggy banks, bank tellers, vendor blocks) stay
// craftable -- those are infrastructure, not currency.
ServerEvents.recipes(event => {
// Block ALL crafting of Waystones items. The welcome-grant script
// gives each new player exactly one regular waystone; any further
// recovery happens via OP /give or future bank vendor.
event.remove({ mod: 'waystones' });
// Block coin manufacturing only -- 5 denominations.
// Real registry IDs: spur, bevel, cog, crown, sun.
[
'numismatics:spur',
'numismatics:bevel',
'numismatics:cog',
'numismatics:crown',
'numismatics:sun',
].forEach(coin => event.remove({ output: coin }));
});
@@ -0,0 +1,93 @@
// Brass & Sigil — Waystones placement policy
//
// Rules:
// - Regular waystones (6 stone variants): 1 per player. Forces players to
// build actual transport infrastructure (trains/roads/canals) for
// long-distance routes instead of waystone-spamming.
// - Sharestones (16 colors): banned. Pair-teleport between solo-placed
// sharestones defeats the "build transport" intent, and a sharestone
// placed just outside someone's claim gives an unrestricted teleport-
// adjacent vector.
// - Portstones (16 colors): banned pending separate design discussion.
// - Warp plates: not restricted (single-block teleport pad; revisit if
// it becomes a workaround).
// - Warp scrolls / warp stones: untouched (consumable / cooldown-bound).
//
// Per-player count lives in player.persistentData.bnsWaystoneCount.
// Decrement on break works because waystones-common.toml has
// restrictedWaystones = ["PLAYER"] -- only the owner can break their own
// waystone, so breaker == owner in normal play.
const WAYSTONE_CAP = 1;
const REGULAR_WAYSTONES = new Set([
'waystones:waystone',
'waystones:mossy_waystone',
'waystones:sandy_waystone',
'waystones:blackstone_waystone',
'waystones:deepslate_waystone',
'waystones:end_stone_waystone',
]);
// Build the colored-block ban list once instead of typing 32 strings.
const COLORS = [
'white', 'orange', 'magenta', 'light_blue', 'yellow', 'lime',
'pink', 'gray', 'light_gray', 'cyan', 'purple', 'blue',
'brown', 'green', 'red', 'black',
];
const BANNED_BLOCKS = new Set();
COLORS.forEach(c => {
BANNED_BLOCKS.add(`waystones:${c}_sharestone`);
BANNED_BLOCKS.add(`waystones:${c}_portstone`);
});
// Helper: replace the just-placed block with air and refund the item.
function refund(event, blockId) {
event.block.set('minecraft:air');
if (event.player) {
event.player.give(Item.of(blockId));
}
}
BlockEvents.placed(event => {
const blockId = event.block.id;
const player = event.player;
// Skip non-player placements (dispensers, mob events) -- nothing to refund.
if (!player) return;
if (BANNED_BLOCKS.has(blockId)) {
refund(event, blockId);
player.tell(Text.red(
'That block is disabled. Use a regular Waystone (1 per player) ' +
'and build transport between distant bases.'
));
return;
}
if (REGULAR_WAYSTONES.has(blockId)) {
const data = player.persistentData;
const current = data.getInt('bnsWaystoneCount');
if (current >= WAYSTONE_CAP) {
refund(event, blockId);
player.tell(Text.red(
`You already have ${WAYSTONE_CAP} waystone. Break your existing one ` +
`to relocate, or build infrastructure to reach distant bases.`
));
return;
}
data.putInt('bnsWaystoneCount', current + 1);
}
});
BlockEvents.broken(event => {
const blockId = event.block.id;
if (!REGULAR_WAYSTONES.has(blockId)) return;
const player = event.player;
if (!player) return; // explosion / world clear -- accept count drift, rare
const data = player.persistentData;
const current = data.getInt('bnsWaystoneCount');
if (current > 0) {
data.putInt('bnsWaystoneCount', current - 1);
}
});
@@ -0,0 +1,53 @@
// Brass & Sigil — first-join welcome grant
//
// Gives each new player exactly 1 Waystone + 1 server manual book on
// first login. Gated by a per-player persistent flag so it fires once
// even if the player relogs.
//
// Future: replace the vanilla written book with a Patchouli book in a
// custom tweak jar once we have enough content to warrant the rich
// formatting. Patchouli is already in the pack so swapping later is
// purely a content+recipe change, no mod-add needed.
const WELCOME_FLAG = 'bnsWelcomeGranted';
const MANUAL_PAGES = [
'Welcome to Brass & Sigil.\n\nThis manual covers the basics. Read it once, then keep it or toss it.',
'WAYSTONES\n\nYou have one Waystone item. Place it at your base -- it is the only one you get.\n\nNeed to travel further? Build trains, roads, or Create Aeronautics planes.',
'MONEY\n\nCoins cannot be crafted.\n\nTake valuables (diamonds etc.) to the bank at spawn. The exchange will trade them for coins.\n\nDynamic rates are planned.',
'SPAWN PLOTS\n\nComing soon: buy a small commercial plot at spawn to set up your shop. One plot per player.\n\nYour home base goes OUT in the world -- claim it with FTB Chunks (free, anywhere).',
'RULES\n\n- Be excellent to each other.\n- No griefing.\n- Lost your Waystone? Ask an OP or buy one from the bank (soon).\n- Report bugs to staff.\n\nHave fun.',
];
function giveManual(player) {
// 1.21 components-based written book: pages are filterable text
// components, title supports the same. Plain strings are accepted.
const book = Item.of('minecraft:written_book');
book.set('minecraft:written_book_content', {
title: { raw: 'Brass & Sigil Manual' },
author: 'Server Staff',
pages: MANUAL_PAGES.map(text => ({ raw: text })),
resolved: true,
});
player.give(book);
}
function giveStarterWaystone(player) {
player.give(Item.of('waystones:waystone'));
}
PlayerEvents.loggedIn(event => {
const player = event.player;
const data = player.persistentData;
if (data.getBoolean(WELCOME_FLAG)) return;
try {
giveStarterWaystone(player);
giveManual(player);
data.putBoolean(WELCOME_FLAG, true);
player.tell(Text.gold('Welcome to Brass & Sigil. Check your inventory for a Waystone and the server manual.'));
} catch (e) {
// If something goes wrong, don't set the flag -- player gets another chance.
console.error(`[bns/welcome] failed for ${player.username}: ${e}`);
}
});
+23 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "Brass-and-Sigil pack.lock.json - generated, do not edit by hand unless you know what you are doing", "$schema": "Brass-and-Sigil pack.lock.json - generated, do not edit by hand unless you know what you are doing",
"name": "Brass and Sigil", "name": "Brass and Sigil",
"version": "0.16.0", "version": "0.18.1",
"minecraft": "1.21.1", "minecraft": "1.21.1",
"loader": { "loader": {
"type": "neoforge", "type": "neoforge",
@@ -481,6 +481,28 @@
"sha1": "488bc70db3730f209e65cbda04c36dbc3918c448", "sha1": "488bc70db3730f209e65cbda04c36dbc3918c448",
"size": 878265, "size": 878265,
"side": "both" "side": "both"
},
{
"source": "modrinth",
"slug": "numismatics",
"versionId": "guON3qvQ",
"version": "1.0.20+neoforge-mc1.21.1",
"path": "mods/CreateNumismatics-1.0.20+neoforge-mc1.21.1.jar",
"url": "https://cdn.modrinth.com/data/Jdbbtt0i/versions/guON3qvQ/CreateNumismatics-1.0.20%2Bneoforge-mc1.21.1.jar",
"sha1": "9883bfd6fef5bd5068f9174ddccba5047849c922",
"size": 693446,
"side": "both"
},
{
"source": "modrinth",
"slug": "patchouli",
"versionId": "BIogJv2D",
"version": "1.21.1-93-neoforge",
"path": "mods/Patchouli-1.21.1-93-NEOFORGE.jar",
"url": "https://cdn.modrinth.com/data/nU0bVIaL/versions/BIogJv2D/Patchouli-1.21.1-93-NEOFORGE.jar",
"sha1": "5413bb9b8fc35ebe46b06b48bf9afafbd8471140",
"size": 646777,
"side": "both"
} }
], ],
"defaultServer": { "defaultServer": {