NeoForge mod with WebSocket API, custom blocks, mob effects, and chord keys
Minaret is a multi-feature NeoForge mod for Minecraft 1.21.1 / 1.21.11:
- WebSocket API for external application integration
- Event broadcasting — server pushes game events to connected clients
- Spawner Agitator block that enhances mob spawners
- Chunk Loader block that keeps chunks loaded
- Warding Post block that repels hostile mobs
- Custom mob effects (Martial Lightning, Homing Archery, Streamer Protect)
- Chord keys (emacs-style key sequences for keybinding combos)
WebSocket API
- RFC 6455 compliant WebSocket server, zero external dependencies
- Optional HTTP Basic Authentication
- Chat broadcast, command execution, effect queries, item use — via JSON
- Server pushes game events to all connected clients
- Java 21 virtual threads — no platform thread blocked per connection
- Zero-allocation frame buffer — single pre-allocated byte[] per connection
Spawner Agitator
- Place below a mob spawner to increase its range and speed
- Stack multiple agitators for faster spawning
- Event-driven architecture with minimal per-tick overhead
- Clean shutdown behavior (no server hang)
Chunk Loader
- Place to keep the chunk force-loaded
- Persists across server restarts (atomic text file save)
- Automatic re-activation on server start
Warding Post
- Repels hostile mobs; radius scales with column height (4 blocks per post stacked)
- Stack multiple posts vertically for larger protection area
- Found as dungeon loot in temples, strongholds, and mineshafts
Mob Effects
- Martial Lightning: enhanced melee with AoE and tier-based damage
- Homing Archery: bow shots fire homing shulker bullets
- Streamer Protect: indicator effect for external integration
Chord Keys (client-side)
- Emacs-style key sequences (e.g.
f>1,f>f>1) - Targets: KeyMapping names or WebSocket JSON commands
- Trie-based state machine with timeout and overlay
- Download the latest
minaret-1.0.0.jarfrom releases - Place the jar in your server's
mods/folder - Start your NeoForge 1.21.1 server
- Configure via
config/minaret-server.toml(optional)
// Connect to WebSocket API
const ws = new WebSocket('ws://localhost:8765');
// Send chat message
ws.send(JSON.stringify({ "message": "Hello from external app!" }));
// Execute server command
ws.send(JSON.stringify({ "command": "time set day" }));
// Receive server events
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.event === 'player_join') console.log(msg.player + ' joined');
if (msg.event === 'player_death') console.log(msg.player + ' died: ' + msg.cause);
};File: config/minaret-server.toml
# WebSocket server binding
websocket_url = "localhost:8765"
# Authentication (optional - leave empty to disable)
auth_username = ""
auth_password = ""| Option | Default | Description |
|---|---|---|
websocket_url |
localhost:8765 |
Host:port for WebSocket server |
auth_username |
"" |
Username for HTTP Basic Auth (empty = disabled) |
auth_password |
"" |
Password for HTTP Basic Auth |
Endpoint: ws://localhost:8765 (configurable)
Authentication: Optional HTTP Basic Auth header:
Authorization: Basic <base64(username:password)>
All messages use JSON with UTF-8 encoding.
{"message": "text", "user": "name", "chat": "discord"}user and chat are optional.
Response: {"status":"success","type":"message"}
{"command": "time set day"}Response:
{"status":"success","type":"command","command":"time set day","result":"1"}
{"status":"error","type":"command","error":"...","command":"...","result":"0"}{"getEffects": "PlayerName"}Response:
{"status":"success","type":"getEffects","player":"PlayerName","effects":[...]}{"use": "PlayerName", "slot": 3}Switches the player's selected slot to slot (0–8), triggers a right-click use, then restores the previous slot.
Response:
{"status":"success","type":"use","player":"PlayerName","slot":"3","item":"minecraft.item.bread"}The server pushes these events to all connected clients automatically:
| Event | Payload |
|---|---|
player_join |
player |
player_leave |
player |
player_death |
player, cause (e.g. fall, drown, mob) |
player_kill |
player, mob (e.g. zombie, creeper) |
player_eat |
player, item, nutrition, saturation |
player_heal |
player, amount — aggregated; fires at ≥10 HP or 1 min |
Examples:
{"event":"player_join","player":"Steve"}
{"event":"player_death","player":"Steve","cause":"fall"}
{"event":"player_kill","player":"Steve","mob":"zombie"}
{"event":"player_eat","player":"Steve","item":"bread","nutrition":5,"saturation":6.0}
{"event":"player_heal","player":"Steve","amount":"12.5"}| Error | Description |
|---|---|
401 Unauthorized |
Invalid/missing authentication |
Invalid JSON |
Malformed JSON message |
Unknown message type |
No recognized field (message, command, getEffects, use) |
Command failed |
Command returned error code 0 |
Player not found |
Named player is not online |
Slot must be 0–8 |
Invalid slot for use action |
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8765', {
headers: {
'Authorization': 'Basic ' + Buffer.from('user:pass').toString('base64')
}
});
ws.on('open', () => {
ws.send(JSON.stringify({ message: 'Bot connected!' }));
ws.send(JSON.stringify({ command: 'give @a diamond 64' }));
});
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.event) {
console.log('Event:', msg);
} else {
console.log('Response:', msg);
}
});import websocket
import json
import base64
def on_message(ws, message):
msg = json.loads(message)
if 'event' in msg:
print(f"Event: {msg}")
else:
print(f"Response: {msg}")
def on_open(ws):
ws.send(json.dumps({"message": "Python bot online!"}))
ws.send(json.dumps({"command": "weather clear"}))
auth = base64.b64encode(b'user:pass').decode('ascii')
ws = websocket.WebSocketApp("ws://localhost:8765",
header=[f"Authorization: Basic {auth}"],
on_open=on_open,
on_message=on_message)
ws.run_forever()npm install -g wscat
# Connect without authentication
wscat -c ws://localhost:8765
# Connect with authentication
wscat -c ws://localhost:8765 -H "Authorization: Basic $(echo -n 'user:pass' | base64)"
# Test messages
{"message": "Hello!"}
{"command": "say WebSocket is working!"}
{"getEffects": "PlayerName"}- Authentication: Optional HTTP Basic Auth during WebSocket handshake
- Permissions: All commands executed with OP level 4
- Local binding: Defaults to localhost
- Audit logging: Authentication attempts and commands logged
| Metric | Value |
|---|---|
| Memory | ~15MB additional |
| Connections | Unbounded (Java 21 virtual threads — no platform thread per connection) |
| Frame buffer | Pre-allocated per connection — zero allocation per received message |
| Latency | Sub-100ms |
| TPS impact | Minimal |
- Java: 21+ (OpenJDK recommended)
- NeoForge: 21.1.172+ (1.21.1) or 21.11.38-beta+ (1.21.11)
- Minecraft: 1.21.1 or 1.21.11
- Gradle: 8.8+
make build # Build all versions
make build-1.21.1 # Build for MC 1.21.1
make build-1.21.11 # Build for MC 1.21.11
# Output: versions/*/build/libs/minaret-*.jarminaret/
├── src/main/java/com/minaret/
│ ├── MinaretMod.java # Mod entry point, registries, lifecycle
│ ├── WebSocketServer.java # RFC 6455 WebSocket server
│ ├── EventBroadcaster.java # Server → client event broadcasting
│ ├── MessageDispatcher.java # Client → server message routing
│ ├── SimpleJson.java # Flat JSON parser/generator
│ ├── MinaretConfig.java # NeoForge config
│ ├── Compat.java # Cross-version reflection utilities
│ ├── SpawnerAgitatorBlock.java # Spawner agitator block
│ ├── SpawnerAgitatorBlockEntity.java # Spawner agitator logic
│ ├── ChunkLoaderBlock.java # Chunk loader block
│ ├── ChunkLoaderBlockEntity.java # Chunk loader block entity
│ ├── ChunkLoaderData.java # Chunk loader persistence
│ ├── WardingPostBlock.java # Warding post block
│ ├── WardingPostBlockEntity.java # Warding post ticker
│ ├── ChordConfig.java # Chord key config
│ ├── MinaretCommands.java # /minaret subcommands
│ ├── *Effect.java / *Handler.java # Mob effects and handlers
│ └── client/
│ └── ChordKeyHandler.java # Chord key state machine
├── versions/
│ ├── 1.21.1/ # MC 1.21.1 subproject
│ └── 1.21.11/ # MC 1.21.11 subproject
└── build.gradle # Multi-version build config
Connection Refused
- Verify mod is installed and server is running
- Check
config/minaret-server.tomlfor correct port
Authentication Failures
- Verify credentials in config file
- Review server logs for authentication errors
Commands Not Executing
- Ensure commands are valid Minecraft commands
- Check server logs for permission errors
- Verify JSON message format is correct
MIT License - see LICENSE for details.
- Remote server management via WebSocket (Discord bots, web panels, mobile apps)
- React to game events in real time (player deaths, kills, food consumption)
- Mob farm automation with spawner agitator stacks
- Persistent chunk loading for farms, machines, and redstone
- Streaming integration via streamer protect effect
- Custom keybinding combos for mod-heavy clients