econ: sell shop + bounty engine + player commands + FTB Ranks 13-tier
Foundation economy stack — fully command-driven, ready for the custom
UI mod to layer on top later. Built tonight as a single feature batch.
KubeJS scripts:
pack/overrides/kubejs/server_scripts/economy/
00_tier_system.js — 13-tier ladder + admin commands
02_sell_shop.js — /bns sell, 9 items, DR + tier-fee
03_bounty_engine.js — /bns bounty, 20 bounties, state machine
04_player_commands.js — /bns help, /bns me, /bns tier upgrade
plots.js: updated to support multi-plot ownership based on tier slots
- plotsOwnedBy() returns list, slot cap from TIER_PLOT_SLOTS table
- /bns plot release now requires plot id (multiple plots possible)
- /bns plot mine added
FTB Ranks server config:
pack/overrides/world/serverconfig/ftbranks/ranks.snbt
13 ranks with ftbchunks.max_claimed_chunks scaling per tier:
Peasant 9 -> Sovereign 1500 (chunks)
Peasant 1 -> Sovereign 200 (force-loaded)
Name format colour codes match the design doc.
Player command surface (everything callable from chat or Telegram bridge):
/bns help full command listing
/bns me tier, balance, plots, bounties
/bns tier upgrade pay spurs, advance to next tier
/bns plot list|info|buy|release|mine
/bns sell list|<item> [count]
/bns bounty list|active|accept|cancel|turnin|completed
/bns admin tier get|set|list (op level 2)
/bns admin info|waystone-*|reset-welcome (pre-existing)
Tier upgrade flow:
1. Player runs /bns tier upgrade
2. KubeJS takes spurs from inventory
3. KubeJS sets player.persistentData.bnsTier += 1
4. KubeJS runs `ftbranks add <player> <rank>` so FTB Ranks applies
the permission nodes (chunk allowance auto-flips)
Cross-file scope: KubeJS 2101 runs all server_scripts in shared Rhino
global scope. Constants (TIER_CONFIG, COIN_VALUES, bnsTier object,
shared functions like giveCoins/countCoins/takeCoins) are declared
once in their owning file and referenced from siblings. Don't redeclare.
Blocked tonight (deferred):
- Villager trade rebalance: KubeJS 2101.7.2 doesn't ship a
villagerTrades event. ServerEvents only exposes command/loaded/
recipes/registry/tags/tick/unloaded. Verified by grepping the
KubeJS jar's ServerEvents class. No KubeJS-villager-trade addon
on Modrinth for NeoForge 1.21.1. Either build into the custom
mod or add a third-party trade-overrides mod (research pending).
- Custom mod UI screens: no Java toolchain on host, and these
need visual iteration with the user anyway.
Verified live: server boots clean, all 4 economy scripts log loaded,
port 25565 binds, no EcmaError or EvaluatorException in KubeJS log.
FTB Ranks reload via rcon confirms "Ranks reloaded from disk!"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
// Brass and Sigil -- player-facing economy commands
|
||||
//
|
||||
// /bns help overview of every /bns subcommand
|
||||
// /bns me quick economic status (tier, balance, plots, bounties)
|
||||
// /bns tier upgrade pay spurs to advance to the next tier
|
||||
//
|
||||
// Player-facing tier upgrade pays spurs, increments the NBT tier, and
|
||||
// runs `ftbranks add <player> <rankId>` so FTB Ranks applies the rank's
|
||||
// permission nodes (ftbchunks.max_claimed_chunks etc.).
|
||||
|
||||
// FTB Ranks rank ID per tier number (must match
|
||||
// pack/overrides/world/serverconfig/ftbranks/ranks.snbt)
|
||||
const TIER_RANK_ID = [
|
||||
null, // 0 unused
|
||||
'peasant',
|
||||
'farmer',
|
||||
'citizen',
|
||||
'merchant',
|
||||
'knight',
|
||||
'baron',
|
||||
'viscount',
|
||||
'earl',
|
||||
'marquess',
|
||||
'duke',
|
||||
'archduke',
|
||||
'grand_duke',
|
||||
'sovereign',
|
||||
];
|
||||
|
||||
ServerEvents.commandRegistry(event => {
|
||||
const { commands: Commands } = event;
|
||||
|
||||
const playerCmds = Commands.literal('bns')
|
||||
|
||||
// /bns help
|
||||
.then(Commands.literal('help').executes(ctx => {
|
||||
const lines = [
|
||||
'§6═══ Brass and Sigil — economy commands ═══',
|
||||
'§eGeneral:',
|
||||
'§7 /bns help this list',
|
||||
'§7 /bns me your economy status',
|
||||
'§eCivic tier:',
|
||||
'§7 /bns tier upgrade pay spurs to advance to next tier',
|
||||
'§eSpawn plots:',
|
||||
'§7 /bns plot list browse available plots',
|
||||
'§7 /bns plot info <id> details for one plot',
|
||||
'§7 /bns plot buy <id> purchase a plot (uses a tier slot)',
|
||||
'§7 /bns plot release <id> release a plot (50% refund)',
|
||||
'§7 /bns plot mine list plots you own',
|
||||
'§eBazaar (sell raw commodities):',
|
||||
'§7 /bns sell list prices for items you can sell',
|
||||
'§7 /bns sell <item-id> [count] sell items from your inventory',
|
||||
'§eBounties:',
|
||||
'§7 /bns bounty list today\'s available bounties',
|
||||
'§7 /bns bounty active your accepted bounties',
|
||||
'§7 /bns bounty accept <id> accept a bounty',
|
||||
'§7 /bns bounty cancel <id> drop a bounty (cooldown applies)',
|
||||
'§7 /bns bounty turnin <id> claim reward for a completed bounty',
|
||||
'§7 /bns bounty completed bounties on cooldown',
|
||||
'§eAdmin (op level 2):',
|
||||
'§7 /bns admin info your raw NBT state',
|
||||
'§7 /bns admin tier get|set|list',
|
||||
'§7 /bns admin waystone-count|waystone-set|reset-welcome',
|
||||
];
|
||||
for (const l of lines) ctx.source.sendSystemMessage(Text.of(l));
|
||||
return 1;
|
||||
}))
|
||||
|
||||
// /bns me — quick status
|
||||
.then(Commands.literal('me').executes(ctx => {
|
||||
const player = ctx.source.player;
|
||||
if (!player) { ctx.source.sendSystemMessage(Text.red('Run from a player context.')); return 0; }
|
||||
const tier = bnsTier.get(player);
|
||||
const cfg = bnsTier.config(tier);
|
||||
const balance = countCoins(player); // shared helper from plots.js
|
||||
const ownedPlots = plotsOwnedBy(player.uuid.toString());
|
||||
const plotCap = playerPlotSlotCap(player);
|
||||
const activeBounties = getActiveMap(player);
|
||||
const bountyCap = playerBountySlotCap(player);
|
||||
const feePct = bnsTier.effectiveShopFeePct(player);
|
||||
|
||||
const lines = [
|
||||
'§6═══ ' + player.username + ' — economy status ═══',
|
||||
'§eCivic tier: §f' + tier + '. ' + cfg.name,
|
||||
'§eSpur balance: §f' + balance,
|
||||
'§ePlots owned: §f' + ownedPlots.length + ' / ' + plotCap,
|
||||
'§eActive bounties: §f' + activeBounties.getAllKeys().size() + ' / ' + bountyCap,
|
||||
'§eBazaar fee: §f' + feePct + '%',
|
||||
'§eWilderness cap: §f' + cfg.wildernessChunks + ' chunks',
|
||||
];
|
||||
if (tier < 13) {
|
||||
const next = bnsTier.config(tier + 1);
|
||||
lines.push('§eNext tier: §f' + next.name + ' (' + next.cost + ' spurs to upgrade)');
|
||||
} else {
|
||||
lines.push('§eNext tier: §f— you are Sovereign, the peak of the realm.');
|
||||
}
|
||||
for (const l of lines) player.tell(Text.of(l));
|
||||
return 1;
|
||||
}))
|
||||
|
||||
// /bns tier upgrade
|
||||
.then(Commands.literal('tier').then(Commands.literal('upgrade').executes(ctx => {
|
||||
const player = ctx.source.player;
|
||||
if (!player) { ctx.source.sendSystemMessage(Text.red('Run from a player context.')); return 0; }
|
||||
const cur = bnsTier.get(player);
|
||||
if (cur >= bnsTier.MAX) {
|
||||
player.tell(Text.gold('You\'re already Sovereign. There is no higher rank.'));
|
||||
return 0;
|
||||
}
|
||||
const nextTier = cur + 1;
|
||||
const nextCfg = bnsTier.config(nextTier);
|
||||
const balance = countCoins(player);
|
||||
if (balance < nextCfg.cost) {
|
||||
player.tell(Text.red(
|
||||
'Upgrade to ' + nextCfg.name + ' costs ' + nextCfg.cost + ' spurs. You have ' + balance + '.'
|
||||
));
|
||||
return 0;
|
||||
}
|
||||
const took = takeCoins(player, nextCfg.cost);
|
||||
if (took < nextCfg.cost) {
|
||||
giveCoins(player, took);
|
||||
player.tell(Text.red('Coin removal failed — refunded. Try again.'));
|
||||
return 0;
|
||||
}
|
||||
// Promote via FTB Ranks
|
||||
const rankId = TIER_RANK_ID[nextTier];
|
||||
Utils.server.runCommandSilent('ftbranks add ' + player.username + ' ' + rankId);
|
||||
// Mirror to NBT
|
||||
bnsTier.set(player, nextTier);
|
||||
player.tell(Text.gold(
|
||||
'╔══ You have been elevated to ' + nextCfg.name + '! ══╗'
|
||||
));
|
||||
player.tell(Text.gold(
|
||||
' ' + nextCfg.wildernessChunks + ' wilderness chunks · ' +
|
||||
nextCfg.plotSlots + ' plot slots · ' +
|
||||
nextCfg.dailyBountySlots + ' bounty slots · ' +
|
||||
(Math.max(0, 25 + nextCfg.shopFeeDiscount)) + '% Bazaar fee'
|
||||
));
|
||||
console.info('[bns/tier] ' + player.username + ' upgraded to tier ' + nextTier + ' (' + nextCfg.name + ') for ' + nextCfg.cost + ' spurs');
|
||||
return 1;
|
||||
})));
|
||||
|
||||
event.register(playerCmds);
|
||||
});
|
||||
|
||||
console.info('[bns/player_commands] loaded — /bns help, /bns me, /bns tier upgrade');
|
||||
Reference in New Issue
Block a user