You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A Minecraft Bedrock behavior pack plugin built on Script API (SAPI), with an external SQLite-backed HTTP backend for persistent data storage. Features include a channel-based chat system, land management, co-op teams, shop, activity logging, and more.
Two disjoint components, one repo: the behavior pack (TypeScript, SAPI) and the database server (Node.js, better-sqlite3). They communicate exclusively via HTTP.
Node.js 18+ (for building the behavior pack and running db-server)
Minecraft Bedrock 1.21.60+ (Preview or Release with Beta APIs enabled)
Install & Build
# Install dependencies for the behavior pack
cd scriptsforminecraftserver
npm install
# Build (TypeScript + esbuild bundle → dist/scripts/main.js)
npm run build
# Lint
npm run lint
# Local deploy to Minecraft dev folder
npm run local-deploy
# Watch mode (auto rebuild & deploy on changes)
npm run local-deploy ----watch
Start the Database Server
# In a separate terminal
cd db-server
node index.js
# Default: http://127.0.0.1:3001# Override port: $env:DB_PORT=4000; node index.js
Player types !ch
│
▼
world.beforeEvents.chatSend
│
▼
Command.trigger("ch")
│
▼
DogeChat.cycleChannel(player) ──► GET /api/sfmc/channels?type=public (raw data from SQLite)
│
▼
DogeChat.setActiveChannel(player, newId) ──► PATCH /api/sfmc/players/{id}
│
▼
DogeChat.loadChannelHistory(player, newId) ──► GET /api/sfmc/messages?channelId=...
│
▼
player.sendMessage() — display history
The only runtime state kept in memory is activeChannelMap: Map<playerId, channelId> for real-time message broadcast — this is session state, not a data cache.
World metadata (seed, gamerules, difficulty, time, etc.)
World Sync
sfmc_players
Player data, online time, active channel, permissions
Player / OnlineTime / Chat
sfmc_chat_channels
Channel definitions & config
DogeChat
sfmc_chat_messages
Chat message history
DogeChat
sfmc_chat_redpackets
Red packet data
DogeChat
sfmc_scoreboards
Scoreboard objective snapshots
Scoreboard Sync
sfmc_activities
Player activity event log
Activity Log
sfmc_coop_data
Key-value store for co-op data
Co-op System
(Holoprint tables)
Holographic display data
Holoprint
REST API
All endpoints under /api/sfmc/*. The server uses path-based routing (grouped by resource).
Channels
Method
Path
Description
GET
/api/sfmc/channels
List channels (filters: search, type, ownerId, minCreatedAt, maxCreatedAt)
POST
/api/sfmc/channels
Create/replace channels (batch)
GET
/api/sfmc/channels/:id
Get single channel
PATCH
/api/sfmc/channels/:id
Update channel fields
DELETE
/api/sfmc/channels/:id
Delete channel
Messages
Method
Path
Description
GET
/api/sfmc/messages
List messages (filters: channelId, from, type, minSentAt, maxSentAt)
POST
/api/sfmc/messages
Save messages (batch, max 100)
Red Packets
Method
Path
Description
GET
/api/sfmc/redpacket
List all red packets
POST
/api/sfmc/redpacket
Create red packet
GET
/api/sfmc/redpacket/:id
Get single red packet
PATCH
/api/sfmc/redpacket/:id
Update red packet (remaining amount/count, receivers)
DELETE
/api/sfmc/redpacket/:id
Delete red packet
Players
Method
Path
Description
GET
/api/sfmc/players
List players (filters: search, name, id, active_channel)
POST
/api/sfmc/players
Create/replace players (batch)
GET
/api/sfmc/players/:id
Get single player
PATCH
/api/sfmc/players/:id
Update player fields
Scoreboards
Method
Path
Description
GET
/api/sfmc/scoreboards
List scoreboard entries
POST
/api/sfmc/scoreboards
Backup scoreboard entries
World
Method
Path
Description
GET
/api/sfmc/world
Get world metadata
POST
/api/sfmc/world
Save world metadata
Activity Logs
Method
Path
Description
POST
/api/sfmc/activities/batch
Batch insert activity entries
GET
/api/sfmc/activities
Query logs (filters: id, event, from, to, name, limit, offset)
GET
/api/sfmc/activities/stats
Activity statistics (id, from, to)
POST
/api/sfmc/activities/cleanup
Purge old entries (params: keepDays, keepAdmin)
Co-op
Method
Path
Description
GET
/api/sfmc/coop/:key
Read co-op data by key
System
Method
Path
Description
GET
/api/health
Health check (returns uptime)
Module Details
Initialization Flow
entry.ts
│
├─ system.beforeEvents.startup
│ ├─ Permission.register() — declare all permission nodes
│ ├─ Command.register() — register all !commands
│ ├─ Fly.init(), Menu.init()
│ ├─ OnlineTime, CreativeArea, etc. registerCommands()
│ └─ ...
│
├─ world.afterEvents.worldLoad
│ ├─ AFK.init()
│ ├─ CoopSystem.init()
│ ├─ ChatSystem.init() → DogeChat.ensureDefaultChannels()
│ ├─ OnlineTime.getInstance().init() → start per-second tick
│ ├─ ScoreboardSync.init() → backup scoreboard to DB
│ ├─ syncWorldData() → save world metadata to DB
│ ├─ ActivityLog.init()
│ └─ ...
│
├─ world.afterEvents.playerSpawn (initialSpawn)
│ ├─ Peace.getInstance().init()
│ ├─ Fly.playerJoinEvent()
│ ├─ AFK.reset()
│ └─ savePlayers() — persist player data on join
│
├─ world.afterEvents.playerLeave
│ ├─ savePlayers() — persist player data on leave
│ └─ OnlineTime.onPlayerLeave() — persist final online time
│
├─ world.beforeEvents.chatSend
│ └─ intercept "!" / "!" → Command.trigger()
│
└─ system.beforeEvents.shutdown
├─ syncWorldData()
└─ ScoreboardsBackup()
Command Processing
Player sends "!help"
│
▼
world.beforeEvents.chatSend
┌─ firstChar === "!" → cancel original message
│
├─ Command.trigger(player, "help")
│ ├─ Command.canExecute(player, permission)
│ │ └─ Permission.check(player, "help.see")
│ ├─ system.run(async () => {
│ │ const result = await callback(player);
│ │ if (result !== undefined) Msg.success(result, player);
│ │ })
│ └─ return
│
└─ (if not "!") → DogeChat.sendChannelMessage() — redirect to active channel
DogeChat Message Delivery
Player sends message in active channel
│
▼
world.beforeEvents.chatSend (message doesn't start with "!")
│
▼
ChatSystem → DogeChat.sendChannelMessage(player, channelId, content)
│
├─ 1. ChatApi.getChannel(channelId) — GET /api/sfmc/channels/:id
│ └─ check config.allowChat, config.slowMode, config.isBroadcast
│
├─ 2. ChatApi.saveMessages([msg]) — POST /api/sfmc/messages
│
├─ 3. Broadcast to activeChannelMap:
│ for each online player:
│ if activeChannelMap[player.id] === channelId:
│ player.sendMessage(display)
│
└─ 4. Update slowModeTracker
Online Time
Per-second tick, pure DB persistence:
system.runInterval(every 20 ticks = 1 second)
│
▼
OnlineTime.tickSecond()
│
├─ for each online player:
│ ├─ load data from Map (loaded from DB on join)
│ ├─ check date/month reset
│ ├─ increment session/today/month/total
│ └─ PATCH /api/sfmc/players/{id} — persist to DB
│
└─ onPlayerLeave → final persist → delete from Map
Activity Log
Event triggers (block break, chat, death, etc.)
│
▼
Enqueue to in-memory batch queue
│
▼
Every 2 seconds: flush
│
▼
POST /api/sfmc/activities/batch
│
▼
SQLite transaction batch INSERT
Retention: 30 days for regular events, permanent for admin.* events. Auto-cleanup every 6 hours.
Scoreboard Sync
Event
Action
Server start
ScoreboardsBackup() → POST /api/sfmc/scoreboards
Server stop
ScoreboardsBackup() via system.beforeEvents.shutdown