Add teleport border guard, dimension-aware TP, fully enchanted context, enchantment guidance for god
This commit is contained in:
@@ -0,0 +1,180 @@
|
|||||||
|
# LangGraph Implementation Idea
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Add a session-based agent gateway in front of Ollama so the Minecraft AI system can:
|
||||||
|
- keep per-player/session memory,
|
||||||
|
- run multi-step tool calls (MCP/web search),
|
||||||
|
- return a final `message + commands` payload to the existing plugin.
|
||||||
|
|
||||||
|
This is a future enhancement. Current system is working and remains the source of truth for command execution safety.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why This Exists
|
||||||
|
|
||||||
|
Current `mc_aigod.py` calls Ollama directly. That is effectively stateless unless full history is re-sent every call.
|
||||||
|
|
||||||
|
A LangGraph sidecar can provide:
|
||||||
|
- persistent sessions/threads,
|
||||||
|
- tool loop orchestration,
|
||||||
|
- model routing,
|
||||||
|
- better observability of intermediate reasoning steps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposed Architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
Minecraft chat -> mc_aigod.py -> LangGraph Gateway API -> Ollama
|
||||||
|
| \
|
||||||
|
| -> MCP tools (web search/wiki/etc)
|
||||||
|
-> Session store (SQLite/Redis)
|
||||||
|
```
|
||||||
|
|
||||||
|
`mc_aigod.py` remains responsible for:
|
||||||
|
- trigger parsing (`pray`, `bible`, `sudo`),
|
||||||
|
- RCON command execution,
|
||||||
|
- whitelist/validation/fixups,
|
||||||
|
- hard safety rules (e.g. first-login kill limits).
|
||||||
|
|
||||||
|
LangGraph gateway is responsible for:
|
||||||
|
- session state,
|
||||||
|
- tool calls,
|
||||||
|
- composing final JSON output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Contract Sketch
|
||||||
|
|
||||||
|
### Start session
|
||||||
|
`POST /v1/session/start`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"player": "slingshooter08",
|
||||||
|
"mode": "god"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"session_id": "sess_abc123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Send message
|
||||||
|
`POST /v1/session/{session_id}/message`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"text": "pray I need wood for shelter",
|
||||||
|
"context": {
|
||||||
|
"server_state": {},
|
||||||
|
"player_state": {},
|
||||||
|
"recent_events": []
|
||||||
|
},
|
||||||
|
"allow_tools": true,
|
||||||
|
"max_tool_steps": 4
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Divine response text",
|
||||||
|
"commands": [
|
||||||
|
"give slingshooter08 minecraft:oak_log 64"
|
||||||
|
],
|
||||||
|
"tool_trace": [
|
||||||
|
{
|
||||||
|
"tool": "web.search",
|
||||||
|
"input": "minecraft oak log item id",
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### End session (optional)
|
||||||
|
`POST /v1/session/{session_id}/close`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LangGraph Flow (Draft)
|
||||||
|
|
||||||
|
1. Load session state by `session_id`
|
||||||
|
2. Add user message + context
|
||||||
|
3. Call command model
|
||||||
|
4. If tool requested:
|
||||||
|
- execute MCP tool
|
||||||
|
- append tool result
|
||||||
|
- loop until final commands or step limit reached
|
||||||
|
5. Call message model with chosen commands
|
||||||
|
6. Return final `{message, commands}`
|
||||||
|
7. Persist updated session state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tooling Plan
|
||||||
|
|
||||||
|
Primary tools to add first:
|
||||||
|
- `web.search` (general search)
|
||||||
|
- `minecraft.wiki_lookup` (targeted page fetch/search)
|
||||||
|
|
||||||
|
Potential later tools:
|
||||||
|
- local documentation lookup,
|
||||||
|
- server analytics lookup,
|
||||||
|
- schematic index lookup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modes to Support
|
||||||
|
|
||||||
|
- `god` (prayer flow, tool-enabled)
|
||||||
|
- `sudo` (translator flow, likely tool-disabled or very limited)
|
||||||
|
- `god_system` (intervention/first-login internal events)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Safety Model (Keep in mc_aigod.py)
|
||||||
|
|
||||||
|
Even after LangGraph is added, keep hard enforcement in plugin runtime:
|
||||||
|
- whitelist command families,
|
||||||
|
- syntax repair + normalization,
|
||||||
|
- max commands cap,
|
||||||
|
- per-flow constraints (e.g. first-login benevolence restrictions),
|
||||||
|
- unauthorized sudo user rejection.
|
||||||
|
|
||||||
|
This ensures model/tool errors cannot directly bypass execution safeguards.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MVP Steps (Later)
|
||||||
|
|
||||||
|
1. Build FastAPI LangGraph gateway with in-memory sessions
|
||||||
|
2. Add `/session/start` + `/session/{id}/message`
|
||||||
|
3. Mirror current two-call behavior (no tools yet)
|
||||||
|
4. Switch `mc_aigod.py` to gateway endpoint
|
||||||
|
5. Add one MCP search tool and bounded tool loop
|
||||||
|
6. Add persistence (SQLite/Redis)
|
||||||
|
7. Add structured logs + tool traces
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Session lifetime policy (per player, per login, time-based expiry?)
|
||||||
|
- Whether `sudo` should ever be tool-enabled
|
||||||
|
- How much tool trace to expose in-game vs log-only
|
||||||
|
- Which MCP stack to standardize on for web search
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
This document is a planning scratchpad for future implementation. It is intentionally practical and API-first so a coder bot can pick it up and implement directly.
|
||||||
+183
-5
@@ -526,6 +526,14 @@ EFFECTS (replace {target} with any online player's username):
|
|||||||
|
|
||||||
MOVEMENT:
|
MOVEMENT:
|
||||||
tp {target} 0 64 0
|
tp {target} 0 64 0
|
||||||
|
tp {target} <x> <y> <z>
|
||||||
|
tp {target} ~ ~10 ~
|
||||||
|
execute in minecraft:the_nether run tp {target} <x> <y> <z>
|
||||||
|
execute in minecraft:the_end run tp {target} 0 64 0
|
||||||
|
execute in minecraft:overworld run tp {target} <x> <y> <z>
|
||||||
|
NOTE: To teleport a player to another dimension ALWAYS use:
|
||||||
|
execute in minecraft:<dimension> run tp <player> <x> <y> <z>
|
||||||
|
NEVER use: tp <player> minecraft:the_nether (this is wrong syntax)
|
||||||
|
|
||||||
WORLD/ENVIRONMENT (affects all players):
|
WORLD/ENVIRONMENT (affects all players):
|
||||||
time set day
|
time set day
|
||||||
@@ -668,6 +676,36 @@ def _parse_llm_json(content: str) -> dict:
|
|||||||
log.warning(f"Repaired JSON: message={len(message)}chars, commands={commands}")
|
log.warning(f"Repaired JSON: message={len(message)}chars, commands={commands}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
ENCHANTMENT_CONTEXT = """
|
||||||
|
=== ENCHANTMENT RULES FOR GOD ===
|
||||||
|
When giving weapons, tools, or armor as a divine gift, you should ALMOST ALWAYS enchant them.
|
||||||
|
Enchanted gifts feel more divine. Unenchanted items are acceptable only as a deliberate
|
||||||
|
choice (e.g. giving basic materials, a punishment of mediocrity, or items that cannot be enchanted).
|
||||||
|
|
||||||
|
Use 1.21 component syntax: give <player> minecraft:<item>[enchantments={ench1:lvl,ench2:lvl}] 1
|
||||||
|
|
||||||
|
MAX ENCHANTMENT REFERENCE (use as baseline for "fully enchanted" or "blessed" gifts):
|
||||||
|
|
||||||
|
SWORD: netherite_sword[enchantments={sharpness:5,unbreaking:3,looting:3,fire_aspect:2,mending:1,sweeping_edge:3}]
|
||||||
|
PICKAXE: netherite_pickaxe[enchantments={efficiency:5,unbreaking:3,fortune:3,mending:1}]
|
||||||
|
AXE: netherite_axe[enchantments={efficiency:5,unbreaking:3,fortune:3,sharpness:5,mending:1}]
|
||||||
|
SHOVEL: netherite_shovel[enchantments={efficiency:5,unbreaking:3,fortune:3,mending:1}]
|
||||||
|
HOE: netherite_hoe[enchantments={efficiency:5,unbreaking:3,fortune:3,mending:1}]
|
||||||
|
BOW: bow[enchantments={power:5,unbreaking:3,infinity:1,flame:1,punch:2}]
|
||||||
|
CROSSBOW: crossbow[enchantments={multishot:1,quick_charge:3,unbreaking:3,mending:1}]
|
||||||
|
TRIDENT: trident[enchantments={channeling:1,loyalty:3,unbreaking:3,mending:1,impaling:5}]
|
||||||
|
HELMET: netherite_helmet[enchantments={protection:4,unbreaking:3,respiration:3,aqua_affinity:1,thorns:3,mending:1}]
|
||||||
|
CHEST: netherite_chestplate[enchantments={protection:4,unbreaking:3,thorns:3,mending:1}]
|
||||||
|
LEGS: netherite_leggings[enchantments={protection:4,unbreaking:3,swift_sneak:3,mending:1}]
|
||||||
|
BOOTS: netherite_boots[enchantments={protection:4,unbreaking:3,feather_falling:4,depth_strider:3,soul_speed:3,mending:1}]
|
||||||
|
FISHING: fishing_rod[enchantments={luck_of_the_sea:3,lure:3,unbreaking:3,mending:1}]
|
||||||
|
ELYTRA: elytra[enchantments={unbreaking:3,mending:1}]
|
||||||
|
SHIELD: shield[enchantments={unbreaking:3,mending:1}]
|
||||||
|
|
||||||
|
You do NOT need to always give max enchants — a modest reward may have fewer.
|
||||||
|
But unenchanted weapons/tools/armor from God should be the exception, not the rule.
|
||||||
|
"""
|
||||||
|
|
||||||
COMMANDS_SYSTEM_PROMPT = (
|
COMMANDS_SYSTEM_PROMPT = (
|
||||||
"You are a Minecraft server command executor. Given a player's prayer and server context, "
|
"You are a Minecraft server command executor. Given a player's prayer and server context, "
|
||||||
"decide what server commands to run (if any) as an act of God.\n\n"
|
"decide what server commands to run (if any) as an act of God.\n\n"
|
||||||
@@ -688,6 +726,7 @@ COMMANDS_SYSTEM_PROMPT = (
|
|||||||
+ COMMAND_PALETTE
|
+ COMMAND_PALETTE
|
||||||
+ "\n=== ITEM LIBRARY ===\n"
|
+ "\n=== ITEM LIBRARY ===\n"
|
||||||
+ ITEM_LIBRARY
|
+ ITEM_LIBRARY
|
||||||
|
+ ENCHANTMENT_CONTEXT
|
||||||
)
|
)
|
||||||
|
|
||||||
SUDO_COMMANDS_SYSTEM_PROMPT = (
|
SUDO_COMMANDS_SYSTEM_PROMPT = (
|
||||||
@@ -700,9 +739,68 @@ SUDO_COMMANDS_SYSTEM_PROMPT = (
|
|||||||
"- If the request cannot be mapped safely, return commands: [].\n"
|
"- If the request cannot be mapped safely, return commands: [].\n"
|
||||||
"- If player says 'me' or 'my', target the requesting player.\n"
|
"- If player says 'me' or 'my', target the requesting player.\n"
|
||||||
"- You will receive LAST 10 SUDO ACTIONS. Use them for continuity and corrections when the player says previous output was wrong.\n"
|
"- You will receive LAST 10 SUDO ACTIONS. Use them for continuity and corrections when the player says previous output was wrong.\n"
|
||||||
"- For give syntax: give <player> minecraft:<item_id> <count>\n"
|
"- For give syntax: give <player> minecraft:<item_id> <count> (count LAST, namespace required)\n"
|
||||||
"- Count is last. Namespace minecraft: is required.\n"
|
|
||||||
"- Return commands only. No commentary.\n"
|
"- Return commands only. No commentary.\n"
|
||||||
|
"\n"
|
||||||
|
"=== TELEPORT SYNTAX ===\n"
|
||||||
|
"Same dimension: tp <player> <x> <y> <z>\n"
|
||||||
|
"Relative: tp <player> ~ ~10 ~\n"
|
||||||
|
"To Nether: execute in minecraft:the_nether run tp <player> <x> <y> <z>\n"
|
||||||
|
"To End: execute in minecraft:the_end run tp <player> <x> 64 <z>\n"
|
||||||
|
"To Overworld: execute in minecraft:overworld run tp <player> <x> <y> <z>\n"
|
||||||
|
"WRONG (never do this): tp <player> minecraft:the_nether\n"
|
||||||
|
"When dimension is unspecified, use a sensible default spawn coord for that dimension.\n"
|
||||||
|
"\n"
|
||||||
|
"=== FULLY ENCHANTED (max enchantments per item type, 1.21 syntax) ===\n"
|
||||||
|
"Use item[enchantments={...}] syntax. Count is always 1 for enchanted items.\n"
|
||||||
|
"\n"
|
||||||
|
"SWORD (netherite_sword):\n"
|
||||||
|
" give <p> minecraft:netherite_sword[enchantments={sharpness:5,unbreaking:3,looting:3,fire_aspect:2,mending:1,sweeping_edge:3}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"PICKAXE (netherite_pickaxe):\n"
|
||||||
|
" give <p> minecraft:netherite_pickaxe[enchantments={efficiency:5,unbreaking:3,fortune:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"AXE (netherite_axe):\n"
|
||||||
|
" give <p> minecraft:netherite_axe[enchantments={efficiency:5,unbreaking:3,fortune:3,sharpness:5,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"SHOVEL (netherite_shovel):\n"
|
||||||
|
" give <p> minecraft:netherite_shovel[enchantments={efficiency:5,unbreaking:3,fortune:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"HOE (netherite_hoe):\n"
|
||||||
|
" give <p> minecraft:netherite_hoe[enchantments={efficiency:5,unbreaking:3,fortune:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"BOW:\n"
|
||||||
|
" give <p> minecraft:bow[enchantments={power:5,unbreaking:3,infinity:1,flame:1,punch:2}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"CROSSBOW:\n"
|
||||||
|
" give <p> minecraft:crossbow[enchantments={multishot:1,quick_charge:3,unbreaking:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"TRIDENT:\n"
|
||||||
|
" give <p> minecraft:trident[enchantments={channeling:1,loyalty:3,riptide:3,unbreaking:3,mending:1,impaling:5}] 1\n"
|
||||||
|
" (note: riptide and loyalty/channeling are mutually exclusive — pick one set)\n"
|
||||||
|
"\n"
|
||||||
|
"HELMET (netherite_helmet):\n"
|
||||||
|
" give <p> minecraft:netherite_helmet[enchantments={protection:4,unbreaking:3,respiration:3,aqua_affinity:1,thorns:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"CHESTPLATE (netherite_chestplate):\n"
|
||||||
|
" give <p> minecraft:netherite_chestplate[enchantments={protection:4,unbreaking:3,thorns:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"LEGGINGS (netherite_leggings):\n"
|
||||||
|
" give <p> minecraft:netherite_leggings[enchantments={protection:4,unbreaking:3,swift_sneak:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"BOOTS (netherite_boots):\n"
|
||||||
|
" give <p> minecraft:netherite_boots[enchantments={protection:4,unbreaking:3,feather_falling:4,depth_strider:3,soul_speed:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"FISHING ROD:\n"
|
||||||
|
" give <p> minecraft:fishing_rod[enchantments={luck_of_the_sea:3,lure:3,unbreaking:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"ELYTRA:\n"
|
||||||
|
" give <p> minecraft:elytra[enchantments={unbreaking:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"SHIELD:\n"
|
||||||
|
" give <p> minecraft:shield[enchantments={unbreaking:3,mending:1}] 1\n"
|
||||||
|
"\n"
|
||||||
|
"When player asks for 'fully enchanted', 'max enchanted', 'best', 'godlike' gear — use the above templates.\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
FIRST_LOGIN_BENEVOLENCE_PROMPT = (
|
FIRST_LOGIN_BENEVOLENCE_PROMPT = (
|
||||||
@@ -921,6 +1019,83 @@ SAFE_PREFIXES = [
|
|||||||
'execute ', 'kill ', 'summon ', 'tellraw ', 'worldborder ',
|
'execute ', 'kill ', 'summon ', 'tellraw ', 'worldborder ',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_border_cache = {"ts": 0.0, "half": None}
|
||||||
|
|
||||||
|
|
||||||
|
def _tp_border_guard_enabled(config) -> bool:
|
||||||
|
return bool(config.get("tp_border_guard_enabled", True))
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_abs_coord(tok: str):
|
||||||
|
tok = (tok or "").strip()
|
||||||
|
if not tok or tok.startswith("~") or tok.startswith("^"):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return float(tok)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_tp_abs_xyz(cmd: str):
|
||||||
|
for m in re.finditer(r'\btp\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)', cmd):
|
||||||
|
x = _parse_abs_coord(m.group(1))
|
||||||
|
y = _parse_abs_coord(m.group(2))
|
||||||
|
z = _parse_abs_coord(m.group(3))
|
||||||
|
if x is None or y is None or z is None:
|
||||||
|
continue
|
||||||
|
return x, y, z
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _worldborder_half_extent(config):
|
||||||
|
now = time.time()
|
||||||
|
if now - float(_border_cache.get("ts", 0.0)) < 10.0 and _border_cache.get("half") is not None:
|
||||||
|
return float(_border_cache["half"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw = rcon("worldborder get", config["rcon_host"], config["rcon_port"], config["rcon_password"])
|
||||||
|
nums = re.findall(r'(-?[\d.]+)', raw or "")
|
||||||
|
if not nums:
|
||||||
|
return None
|
||||||
|
width = float(nums[0])
|
||||||
|
half = max(0.0, width / 2.0)
|
||||||
|
_border_cache["ts"] = now
|
||||||
|
_border_cache["half"] = half
|
||||||
|
return half
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
_OTHER_DIMENSION_RE = re.compile(
|
||||||
|
r'\bexecute\s+in\s+minecraft:(the_nether|the_end|nether|end)\b', re.IGNORECASE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _tp_inside_worldborder(cmd: str, config) -> bool:
|
||||||
|
if not _tp_border_guard_enabled(config):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Nether/End dimension teleports use different coordinate spaces — skip border check.
|
||||||
|
if _OTHER_DIMENSION_RE.search(cmd):
|
||||||
|
return True
|
||||||
|
|
||||||
|
xyz = _extract_tp_abs_xyz(cmd)
|
||||||
|
if not xyz:
|
||||||
|
# Relative/dynamic TP can't be statically checked here.
|
||||||
|
return True
|
||||||
|
|
||||||
|
half = _worldborder_half_extent(config)
|
||||||
|
if half is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
x, _, z = xyz
|
||||||
|
cx = float(config.get("worldborder_center_x", 0.0))
|
||||||
|
cz = float(config.get("worldborder_center_z", 0.0))
|
||||||
|
margin = max(0.0, float(config.get("tp_border_margin", 2.0)))
|
||||||
|
limit = max(0.0, half - margin)
|
||||||
|
|
||||||
|
return abs(x - cx) <= limit and abs(z - cz) <= limit
|
||||||
|
|
||||||
def fix_give_command(cmd: str) -> str:
|
def fix_give_command(cmd: str) -> str:
|
||||||
"""
|
"""
|
||||||
Correct common LLM give command mistakes:
|
Correct common LLM give command mistakes:
|
||||||
@@ -985,7 +1160,7 @@ def fix_effect_command(cmd: str) -> str:
|
|||||||
return fixed
|
return fixed
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
def validate_command(cmd, online_players, fallback_player):
|
def validate_command(cmd, online_players, fallback_player, config=None):
|
||||||
"""Replace placeholders, auto-fix common give syntax errors, check safe prefix."""
|
"""Replace placeholders, auto-fix common give syntax errors, check safe prefix."""
|
||||||
resolved = cmd.replace("{player}", fallback_player).replace("{target}", fallback_player)
|
resolved = cmd.replace("{player}", fallback_player).replace("{target}", fallback_player)
|
||||||
resolved = fix_give_command(resolved)
|
resolved = fix_give_command(resolved)
|
||||||
@@ -993,6 +1168,9 @@ def validate_command(cmd, online_players, fallback_player):
|
|||||||
if not any(resolved.startswith(p) for p in SAFE_PREFIXES):
|
if not any(resolved.startswith(p) for p in SAFE_PREFIXES):
|
||||||
log.warning(f"Command blocked (unknown prefix): {resolved}")
|
log.warning(f"Command blocked (unknown prefix): {resolved}")
|
||||||
return resolved, False
|
return resolved, False
|
||||||
|
if config and _extract_tp_abs_xyz(resolved) and not _tp_inside_worldborder(resolved, config):
|
||||||
|
log.warning(f"Command blocked (tp outside worldborder): {resolved}")
|
||||||
|
return resolved, False
|
||||||
return resolved, True
|
return resolved, True
|
||||||
|
|
||||||
def execute_response(response, context, config, praying_player=None):
|
def execute_response(response, context, config, praying_player=None):
|
||||||
@@ -1050,7 +1228,7 @@ def execute_response(response, context, config, praying_player=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for cmd in commands[:max_cmds]:
|
for cmd in commands[:max_cmds]:
|
||||||
resolved, is_safe = validate_command(cmd, context["online_players"], fallback)
|
resolved, is_safe = validate_command(cmd, context["online_players"], fallback, config)
|
||||||
if not is_safe:
|
if not is_safe:
|
||||||
continue
|
continue
|
||||||
log.info(f"Executing RCON: {resolved}")
|
log.info(f"Executing RCON: {resolved}")
|
||||||
@@ -1242,7 +1420,7 @@ def process_sudo(player, prompt, config):
|
|||||||
|
|
||||||
executed = []
|
executed = []
|
||||||
for cmd in commands:
|
for cmd in commands:
|
||||||
resolved, is_safe = validate_command(cmd, online, player)
|
resolved, is_safe = validate_command(cmd, online, player, config)
|
||||||
if not is_safe:
|
if not is_safe:
|
||||||
continue
|
continue
|
||||||
log.info(f"SUDO execute: {resolved}")
|
log.info(f"SUDO execute: {resolved}")
|
||||||
|
|||||||
@@ -17,6 +17,10 @@
|
|||||||
"sudo_enabled": true,
|
"sudo_enabled": true,
|
||||||
"sudo_user": "slingshooter08",
|
"sudo_user": "slingshooter08",
|
||||||
"sudo_max_commands": 3,
|
"sudo_max_commands": 3,
|
||||||
|
"tp_border_guard_enabled": true,
|
||||||
|
"worldborder_center_x": 0,
|
||||||
|
"worldborder_center_z": 0,
|
||||||
|
"tp_border_margin": 2,
|
||||||
"first_login_benevolence_enabled": true,
|
"first_login_benevolence_enabled": true,
|
||||||
"first_login_benevolence_max_commands": 10,
|
"first_login_benevolence_max_commands": 10,
|
||||||
"first_login_path": "/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/aigod_first_login_seen.json",
|
"first_login_path": "/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/aigod_first_login_seen.json",
|
||||||
|
|||||||
Reference in New Issue
Block a user