Compare commits

..

No commits in common. "5cb642ea3c75beb67b10146b7642b7edcc3417bd" and "44b183db0ed456e081b7ed5750dd47e619af4d45" have entirely different histories.

353 changed files with 2713 additions and 4639 deletions

View File

@ -81,8 +81,6 @@ jobs:
python setup.py build
python post_build.py
python setup.py clean
cd ..
Remove-Item -Recurse -Force src
# Uploads artifact
- name: Upload Artifact
@ -90,4 +88,4 @@ jobs:
with:
name: DR_rs${{env.DR_version}}-${{runner.os}}${{matrix.python-version}}-Build.${{github.run_number}}+${{env.short_sha}}
path: |
mods/dr_game
mods/dr_game/Difficult_Rocket_rs/lib

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
@ -108,7 +109,7 @@ def main():
return 0
dsm.clear_dsm()
dsm.upload_docs('docs/md5.txt')
dsm.fl.logout()
dsm.fl.session.logout('FileStation')
if __name__ == '__main__':

View File

@ -10,24 +10,32 @@ import rtoml
sys.path.append(os.path.abspath(os.curdir))
from Difficult_Rocket import DR_status
from Difficult_Rocket import DR_runtime
args = ['-env', '-github-dev']
# print(sys.argv)
if sys.argv == [__file__]: # 没有输入参数,直接输出默认信息并输出
print(sys.version)
from Difficult_Rocket.utils import tools
# 重置窗口信息
config_file = tools.load_file('./config/main.toml')
config_file = tools.load_file('./configs/main.toml')
config_file['window']['width'] = 1024
config_file['window']['height'] = 768
rtoml.dump(config_file, open('./config/main.toml', 'w'))
rtoml.dump(config_file, open('./configs/main.toml', 'w'))
elif os.path.abspath(os.curdir) in sys.path and '-env' in sys.argv:
with open('./.github/workflows/env.ps1', encoding='utf-8', mode='w') as env_file:
print(f'$env:DR_version = "{DR_status.DR_version}"', file=env_file)
print(f'$env:Build_version = "{DR_status.Build_version}"', file=env_file)
print(f'$env:DR_version = "{DR_runtime.DR_version}"', file=env_file)
print(f'$env:DR_language = "{DR_runtime.language}"', file=env_file)
print(f'$env:DR_long_version = "{DR_runtime.DR_long_version}"', file=env_file)
print(f'$env:Build_version = "{DR_runtime.Build_version}"', file=env_file)
elif os.path.abspath(os.curdir) in sys.path and '-github' in sys.argv:
print(f'DR_version={DR_status.DR_version}')
print(f'Build_version={DR_status.Build_version}')
print(f'DR_version={DR_runtime.DR_version}')
print(f'DR_language={DR_runtime.language}')
print(f'DR_long_version={DR_runtime.DR_long_version}')
print(f'Build_version={DR_runtime.Build_version}')

View File

@ -7,6 +7,7 @@ on:
push:
paths:
- "Difficult_Rocket/**" # 本体修改
- "configs/**" # 配置修改
- "libs/pyglet/**" # pyglet 修改
- ".github/workflows/**" # workflow 修改
- "nuitka_build.py" # 构建脚本修改
@ -14,6 +15,7 @@ on:
pull_request:
paths:
- "Difficult_Rocket/**" # 本体修改
- "configs/**" # 配置修改
- "libs/pyglet/**" # pyglet 修改
- ".github/workflows/**" # workflow 修改
- "nuitka_build.py" # 构建脚本修改

View File

@ -49,12 +49,12 @@ jobs:
run: |
# 设置变量
$urls = @(
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.4/mdbook-i18n',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.4/mdbook-theme',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.4/mdbook-cmdrun',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.4/mdbook-pagetoc',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.4/mdbook-footnote',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.4/mdbook-external-links',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.3/mdbook-i18n',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.3/mdbook-theme',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.3/mdbook-cmdrun',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.3/mdbook-pagetoc',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.3/mdbook-footnote',
'https://github.com/shenjackyuanjie/Minecraft_Science_Tree/releases/download/0.0.3/mdbook-external-links',
'https://github.com/plantuml/plantuml/releases/download/v1.2023.4/plantuml-1.2023.4.jar'
)
# 下载文件

7
.gitignore vendored
View File

@ -166,10 +166,3 @@ other things/
*cmake-build-debug
.pdm.toml
writer-test.xml
test-xml-rs.xml
test-file.xml
test-save.xml
index.html

View File

@ -2,5 +2,9 @@
"rust-analyzer.linkedProjects": [
"mods/dr_game/Difficult_Rocket_rs/src/Cargo.toml",
"libs/pyglet_rs/src/Cargo.toml",
]
],
"python.analysis.extraPaths": [
"./libs"
],
"python.analysis.typeCheckingMode": "basic"
}

47
DR-start.py Normal file
View File

@ -0,0 +1,47 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import os
import sys
import time
hi = """Difficult Rocket is writen by shenjackyuanjie
email: 3695888@qq.com or shyj3695888@163.com
QQ: 3695888"""
errors = {
'TestError': '游戏正在调试中,某处引发了一个 TestError不是bug造成的原因',
'AssertionError': '游戏的某处检查未通过情报告issue',
'error.unknown': '游戏报错了现在输出报错信息请报告issue',
'error.happen': '游戏出现了一个报错!正在处理'
}
def print_path() -> None:
print(f'{__file__=}')
print(f'{sys.path=}')
print(f'{sys.path[0]=}')
print(f'{sys.argv[0]=}')
print(f'{os.getcwd()=}')
print(f'{os.path.abspath(__file__)=}')
print(f'{os.path.realpath(__file__)=}')
print(f'{os.path.split(os.path.split(os.path.realpath(__file__))[0])=}')
# 输出一遍大部分文件位置相关信息 以后可能会加到logs里
def modify_path() -> None:
file_path = os.path.split(os.path.realpath(__file__))[0]
os.chdir(file_path) # 将运行路径切换到文件位置 防止bug
sys.path.append(f'{file_path}/Difficult_Rocket') # 添加local path
sys.path.append(f'{file_path}/libs') # 添加 libs path
if __name__ == '__main__':
print(hi) # hi
# 记录启动信息
start_time_ns = time.time_ns()
start_time_perf_ns = time.perf_counter_ns()
print_path()
modify_path()

131
DR.py
View File

@ -1,70 +1,111 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
"""
writen by shenjackyuanjie
email: 3695888@qq.com
"""
import os
import sys
import time
import cProfile
import traceback
import threading
from pathlib import Path
from io import StringIO
# TODO 默认位置配置文件
# TODO 可自定义工作路径
hi = """Difficult Rocket is writen by shenjackyuanjie
email: 3695888@qq.com or shyj3695888@163.com
QQ: 3695888"""
error_format = {
'TestError': '游戏正在调试中,某处引发了一个 TestError不是bug造成的原因',
'AssertionError': '游戏的某处检查未通过情报告issue',
'error.unknown': '游戏报错了现在输出报错信息请报告issue',
'error.happen': '游戏出现了一个报错!正在处理'
}
def print_path() -> None:
print(f'{__file__=}')
print(f'{sys.path=}')
print(f'{sys.path[0]=}')
print(f'{sys.argv[0]=}')
print(f'{Path.cwd()=}')
print(f'{Path(__file__).absolute()=}')
def modify_path() -> None:
os.chdir(Path(__file__).parent) # 将运行路径切换到文件位置 防止bug
sys.path.append('./Difficult_Rocket') # 添加local path
sys.path.append('./libs') # 添加 libs path
def start(start_time_ns: int) -> None:
from Difficult_Rocket import crash, DR_status
from Difficult_Rocket.runtime import DR_runtime
from Difficult_Rocket.exception import TestError
DR_runtime.start_time_ns = start_time_ns
try:
from Difficult_Rocket import main
main_game = main.Game()
main_game.start()
if DR_status.crash_report_test:
raise TestError('debug crash test')
except:
trace = traceback.format_exc()
crash.create_crash_report(trace)
print(trace)
crash.write_info_to_cache(sys.stdout)
print(crash.all_thread)
print(crash.all_process)
for a_thread in threading.enumerate():
print(a_thread)
if a_thread.is_alive() and a_thread != threading.current_thread() and a_thread != threading.main_thread():
a_thread.join(2) # wait for 2 sec
import pyglet
pyglet.app.exit() # make sure that pyglet has stopped
print(f'{os.curdir=}')
print(f'{os.getcwd()=}')
print(f'{os.path.abspath(os.curdir)=}')
print(f'{os.path.abspath(__file__)=}')
print(f'{os.path.realpath(__file__)=}')
print(f'{os.path.split(os.path.split(os.path.realpath(__file__))[0])=}')
# 输出一遍大部分文件位置相关信息 以后可能会加到logs里
def main() -> int:
print(hi, f"\n{time.ctime()}") # hi
# 记录启动信息
print(hi) # hi
start_time_ns = time.time_ns()
start_time_perf_ns = time.perf_counter_ns()
print_path()
modify_path()
start(start_time_ns)
file_path = os.path.split(os.path.realpath(__file__))[0]
os.chdir(file_path) # 将运行路径切换到文件位置 防止bug
sys.path.append(f'{file_path}/Difficult_Rocket') # 添加local path
sys.path.append(f'{file_path}/libs') # 添加 libs path
from Difficult_Rocket.exception import TestError
from Difficult_Rocket import crash
from Difficult_Rocket import DR_status
try:
from libs import pyglet # 导入pyglet
pyglet.resource.path = ['/textures/']
pyglet.resource.reindex()
from Difficult_Rocket import main, DR_runtime
DR_runtime.start_time_ns = start_time_ns
# from pyglet.gl import glClearColor # 调整背景颜色
# glClearColor(0.5, 0.5, 0.5, 0)
game = main.Game() # 实例化一个游戏
print(time.perf_counter_ns() - start_time_perf_ns, (time.perf_counter_ns() - start_time_perf_ns) / (10 ** 9), 'start') # 输出一下启动用时
cprofile = False # 是否使用cprofile
if cprofile:
cProfile.run('game.start()', sort='calls') # 使用 cprofile 启动
else:
game.start() # 直接启动
if DR_status.crash_report_test:
raise TestError('debugging') # debug 嘛试试crash
except Exception as exp: # 出毛病了
# 解析错误信息
print(error_format['error.happen'])
error = traceback.format_exc()
name = type(exp).__name__
if name in error_format:
print(error_format[name])
else:
print(error_format['error.unknown'])
print(error)
# 输出 crash 信息
crash.create_crash_report(error)
cache_steam = StringIO()
crash.write_info_to_cache(cache_steam)
text = cache_steam.getvalue()
print(text)
else:
crash.record_thread = False
print(crash.all_thread)
print(crash.all_process)
# join all thread
for thread in threading.enumerate():
print(thread)
if thread.name == 'MainThread' or thread == threading.main_thread() or thread == threading.current_thread():
continue
if thread.daemon:
continue
thread.join()
# stop pyglet
import pyglet
pyglet.app.exit()
print("Difficult_Rocket 已关闭")
return 0

View File

@ -4,34 +4,20 @@
# All rights reserved
# -------------------------------
import time
import logging.config
import sys
import importlib
import traceback
import contextlib
import importlib.util
from pathlib import Path
from typing import Optional, List, Tuple
from Difficult_Rocket.api.types import Options, Version
sdk_version = Version("0.8.7.0") # SDK 版本
build_version = Version("2.1.3.0") # 编译文件版本(与游戏本体无关)
game_version = Version("0.8.2.0") # 游戏版本
build_version = Version("2.1.0.0") # 编译文件版本(与游戏本体无关)
Api_version = Version("0.1.1.0") # API 版本
__version__ = sdk_version
__all__ = [
# __init__
'DR_status',
# folder
'api',
'client',
'server',
'command',
'crash',
'exception',
'mod',
'utils',
# file
'main',
'runtime',
]
__version__ = game_version
class _DR_status(Options):
@ -57,7 +43,7 @@ class _DR_status(Options):
crash_report_test: bool = False
# game version status
DR_version: Version = sdk_version # DR SDK 版本
DR_version: Version = game_version # DR SDK 版本
Build_version: Version = build_version # DR 构建 版本
API_version: Version = Api_version # DR SDK API 版本
@ -72,23 +58,64 @@ class _DR_status(Options):
return round(12 * self.gui_scale)
class _DR_runtime(Options):
"""
DR 的运行时配置 / 状态
"""
name = 'DR Runtime'
language: str = 'zh-CN'
mod_path: str = './mods'
DR_Mod_List: List[Tuple[str, Version]] = [] # DR Mod 列表 (name, version)
# run status
start_time_ns: Optional[int] = None
client_setup_cause_ns: Optional[int] = None
server_setup_cause_ns: Optional[int] = None
def load_file(self) -> bool:
with contextlib.suppress(FileNotFoundError):
with open('./configs/main.toml', 'r', encoding='utf-8') as f:
import rtoml
config_file = rtoml.load(f)
self.language = config_file['runtime']['language']
self.mod_path = config_file['game']['mods']['path']
return True
return False
def find_mods(self) -> List[str]:
mods = []
mod_path = Path(self.mod_path)
if not mod_path.exists():
mod_path.mkdir()
return []
paths = mod_path.iterdir()
sys.path.append(self.mod_path)
for mod_path in paths:
try:
if mod_path.is_dir() and mod_path.name != '__pycache__': # 处理文件夹 mod
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
else:
print(f'can not import mod {mod_path} because importlib can not find spec')
elif mod_path.suffix in ('.pyz', '.zip'): # 处理压缩包 mod
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
elif mod_path.suffix == '.pyd': # pyd 扩展 mod
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
elif mod_path.suffix == '.py': # 处理单文件 mod
print(f'importing mod {mod_path=} {mod_path.stem}')
if importlib.util.find_spec(mod_path.stem) is not None:
mods.append(mod_path.stem)
except ImportError:
print(f'ImportError when loading mod {mod_path}')
traceback.print_exc()
return mods
DR_status = _DR_status()
def load_logging():
with open('./config/logger.toml') as f:
import rtoml
logger_config = rtoml.load(f)
log_path = logger_config['handlers']['file']['filename']
log_path = f"logs/{log_path.format(time.strftime('%Y-%m-%d %H-%M-%S', time.gmtime(time.time_ns() / 1000_000_000)))}"
if not Path('logs/').is_dir():
Path('logs/').mkdir()
logger_config['handlers']['file']['filename'] = log_path
logging.config.dictConfig(logger_config)
load_logging()
DR_runtime = _DR_runtime()
if DR_status.playing:
from Difficult_Rocket.utils.thread import new_thread
@ -100,4 +127,4 @@ if DR_status.playing:
@new_thread('think')
def think(some_thing_to_think):
gotcha = think_it(some_thing_to_think)
return gotcha
return gotcha

