Initial commit — Minecraft AI God plugin

- mc_aigod.py: main watcher script (log tail, RCON, two-call LLM)
- Two-call LLM split: qwen3-coder:30b for commands, gemma3:12b for messages
- Divine intervention timer (Poisson process, configurable avg/day)
- Prayer memory (persistent, last 10 exchanges)
- Rolling server log context (last 20 min events)
- Live player context (inventory with rarity, health, food, pos, XP)
- /pray and bible chat detection (no slash — vanilla 1.21 compatible)
- Login notice, bible help system
- debug_commands toggle (in-game command display via tellraw)
- Auto-fix for transposed give command syntax
- JSON repair fallback for truncated LLM responses
- Sentence-aware message chunking for long responses
- mc-aigod.service systemd unit
- mc_aigod_shrink.json example config
- README.md full implementation guide
- Minecraft_Ai_God.md full design document
This commit is contained in:
2026-03-15 19:02:16 -04:00
commit 8ee8be9cc0
27 changed files with 3264 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
*.pyc
__pycache__/
GITEA_API.md
aigod_memory.json
+104
View File
@@ -0,0 +1,104 @@
# Minecraft Quick Command Reference
## RCON — Send a command from CT 644
### Server 1 (mc1, port 25575)
```bash
ssh pve112 "pct exec 644 -- python3 -c \"
import socket, struct, time
def rcon(cmd):
s = socket.socket(); s.settimeout(5); s.connect(('127.0.0.1', 25575))
def pkt(i,t,p): p=p.encode()+b'\x00\x00'; return struct.pack('<iii',len(p)+8,i,t)+p
s.sendall(pkt(1,3,'REDACTED_RCON')); time.sleep(0.2); s.recv(4096)
s.sendall(pkt(2,2,cmd)); time.sleep(0.2); r=s.recv(4096); s.close(); return r[12:-2].decode()
print(rcon('YOUR COMMAND HERE'))
\""
```
### Server 2 (shrink-world, port 25576)
Same as above but use port `25576` and password `REDACTED_RCON`.
---
## Player Commands
| Action | Command |
|---|---|
| Teleport player | `tp <player> <x> <y> <z>` |
| Give item | `give <player> <item> <count>` |
| Set gamemode | `gamemode creative/survival/spectator <player>` |
| Get player position | `data get entity <player> Pos` |
| List online players | `list` |
| Get world border size | `worldborder get` |
| Set world border | `worldborder set <size>` |
| Shrink world border | `worldborder add -<amount> <seconds>` |
## God Kit (1.21 syntax)
```
give <player> netherite_helmet[enchantments={protection:4,unbreaking:3,mending:1,respiration:3,aqua_affinity:1}]
give <player> netherite_chestplate[enchantments={protection:4,unbreaking:3,mending:1}]
give <player> netherite_leggings[enchantments={protection:4,unbreaking:3,mending:1}]
give <player> netherite_boots[enchantments={protection:4,unbreaking:3,mending:1,feather_falling:4,depth_strider:3}]
give <player> netherite_sword[enchantments={sharpness:5,unbreaking:3,mending:1,looting:3,fire_aspect:2,sweeping_edge:3}]
give <player> bow[enchantments={power:5,unbreaking:3,infinity:1,flame:1,punch:2}]
give <player> netherite_pickaxe[enchantments={efficiency:5,unbreaking:3,mending:1,fortune:3}]
give <player> netherite_axe[enchantments={efficiency:5,unbreaking:3,mending:1,sharpness:5}]
give <player> arrow 64
give <player> golden_apple 64
give <player> totem_of_undying 4
give <player> ender_pearl 16
give <player> cooked_beef 64
```
---
## Shrinkborder Datapack (in-game, op required)
| Command | Effect |
|---|---|
| `/function shrinkborder:enable` | Start death-shrinking |
| `/function shrinkborder:disable` | Stop death-shrinking |
| `/function shrinkborder:reset` | Reset border to 1000x1000, disable shrinking |
---
## Services on CT 644
```bash
# Access CT
ssh pve112 "pct exec 644 -- bash"
# God mode watcher (mc1 - slingshooter08 auto creative)
systemctl start/stop/status mc-godmode.service
# Shrink world kit watcher (shrink-world - auto kit on first join + stats)
systemctl start/stop/status mc-shrink-kit.service
# MCSManager itself
systemctl start/stop/status mcsm-web.service
systemctl start/stop/status mcsm-daemon.service
```
---
## Datapacks — Deploy to shrink-world server
```bash
# From this machine (CT 629), copy a datapack folder to the server
DEST=/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/world/datapacks/
# Then reload in-game or via RCON:
# datapack enable "file/<packname>"
# reload
```
---
## Kit Record — Reset a player's first-join kit
```bash
ssh pve112 "pct exec 644 -- cat /opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/kit_given.json"
# To reset a specific player (edit the JSON):
ssh pve112 "pct exec 644 -- nano /opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/kit_given.json"
```
+115
View File
@@ -0,0 +1,115 @@
# Sethpc Minecraft Project Context
## Infrastructure
### MCSManager Panel
- **CT:** 644 on node-112 (sethpc, 192.168.0.112)
- **IP:** 192.168.0.244
- **Web panel:** http://mc.sethpc.xyz (via Caddy on CT 600, node-241)
- **Panel ports:** 23333 (web), 24444 (daemon)
- **OS:** Debian 12, Java 21 (Temurin)
- **Installed via:** https://script.mcsmanager.com/setup.sh
- **Systemd services:** `mcsm-web.service`, `mcsm-daemon.service`
---
## Server 1 — mc1 (Vanilla survival)
| Property | Value |
|---|---|
| Instance ID | `d39f55861cb34204a92a18a9e1c78ca6` |
| Game port | `25565` |
| RCON port | `25575` |
| RCON password | `REDACTED_RCON` |
| Data dir | `/opt/mcsmanager/daemon/data/InstanceData/d39f55861cb34204a92a18a9e1c78ca6/` |
| Version | Minecraft 1.21.x (vanilla) |
| Connect | `192.168.0.244:25565` |
### Services on CT 644
- `mc-godmode.service` — watches `latest.log`, sets `slingshooter08` to creative on every login
- Script: `/usr/local/bin/mc_godmode_watch.sh`
- Calls: `/usr/local/bin/mc_godmode_rcon.py`
- Toggle: `systemctl start/stop mc-godmode.service`
---
## Server 2 — shrink-world
| Property | Value |
|---|---|
| Instance ID | `shrinkborder1234567890abcdef12345` |
| Game port | `25566` |
| RCON port | `25576` |
| RCON password | `REDACTED_RCON` |
| Data dir | `/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/` |
| Version | Minecraft 1.21.x (vanilla, same server.jar as mc1) |
| Connect | `192.168.0.244:25566` |
| World border | 500x500 centered at 0,0 (started at 1000x1000) |
| Difficulty | Hard |
### Datapacks installed
1. **shrinkborder** — detects player deaths, shrinks world border by 1 block per death, alternating N/S and E/W sides. Starts DISABLED.
2. **morespawns** — increases creeper spawn weight from 100 to 500 (5x more creepers)
### Services on CT 644
- `mc-shrink-kit.service` — main watcher script for shrink-world
- Script: `/usr/local/bin/shrink_godkit.py`
- On first login: gives full god kit (netherite armor + tools + consumables)
- On every login: broadcasts world stats
- On every death: broadcasts world stats + increments death counter
- Every hour: broadcasts world stats
- Kit record persisted at: `<data_dir>/kit_given.json`
---
## RCON Helper (Python)
All server communication uses raw RCON sockets (no external library needed):
```python
import socket, struct, time
def rcon(cmd, host='127.0.0.1', port=25575, password='REDACTED_RCON'):
s = socket.socket()
s.settimeout(5)
s.connect((host, port))
def pkt(i, t, p):
p = p.encode() + b'\x00\x00'
return struct.pack('<iii', len(p)+8, i, t) + p
s.sendall(pkt(1, 3, password))
time.sleep(0.2)
s.recv(4096)
s.sendall(pkt(2, 2, cmd))
time.sleep(0.2)
r = s.recv(4096)
s.close()
return r[12:-2].decode()
```
For shrink-world use port `25576` and password `REDACTED_RCON`.
---
## Minecraft 1.21 Notes
- **Enchantment syntax** (1.21+): `give player item[enchantments={sharpness:5,unbreaking:3}]`
- NOT the old NBT `{Enchantments:[{id:...,lvl:...}]}` format
- **Death detection regex:** `(\w+) (died|was slain|was shot|drowned|fell|burned|blew up|suffocated|starved|was killed|hit the ground|went up in flames|tried to swim in lava)`
- **RCON** requires `enable-rcon=true` and `rcon.password=` set in `server.properties`, then server restart
---
## Players
| Username | UUID |
|---|---|
| slingshooter08 | 45374469-5333-43e1-9881-39ad6b5f3301 |
---
## Networking
- Both servers are LAN-only (`192.168.0.244`)
- External access requires port forwarding on router: `25565` and `25566``192.168.0.244`
- Web panel accessible via Caddy at `mc.sethpc.xyz`
- DNS: Pi-hole at `192.168.0.153`
+1307
View File
File diff suppressed because it is too large Load Diff
+314
View File
@@ -0,0 +1,314 @@
# Minecraft AI God
A Python-based "plugin" for vanilla Minecraft servers that integrates a locally-hosted LLM as an in-game God character. Players interact by typing `pray <message>` in chat. God responds with dramatic prose and optionally executes server commands via RCON.
No mods, no plugins, no server restarts required. Works with any vanilla Minecraft 1.21+ server.
---
## How It Works
```
Player types: pray <message>
latest.log ←─ tailed by mc_aigod.py
RCON: immediate acknowledgment to player ("The heavens stir...")
RCON: fetch live server context
- Online players, time of day, weather, world border
RCON: fetch praying player's state
- Full inventory (with rarity annotations), position, health, food, XP, deaths
Call 1 — command_model (qwen3-coder:30b or similar)
- Decides what server commands to execute (JSON only, no prose)
- Low temperature (0.3) for precise structured output
Call 2 — model (gemma3:12b or similar)
- Writes God's spoken message knowing what was decided
- No token competition with commands — full creative freedom
RCON: execute commands + broadcast message
```
**Divine Intervention Timer** — a background thread fires at random intervals (Poisson process, user-defined average per day). If players are online, God acts unprompted. LLM can choose silence (`commands: []`) and nothing happens.
**Memory** — last 10 prayer exchanges stored as conversation history and passed to every LLM call. Persists across service restarts via JSON file. God remembers.
**Server log context** — last 20 minutes of meaningful server events (chat, deaths, joins, leaves) included with every prayer. God knows what's been happening.
---
## Requirements
- Python 3.11+
- `requests` library (`apt install python3-requests`)
- Ollama instance with at least one model pulled
- Minecraft vanilla server with RCON enabled
- Server running on Linux (systemd for service management)
### Minecraft server.properties requirements
```properties
enable-rcon=true
rcon.port=25575
rcon.password=yourpassword
broadcast-rcon-to-ops=false
```
---
## File Structure
```
mc_aigod.py # Main script — deploy to /usr/local/bin/
mc_aigod_shrink.json # Example config — deploy to /etc/mc_aigod.json
mc-aigod.service # Systemd unit — deploy to /etc/systemd/system/
Minecraft_Ai_God.md # Full design document with architecture details
```
---
## Configuration
`/etc/mc_aigod.json`:
| Key | Type | Default | Description |
|---|---|---|---|
| `server_name` | string | required | Server name passed to God's persona |
| `log_path` | string | required | Absolute path to `logs/latest.log` |
| `rcon_host` | string | `"127.0.0.1"` | RCON host |
| `rcon_port` | int | `25575` | RCON port |
| `rcon_password` | string | required | RCON password |
| `ollama_url` | string | required | Ollama base URL e.g. `http://192.168.0.1:11434` |
| `model` | string | required | Message model — creative writing (e.g. `gemma3:12b`) |
| `command_model` | string | falls back to `model` | Commands model — structured JSON (e.g. `qwen3-coder:30b`) |
| `temperature` | float | `0.85` | Message model temperature |
| `max_tokens` | int | `600` | Max tokens for message call |
| `cooldown_seconds` | int | `20` | Per-player prayer cooldown |
| `max_commands_per_response` | int | `6` | Max commands God can issue per prayer |
| `interventions_per_day` | float | `4` | Avg unprompted interventions per 24h. `0` to disable |
| `debug_commands` | bool | `false` | Show executed commands in-game via dark gray tellraw |
| `memory_path` | string | see below | Path to persist prayer memory JSON |
| `god_chat_prefix` | string | `"[GOD]"` | Chat prefix (supports Minecraft color codes) |
Default memory path: `<instance_data_dir>/aigod_memory.json`
### Example config
```json
{
"server_name": "my-server",
"log_path": "/path/to/minecraft/logs/latest.log",
"rcon_host": "127.0.0.1",
"rcon_port": 25575,
"rcon_password": "yourpassword",
"ollama_url": "http://localhost:11434",
"model": "gemma3:12b",
"command_model": "qwen3-coder:30b",
"temperature": 0.85,
"max_tokens": 600,
"cooldown_seconds": 20,
"max_commands_per_response": 6,
"interventions_per_day": 4,
"debug_commands": false,
"memory_path": "/path/to/minecraft/aigod_memory.json",
"god_chat_prefix": "[§6§lGOD§r]"
}
```
---
## Player Usage
Type in chat (no slash — vanilla 1.21 rejects unknown slash commands client-side):
```
pray <message> — send a prayer to God
bible — show help/guidance
```
On login players see:
```
[GOD] GOD ENABLED — Type "bible" in chat for guidance. Type "pray <message>" to pray.
```
---
## God's Capabilities
### Commands God can issue
**Give any item:**
```
give <player> minecraft:<item_id> <count>
give <player> minecraft:<item_id>[enchantments={sharpness:4,unbreaking:3}] 1
xp add <player> <amount> levels
```
**Effects (positive/negative):** regeneration, strength, speed, night_vision, fire_resistance, water_breathing, instant_health, blindness, slowness, weakness, hunger, nausea, levitation, effect clear
**Movement:** `tp <player> <x> <y> <z>`
**World:** `time set day/night`, `weather clear/thunder/rain <duration>`
**Punishment:** `execute at <player> run summon minecraft:lightning_bolt ~ ~ ~`, `kill <player>`
**Mobs:** `execute at <player> run summon minecraft:creeper ~ ~ ~3`
### Item naming rules (Minecraft 1.21)
- Always use `minecraft:` namespace prefix
- Beds: `white_bed`, `red_bed` etc — there is no `minecraft:bed`
- Logs: `oak_log`, `spruce_log` etc — there is no `minecraft:log`
- Wool: `white_wool`, `red_wool` etc — there is no `minecraft:wool`
- Enchantments use component syntax: `item[enchantments={sharpness:5,unbreaking:3}]`
- Give syntax: `give <player> minecraft:<item> <count>` — count is LAST
### God's persona
- Benevolent but just, theatrical and dramatic (Old Testament style)
- Aware of player inventory, health, food, position, deaths
- Aware of server state: time, weather, world border, online players
- Aware of recent server events: deaths, chat, joins, leaves (last 20 min)
- Remembers last 10 prayer exchanges across all players
- Acts on own accord via intervention timer — may choose silence
- Not obligated to grant requests — may reward someone else, punish the requester, or do something unexpected
---
## Model Recommendations
**Command model** — needs reliable structured JSON output and Minecraft syntax knowledge:
- `qwen3-coder:30b` (recommended, ~19GB Q4)
- `qwen2.5:1.5b` (fast/small, acceptable for commands)
**Message model** — needs creative writing, roleplay, dramatic biblical prose:
- `gemma3:12b` (recommended, ~8GB)
- `llama3.1:8b` (good alternative)
Avoid coding models for the message role. Avoid general models for the command role.
Both calls go to the same Ollama instance. If both models fit in VRAM simultaneously there is no swap overhead. If not, Ollama swaps them — adds a few seconds per prayer.
---
## Deployment
```bash
# Install dependencies
apt install python3-requests
# Deploy files
cp mc_aigod.py /usr/local/bin/mc_aigod.py
chmod +x /usr/local/bin/mc_aigod.py
cp mc_aigod_shrink.json /etc/mc_aigod.json # edit as needed
cp mc-aigod.service /etc/systemd/system/mc-aigod.service
# Enable and start
systemctl daemon-reload
systemctl enable --now mc-aigod.service
# Monitor
journalctl -fu mc-aigod.service
tail -f /var/log/mc_aigod.log
tail -f /var/log/mc_aigod_responses.log # full untruncated LLM responses
```
---
## Debugging
**`debug_commands: true` in config** — shows executed commands in-game as dark gray italic text:
```
[~] give slingshooter08 minecraft:spruce_log 64 | weather thunder 6000
```
Never appears in `latest.log`. Toggle off by setting `false` and restarting.
**Log files:**
- `/var/log/mc_aigod.log` — startup, prayers received, RCON results, errors
- `/var/log/mc_aigod_responses.log` — full untruncated LLM responses with commands and messages
**Common issues:**
| Symptom | Cause | Fix |
|---|---|---|
| `Unknown item 'minecraft:64'` | LLM put count before item | Auto-fixed by `fix_give_command()`, also update command model |
| `Unknown item 'minecraft:bed'` | Missing colour prefix | Item library includes warning; auto-namespaced |
| Message truncated | Token limit hit | Increase `max_tokens`; two-call split helps |
| `commands: []` but message says it will give something | LLM treating message as action | CRITICAL rule in prompt: commands array is the only way things happen |
| Prayer not detected | Typed as `/pray` (slash command) | Must type `pray` in chat without slash |
---
## Architecture Notes
### Why no Minecraft plugin?
No Java plugin required. The script tails `latest.log` for chat lines matching `pray ` and `bible`, then acts via RCON. This means:
- Works with any vanilla server version that has RCON
- No server restart required to install or update
- Script restarts independently of the server
### Log detection patterns
```python
# Chat messages in vanilla 1.21:
# [HH:MM:SS] [Server thread/INFO]: <playername> pray message here
# [HH:MM:SS] [Server thread/INFO]: <playername> bible
PRAY_PATTERN = re.compile(r'\[.*?\]: <(\w+)> [Pp]ray (.+)')
BIBLE_PATTERN = re.compile(r'\[.*?\]: <(\w+)> [Bb]ible\s*$')
JOIN_PATTERN = re.compile(r'\[.*?\]: (\w+) joined the game')
```
Note: `/pray` as a slash command does NOT work — vanilla 1.21 rejects unknown commands client-side before they reach the server log.
### Two-call LLM architecture
```
Prayer received
├─► Command call (command_model, temp=0.3, max_tokens=200, format=json)
│ System: terse spec, command palette, item rules
│ Returns: {"commands": [...]}
└─► Message call (model, temp=0.85, max_tokens=600, no format constraint)
System: God persona only
User: prayer + context + "You decided to execute: [commands]"
Returns: plain prose, any length
```
Separating the calls means:
- Commands are never truncated by a long message
- Message has full token budget for dramatic prose
- Each model does what it's best at
### Prayer memory format
Stored as a list of `[player, prayer, god_message]` tuples in JSON. Loaded at startup, appended after every successful prayer, capped at 10 entries. Injected into the message call as alternating `user`/`assistant` messages so the LLM sees genuine conversation history.
### Divine intervention timing
Uses exponential distribution (`random.expovariate`) — the correct model for Poisson arrivals. `interventions_per_day=4` means an average gap of 6 hours but intervals are random and memoryless. Could be 3 in one hour, then nothing for 18 hours.
---
## Sethpc Infrastructure Context
This was developed and deployed on:
- MCSManager on CT 644 (Proxmox, Debian 12, node-112)
- Minecraft shrink-world server: port 25566, RCON 25576
- Ollama on steel141 (192.168.0.141:11434)
- Models: `gemma3:12b` (messages), `qwen3-coder:30b` (commands)
- Service: `mc-aigod.service` on CT 644
- Config: `/etc/mc_aigod.json`
- Script: `/usr/local/bin/mc_aigod.py`
@@ -0,0 +1,12 @@
{
"type": "minecraft:add_spawns",
"biomes": "#minecraft:is_overworld",
"spawners": [
{
"type": "minecraft:creeper",
"weight": 400,
"minCount": 1,
"maxCount": 4
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 26,
"description": "Increased creeper spawn rate"
}
}
@@ -0,0 +1 @@
{"values": ["shrinkborder:load"]}
@@ -0,0 +1 @@
{"values": ["shrinkborder:tick"]}
@@ -0,0 +1,5 @@
# Check if total deaths increased since last tick
execute unless score $deaths_total deaths_total = $deaths_prev deaths_prev run function shrinkborder:on_death
# Update previous death count
scoreboard players operation $deaths_prev deaths_prev = $deaths_total deaths_total
@@ -0,0 +1,2 @@
scoreboard players set $shrink_enabled shrink_enabled 0
tellraw @a ["",{"text":"[ShrinkBorder] ","color":"gold"},{"text":"Border shrinking is now ","color":"white"},{"text":"DISABLED","color":"red"},{"text":".","color":"gray"}]
@@ -0,0 +1,5 @@
scoreboard players set $shrink_enabled shrink_enabled 1
scoreboard players set $deaths_total deaths_total 0
execute as @a run scoreboard players operation $deaths_total deaths_total += @s player_deaths
scoreboard players operation $deaths_prev deaths_prev = $deaths_total deaths_total
tellraw @a ["",{"text":"[ShrinkBorder] ","color":"gold"},{"text":"Border shrinking is now ","color":"white"},{"text":"ENABLED","color":"green"},{"text":"! Die and the walls close in.","color":"gray"}]
@@ -0,0 +1,18 @@
# Initialise scoreboards on world load
scoreboard objectives add deaths_total dummy "Total Deaths"
scoreboard objectives add deaths_prev dummy "Previous Deaths"
scoreboard objectives add border_parity dummy "Border Parity"
scoreboard objectives add shrink_enabled dummy "Shrink Enabled"
scoreboard objectives add player_deaths deathCount
# Set shrink feature to DISABLED by default
scoreboard players set $shrink_enabled shrink_enabled 0
# Initialise parity tracker (0=shrink N/S, 1=shrink E/W)
scoreboard players set $border_parity border_parity 0
# Set world border
worldborder center 0 0
worldborder set 1000
tellraw @a ["",{"text":"[ShrinkBorder] ","color":"gold"},{"text":"Loaded. Shrinking is ","color":"white"},{"text":"DISABLED","color":"red"},{"text":". Use /function shrinkborder:enable to start.","color":"gray"}]
@@ -0,0 +1,12 @@
# A death occurred - shrink the border by 1 on alternating axes
tellraw @a ["",{"text":"[ShrinkBorder] ","color":"gold"},{"text":"A player died! Border shrinking...","color":"red"}]
# Even deaths: shrink N/S
execute if score $border_parity border_parity matches 0 run function shrinkborder:shrink_ns
# Odd deaths: shrink E/W
execute if score $border_parity border_parity matches 1 run function shrinkborder:shrink_ew
# Flip parity
execute if score $border_parity border_parity matches 0 run scoreboard players set $border_parity border_parity 1
execute if score $border_parity border_parity matches 1 run scoreboard players set $border_parity border_parity 0
@@ -0,0 +1,5 @@
scoreboard players set $shrink_enabled shrink_enabled 0
scoreboard players set $border_parity border_parity 0
worldborder center 0 0
worldborder set 1000
tellraw @a ["",{"text":"[ShrinkBorder] ","color":"gold"},{"text":"Border reset to 1000x1000 and shrinking DISABLED.","color":"green"}]
@@ -0,0 +1,2 @@
worldborder add -1 1
tellraw @a ["",{"text":"[ShrinkBorder] ","color":"gold"},{"text":"East wall closed in. ","color":"yellow"},{"text":"Stay inside!","color":"red"}]
@@ -0,0 +1,2 @@
worldborder add -1 1
tellraw @a ["",{"text":"[ShrinkBorder] ","color":"gold"},{"text":"North wall closed in. ","color":"yellow"},{"text":"Stay inside!","color":"red"}]
@@ -0,0 +1,6 @@
# Run every tick - count total deaths across all players
scoreboard players set $deaths_total deaths_total 0
execute as @a run scoreboard players operation $deaths_total deaths_total += @s player_deaths
# Only process if shrinking is enabled
execute if score $shrink_enabled shrink_enabled matches 1 run function shrinkborder:check_deaths
+6
View File
@@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 26,
"description": "Shrinking border on death"
}
}
+14
View File
@@ -0,0 +1,14 @@
[Unit]
Description=Minecraft AI God (/pray watcher)
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/bin/mc_aigod.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
+1078
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
{
"server_name": "shrink-world",
"log_path": "/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/logs/latest.log",
"rcon_host": "127.0.0.1",
"rcon_port": 25576,
"rcon_password": "REDACTED_RCON",
"ollama_url": "http://192.168.0.141:11434",
"model": "gemma3:12b",
"command_model": "qwen3-coder:30b",
"temperature": 0.85,
"max_tokens": 600,
"cooldown_seconds": 20,
"max_commands_per_response": 6,
"interventions_per_day": 4,
"god_chat_prefix": "[§6§lGOD§r]",
"debug_commands": true,
"memory_path": "/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/aigod_memory.json"
}
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=Minecraft God Mode Watcher (mc1 - slingshooter08)
After=mcsm-daemon.service
Requires=mcsm-daemon.service
[Service]
ExecStart=/usr/local/bin/mc_godmode_watch.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=Minecraft Shrink World Auto Kit on Join
After=mcsm-daemon.service
Requires=mcsm-daemon.service
[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/shrink_godkit.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""
Sets slingshooter08 to creative mode on server 1 (mc1).
Called by mc_godmode_watch.sh whenever slingshooter08 joins.
Deployed to: /usr/local/bin/mc_godmode_rcon.py on CT 644
"""
import socket, struct, time, sys
PLAYER = 'slingshooter08'
def rcon(cmd, host='127.0.0.1', port=25575, password='REDACTED_RCON'):
s = socket.socket()
s.settimeout(5)
s.connect((host, port))
def pkt(i, t, p):
p = p.encode() + b'\x00\x00'
return struct.pack('<iii', len(p)+8, i, t) + p
s.sendall(pkt(1, 3, password))
time.sleep(0.2)
s.recv(4096)
s.sendall(pkt(2, 2, cmd))
time.sleep(0.2)
r = s.recv(4096)
s.close()
return r[12:-2].decode()
print(rcon(f'gamemode creative {PLAYER}'))
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# Watches mc1 log for slingshooter08 joining and applies creative mode.
# Deployed to: /usr/local/bin/mc_godmode_watch.sh on CT 644
# Managed by: mc-godmode.service
LOG=/opt/mcsmanager/daemon/data/InstanceData/d39f55861cb34204a92a18a9e1c78ca6/logs/latest.log
PLAYER=slingshooter08
tail -F "$LOG" | grep --line-buffered "joined the game" | while read line; do
if echo "$line" | grep -qi "$PLAYER"; then
sleep 1
python3 /usr/local/bin/mc_godmode_rcon.py
fi
done
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
Shrink-world server watcher:
- Gives full kit to every player on FIRST login only
- Sends stats on join, on death, and every hour
- Tracks total deaths and current world border size
Deployed to: /usr/local/bin/shrink_godkit.py on CT 644
Managed by: mc-shrink-kit.service
"""
import socket, struct, time, sys, subprocess, re, threading, json, os
LOG = '/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/logs/latest.log'
RCON_HOST = '127.0.0.1'
RCON_PORT = 25576
RCON_PASS = 'REDACTED_RCON'
KIT_RECORD = '/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/kit_given.json'
total_deaths = 0
def rcon(cmd):
try:
s = socket.socket()
s.settimeout(5)
s.connect((RCON_HOST, RCON_PORT))
def pkt(i, t, p):
p = p.encode() + b'\x00\x00'
return struct.pack('<iii', len(p)+8, i, t) + p
s.sendall(pkt(1, 3, RCON_PASS))
time.sleep(0.2)
s.recv(4096)
s.sendall(pkt(2, 2, cmd))
time.sleep(0.2)
r = s.recv(4096)
s.close()
return r[12:-2].decode()
except Exception as e:
print(f'RCON error: {e}', file=sys.stderr)
return ''
def load_kit_record():
if os.path.exists(KIT_RECORD):
with open(KIT_RECORD) as f:
return set(json.load(f))
return set()
def save_kit_record(players):
with open(KIT_RECORD, 'w') as f:
json.dump(list(players), f)
kit_given = load_kit_record()
def get_border_size():
try:
r = rcon('worldborder get')
m = re.search(r'([\d.]+) block', r)
if m:
return float(m.group(1))
except:
pass
return None
def get_online_players():
try:
r = rcon('list')
m = re.search(r'There are (\d+) of a max of \d+ players online:(.*)', r)
if m:
count = int(m.group(1))
names = [n.strip() for n in m.group(2).split(',') if n.strip()]
return count, names
except:
pass
return 0, []
def broadcast_stats(context=''):
border = get_border_size()
count, players = get_online_players()
border_str = f'{border:.0f}x{border:.0f}' if border else 'unknown'
player_str = ', '.join(players) if players else 'none'
lines = [
f'tellraw @a ["",{{"text":"--- World Stats ---","color":"gold","bold":true}}]',
f'tellraw @a ["",{{"text":"Border: ","color":"yellow"}},{{"text":"{border_str} blocks","color":"white"}}]',
f'tellraw @a ["",{{"text":"Total Deaths: ","color":"yellow"}},{{"text":"{total_deaths}","color":"white"}}]',
f'tellraw @a ["",{{"text":"Online ({count}): ","color":"yellow"}},{{"text":"{player_str}","color":"white"}}]',
]
if context:
lines.insert(0, f'tellraw @a ["",{{"text":"{context}","color":"aqua","italic":true}}]')
for cmd in lines:
rcon(cmd)
time.sleep(0.05)
def give_kit(player):
print(f'Giving kit to {player}')
cmds = [
f'gamemode survival {player}',
f'give {player} netherite_helmet[enchantments={{protection:4,unbreaking:3,mending:1,respiration:3,aqua_affinity:1}}]',
f'give {player} netherite_chestplate[enchantments={{protection:4,unbreaking:3,mending:1}}]',
f'give {player} netherite_leggings[enchantments={{protection:4,unbreaking:3,mending:1}}]',
f'give {player} netherite_boots[enchantments={{protection:4,unbreaking:3,mending:1,feather_falling:4,depth_strider:3}}]',
f'give {player} netherite_sword[enchantments={{sharpness:5,unbreaking:3,mending:1,looting:3,fire_aspect:2,sweeping_edge:3}}]',
f'give {player} bow[enchantments={{power:5,unbreaking:3,infinity:1,flame:1,punch:2}}]',
f'give {player} netherite_pickaxe[enchantments={{efficiency:5,unbreaking:3,mending:1,fortune:3}}]',
f'give {player} netherite_axe[enchantments={{efficiency:5,unbreaking:3,mending:1,sharpness:5}}]',
f'give {player} arrow 64',
f'give {player} golden_apple 64',
f'give {player} totem_of_undying 4',
f'give {player} ender_pearl 16',
f'give {player} cooked_beef 64',
f'tellraw {player} ["",{{"text":"Welcome to the Shrinking World! ","color":"gold","bold":true}},{{"text":"You have been given a full kit. Good luck!","color":"yellow"}}]',
]
for cmd in cmds:
result = rcon(cmd)
print(f' {result}')
time.sleep(0.05)
def hourly_broadcast():
while True:
time.sleep(3600)
print('Sending hourly stats...')
broadcast_stats('[ Hourly Update ]')
t = threading.Thread(target=hourly_broadcast, daemon=True)
t.start()
proc = subprocess.Popen(['tail', '-F', LOG], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
print('Shrink-world watcher started.')
for line in (proc.stdout or []):
line = line.strip()
if not line:
continue
# Player joined
join_match = re.search(r'\[Server thread/INFO\].*?(\w+) joined the game', line)
if join_match:
player = join_match.group(1)
print(f'JOIN: {player}')
time.sleep(1)
if player.lower() not in kit_given:
give_kit(player)
kit_given.add(player.lower())
save_kit_record(kit_given)
else:
print(f' Kit already given to {player}, skipping')
rcon(f'tellraw {player} ["",{{"text":"Welcome back, {player}!","color":"gold"}}]')
time.sleep(0.5)
broadcast_stats(f'{player} joined the game')
continue
# Player died
death_match = re.search(r'\[Server thread/INFO\]: (\w+) (died|was slain|was shot|drowned|fell|burned|blew up|suffocated|starved|was killed|hit the ground|went up in flames|tried to swim in lava|walked into a cactus|was poked|was squashed|was struck by lightning)', line)
if death_match:
player = death_match.group(1)
total_deaths += 1
print(f'DEATH: {player} (total: {total_deaths})')
time.sleep(0.5)
broadcast_stats(f'{player} died!')
continue