This commit is contained in:
shenjack 2023-06-26 10:14:28 +08:00
parent 55d21b6b02
commit 94b8869506
6 changed files with 171 additions and 32 deletions

View File

@ -355,7 +355,8 @@ class ClientWindow(Window):
@_call_screen_after
def on_command(self, command: line.CommandText):
command.text = command.text.rstrip('\n')
self.logger.info(tr().window.command.text().format(command))
command.text = command.text.rstrip(' ')
self.logger.info(tr().window.command.text().format(f"|{command.text}|"))
command.find('/')
if command.find('stop'):
# self.dispatch_event('on_exit')
@ -383,8 +384,15 @@ class ClientWindow(Window):
self.logger.info(tr().language_available().format(os.listdir('./configs/lang')))
self.save_info()
elif command.find('mods'):
for mod in self.game.mod_manager.loaded_mod_modules.values():
self.logger.info(f"mod: {mod.name} id: {mod.mod_id} version: {mod.version}")
if command.find('list'):
for mod in self.game.mod_manager.loaded_mod_modules.values():
self.logger.info(f"mod: {mod.name} id: {mod.mod_id} version: {mod.version}")
elif command.find('reload'):
if not len(command.text) == 0:
print(f"reload mod: |{command.text}|")
self.game.mod_manager.reload_mod(command.text, game=self.game)
else:
logger.info(tr().window.command.mods.reload.no_mod_id())
@_call_screen_after
def on_message(self, message: line.CommandText):

View File