View File

@ -12,6 +12,8 @@ gitee: @shenjackyuanjie
"""
# from Difficult_Rocket.api import screen, mod, exception
__all__ = [
'exception',
# 错误类定义
@ -21,4 +23,6 @@ __all__ = [
# 类型定义
'mod',
# mod api
'log'
# log api
]

View File

@ -1,17 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
from Difficult_Rocket.utils.camera import (Camera,
CenterCamera,
GroupCamera,
CenterGroupCamera)
__all__ = [
'Camera',
'CenterCamera',
'GroupCamera',
'CenterGroupCamera'
]

View File

@ -1,9 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
__all__ = [
'widget'
]

View File

@ -1,11 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
from Difficult_Rocket.gui.widget.button import PressTextButton
__all__ = [
'PressTextButton'
]

View File

@ -0,0 +1,16 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
from Difficult_Rocket.utils.log import (get_named_client_logger,
get_named_server_logger,
get_named_main_logger)
__all__ = [
'get_named_client_logger',
'get_named_server_logger',
'get_named_main_logger',
]

View File

@ -112,7 +112,6 @@ class BaseScreen(EventDispatcher):
:event:
"""
# def on_draw(self, dt: float, window: ClientWindow): # TODO: wait for pyglet 2.1
def on_draw(self, window: ClientWindow):
"""The window contents must be redrawn.

View File

@ -12,15 +12,12 @@ from Difficult_Rocket.utils.options import (Options,
OptionNotFound,
get_type_hints_)
from libs.MCDR.version import (Version,
VersionRequirement,
VersionParsingError)
from libs.MCDR.version import Version
__all__ = [
# main class
'Options',
'Version',
'VersionRequirement',
# data class
'FontData',
@ -30,7 +27,6 @@ __all__ = [
'OptionsError',
'OptionNameNotDefined',
'OptionNotFound',
'VersionParsingError',
# other
'get_type_hints_',

View File

@ -15,7 +15,7 @@ import traceback
from pathlib import Path
from decimal import Decimal
from typing import Callable, Dict, List, TYPE_CHECKING, Type
from typing import Callable, Dict, List, TYPE_CHECKING
# third function
import rtoml
@ -23,28 +23,23 @@ import pyglet
# from pyglet import gl
# from pyglet.gl import glClearColor
# from pyglet.libs.win32 import _user32
from pyglet.graphics import Group, Batch
from pyglet.window import Window
from pyglet.window import key, mouse
from pyglet.gui.widgets import TextEntry
# Difficult_Rocket function
if TYPE_CHECKING:
from Difficult_Rocket.main import Game
from Difficult_Rocket import DR_status
from Difficult_Rocket.utils import tools
from Difficult_Rocket.command import line
from Difficult_Rocket.api.types import Options
from Difficult_Rocket.command import line
from Difficult_Rocket.utils.translate import tr
from Difficult_Rocket.runtime import DR_runtime
from Difficult_Rocket import DR_runtime
from Difficult_Rocket.api.screen import BaseScreen
from Difficult_Rocket.utils.thread import new_thread
from Difficult_Rocket.client.screen import DRDEBUGScreen
from Difficult_Rocket.client.fps.fps_log import FpsLogger
from Difficult_Rocket.client.guis.widgets import InputBox
from Difficult_Rocket.exception.language import LanguageNotFound
logger = logging.getLogger('client')
from Difficult_Rocket.client.screen import DRScreen, DRDEBUGScreen
class ClientOption(Options):
@ -56,24 +51,20 @@ class ClientOption(Options):
resizeable: bool = True
visible: bool = True
gui_scale: float = 1.0
caption: str = "Difficult Rocket v{DR_version}"
caption: str = "Difficult Rocket v{DR_version}|DR_rs v{DR_Rust_get_version}"
def load_file(self) -> None:
file: dict = tools.load_file('./config/main.toml')
file: dict = tools.load_file('./configs/main.toml')
self.fps = int(file['runtime']['fps'])
self.width = int(file['window']['width'])
self.height = int(file['window']['height'])
self.fullscreen = tools.format_bool(file['window']['full_screen'])
self.resizeable = tools.format_bool(file['window']['resizable'])
self.gui_scale = float(file['window']['gui_scale'])
self.caption = DR_status.format(file['window']['caption'])
self.caption = DR_runtime.format(self.caption)
self.caption = DR_runtime.format(file['window']['caption'])
class Client:
"""
客户端
"""
def __init__(self, game: "Game", net_mode='local'):
start_time = time.time_ns()
# logging
@ -98,9 +89,6 @@ class Client:
self.logger.debug(tr().client.setup.use_time_ns().format(self.use_time))
def start(self):
"""
启动客户端
"""
DR_runtime.running = True
self.window.start_game() # 游戏启动
# TODO 写一下服务端启动相关,还是需要服务端啊
@ -110,11 +98,6 @@ class Client:
def pyglet_load_fonts_folder(folder) -> None:
"""
递归加载字体文件夹
:param folder:
:return:
"""
font_path = Path(folder)
if not font_path.exists():
font_path.mkdir(parents=True)
@ -122,48 +105,13 @@ def pyglet_load_fonts_folder(folder) -> None:
file_folder_list = os.listdir(folder)
for obj in file_folder_list:
if os.path.isfile(os.path.join(folder, obj)):
if obj[-4:] == '.ttf' or obj[-4:] == '.otf':
logger.debug(f'loading font {os.path.join(folder, obj)}')
try:
pyglet.font.add_file(os.path.join(folder, obj))
except Exception:
logger.error(traceback.format_exc())
logger.error(f'loading font {os.path.join(folder, obj)} failed')
if obj[-4:] == '.ttf':
pyglet.font.add_file(os.path.join(folder, obj))
else:
logger.info(f'loading font folder {os.path.join(folder, obj)}')
pyglet_load_fonts_folder(os.path.join(folder, obj))
def _call_back(call_back: Callable) -> Callable:
"""
>>> def call_back():
>>> pass
>>> @_call_back(call_back)
>>> def on_draw(self):
>>> pass
用于在调用窗口函数后调用指定函数 的装饰器
:param call_back: 需要调用的函数
:return: 包装后的函数
"""
def wrapper(func):
@functools.wraps(func)
def warp(self: "ClientWindow", *args, **kwargs):
result = func(self, *args, **kwargs)
call_back(self)
return result
return warp
return wrapper
def _call_screen_after(func: Callable) -> Callable:
"""
>>> @_call_screen_after
>>> def on_draw(self):
>>> pass
用于在调用窗口函数后调用子窗口函数 的装饰器
:param func: 需要包装的函数
:return: 包装后的函数
"""
@functools.wraps(func)
def warped(self: "ClientWindow", *args, **kwargs):
result = func(self, *args, **kwargs)
@ -182,14 +130,6 @@ def _call_screen_after(func: Callable) -> Callable:
def _call_screen_before(func: Callable) -> Callable:
"""
>>> @_call_screen_before
>>> def on_draw(self):
>>> pass
用于在调用窗口函数前调用子窗口函数 的装饰器
:param func: 需要包装的函数
:return: 包装后的函数
"""
@functools.wraps(func)
def warped(self: "ClientWindow", *args, **kwargs):
for title, a_screen in self.screen_list.items():
@ -226,16 +166,16 @@ class ClientWindow(Window):
self.net_mode = net_mode
self.run_input = False
self.command_list: List[str] = []
# config
self.main_config = tools.load_file('./config/main.toml')
self.game_config = tools.load_file('./config/game.config')
# configs
self.main_config = tools.load_file('./configs/main.toml')
self.game_config = tools.load_file('./configs/game.config')
# FPS
self.FPS = Decimal(int(self.main_config['runtime']['fps']))
self.SPF = Decimal('1') / self.FPS
self.fps_log = FpsLogger(stable_fps=int(self.FPS))
# batch
self.main_batch = Batch()
self.main_group = Group(0)
self.part_batch = pyglet.graphics.Batch()
self.label_batch = pyglet.graphics.Batch()
# frame
self.frame = pyglet.gui.Frame(self, order=20)
self.M_frame = pyglet.gui.MovableFrame(self, modifier=key.LCTRL)
@ -243,11 +183,12 @@ class ClientWindow(Window):
# setup
self.setup()
# 命令显示
self.input_box = TextEntry(x=50, y=30, width=300,
batch=self.main_batch, text='', group=Group(1000, parent=self.main_group)) # 实例化
self.command_group = pyglet.graphics.Group(0)
self.input_box = InputBox(x=50, y=30, width=300,
batch=self.label_batch, text='') # 实例化
self.input_box.push_handlers(self)
self.input_box.set_handler('on_commit', self.on_input)
self.push_handlers(self.input_box)
self.set_handlers(self.input_box)
self.input_box.enabled = True
# 设置刷新率
# pyglet.clock.schedule_interval(self.draw_update, float(self.SPF))
@ -261,10 +202,12 @@ class ClientWindow(Window):
self.count = 0
def setup(self):
self.set_icon(pyglet.image.load('assets/textures/icon.png'))
self.set_icon(pyglet.image.load('./textures/icon.png'))
self.load_fonts()
# TODO 读取配置文件,加载不同的屏幕,解耦
self.screen_list['DR_debug'] = DRDEBUGScreen(self)
self.game.dispatch_mod_event('on_client_start', game=self.game, client=self)
self.screen_list['DR_main'] = DRScreen(self)
self.game.dispatch_event('on_client_start', game=self.game, client=self)
def load_fonts(self) -> None:
fonts_folder_path = self.main_config['runtime']['fonts_folder']
@ -273,39 +216,33 @@ class ClientWindow(Window):
pyglet_load_fonts_folder(fonts_folder_path)
def start_game(self) -> None:
self.set_icon(pyglet.image.load('assets/textures/icon.png'))
self.set_icon(pyglet.image.load('./textures/icon.png'))
try:
# pyglet.clock.schedule_interval(self.on_draw, float(self.SPF))
# pyglet.app.run()
# TODO: wait for pyglet 2.1
pyglet.app.run(float(self.SPF))
pyglet.app.event_loop.run(1 / self.main_config['runtime']['fps'])
except KeyboardInterrupt:
self.logger.warning("==========client stop. KeyboardInterrupt info==========")
print("==========client stop. KeyboardInterrupt info==========")
traceback.print_exc()
self.logger.warning("==========client stop. KeyboardInterrupt info end==========")
self.dispatch_event("on_close", 'input')
print("==========client stop. KeyboardInterrupt info end==========")
self.dispatch_event("on_close")
sys.exit(0)
@new_thread('window save_info')
def save_info(self):
self.logger.info(tr().client.config.save.start())
config_file: dict = tools.load_file('./config/main.toml')
config_file: dict = tools.load_file('./configs/main.toml')
config_file['window']['width'] = self.width
config_file['window']['height'] = self.height
config_file['runtime']['language'] = DR_runtime.language
rtoml.dump(config_file, open('./config/main.toml', 'w'))
rtoml.dump(config_file, open('./configs/main.toml', 'w'))
self.logger.info(tr().client.config.save.done())
"""
client api
"""
def add_sub_screen(self, title: str, sub_screen: Type[BaseScreen]):
def add_sub_screen(self, title: str, sub_screen: type(BaseScreen)):
self.screen_list[title] = sub_screen(self)
def remove_sub_screen(self, title: str):
self.screen_list.pop(title)
"""
draws and some event
"""
@ -317,13 +254,11 @@ class ClientWindow(Window):
self.fps_log.update_tick(now_FPS, decimal_tick)
@_call_screen_after
# def on_draw(self, dt: float): # TODO: wait for pyglet 2.1
def on_draw(self):
while (command := self.game.console.get_command()) is not None:
def on_draw(self, *dt):
while command := self.game.console.get_command():
self.on_command(line.CommandText(command))
pyglet.gl.glClearColor(0.1, 0, 0, 0.0)
self.clear()
# self.draw_update(dt) # TODO: wait for pyglet 2.1
self.draw_update(float(self.SPF))
self.draw_batch()
@ -346,9 +281,10 @@ class ClientWindow(Window):
# self.set_location(*self.get_location())
print('on hide!')
@_call_screen_before
@_call_screen_after
def draw_batch(self):
self.main_batch.draw()
self.part_batch.draw()
self.label_batch.draw()
"""
command line event
@ -359,18 +295,15 @@ class ClientWindow(Window):
self.on_command(command_text)
self.input_box.value = ''
def new_command(self):
self.game.console.new_command()
@_call_back(new_command)
@_call_screen_after
def on_command(self, command: line.CommandText):
command.text = command.text.rstrip('\n').rstrip(' ').strip('/')
self.logger.info(tr().window.command.text().format(f"|{command.text}|"))
print(command.find('/'))
self.logger.info(tr().window.command.text().format(command))
if command.find('stop'):
self.logger.info("command stop!")
# HUGE THANKS to Discord @nokiyasos for this fix!
pyglet.app.exit()
# self.dispatch_event('on_exit')
print("command stop!")
pyglet.app.platform_event_loop.stop()
self.dispatch_event('on_close', 'command') # source = command
elif command.find('fps'):
if command.find('log'):
self.logger.debug(self.fps_log.fps_list)
@ -389,19 +322,10 @@ class ClientWindow(Window):
tr._language = lang
self.logger.info(tr().language_set_to())
except LanguageNotFound:
self.logger.info(tr().language_available().format(os.listdir('./config/lang')))
self.logger.info(tr().language_available().format(os.listdir('./configs/lang')))
self.save_info()
elif command.find('mods'):
if command.find('list'):
self.logger.info(tr().mod.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())
# self.command_tree.parse(command.plain_command)
@_call_screen_after
def on_message(self, message: line.CommandText):
@ -424,7 +348,7 @@ class ClientWindow(Window):
...
@_call_screen_after
def on_mouse_motion(self, x, y, dx, dy):
def on_mouse_motion(self, x, y, dx, dy) -> None:
...
@_call_screen_after
@ -464,7 +388,7 @@ class ClientWindow(Window):
if symbol == key.ESCAPE and not (modifiers & ~(key.MOD_NUMLOCK |
key.MOD_CAPSLOCK |
key.MOD_SCROLLLOCK)):
self.dispatch_event('on_close', 'window')
self.dispatch_event('on_close')
if symbol == key.SLASH:
self.input_box._set_focus(True)
self.logger.debug(
@ -500,7 +424,7 @@ class ClientWindow(Window):
@_call_screen_before
def on_close(self, source: str = 'window') -> None:
self.game.dispatch_mod_event('on_close', game=self.game, client=self, source=source)
self.game.dispatch_event('on_close', game=self.game, client=self, source=source)
self.logger.info(tr().window.game.stop_get().format(tr().game[source]()))
self.logger.info(tr().window.game.stop())
# self.fps_log.check_list = False

View File

@ -0,0 +1,302 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
from typing import Optional, Union, Tuple
# from libs import pyglet
# from pyglet import font
from pyglet.text import Label, HTMLLabel
# from pyglet.window import key
from pyglet.gui import widgets
# from pyglet.sprite import Sprite
from pyglet.shapes import Rectangle
# from pyglet.image import AbstractImage
from pyglet.graphics import Batch, Group
from pyglet.text.caret import Caret
from pyglet.text.document import UnformattedDocument
from pyglet.text.layout import IncrementalTextLayout
# from libs import pyperclip
# from libs.pyperclip import paste
from Difficult_Rocket.api.types import FontData, Fonts
# from Difficult_Rocket.client.guis.format import html
from Difficult_Rocket import DR_status
__all__ = ['InputBox']
class TextButton(widgets.WidgetBase):
"""
自带字符的按钮就不用单独做材质了
"""
def __init__(self,
x: int, y: int, width: int, height: int,
text: str,
font: str = Fonts.鸿蒙简体, font_size: int = 13):
super().__init__(x, y, width, height)
self.text = text
self.text_label = Label(
font_name=font, font_size=font_size)
@property
def value(self):
return self.text
def _update_position(self):
self.text_label.position = self._x, self._y
...
if not DR_status.InputBox_use_TextEntry:
class InputBox(widgets.TextEntry):
""" 自定义的输入框 """
def __init__(self, x: int, y: int, width: int,
text: str,
side_width: int = 2,
font_data: Optional[FontData] = None,
color: Optional[Tuple[int, int, int, int]] = (255, 255, 255, 255),
text_color: Optional[Tuple[int, int, int, int]] = (0, 0, 0, 255),
caret_color: Optional[Tuple[int, int, int, int]] = (0, 0, 0),
batch: Optional[Batch] = None, group: Optional[Group] = None):
if font_data is None:
font_data = FontData()
self._doc = UnformattedDocument(text)
self._doc.set_style(0, len(self._doc.text), {**font_data.dict(), 'text_color': text_color})
font = self._doc.get_font()
height = font.ascent - font.descent
self._user_group = group
bg_group = Group(order=0, parent=group)
fg_group = Group(order=1, parent=group)
# Rectangular outline with 2-pixel pad:
self._pad = p = side_width
self._outline = Rectangle(x-p, y-p, width+p+p, height+p+p, color[:3], batch, bg_group)
self._outline.opacity = color[3]
# Text and Caret:
self._layout = IncrementalTextLayout(self._doc, width, height, multiline=False, batch=batch, group=fg_group)
self._layout.x = x
self._layout.y = y
self._caret = Caret(self._layout, color=caret_color)
self._caret.visible = False
self._focus = False
super().__init__()
# InputBox.register_event_type('on_commit')
# class InputBox(widgets.WidgetBase):
# """
# input box
# """
#
# def __init__(self,
# x: int, y: int, width: int, height: int,
# message: str = '',
# font_name: str = translate.微软等宽,
# font_size: int = 15,
# font_bold: bool = False,
# font_italic: bool = False,
# font_stretch: bool = False,
# font_dpi: int = 100,
# text_color: [int, int, int] = (187, 187, 187, 255),
# out_line_color: [int, int, int] = (37, 116, 176),
# cursor_color: [int, int, int] = (187, 187, 187),
# select_color: [int, int, int] = (63, 115, 255),
# out_line: int = 2,
# batch: Batch = None,
# group: Group = None):
# if batch is None:
# batch = Batch()
# if group is None:
# group = Group()
# super().__init__(x, y, width, height)
# self.enabled = False
# self._text = message
# self._cursor_poi = 0
# self.font = font.load(name=font_name, size=font_size,
# bold=font_bold, italic=font_italic, stretch=font_stretch,
# dpi=font_dpi)
# self.font_height = self.font.ascent - self.font.descent
# self.out_bound = out_line
# if DR_status.InputBox_use_TextEntry:
# # 基于IncrementalTextLayout的处理系统
# self._doc = FormattedDocument(message)
# # self._doc.set_style()
# self._layout = IncrementalTextLayout(self._doc, self.font, width, height,
# batch=batch, group=group)
# self._input_box = widgets.TextEntry(x, y, width, height,
# self._doc, self._layout,
# batch=batch, group=group)
# else:
# # 基于Label的处理系统
# self._input_box = Label(x=x + out_line, y=y + out_line,
# width=width, height=self.font_height + self.out_bound * 2,
# color=text_color,
# font_name=font_name, font_size=font_size,
# batch=batch, group=group,
# text=message)
# self._HTML_box = HTMLLabel(x=x + out_line, y=y + out_line + 30,
# width=width, height=self.font_height + self.out_bound * 2,
# batch=batch, group=group,
# text=message)
# self._out_box = Rectangle(x=x - out_line, y=y - out_line,
# color=out_line_color,
# width=width + (out_line * 2), height=height + (out_line * 2),
# batch=batch, group=group)
# self._光标 = Rectangle(x=x + out_line, y=y + out_line,
# color=cursor_color,
# width=1, height=self.font_height,
# batch=batch, group=group)
# self._选择框 = Rectangle(x=x, y=y, width=0, height=self.font_height,
# color=select_color)
# self._选择的字 = Label(x=x, y=y, width=0, height=self.font_height,
# color=text_color,
# font_name=font_name, font_size=font_size,
# batch=batch, group=group,
# text='')
#
# """
# 输入框的属性
# """
#
# # 本身属性
# @property
# def text(self) -> str: # 输入框的文本
# return self._text
#
# @text.setter
# def text(self, value) -> None:
# assert type(value) is str, 'Input Box\'s text must be string!'
# self._text = value
# self._input_box.text = value
# self._HTML_box.text = html.decode_text2HTML(value, show_style=True)
#
# @property
# def cursor_poi(self) -> int: # 光标位置
# return self._cursor_poi
#
# @cursor_poi.setter
# def cursor_poi(self, value) -> None:
# assert type(value) is int, 'Input Box\'s cursor poi must be int!'
# self._cursor_poi = value
# self._光标.x = self.x + self.out_bound + self._input_box.content_width
#
# # 渲染时属性
# @property
# def opacity(self) -> int: # 透明度
# return self._input_box.opacity
#
# @opacity.setter
# def opacity(self, value: int) -> None:
# assert type(value) is int, 'Input Box\'s opacity must be int!'
# self._input_box.opacity = value
# self._out_box.opacity = value
# self._选择的字.opacity = value
# self._选择框.opacity = value
# self._光标.opacity = value
#
# @property
# def visible(self) -> bool: # 是否可见
# return self._input_box.visible
#
# @visible.setter
# def visible(self, value: bool) -> None:
# assert type(value) is bool, 'Input Box\'s visible must be bool!'
# self._input_box.visible = value
# self._out_box.visible = value
# self._选择的字.visible = value
# self._选择框.visible = value
# self._光标.visible = value
#
# @property
# def value(self) -> str:
# return self._text
#
# """
# 事件调用
# """
#
# def _update_position(self):
# self._input_box.position = self._x + self.out_bound, self._y + self.out_bound
# self._out_box.position = self._x - self.out_bound, self._y - self.out_bound
# self._光标.position = self._x + self.out_bound, self._y + self.out_bound
#
# # 输入东西
# def on_text(self, text: str):
# if self.enabled:
# if text in ('\r', '\n'):
# if self.text:
# self.dispatch_event('on_commit', self.text)
# else:
# self.text = f'{self.text[:self.cursor_poi]}{text}{self.text[self.cursor_poi:]}'
# self.cursor_poi += len(text)
#
# # 移动光标
# def on_text_motion(self, motion):
# if self.enabled:
# # 根据按键处理
# # 单格移动光标(上下左右)
# if motion in (key.MOTION_UP, key.MOTION_LEFT): # 往上一个移动
# self.cursor_poi = max(0, self._cursor_poi - 1)
# elif motion in (key.MOTION_DOWN, key.MOTION_RIGHT): # 往下一个移动
# self.cursor_poi = min(len(self.text), self._cursor_poi + 1)
# # 大前后移动(开头或结尾)
# elif motion in (
# key.MOTION_BEGINNING_OF_LINE, key.MOTION_BEGINNING_OF_FILE, key.MOTION_PREVIOUS_PAGE): # 开头
# self.cursor_poi = 0
# elif motion in (key.MOTION_END_OF_LINE, key.MOTION_END_OF_FILE, key.MOTION_NEXT_PAGE): # 结尾
# self.cursor_poi = len(self.text)
# # 删除操作
# elif motion == key.MOTION_BACKSPACE:
# if self.text: # 如果有文字
# self.text = f'{self.text[:self.cursor_poi - 1]}{self.text[self.cursor_poi:]}'
# self.cursor_poi = max(0, self._cursor_poi - 1)
# elif motion == key.MOTION_DELETE:
# if self.text and self.cursor_poi != len(self.text) - 1: # 如果有文字,并且光标不在最后
# self.text = f'{self.text[:self.cursor_poi]}{self.text[self.cursor_poi + 1:]}'
# # 剪贴板操作
# elif motion == key.MOTION_COPY:
# pass
# elif motion == key.MOTION_PASTE:
# paste_text = paste()
# self.text = f'{self.text[:self.cursor_poi]}{paste_text}{self.text[self.cursor_poi:]}'
# self.cursor_poi += len(paste_text)
#
# def on_text_motion_select(self, motion):
# pass
#
# def on_mouse_press(self, x, y, buttons, modifiers):
# if self._check_hit(x, y) and self._input_box.visible:
# self.enabled = True
# else:
# self.enabled = False
#
# def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
# pass
#
# def on_mouse_release(self, x, y, buttons, modifiers):
# pass
#
# def on_commit(self, text: str):
# pass
else:
InputBox = widgets.TextEntry
# class InputBox(widgets.TextEntry):
# pass

View File

@ -7,17 +7,24 @@
import typing
from pyglet.text import Label
from pyglet.clock import get_frequency
from pyglet.graphics import Batch, Group
from pyglet.clock import get_frequency
# Difficult Rocket function
from Difficult_Rocket.api.types import Fonts
# from Difficult_Rocket.utils import translate
from Difficult_Rocket.api.screen import BaseScreen
# from Difficult_Rocket.command.tree import CommandTree
if typing.TYPE_CHECKING:
from Difficult_Rocket.client import ClientWindow
class DRScreen(BaseScreen):
def __init__(self, main_window: "ClientWindow"):
super().__init__(main_window)
class DRDEBUGScreen(BaseScreen):
def __init__(self, main_window: "ClientWindow"):
super().__init__(main_window)
@ -48,3 +55,4 @@ class DRDEBUGScreen(BaseScreen):
def on_draw(self, *dt, window: "ClientWindow"):
self.main_batch.draw()
# print(self.window_pointer.try_if_runs)

View File

@ -44,13 +44,10 @@ class CommandText:
i += 1
def find(self, text: str) -> bool:
startswith = self.text.startswith(text)
if startswith:
find = self.text.find(text)
if find != -1:
if not len(text) == len(self.text):
self.text = self.text[find + len(text):] if not self.text[find+len(text)] == ' ' else self.text[find + len(text) + 1:]
return True
find = self.text.find(text)
if find != -1:
self.text = self.text[find + len(text):]
return True
return False
def re_find(self, text: str) -> Union[str, bool]:

View File

@ -77,7 +77,7 @@ def create_crash_report(info: Optional[str] = None) -> None:
crash_info = crash_info_handler(info)
if 'crash_report' not in os.listdir('./'):
os.mkdir('./crash_report')
date_time = time.strftime('%Y-%m-%d %H-%M-%S', time.localtime())
date_time = time.strftime('%Y-%m-%d %H-%M-%S', time.gmtime(time.time()))
filename = f'crash-{date_time}.md'
cache_stream = io.StringIO()
try:
@ -92,17 +92,16 @@ def create_crash_report(info: Optional[str] = None) -> None:
def write_cache(cache_stream, crash_info):
# 开头信息
cache_stream.write(Head_message.format(now_time=time.strftime('%Y/%m/%d %H:%M:%S', time.localtime())))
cache_stream.write(Head_message.format(now_time=time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(time.time()))))
# 崩溃信息
cache_stream.write(crash_info)
def write_info_to_cache(cache_stream):
# 运行状态信息
from Difficult_Rocket import DR_status
from Difficult_Rocket.runtime import DR_runtime
from Difficult_Rocket import DR_status, DR_runtime
cache_stream.write(Run_message)
cache_stream.write(markdown_line_handler(f'DR Version: {Difficult_Rocket.sdk_version}', level=1))
cache_stream.write(markdown_line_handler(f'DR Version: {Difficult_Rocket.game_version}', level=1))
cache_stream.write(markdown_line_handler(f'DR language: {DR_runtime.language}', level=1))
cache_stream.write(markdown_line_handler(f'Running Dir: {Path(os.curdir).resolve()}', level=1))
cache_stream.write(f"\n{DR_runtime.as_markdown()}")

