Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
.idea
.venv/
.venv-*/
.pytest_cache/
isolated-mcdr.log
rendered-*.txt
*.mcdr
*.pyz
*/__pycache__/
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

1. 配置一个MCDR实例(包括MC服务端)并加入此插件, 将其启动

2. 依照 (配置选项)[## 配置选项] 修改主体插件配置, **特别是自动检测挂载点的配置**, 然后重载此插件(使用指令`!!mount -r`即可)
2. 依照 [配置选项](#配置选项) 修改主体插件配置, **特别是自动检测挂载点的配置**, 然后重载此插件(使用指令`!!mount -r`即可)

3. 修改在第二步配置的`overwrite_name`文件, 建议添加服务器端口和RCON相关配置来获取一致的体验
3. 修改在第二步配置的`overwrite_path`文件, 建议添加服务器端口和RCON相关配置来获取一致的体验

4. 在第二步配置的自动检测目录中, 放入更多MC服务端实例, 然后可以游戏内输入`!!mount -l`查看并修改相关配置选项, 在修改完毕之后将`人工检查`项设置为`True`

Expand Down Expand Up @@ -47,6 +47,8 @@
"mount_name": "MountDemo",
// 分页大小
"list_size": 15,
"pinned_servers": [],
"list_order": "configured",
// 调试模式, 开启后会在控制台输出更多信息
"debug": false
}
Expand Down Expand Up @@ -78,4 +80,11 @@
## 其他
- 在自动检测目录的子目录下添加名为`.mount-ignore`的文件可以使该子目录免于检测
- 通过手动修改配置文件, 可以添加任意目录的服务器作为挂载点
- 实际配置格式均需要满足json格式,即不得包含上例中以`//`开头的注释
- 实际配置格式均需要满足json格式,即不得包含上例中以`//`开头的注释
## 列表和统计

`pinned_servers` 手动置顶优先;`list_order` 为 `configured` 或 `activity`。可信玩家时间用于活跃度排序,同分稳定,先排序再分页;不可用目标仍显示状态与原因。统计使用单调时钟累计,玩家去重仅限运行期,中途加载只记录随后观察到的事件。

`!!mount --list [page]` 查看列表;`!!mount <完整路径或token>` 请求切换,`!!mount --reset` 请求重置,`!!mount --confirm` 确认当前请求,`!!mount --abort` 取消。完整目标优先解析,尾随 ` --confirm` 仅在输入不是完整已配置目标时作为确认参数。公开按钮使用token,重名显示编号,不公开绝对路径。

安装版本1.0.0时按上面的格式创建配置,`servers_path` 必须为字符串数组。统计由插件生成;`schema_version: 2` 标识可信计时格式,`use_time_ns_v2`/`player_time_ns_v2` 为纳秒累计值,`last_mount_ns` 为挂载锚点,不维护永久玩家名单。
19 changes: 14 additions & 5 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@

1. Deploy a MCDR instance(with MC server), and start it with this plugin

2. Edit the main config according to (Config)[## Config], then reload this plugin(by command `!!mount -r` or other command provided by MCDR)
2. Edit the main config according to [Config](#config), then reload this plugin(by command `!!mount -r` or other command provided by MCDR)

3. Edit the file of `overwrite_name` which is configed at setp 2, Recommand to set up server port and rcon to get a consistent experience
3. Edit the file of `overwrite_path` which is configed at setp 2, Recommand to set up server port and rcon to get a consistent experience

4. Add more MC server into the `servers_path` at step 2, then you can use `!!mount -l` to see them and edit their config according to (Config)[## Config], finally make sure set `checked` to true
4. Add more MC server into the `servers_path` at step 2, then you can use `!!mount -l` to see them and edit their config according to [Config](#config), finally make sure set `checked` to true

5. Type `!!mount -l` now, you can see available servers now, and then mount it

Expand Down Expand Up @@ -47,6 +47,8 @@
"mount_name": "MountDemo",
// page size of pagination
"list_size": 15,
"pinned_servers": [],
"list_order": "configured",
// debug mode, will print more info
"debug": false
}
Expand All @@ -69,7 +71,7 @@
// reset method, full for reset all, region for keep up player data(e.g. Parkour record)
"reset_type": "full",
// mcdr plugin dir for this server, '' and '.' means empty
"plugin_dir": "",
"plugin_dir": "",
"stats": {
// Stats for this server, will generate automaticaly
}
Expand All @@ -78,4 +80,11 @@
## Other
- add file with name `.mount-ignore` under folder in auto-detect folder to not detect that folder
- by editing config file, you can add any server in any folder as mountable server
- the actual config file must be json format, so remove the comments starting with `//` from above config sample
- the actual config file must be json format, so remove the comments starting with `//` from above config sample
## List and statistics

`pinned_servers` takes priority. `list_order` accepts `configured` or `activity`; trusted player time determines activity order, ties stay stable and sorting precedes pagination. Unavailable targets show their status and reason. Monotonic counters accumulate observed events; player deduplication lasts only for the running session.

Use `!!mount --list [page]`, `!!mount <full path or token>`, `!!mount --reset`, `!!mount --confirm` and `!!mount --abort`. Exact configured targets take precedence over the trailing ` --confirm` parameter. Public buttons use tokens and duplicate names use numbered labels without exposing absolute paths.

For version1.0.0 create configuration in the format above. `servers_path` must be an array of strings. Generated statistics use `schema_version: 2`, nanosecond `use_time_ns_v2`/`player_time_ns_v2` counters and the `last_mount_ns` anchor; no permanent player list is stored.
14 changes: 14 additions & 0 deletions lang/en_us.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
mount:
flow:
switch: '[Switch]'
reset: '[Reset]'
previous: '[Previous]'
next: '[Next]'
pinned: '[Pinned]'
current: '[Current]'
available: '[Available]'
occupied: '[Unavailable: occupied]'
unchecked: '[Unavailable: unchecked]'
missing: '[Unavailable: directory missing]'
invalid: '[Unavailable: invalid configuration]'
page_corrected: 'Invalid page; showing page 1.'
activity_fallback: 'Activity data is untrusted; using configured order after pinned servers.'
help_msg:
welcome: "This MCDR server has multi MC server support by mount plugin, type §e!!mount§r to get help"
click_to_fill: "Click to fill {cmd}"
Expand Down
14 changes: 14 additions & 0 deletions lang/zh_cn.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
mount:
flow:
switch: '[切换]'
reset: '[重置]'
previous: '[上一页]'
next: '[下一页]'
pinned: '[置顶]'
current: '[当前服]'
available: '[可用]'
occupied: '[不可用:已占用]'
unchecked: '[不可用:未检查]'
missing: '[不可用:目录不存在]'
invalid: '[不可用:配置失效]'
page_corrected: '页码无效,已返回第 1 页。'
activity_fallback: '活跃度数据不可信,置顶后按配置顺序显示。'
help_msg:
welcome: "此服务器通过mount插件添加了多挂载点支持, 输入§e!!mount§r可获取帮助信息"
click_to_fill: "点击以填入{cmd}"
Expand Down
4 changes: 2 additions & 2 deletions mcdreforged.plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "mount",
"version": "0.3.1",
"version": "1.0.0",
"name": "Mount",
"description": {
"en_us": "Mount different minecraft server into a mcdr server, with overwritten properties!",
Expand All @@ -21,4 +21,4 @@
"README.md",
"README_en.md"
]
}
}
122 changes: 73 additions & 49 deletions mount/MountManager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import functools
import hashlib
import math
import os.path
import time
Expand All @@ -15,6 +16,7 @@
from .constants import *
from .detect_helper import DetectHelper
from .MountSlot import MountSlot
from .list_order import order_slots
from .reset_helper import ResetHelper
from .utils import logger, psi, rtr, debug

Expand Down Expand Up @@ -182,6 +184,7 @@ def request_operation(self, mode: str, source: CommandSource, path: Optional[str
@single_op(Operation.REQUEST_MOUNT)
def request_mount(self, source: CommandSource, path: str, with_confirm: bool = False):
global current_op
path = self.resolve_target(path)
debug("Received mount request, evaluating...")

if path == self.current_slot.path:
Expand Down Expand Up @@ -299,57 +302,78 @@ def abort_operation(self, source: CommandSource):
self.next_slot.release(self._config.mount_name)
self.next_slot = None

@new_thread('mount-list')
def list_servers(self, src: CommandSource, page: int = 1):
debug(f"Received list request for page {page}")
list_size = self._config.list_size
available_servers = self._config.available_servers
max_page = math.ceil(len(available_servers) / list_size)
if not 1 <= page <= max_page:
def message(self, src, key, **kwargs):
src.reply(rtr('flow.' + key, **kwargs))
def button(self, key, command):
return RText(rtr('flow.' + key)).c(RAction.run_command, COMMAND_PREFIX + ' ' + command)
def target_token(self, path):
return 'slot:' + hashlib.sha256(path.encode('utf-8')).hexdigest()
def resolve_target(self, target):
if target.startswith('slot:'):
return next((path for path in self.servers_as_list if self.target_token(path) == target), '')
return target
def target_name(self, path):
name = os.path.basename(os.path.normpath(path))
matches = [p for p in self.servers_as_list if os.path.basename(os.path.normpath(p)) == name]
return name + (' #'+str(matches.index(path)+1) if len(matches) > 1 and path in matches else '')

def list_servers(self, src, page=1):
views, slots, reasons = [], {}, {}
degraded = False
for path in self.servers_as_list:
try:
if not os.path.isdir(path):
raise ValueError('missing path')
slot = MountSlot(path)
score = slot.stats.player_time_ns_v2
if slot.stats.schema_version != 2 or type(score) not in (int, float) or not math.isfinite(score) or score < 0:
score, degraded = 0, True
views.append((path, max(0, score)))
slots[path] = slot
except Exception:
reasons[path] = 'missing' if not os.path.isdir(path) else 'invalid'
degraded = True
views.append((path, 0))
views = order_slots(views, self._config.pinned_servers,
'configured' if degraded else self._config.list_order)
size = self._config.list_size
if type(size) is not int or size <= 0:
size = 15
pages = max(1, math.ceil(len(views)/size))
try:
page = int(page)
except (ValueError, TypeError):
page = 0
if not 1 <= page <= pages:
page = 1

left = (page - 1) * list_size
right = min(len(available_servers), page * list_size)
src.reply(RText(rtr('list.title')))
for server in available_servers[left: right]:
slot = MountSlot(path=server)
src.reply(
slot
.get_config()
.as_list_entry(slot.name, slot.path,
self._config.mount_name, self._config.current_server
))

# <<< curr/total >>>
link_color = {
True: RColor.green,
False: RColor.gray
}

left_link: RText
if page <= 1:
left_link = RText('<<<', color=link_color[False]).h(rtr('list.no_more_page'))
else:
left_link = RText('<<<', color=link_color[True]).h(rtr('list.prev_page')) \
.c(RAction.suggest_command, COMMAND_PREFIX + ' --list ' + str(page - 1))

right_link: RText
if page >= max_page:
right_link = RText('>>>', color=link_color[False]).h(rtr('list.no_more_page'))
else:
right_link = RText('>>>', color=link_color[True]).h(rtr('list.next_page')) \
.c(RAction.suggest_command, COMMAND_PREFIX + ' --list ' + str(page + 1))

footer = RTextList(
left_link,
f' {page} / {max_page} ',
right_link
)
if left == right:
self.message(src, 'page_corrected')
src.reply(rtr('list.title'))
if degraded and self._config.list_order == 'activity':
self.message(src, 'activity_fallback')
for path, _ in views[(page-1)*size:page*size]:
slot, current = slots.get(path), path == self.current_slot.path
row = RTextList(self.target_name(path), ' ')
if path in self._config.pinned_servers:
row.append(rtr('flow.pinned'), ' ')
if current:
row.append(rtr('flow.current'), ' ')
if slot:
row.append(slot.desc, ' ')
reason = ('unchecked' if not slot.checked else 'occupied'
if slot.occupied_by and (not current or slot.occupied_by != self._config.mount_name)
else 'available')
row.append(rtr('flow.'+reason), ' ')
if current and slot.reset_path and reason == 'available':
row.append(self.button('reset', '--reset'))
elif not current and reason == 'available':
row.append(self.button('switch', self.target_token(path)))
else:
row.append(rtr('flow.'+reasons[path]))
src.reply(row)
if not views:
src.reply(rtr('list.empty'))
else:
src.reply(footer)

src.reply(RTextList(self.button('previous', '--list '+str(page-1)) if page > 1 else rtr('flow.previous'),
' {}/{} '.format(page,pages), self.button('next', '--list '+str(page+1)) if page < pages else rtr('flow.next')))

def get_config(self, config_key, src: Optional[CommandSource] = None):
if src is not None:
Expand Down
Loading
Loading