@ -10,29 +10,49 @@ import logging
import traceback
import importlib
from pathlib import Path
from typing import List, Dict, Optional
from typing import List, Dict, Optional, TypeVar
from Difficult_Rocket.mod.api import ModInfo
from Difficult_Rocket.utils.translate import tr
from Difficult_Rocket.api.types import Options, Version
Game = TypeVar('Game')
logger = logging.getLogger('mod_manager')
ONE_FILE_SUFFIX = ('.py', '.pyc', '.pyd')
PACKAGE_SUFFIX = ('.pyz', '.zip', '.dr_mod')
def _add_path_to_sys(paths: List[Path]):
for path in paths:
if str(path) not in sys.path:
sys.path.append(str(path))
class ModManager(Options):
name = 'Mod Manager'
mods_path: List[Path] = [Path('./mods')]
find_mod_paths: Dict[str, Path] = {}
loaded_mod_modules: Dict[str, ModInfo] = {}
def get_mod_module(self, mod_name: str) -> Optional[ModInfo]:
"""
获取指定 mod 的模块
:param mod_name: mod
:return:
"""
for mod in self.loaded_mod_modules.values():
if mod.name == mod_name:
return mod
return None
def dispatch_event(self, event_name: str, *args, **kwargs):
"""
分发事件
:param event_name:
:param args:
:param kwargs:
:param event_name: 事件名
:param args: 事件参数
:param kwargs: 事件参数
:return:
"""
for mod in self.loaded_mod_modules.values():
@ -42,14 +62,68 @@ class ModManager(Options):
except Exception as e:
logger.error(tr().mod.event.error().format(mod, event_name, e, traceback.format_exc()))
def load_mods(self, extra_path: Optional[List[Path]] = None) -> List[type(ModInfo)]:
def load_mod(self, mod_path: Path) -> Optional[type(ModInfo)]:
"""
加载指定路径下的 mod
:param mod_path: mod 的路径
:return:
"""
if not mod_path.exists():
logger.error(tr().mod.load.faild.not_exist().format(mod_path))
return None
_add_path_to_sys([mod_path.parent])
try:
if mod_path.name == '__pycache__':
# 忽略 __pycache__ 文件夹 (Python 编译文件)
return None
logger.info(tr().mod.load.loading().format(mod_path))
if mod_path.is_dir() or mod_path.suffix in PACKAGE_SUFFIX or mod_path.suffix in ONE_FILE_SUFFIX:
# 文件夹 mod
loading_mod = importlib.import_module(mod_path.name)
if not hasattr(loading_mod, 'mod_class') or not issubclass(loading_mod.mod_class, ModInfo):
logger.warning(tr().mod.load.faild.no_mod_class().format(mod_path))
return None
mod_class: type(ModInfo) = loading_mod.mod_class # 获取 mod 类
if mod_class.mod_id not in self.find_mod_paths:
self.find_mod_paths[mod_class.mod_id] = mod_path
return mod_class
except ImportError:
logger.warning(tr().mod.load.faild.error().format(mod_path, traceback.format_exc()))
return None
def find_mods_in_path(self, extra_mods_path: Optional[List[Path]] = None) -> List[Path]:
"""
查找所有 mod 路径
:return: 找到的 mod 的路径 (未校验)
"""
find_path = self.mods_path + (extra_mods_path if extra_mods_path is not None else [])
mods_path = []
start_time = time.time()
for path in find_path:
if not path.exists():
path.mkdir(parents=True)
continue
for mod in path.iterdir():
if mod.name == '__pycache__':
# 忽略 __pycache__ 文件夹 (Python 编译文件)
continue
if mod.is_dir() or mod.suffix in PACKAGE_SUFFIX or mod.suffix in ONE_FILE_SUFFIX:
# 文件夹 mod
mods_path.append(mod)
logger.info(tr().mod.finded().format(len(mods_path), time.time() - start_time))
return mods_path
def load_mods(self,
extra_path: Optional[List[Path]] = None,
extra_mod_path: Optional[List[Path]] = None) -> List[type(ModInfo)]:
"""
加载所有 mod (可提供额外的 mod 路径)
:param extra_path: 额外的 mod 路径
:param extra_mod_path: 额外的找到的 mod 路径
:return:
"""
find_path = self.mods_path + (extra_path if extra_path is not None else [])
sys.path += [str(path_) for path_ in find_path]
_add_path_to_sys(find_path)
mods = []
start_time = time.time()
logger.info(tr().mod.load.start().format(find_path))
@ -58,37 +132,75 @@ class ModManager(Options):
path.mkdir(parents=True)
continue
for mod in path.iterdir():
try:
if mod.name == '__pycache__':
# 忽略 __pycache__ 文件夹 (Python 编译文件)
continue
logger.info(tr().mod.load.loading().format(mod))
if mod.is_dir() or mod.suffix in PACKAGE_SUFFIX or mod.suffix in ONE_FILE_SUFFIX:
# 文件夹 mod
loading_mod = importlib.import_module(mod.name)
if not hasattr(loading_mod, 'mod_class'):
logger.warning(tr().mod.load.faild.no_mod_class().format(mod))
continue
mod_class: type(ModInfo) = loading_mod.mod_class # 获取 mod 类
mods.append(mod_class)
except ImportError:
logger.warning(tr().mod.load.faild.error().format(mod))
if (cache := self.load_mod(mod)) is not None:
mods.append(cache)
if extra_mod_path is not None:
for path in extra_mod_path:
if (cache := self.load_mod(path)) is not None:
mods.append(cache)
logger.info(tr().mod.load.use_time().format(time.time() - start_time))
return mods
def init_mods(self, mods: List[type(ModInfo)]):
"""
加载 mod
:param mods:
:param mods: 要加载的 mod ModInfo
:return:
"""
start_time = time.time()
for mod_class in mods:
try:
init_mod = mod_class()
self.loaded_mod_modules[init_mod.name] = init_mod
self.loaded_mod_modules[init_mod.mod_id] = init_mod
logger.info(tr().mod.init.success().format(init_mod, init_mod.version))
except Exception as e:
logger.error(tr().mod.init.faild().format(mod_class, e, traceback.format_exc()))
continue
logger.info(tr().mod.init.use_time().format(time.time() - start_time))
def unload_mod(self, mod_id: str, game: Game) -> Optional[ModInfo]:
"""
卸载 mod
:param mod_id: 要卸载的 mod id
:param game: 游戏实例
:return: 卸载的 mod ModInfo
"""
if not (mod_class := self.loaded_mod_modules.get(mod_id)) and (mod_class := self.get_mod_module(mod_id)) is None:
logger.warning(tr().mod.unload.faild.not_find().format(mod_id))
return None
try:
mod_class.on_unload(game=game)
self.loaded_mod_modules.pop(mod_class.mod_id)
logger.info(tr().mod.unload.success().format(mod_id))
return mod_class
except Exception as e:
logger.error(tr().mod.unload.faild.error().format(mod_id, e, traceback.format_exc()))
return None
def reload_mod(self, mod_id: str, game: Game):
"""
重载 mod
:param mod_id:
:param game:
:return:
"""
unload = self.unload_mod(mod_id, game)
if unload is None:
return
mod_class: Optional[ModInfo] = None
if unload.mod_id not in self.find_mod_paths:
logger.warning(tr().mod.reload.faild.not_find().format(unload.mod_id))
paths = self.find_mods_in_path()
for path in paths:
mod_class = self.load_mod(path)
if mod_class is not None and mod_class.mod_id == unload.mod_id:
self.init_mods([mod_class])
break
else:
mod_class = self.load_mod(self.find_mod_paths[unload.mod_id])
if mod_class is not None:
self.init_mods([mod_class])
if mod_id in self.loaded_mod_modules and mod_class is not None:
self.loaded_mod_modules[mod_id].on_load(game=game, old_self=mod_class)
logger.info(tr().mod.reload.success().format(mod_id))

View File