View File

@ -1,125 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
from typing import Optional, Union, Tuple
# from libs import pyglet
# from pyglet import font
from pyglet.text import Label
from pyglet.window import mouse
from pyglet.gui import widgets
# from pyglet.sprite import Sprite
from pyglet.shapes import Rectangle
# from pyglet.image import AbstractImage
from pyglet.graphics import Batch, Group
from Difficult_Rocket.api.types import Fonts
# from Difficult_Rocket import DR_status
RGBA = Tuple[int, int, int, int]
class BaseTheme:
"""
用于定义主题的类
"""
def __init__(self,
main_color: RGBA = (39, 73, 114, 256),
secondary_color: RGBA = (66, 150, 250, 256),
main_font: str = Fonts.鸿蒙简体,
):
self.main_color = main_color
self.secondary_color = secondary_color
self.main_font = main_font
class PressTextButton(widgets.WidgetBase):
"""
自带 字符 + 材质 的按钮就不用单独做材质了
"""
def __init__(self,
x: int,
y: int,
width: int,
height: int,
text: str,
font: str = Fonts.鸿蒙简体,
font_size: int = 13,
batch: Optional[Batch] = None,
group: Optional[Group] = None,
):
super().__init__(x, y, width, height)
self.back_ground_batch = batch or Batch()
self.front_batch = batch or Batch()
if group:
self.front_group = Group(order=10, parent=group)
self.back_ground_group = Group(order=5, parent=group)
else:
self.front_group = Group(order=5)
self.back_ground_group = Group(order=10)
self.pressed = False
self.untouched_color = (39, 73, 114, 255)
self.touched_color = (66, 150, 250, 255)
self.hit_color = (15, 135, 250, 255)
# from ImGui
self.text = text
self.text_label = Label(font_name=font, font_size=font_size,
batch=self.front_batch, group=self.front_group,
x=self._x, y=self._y, text=self.text)
self.back_rec = Rectangle(x=self._x, y=self._y, width=self._width, height=self._height,
color=self.untouched_color, # ImGui color
batch=self.back_ground_batch, group=self.back_ground_group)
@property
def value(self):
return self.text
def __contains__(self, item):
return item in self.back_rec
def on_mouse_motion(self, x, y, dx, dy):
if (x, y) in self.back_rec:
self.back_rec.color = self.touched_color
else:
self.pressed = False
self.back_rec.color = self.untouched_color
def on_mouse_press(self, x, y, buttons, modifiers) -> bool:
if (x, y) in self and buttons == mouse.LEFT:
self.back_rec.color = self.hit_color
self.dispatch_event('on_press', x, y)
self.pressed = True
return True
return False
def on_mouse_release(self, x, y, buttons, modifiers):
if self.pressed and (x, y) in self:
self.back_rec.color = self.touched_color
self.pressed = False
def _update_position(self):
self.text_label.position = self._x, self._y
self.back_rec.position = self._x, self._y
self.back_rec.width = self._width
self.back_rec.height = self._height
...
PressTextButton.register_event_type('on_press')

View File

@ -11,24 +11,33 @@ github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
import sys
import time
import logging
import traceback
import importlib
import importlib.util
import logging.config
import multiprocessing
from io import StringIO
from pathlib import Path
from typing import List, Optional, Dict
from typing import TYPE_CHECKING, List, Optional, Dict, TypeVar
if __name__ == '__main__': # been start will not run this
sys.path.append('/bin/libs')
sys.path.append('/bin')
if TYPE_CHECKING:
from Difficult_Rocket.api.mod import ModInfo
else:
ModInfo = TypeVar('ModInfo')
from Difficult_Rocket.utils import tools
from Difficult_Rocket.api.types import Options
from Difficult_Rocket.utils.translate import tr
from Difficult_Rocket.runtime import DR_runtime
from Difficult_Rocket.mod.loader import ModManager
from Difficult_Rocket.utils.thread import new_thread
from Difficult_Rocket.crash import write_info_to_cache
from Difficult_Rocket import client, server, DR_status
from Difficult_Rocket import client, server, DR_status, DR_runtime
class Console(Options):
@ -59,9 +68,6 @@ class Console(Options):
def get_command(self) -> Optional[str]:
return self.caches.pop(0) if self.caches else None
def new_command(self) -> None:
return None
class Game(Options):
name = 'MainGame'
@ -72,21 +78,75 @@ class Game(Options):
console_class: Console = Console
main_config: Dict
logging_config: Dict
logger: logging.Logger
mod_manager: ModManager
mod_module: List["ModInfo"] = []
def dispatch_mod_event(self, event_name: str, *args, **kwargs) -> None:
self.mod_manager.dispatch_event(event_name, *args, **kwargs)
def init_logger(self) -> None:
log_path = self.logging_config['handlers']['file']['filename']
log_path = f"logs/{log_path.format(time.strftime('%Y-%m-%d %H-%M-%S' , time.gmtime(DR_runtime.start_time_ns / 1000_000_000)))}"
mkdir = False
if not Path('logs/').is_dir():
Path('logs/').mkdir()
mkdir = True
self.logging_config['handlers']['file']['filename'] = log_path
logging.config.dictConfig(self.logging_config)
self.logger = logging.getLogger('main')
if mkdir:
self.logger.info(tr().main.logger.mkdir())
def init_mods(self) -> None:
"""验证/加载 mod"""
from Difficult_Rocket.mod import loader as mod_loader
mod_loader.logger = logging.getLogger('mod_manager')
self.mod_manager = ModManager()
mod_class = self.mod_manager.load_mods()
self.mod_manager.init_mods(mod_class)
self.dispatch_mod_event('on_load', game=self)
self.mod_module = []
mods = []
mod_path = Path(DR_runtime.mod_path)
if not mod_path.exists():
self.logger.info(tr().main.mod.find.faild.no_mod_folder())
return
# 寻找有效 mod
paths = mod_path.iterdir()
sys.path.append(DR_runtime.mod_path)
for mod_path in paths:
try:
if mod_path.name == '__pycache__':
continue
self.logger.info(tr().main.mod.find.start().format(mod_path))
if mod_path.is_dir():
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
else:
self.logger.warning(tr().main.mod.load.faild.info().format(mod_path.name, tr().main.mod.find.faild.no_spec()))
elif mod_path.suffix in ('.pyz', '.zip', '.pyd', '.py'):
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
except ImportError as e:
self.logger.warning(tr().main.mod.find.faild().format(mod_path, e))
self.logger.info(tr().main.mod.find.done())
# 加载有效 mod
module = []
for mod in mods:
try:
self.logger.info(tr().main.mod.load.start().format(mod))
mod_module = importlib.import_module(mod)
if not hasattr(mod_module, "mod_class"):
self.logger.warning(tr().main.mod.load.faild.info().format(mod, tr().main.mod.load.faild.no_mod_class()))
del mod_module # 释放内存
continue
mod_class: type(ModInfo) = mod_module.mod_class
mod_class = mod_class()
module.append(mod_class)
self.logger.info(tr().main.mod.load.info().format(mod_class.mod_id, mod_class.version))
except ImportError as e:
self.logger.warning(tr().main.mod.load.faild.info().format(mod, e))
self.logger.info(tr().main.mod.load.done())
self.mod_module = module
mod_list = []
for mod in module:
mod_list.append((mod.mod_id, mod.version))
# 调用 on_load
self.dispatch_event('on_load', game=self)
DR_runtime.DR_Mod_List = mod_list
def init_console(self) -> None:
self.console = self.console_class()
@ -106,15 +166,32 @@ class Game(Options):
else:
self.client.start()
def dispatch_event(self, event_name: str, *args, **kwargs) -> None:
"""向 mod 分发事件"""
for mod in self.mod_module:
if hasattr(mod, event_name):
try:
getattr(mod, event_name)(*args, **kwargs)
except Exception:
error = traceback.format_exc()
self.logger.error(tr().main.mod.event.error().format(event_name, error, mod.mod_id))
def log_env(self) -> None:
self.logger.info(f'\n{self.as_markdown()}')
cache_steam = StringIO()
write_info_to_cache(cache_steam)
text = cache_steam.getvalue()
self.logger.info(text)
self.flush_option()
config_cache = self.logging_config.copy()
self.logging_config = {"logging_config": "too long to show"}
self.logger.info(f"\n{self.as_markdown()}")
self.logging_config = config_cache
def setup(self) -> None:
self.client = client.Client(game=self, net_mode='local')
self.server = server.Server(net_mode='local')
def init(self, **kwargs) -> bool:
self.logger = logging.getLogger('main')
self.load_file()
self.setup()
self.log_env()
@ -122,6 +199,9 @@ class Game(Options):
def load_file(self) -> bool:
"""加载文件"""
self.logging_config = tools.load_file('configs/logger.toml')
self.init_logger()
self.init_mods()
self.init_console()
return True

View File