@ -22,16 +22,23 @@ logger.logfile_datefmt = "Log file date format : "
game_start.at = "Game MainThread start at: {}"
[mod]
find.finded = "Mod founded: {}"
load.start = "Loading Mod in path {}"
load.use_time = "Mod loading has used: {} second"
load.done = "All Mod loaded"
load.loading = "Loading Mod: {}"
load.faild.error = "Mod loading faild: {} error: {}"
load.faild.not_exist = "Mod loading faild: {} mod path not exist"
load.faild.no_mod_class = "Mod loading faild: {} no Mod class"
init.success = "mod id: {} version: {}"
init.faild = "Mod init faild: {} error: {}\nstack: {}"
init.use_time = "Mod init has used: {} second"
event.error = "Mod event {} error {} Mod: {}\nstack: {}"
unload.not_find = "Mod unload faild: {} no Mod found"
unload.faild = "Mod unload faild: {} error: {}\nstack: {}"
unload.success = "Mod unload success: {}"
reload.faild.not_find = "Mod reload faild: {} no Mod found, trying to find mod again"
reload.success = "Mod reload success: {}"
[client]
setup.start = "Client start loading"
@ -57,8 +64,6 @@ text.input = "input text \"{}\""
text.new_line = "new line"
text.motion = "text move {}"
text.motion_select = "text select {}"
command.text = "input command: {}"
message.text = "input message: {}"
libs.local = "using local pyglet, version: {}"
libs.outer = "using global pyglet, version: {}\n(may cause bug)"
fonts.found = "found fonts in font lib: {}"
@ -66,6 +71,9 @@ fonts.load = "loading fonts: {}"
game.stop_get = "Received closing commands from {}, game closing"
game.stop = "game closing, saving data……"
game.end = "game closed"
command.text = "input command: {}"
message.text = "input message: {}"
command.mods.reload.no_mod_id = "no mod id specified"
[server]
setup.start = "Server start loading"

View File

@ -22,16 +22,23 @@ logger.logfile_datefmt = "日志文件日期格式:"
game_start.at = "游戏主线程开始于:"
[mod]
find.finded = "找到 Mod: {}"
load.start = "开始加载路径 {} 下的 Mod"
load.use_time = "Mod 加载消耗时间: {} 秒"
load.done = "所有 Mod 加载完成"
load.loading = "正在加载 Mod: {}"
load.faild.error = "Mod 加载失败: {} 错误信息: {}"
load.faild.not_exist = "Mod 加载失败: {} mod 路径不存在"
load.faild.no_mod_class = "Mod 加载失败: {} 没有找到 Mod 类"
init.success = "mod id: {} 版本号: {}"
init.faild = "Mod 初始化失败: {} 错误信息: {}\n堆栈信息: {}"
init.use_time = "Mod 初始化消耗时间: {} 秒"
event.error = "Mod 事件 {} 发生错误 {} Mod: {}\n堆栈信息: {}"
unload.faild.not_find = "Mod 卸载失败: {} 没有找到 Mod"
unload.faild.error = "Mod 卸载失败: {} 错误信息: {}\n堆栈信息: {}"
unload.success = "Mod 卸载成功: {}"
reload.faild.not_find = "Mod 重载失败: {} 没有找到 Mod 原始路径,正在尝试重新查找 mod"
reload.success = "Mod 重载成功: {}"
[client]
setup.start = "客户端加载开始"
@ -57,8 +64,6 @@ text.input = "输入字符 \"{}\""
text.new_line = "换行"
text.motion = "光标移动 {}"
text.motion_select = "光标选择 {}"
command.text = "输入命令: {}"
message.text = "输入信息: {}"
libs.local = "正在使用本地 pyglet 库 版本为: {}"
libs.outer = "正在使用全局 pyglet 库 版本为: {}\n(可能会造成bug因为本地库版本为2.0dev9)"
fonts.found = "在字体列表中找到以下字体库: {}"
@ -66,6 +71,9 @@ fonts.load = "正在加载字体: {}"
game.stop_get = "从{}传入关闭指令,关闭游戏中"
game.stop = "游戏正在关闭,保存数据中···"
game.end = "游戏已经关闭"
command.text = "输入命令: {}"
message.text = "输入信息: {}"
command.mods.reload.no_mod_id = "没有指定 mod id"
[server]
setup.start = "服务端开始加载"

View File

@ -286,6 +286,8 @@ pub mod console {
let mut input = String::new();
let _ = std_in.read_line(&mut input);
if !input.is_empty() {
// 预处理
input = input.trim().to_string();
keyboard_input_sender.send(input).unwrap();
}
}

View File

@ -77,7 +77,8 @@ class DR_mod(ModInfo):
game.console_class = RustConsole # 替换掉原来的 console 类
if old_self:
game.client.window.add_sub_screen("SR1_ship", old_self.screen)
from .sr1_ship import SR1ShipRender
game.client.window.add_sub_screen("SR1_ship", SR1ShipRender)
else:
self.config.flush_option()
logger.info("on_load")
@ -86,11 +87,11 @@ class DR_mod(ModInfo):
def on_client_start(self, game: Game, client: ClientWindow):
from .sr1_ship import SR1ShipRender
self.screen = SR1ShipRender
client.add_sub_screen("SR1_ship", SR1ShipRender)
logger.info('on_client_start added sub screen')
def on_unload(self, game: Game):
game.client.window.screen_list.pop("SR1_ship")
if DR_mod_runtime.DR_rust_available:
game.console.stop()
game.console_class = Console