@ -4,203 +4,38 @@
# All rights reserved
# -------------------------------
import sys
import time
import logging
import traceback
import importlib
from pathlib import Path
from typing import List, Dict, Optional, TypeVar
from typing import List, Dict, Optional
from Difficult_Rocket.mod.api import ModInfo
from Difficult_Rocket.utils.translate import tr
from Difficult_Rocket.api.screen import BaseScreen
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))
from Difficult_Rocket.mod.api import ModInfo
# from Difficult_Rocket import DR_status, DR_runtime
class ModManager(Options):
name = 'Mod Manager'
logger: logging.Logger
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]:
def find_mods(self) -> List[Path]:
"""
获取指定 mod 的模块
:param mod_name: mod
查找mods文件夹下的所有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: 事件参数
:return:
"""
for mod in self.loaded_mod_modules.values():
if hasattr(mod, event_name):
try:
getattr(mod, event_name)(*args, **kwargs)
except Exception as e:
logger.error(tr().mod.event.error().format(mod, event_name, e, traceback.format_exc()))
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 [])
_add_path_to_sys(find_path)
mods = []
start_time = time.time()
logger.info(tr().mod.load.start().format(find_path))
for path in find_path:
for path in self.mods_path:
if not path.exists():
path.mkdir(parents=True)
continue
for mod in path.iterdir():
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: 要加载的 mod ModInfo
:return:
"""
start_time = time.time()
for mod_class in mods:
try:
init_mod = mod_class()
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))
def init(self) -> None:
self.logger = logging.getLogger('client')
self.logger.name = 'mod_manager'
self.logger.info('Mod Manager init')

View File

@ -1,79 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import sys
import importlib
import traceback
import contextlib
import importlib.util
from pathlib import Path
from typing import Optional, List, Tuple
from Difficult_Rocket.api.types import Options, Version
__all__ = [
'DR_runtime'
]
class _DR_runtime(Options):
"""
DR 的运行时配置 / 状态
"""
name = 'DR Runtime'
language: str = 'zh-CN'
mod_path: str = './mods'
DR_Mod_List: List[Tuple[str, Version]] = [] # DR Mod 列表 (name, version)
# run status
start_time_ns: Optional[int] = None
client_setup_cause_ns: Optional[int] = None
server_setup_cause_ns: Optional[int] = None
def load_file(self) -> bool:
with contextlib.suppress(FileNotFoundError):
with open('./config/main.toml', 'r', encoding='utf-8') as f:
import rtoml
config_file = rtoml.load(f)
self.language = config_file['runtime']['language']
self.mod_path = config_file['game']['mods']['path']
return True
return False
def find_mods(self) -> List[str]:
mods = []
mod_path = Path(self.mod_path)
if not mod_path.exists():
mod_path.mkdir()
return []
paths = mod_path.iterdir()
sys.path.append(self.mod_path)
for mod_path in paths:
try:
if mod_path.is_dir() and mod_path.name != '__pycache__': # 处理文件夹 mod
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
else:
print(f'can not import mod {mod_path} because importlib can not find spec')
elif mod_path.suffix in ('.pyz', '.zip'): # 处理压缩包 mod
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
elif mod_path.suffix == '.pyd': # pyd 扩展 mod
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
elif mod_path.suffix == '.py': # 处理单文件 mod
print(f'importing mod {mod_path=} {mod_path.stem}')
if importlib.util.find_spec(mod_path.stem) is not None:
mods.append(mod_path.stem)
except ImportError:
print(f'ImportError when loading mod {mod_path}')
traceback.print_exc()
return mods
DR_runtime = _DR_runtime()

View File

@ -36,7 +36,7 @@ class Server:
# os.set
self.process_name = 'server process'
# config
self.config = tools.load_file('config/main.toml')
self.config = tools.load_file('configs/main.toml')
# self.dev = Dev
# self.net_mode = net_mode
self.logger.info(tr().server.setup.use_time().format(time.time() - start_time))

View File

@ -4,10 +4,3 @@
# All rights reserved
# -------------------------------
__all__ = [
'camera',
'options',
'thread',
'tools',
'translate'
]

View File

@ -1,263 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
# Huge thanks to pyglet developers
from typing import Tuple, Optional
from pyglet.gl import gl_compat, gl
from pyglet.math import Mat4, Vec3
from pyglet.graphics import Group
class Camera:
"""
>>> from pyglet.window import Window
>>> window = Window()
>>> camera = Camera(window)
>>> @window.event
>>> def on_draw():
>>> camera.begin()
>>> window.clear()
>>> camera.end()
"""
def __init__(self,
window,
zoom: Optional[float] = 1.0,
dx: Optional[float] = 1.0,
dy: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0) -> None:
self.window = window
self.dx = dx
self.dy = dy
self.zoom = zoom
self.min_zoom = min_zoom
self.max_zoom = max_zoom
self._stored_view = window.view
@property
def position(self) -> Tuple[float, float]:
return self.dx, self.dy
@position.setter
def position(self, value: Tuple[float, float]):
self.dx, self.dy = value
@property
def zoom_level(self) -> float:
return self.zoom
@zoom_level.setter
def zoom_level(self, value: float) -> None:
self.zoom = min(max(value, self.min_zoom), self.max_zoom)
def begin(self) -> None:
view = self.window.view
self._stored_view = view
x = self.window.width / self.zoom + (self.dx / self.zoom)
y = self.window.height / self.zoom + (self.dy / self.zoom)
view_matrix = view.translate((x * self.zoom, y * self.zoom, 0))
view_matrix = view_matrix.scale((self.zoom, self.zoom, 1))
self.window.view = view_matrix
def end(self) -> None:
self.window.view = self._stored_view
def reset(self):
self.zoom = 1
self.dx = 0
self.dy = 0
def __enter__(self):
self.begin()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.end()
class CenterCamera(Camera):
"""
A camera that centers the view in the center of the window
>>> from pyglet.window import Window
>>> window = Window()
>>> camera = CenterCamera(window)
>>> @window.event
>>> def on_draw():
>>> camera.begin()
>>> window.clear()
>>> camera.end()
"""
def begin(self) -> None:
view = self.window.view
self._stored_view = view
x = self.window.width / 2.0 / self.zoom + (self.dx / self.zoom)
y = self.window.height / 2.0 / self.zoom + (self.dy / self.zoom)
view_matrix = view.translate((x * self.zoom, y * self.zoom, 0))
view_matrix = view_matrix.scale((self.zoom, self.zoom, 1))
self.window.view = view_matrix
def end(self) -> None:
self.window.view = self._stored_view
class GroupCamera(Group):
"""
A camera by group
can be used by just added to your widget
"""
def __init__(self,
window,
order: int = 0,
parent: Optional[Group] = None,
view_x: Optional[int] = 0,
view_y: Optional[int] = 0,
zoom: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0):
super().__init__(order=order, parent=parent)
self._window = window
self._previous_view = None
self._view_x = view_x
self._view_y = view_y
self._zoom = zoom
self.min_zoom = min_zoom
self.max_zoom = max_zoom
@property
def view_x(self) -> int:
return self._view_x
@view_x.setter
def view_x(self, value: int):
self._view_x = value
@property
def view_y(self) -> int:
return self._view_y
@view_y.setter
def view_y(self, value: int):
self._view_y = value
@property
def zoom(self) -> float:
return min(max(self._zoom, self.min_zoom), self.max_zoom)
@zoom.setter
def zoom(self, value: float):
self._zoom = value
def reset(self):
self._view_x = 0
self._view_y = 0
self.zoom = 1
def set_state(self):
self._previous_view = self._window.view
view = Mat4.from_translation(Vec3(self._view_x, self._view_y, 0))
if self._zoom == 1.0:
self._window.view = view
else:
view = view.scale(Vec3(self._zoom, self._zoom, 1))
self._window.view = view
def unset_state(self):
self._window.view = self._previous_view
class CenterGroupCamera(GroupCamera):
"""
A camera by group
can be used by just added to your widget
"""
def set_state(self):
self._previous_view = self._window.view
x = (self._window.width / 2) / self._zoom + (self._view_x / self._zoom)
y = (self._window.height / 2) / self._zoom + (self._view_y / self._zoom)
view = Mat4.from_translation(Vec3(x * self._zoom, y * self._zoom, 0))
# 不懂就问 为啥这里 * zoom 下面还 * zoom
if self._zoom == 1.0:
self._window.view = view
else:
view = view.scale(Vec3(self._zoom, self._zoom, 1))
self._window.view = view
def unset_state(self):
self._window.view = self._previous_view
class CenterGroupFrame(Group):
"""
A camera by group
can be used by just added to your widget
"""
def __init__(self,
window,
order: int = 0,
parent: Optional[Group] = None,
dx: Optional[int] = 0,
dy: Optional[int] = 0,
width: Optional[int] = 0,
height: Optional[int] = 0,
zoom: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0):
super().__init__(order=order, parent=parent)
self.window = window
self.dx = dx
self.dy = dy
self._width = width
self._height = height
self._zoom = zoom
self.min_zoom = min_zoom
self.max_zoom = max_zoom
@property
def zoom(self) -> float:
return self._zoom
@zoom.setter
def zoom(self, value: float):
self._zoom = min(max(value, self.min_zoom), self.max_zoom)
def set_state(self):
self._previous_view = self.window.view
gl.glScissor(int(self.dx), int(self.dy), int(self._width), int(self._height))
gl.glViewport(int(self.dx), int(self.dy), int(self.window.width), int(self.window.height))
gl.glEnable(gl.GL_SCISSOR_TEST)
x = (self.window.width / 2) / self._zoom + (self.dx / self._zoom)
y = (self.window.height / 2) / self._zoom + (self.dy / self._zoom)
view = Mat4.from_translation(Vec3(x * self._zoom, y * self._zoom, 0))
view.scale(Vec3(self._zoom, self._zoom, 1))
self.window.view = view
def unset_state(self):
self.window.view = self._previous_view
gl.glDisable(gl.GL_SCISSOR_TEST)
gl.glViewport(0, 0, int(self.window.width), int(self.window.height))

View File

@ -0,0 +1,34 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import logging
from typing import Callable
__all__ = [
'get_named_client_logger',
'get_named_server_logger',
'get_named_main_logger',
]
def _gen_get_named_logger(from_name: str) -> Callable[[str], logging.Logger]:
def get_named_logger(name: str) -> logging.Logger:
logger = logging.getLogger(from_name)
logger.name = f'{from_name}.{name}'
return logger
return get_named_logger
get_named_client_logger = _gen_get_named_logger('client')
# 用于获取一个基于 client 配置的 logger
get_named_server_logger = _gen_get_named_logger('server')
# 用于获取一个基于 server 配置的 logger
get_named_main_logger = _gen_get_named_logger('main')
# 用于获取一个基于 main 配置的 logger

View File

@ -4,21 +4,19 @@
# All rights reserved
# -------------------------------
import shutil
import warnings
import traceback
from io import StringIO
from dataclasses import dataclass
from typing import get_type_hints, Type, List, Union, Dict, Any, Callable, Tuple, Optional, TYPE_CHECKING, Iterable
__all__ = [
'get_type_hints_',
'Options',
'OptionsError',
'OptionNotFound',
'OptionNameNotDefined',
'Fonts',
'FontData'
]
__all__ = ['get_type_hints_',
'Options',
'Fonts',
'FontData',
'OptionsError',
'OptionNotFound',
'OptionNameNotDefined']
def get_type_hints_(cls: Type):
@ -176,12 +174,15 @@ class Options:
self.cached_options = self.option()
return self.cached_options
def option_with_len(self) -> Tuple[List[Tuple[str, Any, Type]], int, int, int]:
def option_with_len(self, longest: Optional[int] = None) -> Tuple[List[Tuple[str, Union[Any, Type], Type]], int, int, int]:
"""
返回一个可以用于打印的 option 列表
:return:
"""
options = self.flush_option()
if longest is None:
options = self.flush_option()
else:
options = self.str_option(longest)
max_len_key = 1
max_len_value = 1
max_len_value_t = 1
@ -191,61 +192,19 @@ class Options:
max_len_key = max(max_len_key, len(key))
max_len_value = max(max_len_value, len(str(value)))
max_len_value_t = max(max_len_value_t, len(str(value_t)))
option_list.append([key, value, value_t])
return [option_list, max_len_key, max_len_value, max_len_value_t] # noqa
option_list.append((key, value, value_t))
return option_list, max_len_key, max_len_value, max_len_value_t
def as_markdown(self, longest: Optional[int] = None) -> str:
"""
返回一个 markdown 格式的 option 字符串
:param longest: 最长的输出长度
:return: markdown 格式的 option 字符串
"""
value = self.option_with_len()
value = self.option_with_len(longest)
cache = StringIO()
option_len = max(value[1], len('Option'))
value_len = max(value[2], len('Value'))
value_type_len = max(value[3], len('Value Type'))
# | Option | Value | Value Type |
shortest = len('Option" "Value" "Value Type')
if longest is not None:
console_width = max(longest, shortest)
else:
console_width = shutil.get_terminal_size(fallback=(100, 80)).columns
console_width = max(console_width, shortest)
# 为每一栏 预分配 1/3 或者 需要的宽度 (如果不需要 1/3)
option_len = min(option_len, console_width // 3)
value_len = min(value_len, console_width // 3)
value_type_len = min(value_type_len, console_width // 3)
# 先指定每一个列的输出最窄宽度, 然后去尝试增加宽度
# 循环分配新空间之前 首先检查是否已经不需要多分配 (and 后面)
while option_len + value_len + value_type_len + 16 < console_width\
and (option_len < value[1]
or value_len < value[2]
or value_type_len < value[3]):
# 每一个部分的逻辑都是
# 如果现在的输出长度小于原始长度
# 并且长度 + 1 之后的总长度依然在允许范围内
# 那么就 + 1
if option_len < value[1] and option_len + value_len + value_type_len + 15 < console_width:
option_len += 1
if value_len < value[2] and option_len + value_len + value_type_len + 15 < console_width:
value_len += 1
if value_type_len < value[3] and option_len + value_len + value_type_len + 15 < console_width:
value_type_len += 1
# 实际上 对于列表(可变对象) for 出来的这个值是一个引用
# 所以可以直接修改 string
for v in value[0]:
if len(str(v[0])) > option_len:
v[0] = f'{str(v[0])[:value_len - 3]}...'
if len(str(v[1])) > value_len:
v[1] = f'{str(v[1])[:value_len - 3]}...'
if len(str(v[2])) > value_type_len:
v[2] = f'{str(v[2])[:value_len - 3]}..'
cache.write(
f"| Option{' ' * (option_len - 3)}| Value{' ' * (value_len - 2)}| Value Type{' ' * (value_type_len - 7)}|\n")
cache.write(f'|:{"-" * (option_len + 3)}|:{"-" * (value_len + 3)}|:{"-" * (value_type_len + 3)}|\n')

View File

@ -16,21 +16,22 @@ import sys
import time
import math
import json
import rtoml
import logging
import configparser
from pathlib import Path
from typing import Union, Optional
from typing import Union
from xml.etree import ElementTree
import rtoml
from defusedxml.ElementTree import parse
from Difficult_Rocket.exception.unsupport import NoMoreJson5
# logger
tools_logger = logging.getLogger('tools')
tools_logger = logging.getLogger('part-tools')
"""
file config
file configs
"""
file_error = {FileNotFoundError: 'no {filetype} file was founded!:\n file name: {filename}\n file_type: {filetype}\n stack: {stack}',
@ -38,12 +39,10 @@ file_error = {FileNotFoundError: 'no {filetype} file was founded!:\n file name:
Exception: 'get some {error_type} when read {filetype} file {filename}! \n file type: {} \n file name: {} \n stack: {stack}'}
def load_file(file_name: Union[str, Path],
stack: Optional[Union[str, list, dict]] = None,
raise_error: Optional[bool] = True,
encoding: Optional[str] = 'utf-8') -> Union[dict, ElementTree.ElementTree]:
if isinstance(file_name, Path):
file_name = str(file_name)
def load_file(file_name: str,
stack: Union[str, list, dict, None] = None,
raise_error: bool = True,
encoding: str = 'utf-8') -> Union[dict, ElementTree.ElementTree]:
f_type = file_name[file_name.rfind('.') + 1:] # 从最后一个.到末尾 (截取文件格式)
get_file = NotImplementedError('解析失败,请检查文件类型/文件内容/文件是否存在!')
try:
@ -99,7 +98,7 @@ def save_dict_file(file_name: str,
# main config
main_config_file = load_file('./config/main.toml')
main_config_file = load_file('./configs/main.toml')
def get_At(name, in_xml, need_type=str):

View File

@ -14,13 +14,11 @@ gitee: @shenjackyuanjie
import os
import inspect
from pathlib import Path
from dataclasses import dataclass
from typing import Union, Tuple, Any, List, Dict, Hashable, Optional
from Difficult_Rocket import DR_status
from Difficult_Rocket import DR_runtime, DR_status
from Difficult_Rocket.utils import tools
from Difficult_Rocket.runtime import DR_runtime
from Difficult_Rocket.exception.language import (LanguageNotFound,
TranslateKeyNotFound)
@ -140,12 +138,12 @@ class Translates:
def __str__(self):
if not any(not x[0] for x in self._get_list):
return str(self._value)
return self._value
if self._config.crack_normal:
return f'{".".join(f"{gets[1]}({gets[0]})" for gets in self._get_list)}'
elif self._config.insert_crack:
return f'{self._value}.{".".join(gets[1] for gets in self._get_list if not gets[0])}'
return str(self._value)
return self._value
class Tr:
@ -154,20 +152,15 @@ class Tr:
GOOD
"""
def __init__(self,
language: str = None,
config: Optional[TranslateConfig] = None,
lang_path: Optional[Path] = None):
def __init__(self, language: str = None, config: Optional[TranslateConfig] = None):
"""
诶嘿我抄的MCDR
:param language: Tr 所使用的的语言
:param config: 配置
:param lang_path: 语言文件夹路径
"""
self.language_name = language if language is not None else DR_runtime.language
self.language_path = lang_path if lang_path is not None else Path('assets/lang')
self.translates: Dict[str, Union[str, Dict]] = tools.load_file(self.language_path / f'{self.language_name}.toml')
self.default_translate: Dict = tools.load_file(f'{self.language_path}/{DR_status.default_language}.toml')
self.translates: Dict[str, Union[str, Dict]] = tools.load_file(f'configs/lang/{self.language_name}.toml')
self.default_translate: Dict = tools.load_file(f'configs/lang/{DR_status.default_language}.toml')
self.default_config = config.set('source', self) if config is not None else TranslateConfig(source=self)
self.translates_cache = Translates(value=self.translates, config=self.default_config.copy())
@ -191,11 +184,11 @@ class Tr:
if lang == ' ' or lang == '':
raise LanguageNotFound('Can not be empty')
lang = lang or self.language_name
if not os.path.exists(f'{self.language_path}/{lang}.toml'):
print(f"lang: {os.path.exists(f'{self.language_path}/{lang}.toml')} language = {lang} {self.language_name=}")
if not os.path.exists(f'./configs/lang/{lang}.toml'):
print(f"lang: {os.path.exists(f'./configs/lang/{lang}.toml')} language = {lang} {self.language_name=}")
raise LanguageNotFound(lang)
self.translates: Dict[str, Union[str, Dict]] = tools.load_file(f'{self.language_path}/{lang}.toml')
self.default_translate: Dict = tools.load_file(f'{self.language_path}/{DR_runtime.default_language}.toml')
self.translates: Dict[str, Union[str, Dict]] = tools.load_file(f'configs/lang/{lang}.toml')
self.default_translate: Dict = tools.load_file(f'configs/lang/{DR_runtime.default_language}.toml')
self.translates_cache = Translates(value=self.translates, config=self.default_config.copy())
self.language_name = lang
DR_runtime.language = self.language_name

View File

@ -1,5 +1,3 @@
<div style="text-align: center;">
# Difficult Rocket
中文 | [English](./docs/README-en.md)
@ -11,28 +9,22 @@
## 请注意 这个仓库未来只会发布 `DR SDK` 的更新 `DR game` 的更新会在 [这里](https://github.com/shenjackyuanjie/DR-game) 发布
![demo](docs/src/demo.png)
<a href="https://996.icu"><img src="https://img.shields.io/badge/link-996.icu-red.svg" alt="996.icu" /></a>
[![Generic badge](https://img.shields.io/badge/SemVer-2.0.0-blue.svg)](https://Semver.org/)
[![Generic badge](https://img.shields.io/badge/编写于_Python_版本-3.8.10-blue.svg)](https://Python.org)
[![Generic badge](https://img.shields.io/badge/编写于_Pyglet_版本-2.0.8-blue.svg)](https://pyglet.org)
[![Generic badge](https://img.shields.io/badge/编写于_Pyglet_版本-2.0.7-blue.svg)](https://pyglet.org)
[![Generic badge](https://img.shields.io/badge/Python-_3.8_|_3.9_|_3.10_|_3.11_-blue.svg)](https://Python.org)
## 版本
[关于版本号的说明](./docs/src/version.md)
[![Generic badge](https://img.shields.io/badge/Release-0.8.5.1-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Pre_Release-0.8.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Devloping-0.8.6-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Release-0.8.2.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Pre_Release-0.8.2.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Devloping-0.8.3-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![language badge](https://stats.deeptrain.net/repo/shenjackyuanjie/Difficult-Rocket?theme=dark)](https://stats.deeptrain.net/repo/shenjackyuanjie/Difficult-Rocket?theme=dark)
[DR sdk 最新 Action 构建](https://nightly.link/shenjackyuanjie/Difficult-Rocket/workflows/nuitka/main)
[DR rs 最新 Action 构建](https://nightly.link/shenjackyuanjie/Difficult-Rocket/workflows/dr_rs/main)
## English README please look [here](./docs/README-en.md)
> 这是一个用Python制作的类Simple Rocket游戏(简称:火箭模拟器)
@ -50,27 +42,27 @@
## 环境需求 (测试过的 / 开发平台)
- `开发平台 1 - Windows 10 x64 22H2`
- Python `3.8.10` / `3.10.11`
- Python `3.8.10`
- pillow `9.5.0`
- psutil `5.9.5`
- rtoml `0.9.0`
- tomlkit `0.11.8`
- tomlkit `0.11.7`
- defusedxml `0.7.1`
- objprint `0.2.2`
- viztracer `0.15.6`
- vizplugins `0.1.3`
- nuitka `1.6.6`
- nuitka `1.5.6`
- ordered-set `4.1.0`
- imageio `2.31.0`
- imageio `2.27.0`
- wheel `0.40.0`
- setuptools `67.8.0`
- setuptools-rust `1.6.0`
- setuptools `67.6.1`
- setuptools-rust `1.5.2`
- `AMD R5 5600X`
- `AMD RX 550 4G`
## 需要的 Python 模块
- `pyglet` (已经内置 V2.0.8 路径:`./libs/pyglet`)
- `pyglet` (已经内置 V2.0.5 路径:`./libs/pyglet`)
- `xmltodict` (已经内置 V0.12.0 路径:`./libs/xmltodict`)
- `pyperclip` (已经内置 V1.8.2 路径: `./libs/pyperclip`)
@ -81,30 +73,28 @@
# DR contributing
# for images
# not for pypy >= 3.10
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
pillow >= 9.5.0
# for sys info
psutil >= 5.9.5
# for files
rtoml >= 0.9.0
tomlkit >= 0.11.8
tomlkit >= 0.11.7
defusedxml >= 0.7.1
# for debug
objprint >= 0.2.2
viztracer >= 0.15.6; platform_python_implementation != "PyPy"
vizplugins >= 0.1.3; platform_python_implementation != "PyPy"
viztracer >= 0.15.6
vizplugins >= 0.1.3
# for compile
nuitka >= 1.7.5
nuitka >= 1.5.6
ordered-set >= 4.1.0
imageio >= 2.31.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
imageio >= 2.27.0
wheel >= 0.40.0
setuptools >= 67.8.0
setuptools-rust >= 1.6.0
setuptools >= 67.6.1
setuptools-rust >= 1.5.2
```
## 感谢
@ -114,8 +104,8 @@ setuptools-rust >= 1.6.0
- `tomlkit` / `rtoml` : toml 解析器
- `xmltodict`: xml 与 dict 转换器
- `pyperclip`: 剪贴板!
- [rapier2d](https://rapier.rs/) : 物理模拟引擎
- [pyo3](https://pyo3.rs/main): Rust Python 扩展
- `rapier2d`: 物理模拟引擎
- `pyo3`: Rust Python 扩展
- 主要贡献者
- [@Rayawa](https://github.com/Rayawa) : 文档矫正 & 翻译部分 lang
@ -123,8 +113,6 @@ setuptools-rust >= 1.6.0
- [@Billchyi](https://github.com/Billchyi) : 文档矫正
- [@MSDNicrosoft](https://github.com/MSDNicrosoft) : 优化代码
</div>
## 相关链接
## 关于分享协议

View File

@ -180,7 +180,7 @@
<Part partType="engine-2" id="112" x="-13.000000" y="-5.500000" angle="0.000000" angleV="0.000000" editorAngle="0" activated="0" exploded="0" flippedX="0" flippedY="0">
<Engine fuel="0.000000"/>
</Part>
<Part partType="port-1" id="167" x="-13.000000" y="-7.500000" angle="4.712389" angleV="0.000000" editorAngle="3" activated="1" exploded="0" flippedX="1" flippedY="0"/>
<Part partType="port-1" id="167" x="-13.000000" y="-7.500000" angle="4.712389" angleV="0.000000" editorAngle="3" activated="0" exploded="0" flippedX="1" flippedY="0"/>
<Part partType="engine-2" id="113" x="-19.000000" y="-5.500000" angle="0.000000" angleV="0.000000" editorAngle="0" activated="0" exploded="0" flippedX="0" flippedY="0">
<Engine fuel="0.000000"/>
</Part>

View File

@ -20,26 +20,15 @@ logger.logfile_level = "Log file record level : "
logger.logfile_fmt = "Log file record format : "
logger.logfile_datefmt = "Log file date format : "
game_start.at = "Game MainThread start at: {}"
[mod]
list = "Mod list: "
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: {}"
mod.find.start = "Checking Mod: {}"
mod.find.faild.no_spec = "importlib can't find spec"
mod.find.faild.no_mod_folder = "Can't find mod folder"
mod.find.done = "All Mod checked"
mod.load.start = "Loading Mod: {}"
mod.load.info = "mod id: {} version: {}"
mod.load.faild.info = "Mod load failed: {} error info: {}"
mod.load.faild.no_mod_class = "Can't find Mod class"
mod.load.done = "All Mod loaded"
[client]
setup.start = "Client start loading"
@ -65,6 +54,8 @@ 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: {}"
@ -72,9 +63,6 @@ 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"
@ -83,6 +71,17 @@ os.pid_is = "Server PID: {} PPID: {}"
[game]
input = "console"
window = "window"
command = "in game commands"
window = "window"
require_DR_rs = "require DR_rs module"
[client.sr1_render]
setup.start = "SR1 Renderer start loading"
setup.use_time = "SR1 Renderer loading has used: {} second"
xml.loading = "Loading XML file: {}"
xml.load_done = "XML file loaded"
xml.load_time = "XML file loading has used: {} second"
ship.load = "Loading ship: {}"
ship.load_time = "Ship loading has used: {} second"
ship.info = "Ship info:\n- Parts: {}\n- Weight: {}"
ship.render.done = "Ship render done"

View File

@ -20,26 +20,16 @@ logger.logfile_level = "日志文件记录级别:"
logger.logfile_fmt = "日志文件记录格式:"
logger.logfile_datefmt = "日志文件日期格式:"
game_start.at = "游戏主线程开始于:"
[mod]
list = "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 重载成功: {}"
mod.find.start = "正在校验 Mod: {}"
mod.find.faild.no_spec = "importlib 无法找到 spec"
mod.find.faild.no_mod_folder = "没有找到 Mod 文件夹"
mod.find.done = "所有 Mod 校验完成"
mod.load.start = "正在加载 Mod: {}"
mod.load.info = "mod id: {} 版本号: {}"
mod.load.faild.info = "Mod 加载失败: {} 错误信息: {}"
mod.load.faild.no_mod_class = "没有找到 Mod 类"
mod.load.done = "所有 Mod 加载完成"
mod.event.error = "Mod 事件 {} 发生错误 {} Mod: {}"
[client]
setup.start = "客户端加载开始"
@ -65,6 +55,8 @@ 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 = "在字体列表中找到以下字体库: {}"
@ -72,9 +64,6 @@ fonts.load = "正在加载字体: {}"
game.stop_get = "从{}传入关闭指令,关闭游戏中"
game.stop = "游戏正在关闭,保存数据中···"
game.end = "游戏已经关闭"
command.text = "输入命令: {}"
message.text = "输入信息: {}"
command.mods.reload.no_mod_id = "没有指定 mod id"
[server]
setup.start = "服务端开始加载"
@ -83,6 +72,18 @@ os.pid_is = "服务端 PID: {} PPID: {}"
[game]
input = "控制台"
window = "窗口"
command = "游戏内命令行"
window = "窗口"
require_DR_rs = "需要 DR_rs 模块"
[client.sr1_render]
setup.start = "SR1 渲染器开始载入"
setup.use_time = "SR1 渲染器载入消耗时间: {} 秒"
xml.loading = "正在加载XML文件: {}"
xml.load_done = "XML 文件加载完成"
xml.load_time = "XML 文件加载消耗时间: {} 秒"
ship.load = "正在加载飞船: {}"
ship.load_time = "飞船加载消耗时间: {} 秒"
#ship.info = "飞船信息:\n- 部件数量: {}\n- 部件重量: {}\n- 文件大小: {}"
ship.info = "飞船信息:\n- 部件数量: {}\n- 部件重量: {}"
ship.render.done = "飞船渲染完成"

View File

@ -22,7 +22,7 @@ datefmt = "%Y-%m-%d %H:%M:%S"
[handlers.console]
class = "logging.StreamHandler"
formatter = "file"
level = "INFO"
level = "DEBUG"
[handlers.file]
class = "logging.FileHandler"

View File

@ -3,12 +3,12 @@ fps = 60
language = "zh-CN"
date_fmt = "%Y-%m-%d %H-%M-%S"
write_py_v = "3.8.10"
fonts_folder = "assets/fonts"
fonts_folder = "libs/fonts"
[window]
style = "None"
width = 1112
height = 793
width = 1021
height = 1078
visible = true
gui_scale = 1
caption = "Difficult Rocket v{DR_version}"

2
docs/.gitignore vendored
View File

@ -3,5 +3,3 @@ index.html
book
#theme
README-en.html

View File

@ -9,28 +9,22 @@
## Notice: This repo will only publish `DR SDK` updates, `DR game` updates will be published [here](https://github.com/shenjackyuanjie/DR-game)
![demo](/src/demo.png)
<a href="https://996.icu"><img src="https://img.shields.io/badge/link-996.icu-red.svg" alt="996.icu" /></a>
[![Generic badge](https://img.shields.io/badge/SemVer-2.0.0-blue.svg)](https://Semver.org/)
[![Generic badge](https://img.shields.io/badge/Write_with_Python-3.8.10-blue.svg)](https://Python.org)
[![Generic badge](https://img.shields.io/badge/Write_with_Pyglet-2.0.8-blue.svg)](https://pyglet.org)
[![Generic badge](https://img.shields.io/badge/Write_with_Pyglet-2.0.5-blue.svg)](https://pyglet.org)
[![Generic badge](https://img.shields.io/badge/Python-_3.8_|_3.9_|_3.10_|_3.11_-blue.svg)](https://Python.org)
## Version
[About Versions](src/version.md)
[![Generic badge](https://img.shields.io/badge/Release-0.8.5.1-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Pre_Release-0.8.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Devloping-0.8.6-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Release-0.8.2.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Pre_Release-0.8.2.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Devloping-0.8.3-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![language badge](https://stats.deeptrain.net/repo/shenjackyuanjie/Difficult-Rocket?theme=dark)](https://stats.deeptrain.net/repo/shenjackyuanjie/Difficult-Rocket?theme=dark)
[DR sdk latest Action build](https://nightly.link/shenjackyuanjie/Difficult-Rocket/workflows/nuitka/main)
[DR rs latest Action build](https://nightly.link/shenjackyuanjie/Difficult-Rocket/workflows/dr_rs/main)
## 中文README请移步 [这里](../README.md)
> Difficult-rocket is a Simple Rocket liked game build with Python (in short: rocket simulator)
@ -48,7 +42,7 @@
## Environment (been tested / developed on)
- `Develop platform 1 - Windows 10 x64 22H2`
- Python `3.8.10` / `3.10.11`
- Python `3.8.10`
- pillow `9.5.0`
- psutil `5.9.5`
- rtoml `0.9.0`
@ -57,7 +51,7 @@
- objprint `0.2.2`
- viztracer `0.15.6`
- vizplugins `0.1.3`
- nuitka `1.6.6`
- nuitka `1.6.1`
- ordered-set `4.1.0`
- imageio `2.31.0`
- wheel `0.40.0`
@ -68,7 +62,7 @@
## Required python modules
- `pyglet` (pre-installed V2.0.8 path:`./libs/pyglet`)
- `pyglet` (pre-installed V2.0.5 path:`./libs/pyglet`)
- `xmltodict` (pre-installed V0.12.0 path:`./libs/xmltodict`)
- `pyperclip` (pre-installed V1.8.2 path: `./libs/pyperclip`)
@ -79,30 +73,28 @@
# DR contributing
# for images
# not for pypy >= 3.10
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
pillow >= 9.5.0
# for sys info
psutil >= 5.9.5
# for files
rtoml >= 0.9.0
tomlkit >= 0.11.8
tomlkit >= 0.11.7
defusedxml >= 0.7.1
# for debug
objprint >= 0.2.2
viztracer >= 0.15.6; platform_python_implementation != "PyPy"
vizplugins >= 0.1.3; platform_python_implementation != "PyPy"
viztracer >= 0.15.6
vizplugins >= 0.1.3
# for compile
nuitka >= 1.7.5
nuitka >= 1.5.6
ordered-set >= 4.1.0
imageio >= 2.31.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
imageio >= 2.27.0
wheel >= 0.40.0
setuptools >= 67.8.0
setuptools-rust >= 1.6.0
setuptools >= 67.6.1
setuptools-rust >= 1.5.2
```
## thanks to
@ -112,8 +104,8 @@ setuptools-rust >= 1.6.0
- `tomlkit` / `rtoml` toml parser
- `xmltodict`: translate data between xml and dict
- `pyperclip`: paste board!
- [rapier2d](https://rapier.rs/) : Phy simulate engine
- [pyo3](https://pyo3.rs/main) : Rust Python Binding
- `rapier2d`: Phy simulate engine
- `pyo3`: Rust Python Binding
- Main contributors
- [@Rayawa](https://github.com/Rayawa) : Check mistake in docs & some translates

View File

@ -41,9 +41,6 @@ page-break = true # insert page-break after each chapter
[rust]
edition = "2021" # the default edition for code blocks
# 从本地运行一些东西拿到 markdown 里来
[preprocessor.cmdrun]
### 扩展部分
# 检查文档内链接
@ -58,10 +55,12 @@ edition = "2021" # the default edition for code blocks
# 添加右侧目录
[preprocessor.theme]
turn-off = true
pagetoc = true
pagetoc-width = "13%"
sidebar-width = "300px"
# 从本地运行一些东西拿到 markdown 里来
[preprocessor.cmdrun]
# 在新页面中打开链接
[preprocessor.external-links]

View File

@ -25,7 +25,6 @@
- [部件可成环](./plan_features/multi-connect.md)
- [多指令舱控制](./plan_features/multi-control.md)
- [轨道规划](./plan_features/orbit-plan.md)
- [自定义部件收集](./plan_features/custom-part.md)
- [开发文档](./howto/readme.md)
- [client](./howto/client.md)

View File

@ -2,38 +2,7 @@
# DR 构建 更新日志
- 最新构建版本号
- build_version: 2.1.3.0
## 20230715 build 2.1.3.0
### 修改
- 添加 `email` `win32con` `smtplib` `win32evtlog` `win32evtlogutil` `win32api``--no-follow-import`
- Add `email`, `win32con`, `smtplib`, `win32evtlog`, `win32evtlogutil`, `win32api` to `--no-follow-import`
- `include_data_dir` 移除 `libs/fonts` `textures`
- 改为 `assets``config`
## 20230708 build 2.1.2.0
### 修改
- 同步了 `lib-not-dr` 的修改
- `NuitkaCompiler`
- 添加了 `run_after_build` 选项
- `--run`
- 添加了 `compat_nuitka_version` 选项
- 目前是 `1.7.1`
## 20230630 build 2.1.1.0
### 修改
- 现在本地构建会根据系统名称修改输出目录
- 例如
- 在 `Windows` 上构建会输出到 `build\nuitka-windows`
- Now the local build will modify the output directory according to the system name
- For example
- Build on `Windows` will output to `build\nuitka-windows`
- build_version: 2.1.0.0
## 20230611 build 2.1.0.0
@ -44,20 +13,13 @@
- 在 `Windows``--no-follow-import` `pyglet.libs.x11`
- 大幅提升编译效率
- 缩短编译时间
- Now `pyglet` compatibility modules for other platforms will be automatically excluded on each platform
- For example
- `--no-follow-import` `pyglet.libs.x11` on `Windows`
- Greatly improve compilation efficiency
- Shorten compilation time
## 20230609 build 2.0.0.0
### Breaking Change
- 将构建脚本生成方式改为通过 `libs/utils/nuitka.py` 生成
- Change the way the build script is generated to generate it through `libs/utils/nuitka.py`
- GitHub Actions 也使用相同脚本构建
- GitHub Actions also uses the same script to build
## 202306 build 1.3.0.0

View File

@ -2,251 +2,8 @@
# DR game/DR rs 更新日志
- 最新版本号
- DR game: 0.3.3.0
- DR rs: 0.2.21.0
## 20230812 DR game 0.3.3.0
### Changes
- 将 `sr1_ship` 渲染器使用的 Camera 改成 `CenterGroupCamera`
- 删除了之前的 Camera 相关代码
- 将用于渲染到材质的代码部分改为使用 `glScissor``glViewport`
- 优化了一点性能 ( 毕竟是OpenGL )
## 20230809 DR game 0.3.2.1
### Fix
- 因为把部件加载图片的数据源改成从 `SR1PartType_rs` 里取
- 所以修改了 `SR1Textures` 的加载逻辑
- 可以自动忽略文件名最后的 `.png`
## 20230808 DR rs 0.2.21.0
### Add
- 在 `__init__.py` 里添加了
- `sprite`
- `type`
- 的导出 (实际上就是 typing)
- `SR1PartType_rs`
- 添加了 `type` getter
摸鱼真开心
## 20230724 DR rs 0.2.20.2
### Fix
- [#49](https://github.com/shenjackyuanjie/Difficult-Rocket/issues/49)
- missing field `touchingGround`
- SR1 says: `touchingGround` field is NOT Required
- make them happy
- SR1 说: `touchingGround` 字段也是可选的
- 让他们开心
- 我就看看我能发多少个 issue
## 20230724 DR rs 0.2.20.1
### Fix
- [#48](https://github.com/shenjackyuanjie/Difficult-Rocket/issues/48)
- `missing field version`
- SR1 says: `version` field is NOT Required
- make them happy
- SR1 说: `version` 字段也是可选的
- 让他们开心
- 我谢谢您啊 Jundroo
- 我就看看我能发多少个 issue
## 20230724 DR rs 0.2.20.0
### Fix
- [#47](https://github.com/shenjackyuanjie/Difficult-Rocket/issues/47)
- `editorAngle field is Option<i32>`
- SR1 says: `editorAngle` field is Optional
- make them happy
- SR1 说: `editorAngle` 字段是可选的
- 让他们开心
## 20230721 DR rs 0.2.19.0
### Add
- `PySR1Ship`
- `as_list`
## 20230721 DR game 0.3.2.0
### BUG Fix
- [#46](https://github.com/shenjackyuanjie/Difficult-Rocket/issues/46)
- 渲染偏移 bug
## 20230721 DR rs 0.2.18.0
### Add
- 导出了 export
- `PySR1PartData`
- `PySaveStatus`
- `map_ptype_textures(part_type: str) -> str`
## 20230721 DR rs 0.2.17.0
### BreakingChanges
- `SR1Ship_rs`
- `__init__`
- 将 `part_list` 参数从传入 `PartList.xml` 路径 改为直接传入 `SR1PartList_rs` 实例
- Change the `part_list` parameter from passing in the `PartList.xml` path to directly passing in the `SR1PartList_rs` instance
## 20230721 DR rs 0.2.16.0
### Dependencies
in [#45](https://github.com/shenjackyuanjie/Difficult-Rocket/pull/45)
- `pyo3`
- `0.19.0` -> `0.19.1`
- `xml-rs`
- `0.8.14` -> `0.8.16`
- `serde`
- `1.0.164` -> `1.0.173`
## DR game 0.3.1.2 / 0.3.1.3
- 加回了显示 delta 的那根线
- Add back the line that displays delta
## DR rs 0.2.15.2
### Add
- `SR1PartData_rs`
- `get_id -> IdType`
- `get_x -> f64`
- `get_y -> f64`
- `get_activate -> bool`
- `get_angle_v -> f64`
- `get_explode -> bool`
## DR rs 0.2.15.1
### Changes
- `types.rs` & `python.rs` 利用可用的 Clippy 改进了代码
- `types.rs` & `python.rs` improved the code with available Clippy
## DR game 0.3.1.1
### Fix
- `sr_tr` 加载语言文件的路径并没有跟随目录名称改变
- `sr_tr` does not follow the directory name change when loading the language file path
## DR game 0.3.1.0
- 使用 `Difficult_Rocket.api.camera.Camera`
- Use `Difficult_Rocket.api.camera.Camera` class
## DR game 0.3.0.0
有一些修改(忘记记录了
## DR rs 0.3.0.0
### 修改
- 适配了 `DR sdk` 的关于 mods 的修改
- Adapted the modification of mods about `DR sdk`
- `RustConsole`
- 现在输入体验更好了
- Now the input experience is better
## DR rs 0.2.15.0
### 修改
- 现在支持新的 mods reload 和 unload 了
- 适配了 `DR sdk` 的关于 mods 的修改
- Now support new mods reload and unload
- Adapted the modification of mods about `DR sdk`
### 添加
- `IdType = i64`
- 统一的 id 类型
- Unified id type
- `PySR1Ship`
- `get_connection -> Vec<(i32, i32, IdType, IdType)>`
- 获取飞船的连接信息
- Get the connection information of the ship
## DR rs 0.2.14.0
### 删除
- 删除了多个 xml 测试读取函数
- Remove multiple xml test read functions
### 改进
- 改进了 xml writer 的 `SR1Ship` 写入
- Improve the xml writer's `SR1Ship` write
## DR game 0.2.1.0
### 修改
- 将 `sr1_ship` 中的 `Camera_rs` 改为 `Difficult_Rocket.utils.camera.Camera`
- Change `Camera_rs` in `sr1_ship` to `Difficult_Rocket.utils.camera.Camera`
- 添加了部件的连接线(都是彩色哒)
- Add the connection line of the part (all are colored)
## DR rs 0.2.13.0
### 删除
- 删除了 `render.rs`
- 没必要拿 rust 写这玩意(
- 用 `DR game``camera` 代替
- Delete `render.rs`
- No need to write this thing with rust (
- Use `DR game`'s `camera` instead
### 添加
- 添加了 基于 `quick-xml::writer::Writer` 的 xml `SR1Ship` 写入
- 折磨啊啊啊啊啊啊啊啊
- Add xml `SR1Ship` write based on `quick-xml::writer::Writer`
- Torture ah ah ah ah ah ah ah ah
## DR rs 0.2.12.0
### 添加
- 添加了 xml 的读取测试
- Add xml read test
## DR game 0.1.2.0
### 修改
- 现在渲染飞船的时候不会再显示那个白色框了
- 改为一个彩色的框
- Now, the ship will not be displayed in the white box
- Change to a colored box
## DR rs 0.2.11.0
### 添加
- `Python::data::PySR1Ship`
- `get_part_box(&self, part_id: i64) -> Option<(f64, f64), (f64, f64)>`
- 用于获取对应 id 的实际碰撞箱
- DR game: 0.2.0.0
- DR rs: 0.2.10.1
## DR game 0.2.0.0

View File

@ -2,197 +2,10 @@
# DR SDK 更新日志
- 最新版本号
- DR sdk: 0.8.7.0
- DR api: 0.1.1.0
## DR sdk 0.8.7.0
### Add
- 添加了 `Difficult_Rocket.utils.camera.GroupCamera`
- 和 `Difficult_Rocket.utils.camera.CenterGroupCamera`
- 实际上就是使用 `pyglet.graphics.Group` 来实现的 `Camera`
- 具有相同的功能
- 顺便同样在 `api.camera` 里添加了导出
- 这次我一定不会再忘记导出了
- Added `Difficult_Rocket.utils.camera.GroupCamera`
- And `Difficult_Rocket.utils.camera.CenterGroupCamera`
- Actually, it is implemented `Camera` using `pyglet.graphics.Group`
- Has the same function
- By the way, the export was also added in `api.camera`
- This time I will never forget to export it again
- 为所有 `xxCamera` 添加了
- `reset` 方法
- 用于一键重置缩放+平移
- Added `reset` method for all `xxCamera`
- Used to reset zoom + translation with one click
## DR sdk 0.8.6.1
### Fix
- 现在 `DR.py` 启动之后如果崩溃会在控制台输出完整的 Crash report 了
- Now, after `DR.py` starts, if it crashes, the complete Crash report will be output to the console
## DR sdk 0.8.6.0
重构了一下项目结构
Refactored the project structure
### Fix
- issue [#42](https://github.com/shenjackyuanjie/Difficult-Rocket/issues/42)
- Crash report 的时区不正确
- Crash report time zone is incorrect
### Change
- 将大部分资源文件移动到 `assets/`
- `libs/fonts` -> `assets/fonts`
- `configs/lang` -> `assets/lang`
- `configs/xxx.xml` -> `assets/builtin/xxx.xml`
- `textures` -> `assets/textures`
- Move all the resources to `assets/`
## DR sdk 0.8.5.2
### Fix
- `crash` 引用了已经重命名的 `Difficult_Rocket.game_version` (`Difficult_Rocket.sdk_version`)
- `Difficult_Rocket.api.types`
- `VersionRequirement`
- `VersionParsingError`
### Change
- 修改了 `DR.py`
- 实际上是 `DR-start.py` 的改名
- Changed `DR.py`
- Actually renamed `DR-start.py`
### Clean
- 删除了 `DR-start.py`
- 实际上是改成了 `DR.py`
- Removed `DR-start.py`
- Actually changed to `DR.py`
- 删除了 `libs/utils/dsm.py`
- Deleted `libs/utils/dsm.py`
## DR sdk 0.8.5.1
### API
- 将 `Camera``CenterCamera` 添加到 `Difficult_Rocket.api.camera`
- Add `Camera` and `CenterCamera` to `Difficult_Rocket.api.camera`
## DR sdk 0.8.5.0
### Changes
- `Difficult_Rocket.__init__`
- 重命名 / Rename
- `game_version` -> `sdk_version`
- `ModManager`
- `get_mod_module(mod_name: str) -> Optional[ModInfo]`
- 通过 mod 名称获取 mod 的信息
- Get mod information by mod name
- `load_mod(mod_path: Path) -> Optional[ModInfo]`
- 加载指定路径的 mod
- Load the mod at the specified path
- `find_mods_in_path(extra_mods_path: Optional[List[Path]]) -> List[Path]`
- 在指定的路径中查找 mod 并返回 mod 的路径
- Find mods in the specified path and return the path of the mod
- `unload_mod(mod_id: str, game: Game) -> Optional[ModInfo]`
- 卸载指定的 mod 并返回 mod 的信息
- Unload the specified mod and return the mod information
- `reload_mod(mod_id: str, game: Game)`
- 重新加载指定的 mod
- Reload the specified mod
### Remove
- `DR.client.guis.widget.InputBox`
- 没用了 `TextEntry` 已经好了 可以直接用了
- Use Less, the `TextEntry` is ok to use
### Command
- `mods`
- `list`
- 列出所有已加载的 mod
- List all loaded mods
- `reload <mod_id>`
- 重新加载指定的 mod
- Reload the specified mod
## DR sdk 0.8.4.1
### Add
- `utils.camera`
- `Camera`
- 一个 2D 摄影机,可以用于高效变换渲染坐标
- `CenterCamera`
- 一个中心对器的 2D 摄影机,可以用于高效变换渲染坐标
## DR sdk 0.8.4.0
### Fix
- issue #33 (<https://github.com/shenjackyuanjie/Difficult-Rocket/issues/33>)
- 修复了实际上并不会加载 `.otf` 格式的字体文件的问题
### language
- 去除了 `dr_game` 相关的键值
- 现在这些键值已经在 `dr_game` 自己的语言文件中了
- Removed the key values related to `dr_game`
- Now these key values are in the language file of `dr_game` itself
- 将 mod 加载部分的键值独立出来
- 这样调用起来更方便一些
- Separate the key values of the mod loading part
- This makes it easier to call
### 修改
- `utils.translate`
- `Tr`
- 现在支持自定义语言文件的路径了
- `lang_path`
- 用于给 mod 加载自己的语言文件
- 现在 `logging` 的配置转移到 `__init__` 中了
- 保证调用 `logging` 的时候可以正常使用
- Now the configuration of `logging` is moved to `__init__`
- Ensure that `logging` can be used normally when called
- 现在输入命令之后不会输出一个 `True`/`False` 了
- (实际上是用来检测命令是不是用 `/` 开头的)
- Now, the command will not output a `True`/`False`
- (Actually used to detect whether the command starts with `/`)
- `CommandText`
- `find`
- 现在会先用 `str.startswith` 检测是否以要求的字符串开头
- 实际上就是丐版 `re.match`
- 并且会在匹配上之后 如果匹配内容后面第一个字符是空格 则会截取掉空格
- Now, it will first use `str.startswith` to detect whether it starts with the required string
- Actually a poor version of `re.match`
- And after matching, if the first character after the matching content is a space, the space will be intercepted
- DR sdk: 0.8.2.0
## DR sdk 0.8.3.0
### 删除
- `pyglet_rs`
- 事实证明这玩意没啥用
- Removed pyglet_rs
### Fix
- issue #31 (https://github.com/shenjackyuanjie/Difficult-Rocket/issues/31)
- 窗口标题不正确 (实际上是因为只使用 `DR_runtime` 进行格式化)
- Window title is incorrect
### 添加
- `DR_status`
@ -207,6 +20,13 @@ Refactored the project structure
- Mod loader
- 添加了对支持版本号的 warnings
- Added warnings for supporting version numbers
- API
- `Difficult_Rocket.api.log`
- `get_named_client_logger`
- `get_named_server_logger`
- `get_named_main_logger`
- 分别用于获取 基于 对应名称配置的 logger
- Get the logger for the corresponding name configuration
### 移动

View File

@ -18,5 +18,3 @@
- `python -m pip install -r requre`
3. 使用 `build_rs.ps1` 尝试编译一次 `DR_rs`
- `./build_rs.ps1` 38/39/310/311
<!-- cmdrun pwsh test.ps1 -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

View File

@ -23,7 +23,6 @@
| 部件平移 | part-move | planing |
| 存档额外信息 | extra-save-info | planing |
| 自定义部件分组开关 | custom-stage-on-off | planing |
| 各种奇怪部件 | custom-part | planing |
# 文件范例

View File

@ -1,33 +0,0 @@
# 各种自定义部件 custom-part.md `custom part`
## 2023 07 15
## shenjack 和 qq 群的各位
- 北风百草
- 小阳阳
## 状态
- 计划中
## 描述
- 各种奇怪自定义部件的收集
## 列表
### `核推`
- 消耗工质
- 产生大量热量
- 需要散热
### `装饰性外壳`
- 没有碰撞箱
- 或者最外层有碰撞箱
### `部件 Pro`
- 超级大

View File

@ -19,5 +19,5 @@ pyglet 坐标轴原点是左上角 0, 0
我check一下微调器的代码原理应该一样有个函数我看看怎么实现
# 素材提供
[背景候选1号](../../assets/textures/runtime/background.png)
[背景候选1号](../../textures/runtime/background.png)
来自 @底层萌新 QQ1744251171

View File

@ -1,8 +0,0 @@
# ls 之后将每一行输出包裹在 ` 里面
echo "<pre>"
Get-ChildItem . | ForEach-Object {
echo $_
}
echo "</pre>"

25
docs/theme/index.hbs vendored
View File

@ -110,34 +110,12 @@
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
{{> header}}
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
@ -205,6 +183,7 @@
<div id="content" class="content">
<main>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
{{{ content }}}

View File

@ -7,11 +7,11 @@ Thanks a lot to Fallen_Breath and MCDR contributors
GNU Lesser General Public License v3.0 (GNU LGPL v3)
"""
import re
from typing import List, Callable, Tuple, Optional, Union
"""
Plugin Version
"""
import re
from typing import List, Callable, Tuple, Optional, Union
# beta.3 -> (beta, 3), random -> (random, None)
@ -217,4 +217,4 @@ class VersionRequirement:
class VersionParsingError(ValueError):
pass
pass

View File

@ -9,7 +9,7 @@ import sys
from typing import TYPE_CHECKING
#: The release version
version = '2.0.9'
version = '2.0.7'
__version__ = version
MIN_PYTHON_VERSION = 3, 8
@ -336,7 +336,6 @@ if TYPE_CHECKING:
from . import app
from . import canvas
from . import clock
from . import customtypes
from . import event
from . import font
from . import gl
@ -357,7 +356,6 @@ else:
app = _ModuleProxy('app')
canvas = _ModuleProxy('canvas')
clock = _ModuleProxy('clock')
customtypes = _ModuleProxy('customtypes')
event = _ModuleProxy('event')
font = _ModuleProxy('font')
gl = _ModuleProxy('gl')

View File

@ -122,24 +122,12 @@ class EventLoop(event.EventDispatcher):
def run(self, interval=1/60):
"""Begin processing events, scheduled functions and window updates.
:Parameters:
`interval` : float or None [default: 1/60]
Windows redraw interval, in seconds (framerate).
If `interval == 0`, windows will redraw at maximum rate.
If `interval is None`, Pyglet will not call its redraw function.
The user must schedule (or call on demand) a custom redraw
function for each window, allowing a custom framerate per window.
(see example in documentation)
This method returns when :py:attr:`has_exit` is set to True.
Developers are discouraged from overriding this method, as the
implementation is platform-specific.
"""
if interval is None:
# User application will manage a custom _redraw_windows() method
pass
elif interval == 0:
if not interval:
self.clock.schedule(self._redraw_windows)
else:
self.clock.schedule_interval(self._redraw_windows, interval)

View File

@ -78,12 +78,6 @@ class CocoaScreen(Screen):
self.height = mode.height
def restore_mode(self):
match_attrs = ['width', 'height', 'depth', 'rate']
current_mode = self.get_mode()
if all(getattr(current_mode, attr) == getattr(self._default_mode, attr) for
attr in match_attrs):
# Already in default mode
return
quartz.CGDisplaySetDisplayMode(self._cg_display_id, self._default_mode.cgmode, None)
quartz.CGDisplayRelease(self._cg_display_id)

Some files were not shown because too many files have changed in this diff Show More