ruff format!

This commit is contained in:
shenjack 2023-12-03 16:54:07 +08:00
parent 5361c10868
commit 960811f684
Signed by: shenjack
GPG Key ID: 7B1134A979775551
51 changed files with 1725 additions and 1121 deletions

View File

@ -14,30 +14,32 @@ class DSM:
def __init__(self, docs_path: str, dsm_path: str):
self.docs_path = docs_path
self.dsm_path = dsm_path
self.token = os.environ['DSM_TOKEN']
self.fl = filestation.FileStation(ip_address='hws.shenjack.top',
port=5000,
username='github',
password=self.token,
secure=False,
cert_verify=False,
dsm_version=7,
debug=True,
interactive_output=False)
self.token = os.environ["DSM_TOKEN"]
self.fl = filestation.FileStation(
ip_address="hws.shenjack.top",
port=5000,
username="github",
password=self.token,
secure=False,
cert_verify=False,
dsm_version=7,
debug=True,
interactive_output=False,
)
def list_files(self):
# 输出 文档构建目录 的内容
print(f'==========输出 {self.docs_path} 的内容==========')
print(f"==========输出 {self.docs_path} 的内容==========")
for root, dirs, files in os.walk(self.docs_path):
print(root, dirs)
print('==========就这些==========')
print("==========就这些==========")
def clear_dsm(self):
# 清空 DSM 的 /web/dr 目录
delete_task = self.fl.start_delete_task(self.dsm_path, recursive=True)
delete_task_id = delete_task['taskid']
delete_task_id = delete_task["taskid"]
time.sleep(1) # 等待 1 秒 保证任务已经完成
pprint(self.fl.get_delete_status(delete_task_id)['data']['finished'])
pprint(self.fl.get_delete_status(delete_task_id)["data"]["finished"])
def check_md5(self, local_md5: str) -> bool:
"""
@ -47,24 +49,26 @@ class DSM:
"""
# 打开提供的md5文件
try:
with open(local_md5, 'r', encoding='utf-8') as f:
with open(local_md5, "r", encoding="utf-8") as f:
md5 = f.read()
except FileNotFoundError:
print(f'文件 {local_md5} 不存在')
print(f"文件 {local_md5} 不存在")
return False
# 检测是否存在 dsm 上的 md5.txt
dsm_files = self.fl.get_file_list(folder_path='/web/dr')
if dsm_files.get('error'):
print(dsm_files['error'])
dsm_files = self.fl.get_file_list(folder_path="/web/dr")
if dsm_files.get("error"):
print(dsm_files["error"])
return False
dsm_files = dsm_files['data']['files']
if '/web/dr/md5.txt' not in [file['path'] for file in dsm_files]:
print('dsm md5.txt 不存在')
dsm_files = dsm_files["data"]["files"]
if "/web/dr/md5.txt" not in [file["path"] for file in dsm_files]:
print("dsm md5.txt 不存在")
return False
# 下载 dsm 上的 md5.txt
try:
self.fl.get_file(path='/web/dr/md5.txt', mode='download', dest_path='./docs/book')
with open('./docs/book/md5.txt', 'r', encoding='utf-8') as f:
self.fl.get_file(
path="/web/dr/md5.txt", mode="download", dest_path="./docs/book"
)
with open("./docs/book/md5.txt", "r", encoding="utf-8") as f:
md5_last = f.read()
if md5 == md5_last:
return True
@ -74,43 +78,47 @@ class DSM:
def upload_docs(self, local_md5: str):
# 上传本地构建的文档到 DSM
print(f'==========上传 {self.docs_path}{self.dsm_path}==========')
print(f"==========上传 {self.docs_path}{self.dsm_path}==========")
# 使用 os.walk 递归遍历文件夹 依次按照对应路径上传
# 上传的时候 目标路径为本地路径的相对路径
for root, dirs, files in os.walk(self.docs_path):
for file in files:
file_path = os.path.join(root, file)
dest_path = f'{self.dsm_path}{root[len(self.docs_path):]}'
dest_path = dest_path.replace('\\', '/')
dest_path = f"{self.dsm_path}{root[len(self.docs_path):]}"
dest_path = dest_path.replace("\\", "/")
# 输出 文件路径 和 目标路径
print(f'{file_path} -> {dest_path}', end=' ')
pprint(self.fl.upload_file(dest_path=dest_path,
file_path=file_path,
overwrite=True))
print(f"{file_path} -> {dest_path}", end=" ")
pprint(
self.fl.upload_file(
dest_path=dest_path, file_path=file_path, overwrite=True
)
)
# self.fl.upload_file(dest_path=dest_path,
# file_path=file_path,
# overwrite=True)
# 上传本地的 md5 文件
print(f'{local_md5} -> {self.dsm_path}', end=' ')
pprint(self.fl.upload_file(dest_path=self.dsm_path,
file_path=local_md5,
overwrite=True))
print('==========上传完成==========')
print(f"{local_md5} -> {self.dsm_path}", end=" ")
pprint(
self.fl.upload_file(
dest_path=self.dsm_path, file_path=local_md5, overwrite=True
)
)
print("==========上传完成==========")
def main():
docs_path = 'docs/book'
dsm_path = '/web/dr'
docs_path = "docs/book"
dsm_path = "/web/dr"
dsm = DSM(docs_path, dsm_path)
dsm.list_files()
if dsm.check_md5('docs/md5.txt'):
print('md5 一致,不需要上传')
if dsm.check_md5("docs/md5.txt"):
print("md5 一致,不需要上传")
return 0
dsm.clear_dsm()
dsm.upload_docs('docs/md5.txt')
dsm.upload_docs("docs/md5.txt")
dsm.fl.logout()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@ -15,22 +15,23 @@ try:
except ImportError:
raise
args = ['-env', '-github-dev']
args = ["-env", "-github-dev"]
if sys.argv == [__file__]: # 没有输入参数,直接输出默认信息并输出
print(sys.version)
from Difficult_Rocket.utils import tools
# 重置窗口信息
config_file = tools.load_file('./config/main.toml')
config_file['window']['width'] = 1024
config_file['window']['height'] = 768
rtoml.dump(config_file, open('./config/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:
# 重置窗口信息
config_file = tools.load_file("./config/main.toml")
config_file["window"]["width"] = 1024
config_file["window"]["height"] = 768
rtoml.dump(config_file, open("./config/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)
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}')
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}")

View File

@ -18,19 +18,19 @@ __version__ = sdk_version
__all__ = [
# __init__
'DR_status',
"DR_status",
# folder
'api',
'client',
'server',
'command',
'crash',
'exception',
'mod',
'utils',
"api",
"client",
"server",
"command",
"crash",
"exception",
"mod",
"utils",
# file
'main',
'runtime',
"main",
"runtime",
]
@ -38,23 +38,24 @@ class _DR_status(Options):
"""
DR 的特性开关 / 基本状态
"""
name = 'DR Option'
name = "DR Option"
# run status
client_running: bool = False
server_running: bool = False
# feature switch
InputBox_use_TextEntry: bool = True
record_threads: bool = True
InputBox_use_TextEntry: bool = True
record_threads: bool = True
report_translate_not_found: bool = True
use_multiprocess: bool = False
use_cProfile: bool = False
use_local_logging: bool = False
use_multiprocess: bool = False
use_cProfile: bool = False
use_local_logging: bool = False
# tests
playing: bool = False
debugging: bool = False
crash_report_test: bool = False
playing: bool = False
debugging: bool = False
crash_report_test: bool = False
# game version status
DR_version: Version = sdk_version # DR SDK 版本
@ -62,7 +63,7 @@ class _DR_status(Options):
API_version: Version = Api_version # DR SDK API 版本
# game options
default_language: str = 'zh-CN'
default_language: str = "zh-CN"
# window option
gui_scale: float = 1.0 # default 1.0 2.0 -> 2x 3 -> 3x
@ -76,14 +77,15 @@ DR_status = _DR_status()
def load_logging():
with open('./config/logger.toml') as f:
with open("./config/logger.toml") as f:
import rtoml
logger_config = rtoml.load(f)
log_path = logger_config['handlers']['file']['filename']
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
if not Path("logs/").is_dir():
Path("logs/").mkdir()
logger_config["handlers"]["file"]["filename"] = log_path
logging.config.dictConfig(logger_config)
@ -96,8 +98,7 @@ if DR_status.playing:
def think_it(something):
return something
@new_thread('think')
@new_thread("think")
def think(some_thing_to_think):
gotcha = think_it(some_thing_to_think)
return gotcha
return gotcha

View File

@ -13,12 +13,12 @@ gitee: @shenjackyuanjie
__all__ = [
'exception',
"exception",
# 错误类定义
'screen',
"screen",
# screen api
'types',
"types",
# 类型定义
'mod',
"mod",
# mod api
]

View File

@ -4,14 +4,11 @@
# All rights reserved
# -------------------------------
from Difficult_Rocket.utils.camera import (Camera,
CenterCamera,
GroupCamera,
CenterGroupCamera)
from Difficult_Rocket.utils.camera import (
Camera,
CenterCamera,
GroupCamera,
CenterGroupCamera,
)
__all__ = [
'Camera',
'CenterCamera',
'GroupCamera',
'CenterGroupCamera'
]
__all__ = ["Camera", "CenterCamera", "GroupCamera", "CenterGroupCamera"]

View File

@ -16,4 +16,4 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Difficult_Rocket.api.exception import command, logger, main, threading, unsupport
__all__ = ['command', 'logger', 'main', 'threading', 'unsupport']
__all__ = ["command", "logger", "main", "threading", "unsupport"]

View File

@ -18,7 +18,7 @@ from Difficult_Rocket.exception.command import (
CommandQMarkConflict,
CommandQMarkMissing,
CommandQMarkPreMissing,
CommandQMarkSufMissing
CommandQMarkSufMissing,
)
__all__ = [
@ -28,5 +28,5 @@ __all__ = [
"CommandQMarkPosError",
"CommandQMarkConflict",
"CommandQMarkSufMissing",
"CommandQMarkPreMissing"
"CommandQMarkPreMissing",
]

View File

@ -4,10 +4,6 @@
# All rights reserved
# -------------------------------
from Difficult_Rocket.exception.logger import (
LogFileLockTimeOutError
)
from Difficult_Rocket.exception.logger import LogFileLockTimeOutError
__all__ = [
"LogFileLockTimeOutError"
]
__all__ = ["LogFileLockTimeOutError"]

View File

@ -4,10 +4,6 @@
# All rights reserved
# -------------------------------
from Difficult_Rocket.exception.threading import (
LockTimeOutError
)
from Difficult_Rocket.exception.threading import LockTimeOutError
__all__ = [
"LockTimeOutError"
]
__all__ = ["LockTimeOutError"]

View File

@ -17,14 +17,14 @@ from Difficult_Rocket.exception.unsupport import (
ThinkError,
BrainError,
BigBrainError,
GrammarError
GrammarError,
)
__all__ = [
'NoMoreJson5',
'Nope418ImATeapot',
'ThinkError',
'BrainError',
'BigBrainError',
'GrammarError'
"NoMoreJson5",
"Nope418ImATeapot",
"ThinkError",
"BrainError",
"BigBrainError",
"GrammarError",
]

View File

@ -4,6 +4,4 @@
# All rights reserved
# -------------------------------
__all__ = [
'widget'
]
__all__ = ["widget"]

View File

@ -6,6 +6,4 @@
from Difficult_Rocket.gui.widget.button import PressTextButton
__all__ = [
'PressTextButton'
]
__all__ = ["PressTextButton"]

View File

@ -6,6 +6,4 @@
from Difficult_Rocket.mod.api import ModInfo
__all__ = [
"ModInfo"
]
__all__ = ["ModInfo"]

View File

@ -25,7 +25,7 @@ class BaseScreen(EventDispatcher, Options):
DR 页面API
"""
name: str = 'BaseScreen'
name: str = "BaseScreen"
def __init__(self, main_window: ClientWindow):
super().__init__()
@ -33,6 +33,7 @@ class BaseScreen(EventDispatcher, Options):
self.window_pointer = main_window
if TYPE_CHECKING:
def on_command(self, command: CommandText, window: ClientWindow):
"""
命令输入事件
@ -56,6 +57,7 @@ class BaseScreen(EventDispatcher, Options):
"""
Pyglet 定义的事件
"""
def on_activate(self, window: ClientWindow):
"""The window was activated.
@ -151,7 +153,9 @@ class BaseScreen(EventDispatcher, Options):
:event:
"""
def on_file_drop(self, x: int, y: int, paths: List[PathLike] , window: ClientWindow):
def on_file_drop(
self, x: int, y: int, paths: List[PathLike], window: ClientWindow
):
"""File(s) were dropped into the window, will return the position of the cursor and
a list of paths to the files that were dropped.
@ -212,7 +216,16 @@ class BaseScreen(EventDispatcher, Options):
:event:
"""
def on_mouse_drag(self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int, window: ClientWindow):
def on_mouse_drag(
self,
x: int,
y: int,
dx: int,
dy: int,
buttons: int,
modifiers: int,
window: ClientWindow,
):
"""The mouse was moved with one or more mouse buttons pressed.
This event will continue to be fired even if the mouse leaves
@ -236,7 +249,9 @@ class BaseScreen(EventDispatcher, Options):
:event:
"""
def on_mouse_press(self, x: int, y: int, button: int, modifiers: int, window: ClientWindow):
def on_mouse_press(
self, x: int, y: int, button: int, modifiers: int, window: ClientWindow
):
"""A mouse button was pressed (and held down).
:Parameters:
@ -253,7 +268,9 @@ class BaseScreen(EventDispatcher, Options):
:event:
"""
def on_mouse_release(self, x: int, y: int, button: int, modifiers: int, window: ClientWindow):
def on_mouse_release(
self, x: int, y: int, button: int, modifiers: int, window: ClientWindow
):
"""A mouse button was released.
:Parameters:
@ -270,7 +287,9 @@ class BaseScreen(EventDispatcher, Options):
:event:
"""
def on_mouse_scroll(self, x: int, y: int, scroll_x: float, scroll_y: float, window: ClientWindow):
def on_mouse_scroll(
self, x: int, y: int, scroll_x: float, scroll_y: float, window: ClientWindow
):
"""The mouse wheel was scrolled.
Note that most mice have only a vertical scroll wheel, so
@ -471,28 +490,28 @@ class BaseScreen(EventDispatcher, Options):
"""
BaseScreen.register_event_type('on_key_press')
BaseScreen.register_event_type('on_key_release')
BaseScreen.register_event_type('on_text')
BaseScreen.register_event_type('on_text_motion')
BaseScreen.register_event_type('on_text_motion_select')
BaseScreen.register_event_type('on_mouse_motion')
BaseScreen.register_event_type('on_mouse_drag')
BaseScreen.register_event_type('on_mouse_press')
BaseScreen.register_event_type('on_mouse_release')
BaseScreen.register_event_type('on_mouse_scroll')
BaseScreen.register_event_type('on_mouse_enter')
BaseScreen.register_event_type('on_mouse_leave')
BaseScreen.register_event_type('on_close')
BaseScreen.register_event_type('on_expose')
BaseScreen.register_event_type('on_resize')
BaseScreen.register_event_type('on_move')
BaseScreen.register_event_type('on_activate')
BaseScreen.register_event_type('on_deactivate')
BaseScreen.register_event_type('on_show')
BaseScreen.register_event_type('on_hide')
BaseScreen.register_event_type('on_context_lost')
BaseScreen.register_event_type('on_context_state_lost')
BaseScreen.register_event_type('on_file_drop')
BaseScreen.register_event_type('on_draw')
BaseScreen.register_event_type('on_refresh')
BaseScreen.register_event_type("on_key_press")
BaseScreen.register_event_type("on_key_release")
BaseScreen.register_event_type("on_text")
BaseScreen.register_event_type("on_text_motion")
BaseScreen.register_event_type("on_text_motion_select")
BaseScreen.register_event_type("on_mouse_motion")
BaseScreen.register_event_type("on_mouse_drag")
BaseScreen.register_event_type("on_mouse_press")
BaseScreen.register_event_type("on_mouse_release")
BaseScreen.register_event_type("on_mouse_scroll")
BaseScreen.register_event_type("on_mouse_enter")
BaseScreen.register_event_type("on_mouse_leave")
BaseScreen.register_event_type("on_close")
BaseScreen.register_event_type("on_expose")
BaseScreen.register_event_type("on_resize")
BaseScreen.register_event_type("on_move")
BaseScreen.register_event_type("on_activate")
BaseScreen.register_event_type("on_deactivate")
BaseScreen.register_event_type("on_show")
BaseScreen.register_event_type("on_hide")
BaseScreen.register_event_type("on_context_lost")
BaseScreen.register_event_type("on_context_state_lost")
BaseScreen.register_event_type("on_file_drop")
BaseScreen.register_event_type("on_draw")
BaseScreen.register_event_type("on_refresh")

View File

@ -7,47 +7,48 @@
from typing import Dict, Union
from dataclasses import dataclass
from lib_not_dr.types.options import (Options,
OptionsError,
OptionNameNotDefined,
OptionNotFound,
get_type_hints_)
from libs.MCDR.version import (Version,
VersionRequirement,
VersionParsingError)
from lib_not_dr.types.options import (
Options,
OptionsError,
OptionNameNotDefined,
OptionNotFound,
get_type_hints_,
)
from libs.MCDR.version import Version, VersionRequirement, VersionParsingError
class Fonts(Options):
# font's value
HOS: str = 'HarmonyOS Sans'
HOS_S: str = 'HarmonyOS Sans SC'
HOS_T: str = 'HarmonyOS Sans TC'
HOS_C: str = 'HarmonyOS Sans Condensed'
HOS: str = "HarmonyOS Sans"
HOS_S: str = "HarmonyOS Sans SC"
HOS_T: str = "HarmonyOS Sans TC"
HOS_C: str = "HarmonyOS Sans Condensed"
鸿蒙字体: str = HOS
鸿蒙简体: str = HOS_S
鸿蒙繁体: str = HOS_T
鸿蒙窄体: str = HOS_C
CC: str = 'Cascadia Code'
CM: str = 'Cascadia Mono'
CCPL: str = 'Cascadia Code PL'
CMPL: str = 'Cascadia Mono PL'
CC: str = "Cascadia Code"
CM: str = "Cascadia Mono"
CCPL: str = "Cascadia Code PL"
CMPL: str = "Cascadia Mono PL"
微软等宽: str = CC
微软等宽无线: str = CM
微软等宽带电线: str = CCPL
微软等宽带电线无线: str = CMPL
得意黑: str = '得意黑'
得意黑: str = "得意黑"
# SS = smiley-sans
SS: str = 得意黑
@dataclass
class FontData:
""" 用于保存字体的信息 """
"""用于保存字体的信息"""
font_name: str = Fonts.鸿蒙简体
font_size: int = 13
bold: bool = False
@ -55,29 +56,28 @@ class FontData:
stretch: bool = False
def dict(self) -> Dict[str, Union[str, int, bool]]:
return dict(font_name=self.font_name,
font_size=self.font_size,
bold=self.bold,
italic=self.italic,
stretch=self.stretch)
return dict(
font_name=self.font_name,
font_size=self.font_size,
bold=self.bold,
italic=self.italic,
stretch=self.stretch,
)
__all__ = [
# main class
'Options',
'Version',
'VersionRequirement',
"Options",
"Version",
"VersionRequirement",
# data class
'FontData',
'Fonts',
"FontData",
"Fonts",
# exception
'OptionsError',
'OptionNameNotDefined',
'OptionNotFound',
'VersionParsingError',
"OptionsError",
"OptionNameNotDefined",
"OptionNotFound",
"VersionParsingError",
# other
'get_type_hints_',
"get_type_hints_",
]

View File

@ -1,4 +1,3 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
@ -20,6 +19,7 @@ from typing import Callable, Dict, List, TYPE_CHECKING, Type
# third function
import rtoml
import pyglet
# from pyglet import gl
# from pyglet.gl import glClearColor
# from pyglet.libs.win32 import _user32
@ -44,7 +44,7 @@ from Difficult_Rocket.client.fps.fps_log import FpsLogger
from Difficult_Rocket.exception.language import LanguageNotFound
logger = logging.getLogger('client')
logger = logging.getLogger("client")
class ClientOption(Options):
@ -59,14 +59,14 @@ class ClientOption(Options):
caption: str = "Difficult Rocket v{DR_version}"
def load_file(self) -> None:
file: dict = tools.load_file('./config/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'])
file: dict = tools.load_file("./config/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)
@ -74,27 +74,36 @@ class Client:
"""
客户端
"""
def __init__(self, game: "Game", net_mode='local'):
def __init__(self, game: "Game", net_mode="local"):
start_time = time.time_ns()
# logging
self.logger = logging.getLogger('client')
self.logger = logging.getLogger("client")
self.logger.info(tr().client.setup.start())
# config
self.config = ClientOption()
# value
self.process_id = 'Client'
self.process_name = 'Client process'
self.process_id = "Client"
self.process_name = "Client process"
self.process_pid = os.getpid()
self.net_mode = net_mode
self.game = game
self.window = ClientWindow(game=game, net_mode=self.net_mode,
width=self.config.width, height=self.config.height,
fullscreen=self.config.fullscreen, caption=self.config.caption,
resizable=self.config.resizeable, visible=self.config.visible,
file_drops=True)
self.window = ClientWindow(
game=game,
net_mode=self.net_mode,
width=self.config.width,
height=self.config.height,
fullscreen=self.config.fullscreen,
caption=self.config.caption,
resizable=self.config.resizeable,
visible=self.config.visible,
file_drops=True,
)
end_time = time.time_ns()
self.use_time = end_time - start_time
self.logger.info(tr().client.setup.use_time().format(Decimal(self.use_time) / 1000000000))
self.logger.info(
tr().client.setup.use_time().format(Decimal(self.use_time) / 1000000000)
)
self.logger.debug(tr().client.setup.use_time_ns().format(self.use_time))
def start(self):
@ -106,7 +115,7 @@ class Client:
# TODO 写一下服务端启动相关,还是需要服务端啊
def __repr__(self):
return f'<Client {self.process_name} {self.process_pid}>'
return f"<Client {self.process_name} {self.process_pid}>"
def pyglet_load_fonts_folder(folder) -> None:
@ -125,12 +134,18 @@ def pyglet_load_fonts_folder(folder) -> None:
dir_path = Path(dir_path)
for file_name in file_names:
file_name = Path(file_name)
if file_name.suffix in ('.ttf', '.otf'):
logger.debug(tr().client.load.font.file().format(str(dir_path / file_name)))
if file_name.suffix in (".ttf", ".otf"):
logger.debug(
tr().client.load.font.file().format(str(dir_path / file_name))
)
try:
pyglet.font.add_file(str(dir_path / file_name))
except Exception:
logger.error(tr().client.load.font.error().format(str(dir_path / file_name), traceback.format_exc()))
logger.error(
tr()
.client.load.font.error()
.format(str(dir_path / file_name), traceback.format_exc())
)
end_time = time.time_ns()
use_time = end_time - start_time
logger.info(tr().client.load.font.use_time().format(use_time / 1000000000))
@ -147,13 +162,16 @@ def _call_back(call_back: Callable) -> Callable:
: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
@ -166,6 +184,7 @@ def _call_screen_after(func: Callable) -> Callable:
:param func: 需要包装的函数
:return: 包装后的函数
"""
@functools.wraps(func)
def warped(self: "ClientWindow", *args, **kwargs):
result = func(self, *args, **kwargs)
@ -192,6 +211,7 @@ def _call_screen_before(func: Callable) -> Callable:
:param func: 需要包装的函数
:return: 包装后的函数
"""
@functools.wraps(func)
def warped(self: "ClientWindow", *args, **kwargs):
for title, a_screen in self.screen_list.items():
@ -210,8 +230,7 @@ def _call_screen_before(func: Callable) -> Callable:
class ClientWindow(Window):
def __init__(self, game: "Game", net_mode='local', *args, **kwargs):
def __init__(self, game: "Game", net_mode="local", *args, **kwargs):
"""
@param net_mode:
@ -221,7 +240,7 @@ class ClientWindow(Window):
start_time = time.time_ns()
super().__init__(*args, **kwargs)
# logging
self.logger = logging.getLogger('client')
self.logger = logging.getLogger("client")
self.logger.info(tr().window.setup.start())
# value
self.game = game
@ -229,11 +248,11 @@ class ClientWindow(Window):
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')
self.main_config = tools.load_file("./config/main.toml")
self.game_config = tools.load_file("./config/game.config")
# FPS
self.FPS = Decimal(int(self.main_config['runtime']['fps']))
self.SPF = Decimal('1') / self.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()
@ -245,10 +264,16 @@ 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.input_box = TextEntry(
x=50,
y=30,
width=300,
batch=self.main_batch,
text="",
group=Group(1000, parent=self.main_group),
) # 实例化
self.input_box.push_handlers(self)
self.input_box.set_handler('on_commit', self.on_input)
self.input_box.set_handler("on_commit", self.on_input)
self.push_handlers(self.input_box)
self.input_box.enabled = True
# 设置刷新率
@ -263,19 +288,19 @@ 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("assets/textures/icon.png"))
self.load_fonts()
self.screen_list['DR_debug'] = DRDEBUGScreen(self)
self.game.dispatch_mod_event('on_client_start', game=self.game, client=self)
self.screen_list["DR_debug"] = DRDEBUGScreen(self)
self.game.dispatch_mod_event("on_client_start", game=self.game, client=self)
def load_fonts(self) -> None:
fonts_folder_path = self.main_config['runtime']['fonts_folder']
fonts_folder_path = self.main_config["runtime"]["fonts_folder"]
# 加载字体路径
# 淦,还写了个递归来处理
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("assets/textures/icon.png"))
try:
# pyglet.clock.schedule_interval(self.on_draw, float(self.SPF))
# pyglet.app.run()
@ -284,18 +309,20 @@ class ClientWindow(Window):
except KeyboardInterrupt:
self.logger.warning("==========client stop. KeyboardInterrupt info==========")
traceback.print_exc()
self.logger.warning("==========client stop. KeyboardInterrupt info end==========")
self.dispatch_event("on_close", 'input')
self.logger.warning(
"==========client stop. KeyboardInterrupt info end=========="
)
self.dispatch_event("on_close", "input")
sys.exit(0)
@new_thread('window save_info')
@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['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'))
config_file: dict = tools.load_file("./config/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"))
self.logger.info(tr().client.config.save.done())
"""
@ -323,7 +350,7 @@ class ClientWindow(Window):
def on_draw(self):
while (command := self.game.console.get_command()) is not None:
self.on_command(line.CommandText(command))
pyglet.gl.glClearColor(21/255, 22/255, 23/255, 0.0)
pyglet.gl.glClearColor(21 / 255, 22 / 255, 23 / 255, 0.0)
self.clear()
# self.draw_update(dt) # TODO: wait for pyglet 2.1
self.draw_update(float(self.SPF))
@ -346,7 +373,7 @@ class ClientWindow(Window):
@_call_screen_after
def on_hide(self):
# self.set_location(*self.get_location())
print('on hide!')
print("on hide!")
@_call_screen_before
def draw_batch(self):
@ -359,7 +386,7 @@ class ClientWindow(Window):
def on_input(self, message: str) -> None:
command_text = line.CommandText(message)
self.on_command(command_text)
self.input_box.value = ''
self.input_box.value = ""
def new_command(self):
self.game.console.new_command()
@ -367,38 +394,44 @@ class ClientWindow(Window):
@_call_back(new_command)
@_call_screen_after
def on_command(self, command: line.CommandText):
command.text = command.text.rstrip('\n').rstrip(' ').strip('/')
command.text = command.text.rstrip("\n").rstrip(" ").strip("/")
self.logger.info(tr().window.command.text().format(f"|{command.text}|"))
if command.find('stop'):
if command.find("stop"):
self.logger.info("command stop!")
# HUGE THANKS to Discord @nokiyasos for this fix!
pyglet.app.exit()
elif command.find('fps'):
if command.find('log'):
elif command.find("fps"):
if command.find("log"):
self.logger.debug(self.fps_log.fps_list)
elif command.find('max'):
elif command.find("max"):
self.logger.info(self.fps_log.max_fps)
# self.command.push_line(self.fps_log.max_fps, block_line=True)
elif command.find('min'):
elif command.find("min"):
self.logger.info(self.fps_log.min_fps)
# self.command.push_line(self.fps_log.min_fps, block_line=True)
elif command.find('default'):
self.set_size(int(self.main_config['window_default']['width']),
int(self.main_config['window_default']['height']))
elif command.find('lang'):
elif command.find("default"):
self.set_size(
int(self.main_config["window_default"]["width"]),
int(self.main_config["window_default"]["height"]),
)
elif command.find("lang"):
try:
lang = command.text[5:]
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("./config/lang"))
)
self.save_info()
elif command.find('mods'):
if command.find('list'):
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'):
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)
@ -448,34 +481,40 @@ class ClientWindow(Window):
@_call_screen_after
def on_mouse_press(self, x, y, button, modifiers) -> None:
self.logger.debug(
tr().window.mouse.press().format(
[x, y], tr().window.mouse[mouse.buttons_string(button)]()
)
tr()
.window.mouse.press()
.format([x, y], tr().window.mouse[mouse.buttons_string(button)]())
)
@_call_screen_after
def on_mouse_release(self, x, y, button, modifiers) -> None:
self.logger.debug(
tr().window.mouse.release().format(
[x, y], tr().window.mouse[mouse.buttons_string(button)]()
)
tr()
.window.mouse.release()
.format([x, y], tr().window.mouse[mouse.buttons_string(button)]())
)
@_call_screen_after
def on_key_press(self, symbol, modifiers) -> None:
if symbol == key.ESCAPE and not (modifiers & ~(key.MOD_NUMLOCK |
key.MOD_CAPSLOCK |
key.MOD_SCROLLLOCK)):
self.dispatch_event('on_close', 'window')
if symbol == key.ESCAPE and not (
modifiers & ~(key.MOD_NUMLOCK | key.MOD_CAPSLOCK | key.MOD_SCROLLLOCK)
):
self.dispatch_event("on_close", "window")
if symbol == key.SLASH:
self.input_box._set_focus(True)
self.logger.debug(
tr().window.key.press().format(key.symbol_string(symbol), key.modifiers_string(modifiers)))
tr()
.window.key.press()
.format(key.symbol_string(symbol), key.modifiers_string(modifiers))
)
@_call_screen_after
def on_key_release(self, symbol, modifiers) -> None:
self.logger.debug(
tr().window.key.release().format(key.symbol_string(symbol), key.modifiers_string(modifiers)))
tr()
.window.key.release()
.format(key.symbol_string(symbol), key.modifiers_string(modifiers))
)
@_call_screen_after
def on_file_drop(self, x, y, paths):
@ -483,11 +522,11 @@ class ClientWindow(Window):
@_call_screen_after
def on_text(self, text):
if text == '\r':
if text == "\r":
self.logger.debug(tr().window.text.new_line())
else:
self.logger.debug(tr().window.text.input().format(text))
if text == 't':
if text == "t":
self.input_box.enabled = True
@_call_screen_after
@ -501,8 +540,10 @@ class ClientWindow(Window):
self.logger.debug(tr().window.text.motion_select().format(motion_string))
@_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)
def on_close(self, source: str = "window") -> None:
self.game.dispatch_mod_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

@ -18,9 +18,7 @@ from decimal import Decimal
class FpsLogger:
def __init__(self,
stable_fps: int = 60,
count: int = 700):
def __init__(self, stable_fps: int = 60, count: int = 700):
self.stable_fps = stable_fps
self.count = count
self._fps = stable_fps
@ -30,9 +28,7 @@ class FpsLogger:
self._max_fps = stable_fps
self._min_fps = stable_fps
def update_tick(self,
pyglet_fps: float,
tick: Decimal):
def update_tick(self, pyglet_fps: float, tick: Decimal):
if pyglet_fps != 0:
self.fps_list.append(pyglet_fps)
elif tick == 0:
@ -40,11 +36,13 @@ class FpsLogger:
else:
self.fps_list.append(float(1 / tick))
if len(self.fps_list) > self.count:
self.fps_list = self.fps_list[-self.count + 1:] # 整个列表往前挪一位
self.fps_list = self.fps_list[-self.count + 1 :] # 整个列表往前挪一位
if len(self.get_fps_list) > self.count:
self.get_fps_list = self.get_fps_list[-self.count + 1:] # 整个列表往前挪一位
self.get_fps_list = self.get_fps_list[-self.count + 1 :] # 整个列表往前挪一位
try:
self._fps = statistics.geometric_mean(self.fps_list[-100:]) # 取最后100个值的平均值
self._fps = statistics.geometric_mean(
self.fps_list[-100:]
) # 取最后100个值的平均值
self.middle_fps = statistics.median(self.fps_list) # 取中间值
except Exception:
print(self.fps_list)

View File

@ -18,10 +18,10 @@ from Difficult_Rocket.api.types import Fonts
from pyglet.text.formats import structured
default_style = {
'font_name': 'Times New Roman',
'font_size': 12,
'bold': False,
'italic': False
"font_name": "Times New Roman",
"font_size": 12,
"bold": False,
"italic": False,
}
@ -34,17 +34,19 @@ class SingleTextStyle:
单个字符的字体样式
"""
def __init__(self,
font_name: str = '',
font_size: int = 12,
bold: bool = False,
italic: bool = False,
color: str = 'white',
text_tag: list = None,
show: bool = True,
prefix: str = '',
suffix: str = '',
text: str = ''):
def __init__(
self,
font_name: str = "",
font_size: int = 12,
bold: bool = False,
italic: bool = False,
color: str = "white",
text_tag: list = None,
show: bool = True,
prefix: str = "",
suffix: str = "",
text: str = "",
):
self.font_name = font_name
self.font_size = font_size
self.bold = bold
@ -66,7 +68,7 @@ class SingleTextStyle:
@tag.setter
def tag(self, value: list):
assert isinstance(value, list), 'SingleTextStyle.tag must be list'
assert isinstance(value, list), "SingleTextStyle.tag must be list"
for tag in value:
if tag not in self._tag:
self._tag.append(tag)
@ -76,13 +78,15 @@ class SingleTextStyle:
对运算操作的支持
"""
def __add__(self, other: 'SingleTextStyle') -> 'SingleTextStyle':
def __add__(self, other: "SingleTextStyle") -> "SingleTextStyle":
"""
叠加两个字体样式 优先使用 other 的样式
:param other: 叠加的字体样式
:return: 叠加后的字体样式
"""
assert isinstance(other, SingleTextStyle), f'SingleTextStyle + other\n other must be the same type, not a {type(other)}'
assert isinstance(
other, SingleTextStyle
), f"SingleTextStyle + other\n other must be the same type, not a {type(other)}"
return SingleTextStyle(
font_name=other.font_name or self.font_name,
font_size=other.font_size or self.font_size,
@ -93,16 +97,18 @@ class SingleTextStyle:
show=other.show or self.show,
prefix=other.prefix + self.prefix,
suffix=other.suffix + self.suffix,
text=self.text
text=self.text,
)
def __iadd__(self, other: 'SingleTextStyle') -> 'SingleTextStyle':
def __iadd__(self, other: "SingleTextStyle") -> "SingleTextStyle":
"""
叠加两个字体样式 优先使用 other 的样式
:param other: 叠加的字体样式
:return: 叠加后的字体样式
"""
assert isinstance(other, SingleTextStyle), f'SingleTextStyle += other\n other must be the same type, not a {type(other)}'
assert isinstance(
other, SingleTextStyle
), f"SingleTextStyle += other\n other must be the same type, not a {type(other)}"
self.font_name = other.font_name or self.font_name
self.font_size = other.font_size or self.font_size
self.bold = other.bold or self.bold
@ -119,7 +125,7 @@ class SingleTextStyle:
对各种判定的支持
"""
def have_tag(self, other: 'SingleTextStyle') -> bool:
def have_tag(self, other: "SingleTextStyle") -> bool:
"""
比较两个字体样式tag是否相同
:param other: 叠加的字体样式
@ -128,19 +134,21 @@ class SingleTextStyle:
assert isinstance(other, SingleTextStyle)
return other.tag in self.tag
def same_font(self, other: 'SingleTextStyle') -> bool:
def same_font(self, other: "SingleTextStyle") -> bool:
"""
比较两个字体样式的字体属性是否相同
:param other: 叠加的字体样式
:return: 是否相同
"""
assert isinstance(other, SingleTextStyle)
return (self.font_name == other.font_name and
self.font_size == other.font_size and
self.color == other.color and
self.show == other.show)
return (
self.font_name == other.font_name
and self.font_size == other.font_size
and self.color == other.color
and self.show == other.show
)
def same_bold(self, other: 'SingleTextStyle') -> bool:
def same_bold(self, other: "SingleTextStyle") -> bool:
"""
比较两个字体样式的加粗属性是否相同
:param other: 叠加的字体样式
@ -149,7 +157,7 @@ class SingleTextStyle:
assert isinstance(other, SingleTextStyle)
return self.bold == other.bold
def same_italic(self, other: 'SingleTextStyle') -> bool:
def same_italic(self, other: "SingleTextStyle") -> bool:
"""
比较两个字体样式的斜体属性是否相同
:param other: 叠加的字体样式
@ -170,9 +178,9 @@ class SingleTextStyle:
if suffix:
return font_HTML_end
text = f'<font face="{self.font_name}" color={self.color}'
if self.font_size != default_style['font_size']:
text += f' real_size={self.font_size}'
text += '>'
if self.font_size != default_style["font_size"]:
text += f" real_size={self.font_size}"
text += ">"
return text
def HTML_bold(self, suffix: bool = False) -> str:
@ -184,9 +192,9 @@ class SingleTextStyle:
if self.bold:
if suffix:
return bold_HTML_end
return '<b>'
return "<b>"
else:
return ''
return ""
def HTML_italic(self, suffix: bool = False) -> str:
"""
@ -196,9 +204,9 @@ class SingleTextStyle:
if self.italic:
if suffix:
return italic_HTML_end
return '<i>'
return "<i>"
else:
return ''
return ""
def HTML(self, suffix: bool = False) -> str:
"""
@ -206,9 +214,11 @@ class SingleTextStyle:
:return: HTML 格式字符
"""
return (
(font_HTML_end
+ (bold_HTML_end if self.bold else '')
+ (italic_HTML_end if self.italic else ''))
(
font_HTML_end
+ (bold_HTML_end if self.bold else "")
+ (italic_HTML_end if self.italic else "")
)
if suffix
else self.HTML_bold() + self.HTML_italic() + self.HTML_font()
)
@ -217,37 +227,44 @@ class SingleTextStyle:
# [\u4e00-\u9fa5] 中文字符
default_fonts_config = [
{
'match': re.compile(r''), # 匹配的字符 匹配选项是re.compile()
'shown': re.compile(r''), # 匹配到的字符中显示的部分 匹配选项是re.compile()
'style': SingleTextStyle(font_name=Fonts.鸿蒙简体, font_size=15, bold=False, italic=False, show=True, color='white')
"match": re.compile(r""), # 匹配的字符 匹配选项是re.compile()
"shown": re.compile(r""), # 匹配到的字符中显示的部分 匹配选项是re.compile()
"style": SingleTextStyle(
font_name=Fonts.鸿蒙简体,
font_size=15,
bold=False,
italic=False,
show=True,
color="white",
),
},
{
'match': re.compile(r'[a-zA-Z0-9]'),
'shown': re.compile(r'[a-zA-Z0-9]'),
'style': SingleTextStyle(font_name=Fonts.微软等宽, font_size=15)
"match": re.compile(r"[a-zA-Z0-9]"),
"shown": re.compile(r"[a-zA-Z0-9]"),
"style": SingleTextStyle(font_name=Fonts.微软等宽, font_size=15),
},
# Markdown 语法规则匹配
{
# Markdown 粗体语法规则匹配
'match': re.compile(r'\*\*(.*?(?<!\s))\*\*'),
'shown': re.compile(r'(?<=\*\*)(.*?(?<!\s))(?=\*\*)'),
'tag': {
"match": re.compile(r"\*\*(.*?(?<!\s))\*\*"),
"shown": re.compile(r"(?<=\*\*)(.*?(?<!\s))(?=\*\*)"),
"tag": {
# 为 match 匹配到的字符添加标签
'match': re.compile(r'\*\*'),
'style': SingleTextStyle(text_tag=['bold'])
"match": re.compile(r"\*\*"),
"style": SingleTextStyle(text_tag=["bold"]),
},
'style': SingleTextStyle(bold=True)
"style": SingleTextStyle(bold=True),
},
{
# Markdown 斜体语法规则匹配
'match': re.compile(r'\*(.*?(?<!\s))\*'),
'shown': re.compile(r'(?<=\*)(.*?(?<!\s))(?=\*)'),
'ignore': {
"match": re.compile(r"\*(.*?(?<!\s))\*"),
"shown": re.compile(r"(?<=\*)(.*?(?<!\s))(?=\*)"),
"ignore": {
# 如果匹配到的字符含有 tag 就忽略本次解析
'match': re.compile(r'\*'),
'tag': SingleTextStyle(text_tag=['italic'])
"match": re.compile(r"\*"),
"tag": SingleTextStyle(text_tag=["italic"]),
},
'style': SingleTextStyle(italic=True)
"style": SingleTextStyle(italic=True),
},
{
# Markdown 链接规则匹配
@ -255,23 +272,21 @@ default_fonts_config = [
# 即:链接名称不能是空格等空白字符开头,链接名称不能是空格等空白字符结尾
# 匹配的内容:[abc](def)
# 显示的内容abc
'match': re.compile(r'\[(.*?(?<!\s))]\((.*?(?<!\s))\)'),
'shown': re.compile(r'(?<=\[)(.*?(?<!\s))(?=]\((.*?(?<!\s))\))'),
'style': SingleTextStyle(bold=True)
}
"match": re.compile(r"\[(.*?(?<!\s))]\((.*?(?<!\s))\)"),
"shown": re.compile(r"(?<=\[)(.*?(?<!\s))(?=]\((.*?(?<!\s))\))"),
"style": SingleTextStyle(bold=True),
},
]
font_HTML_end = '</font>'
bold_HTML = '<b>'
bold_HTML_end = '</b>'
italic_HTML = '<i>'
italic_HTML_end = '</i>'
font_HTML_end = "</font>"
bold_HTML = "<b>"
bold_HTML_end = "</b>"
italic_HTML = "<i>"
italic_HTML_end = "</i>"
def decode_text2HTML(text: str,
configs=None,
show_style: bool = False) -> str:
def decode_text2HTML(text: str, configs=None, show_style: bool = False) -> str:
if not text:
return ''
return ""
if configs is None:
configs = default_fonts_config
style_list = [SingleTextStyle(text=text[x]) for x in range(len(text))]
@ -279,36 +294,51 @@ def decode_text2HTML(text: str,
# 根据输入的配置对每一个字符进行样式设定
for config in configs:
# 根据 配置"文件"
match_texts = config['match'].finditer(text) # 使用 config.match 匹配
match_texts = config["match"].finditer(text) # 使用 config.match 匹配
for match_text in match_texts: # 每一个匹配到的匹配项
text_match = match_text.group() # 缓存一下匹配到的字符,用于匹配显示的字符
shown_texts = config['shown'].finditer(text_match) # 使用 config.shown 匹配
shown_texts = config["shown"].finditer(text_match) # 使用 config.shown 匹配
match_start, match_end = match_text.span()
if 'ignore' in config: # 如果样式选项包含忽略某些字符的tag
ignore_texts = config['ignore']['match'].finditer(text_match) # 根据选项匹配可能忽略的字符
if "ignore" in config: # 如果样式选项包含忽略某些字符的tag
ignore_texts = config["ignore"]["match"].finditer(
text_match
) # 根据选项匹配可能忽略的字符
ignore = False # 忽略先为False
for ignore_text in ignore_texts: # 每一个可能忽略的字符
if ignore: # 为了方便退出
break
for ignore_index in range(match_start + ignore_text.span()[0], match_start + ignore_text.span()[1]): # 对每一个可能的字符进行检测
if style_list[ignore_index].have_tag(config['ignore']['tag']): # 如果确实包含要忽略的
for ignore_index in range(
match_start + ignore_text.span()[0],
match_start + ignore_text.span()[1],
): # 对每一个可能的字符进行检测
if style_list[ignore_index].have_tag(
config["ignore"]["tag"]
): # 如果确实包含要忽略的
ignore = True # 忽略为True
break
if ignore:
continue # 跳过本次匹配
if 'tag' in config: # 如果样式选项包含对部分字符添加tag
tag_texts = config['tag']['match'].finditer(text_match) # 根据配置的正则表达式匹配要添加tag的字符
if "tag" in config: # 如果样式选项包含对部分字符添加tag
tag_texts = config["tag"]["match"].finditer(
text_match
) # 根据配置的正则表达式匹配要添加tag的字符
for tag_text in tag_texts: # 对每一个匹配到的~~~~~~
for tag_index in range(match_start + tag_text.span()[0], match_start + tag_text.span()[1]): # 用于遍历匹配到的字符
style_list[tag_index] += config['tag']['style']
for tag_index in range(
match_start + tag_text.span()[0], match_start + tag_text.span()[1]
): # 用于遍历匹配到的字符
style_list[tag_index] += config["tag"]["style"]
# 为匹配到的字符添加样式
for match_index in range(match_start, match_end): # 用于遍历匹配到的字符
# 这里用 match index 来精确读写列表里的元素,毕竟 re.Match 返回的 span 是两个标点,得遍历
style_list[match_index] += config['style'] # 字体样式列表的 [match_index] += config['style'] 的样式
style_list[match_index] += config[
"style"
] # 字体样式列表的 [match_index] += config['style'] 的样式
style_list[match_index].show = show_style # 设置显示属性变为 False
# 为每一个显示的字符设置显示属性
for shown_text in shown_texts: # 每一个显示的匹配项
for shown_index in range(match_start + shown_text.span()[0], match_start + shown_text.span()[1]):
for shown_index in range(
match_start + shown_text.span()[0], match_start + shown_text.span()[1]
):
style_list[shown_index].show = True
# 字体样式列表的 [shown_index] 设置显示属性变为 True
# 开始根据配置好的样式输出HTML文本
@ -317,25 +347,35 @@ def decode_text2HTML(text: str,
if style_list[style_index].show: # 如果这个字符显示
if style_list[style_index - 1].show: # 开始根据前面的情况处理每种单独的标签
if not style_list[style_index - 1].same_font(style_list[style_index]):
style_list[style_index - 1].suffix += style_list[style_index - 1].HTML_font(suffix=True)
style_list[style_index - 1].suffix += style_list[
style_index - 1
].HTML_font(suffix=True)
style_list[style_index].prefix += style_list[style_index].HTML_font()
if not style_list[style_index - 1].same_bold(style_list[style_index]):
style_list[style_index - 1].suffix += style_list[style_index - 1].HTML_bold(suffix=True)
style_list[style_index - 1].suffix += style_list[
style_index - 1
].HTML_bold(suffix=True)
style_list[style_index].prefix += style_list[style_index].HTML_bold()
if not style_list[style_index - 1].same_italic(style_list[style_index]):
style_list[style_index - 1].suffix += style_list[style_index - 1].HTML_italic(suffix=True)
style_list[style_index].prefix += style_list[style_index].HTML_italic()
style_list[style_index - 1].suffix += style_list[
style_index - 1
].HTML_italic(suffix=True)
style_list[style_index].prefix += style_list[
style_index
].HTML_italic()
else: # 如果前面一个字符不显示(且这个字符显示)
style_list[style_index].prefix += style_list[style_index].HTML() # 那么就直接给这个字符的前缀添加
style_list[style_index].prefix += style_list[
style_index
].HTML() # 那么就直接给这个字符的前缀添加
elif style_list[style_index - 1].show: # 如果前面一个字符显示(且这个字符不显示)
style_list[style_index - 1].suffix += style_list[style_index - 1].HTML(suffix=True)
style_list[style_index - 1].suffix += style_list[style_index - 1].HTML(
suffix=True
)
if style_list[-1].show:
style_list[-1].suffix += style_list[-1].HTML(suffix=True)
formatted_HTML_text = ''.join(
style.prefix + style.text + style.suffix
for style in style_list
if style.show
formatted_HTML_text = "".join(
style.prefix + style.text + style.suffix for style in style_list if style.show
)
del style_list # 主动删掉 style_list 释放内存
return formatted_HTML_text # 返回DONE

View File

@ -21,5 +21,5 @@ class FontsLabel(DocumentLabel):
def __init__(self, x, y, width, height):
super().__init__(x, y, width, height)
self._text = 'a'
self.formatted_text = 'a'
self._text = "a"
self.formatted_text = "a"

View File

@ -23,12 +23,19 @@ class DRDEBUGScreen(BaseScreen):
super().__init__(main_window)
self.main_batch = Batch()
self.main_group = Group(order=1)
self.fps_label = Label(x=10, y=main_window.height - 10,
width=main_window.width - 20, height=20,
anchor_x='left', anchor_y='top',
font_name=Fonts.微软等宽无线, font_size=20,
multiline=True,
batch=self.main_batch, group=self.main_group)
self.fps_label = Label(
x=10,
y=main_window.height - 10,
width=main_window.width - 20,
height=20,
anchor_x="left",
anchor_y="top",
font_name=Fonts.微软等宽无线,
font_size=20,
multiline=True,
batch=self.main_batch,
group=self.main_group,
)
self.fps_label.text = "11111114514"
def draw_update(self, tick: float, window: "ClientWindow"):
@ -37,10 +44,10 @@ class DRDEBUGScreen(BaseScreen):
def update_label(self, window: "ClientWindow"):
now_FPS = get_frequency()
self.fps_label.text = (
f'FPS: {window.fps_log.fps: >5.1f}('
f'{window.fps_log.middle_fps: >5.1f})[{now_FPS: >.7f}]\n '
f'{window.fps_log.max_fps: >7.1f} '
f'{window.fps_log.min_fps:>5.1f}'
f"FPS: {window.fps_log.fps: >5.1f}("
f"{window.fps_log.middle_fps: >5.1f})[{now_FPS: >.7f}]\n "
f"{window.fps_log.max_fps: >7.1f} "
f"{window.fps_log.min_fps:>5.1f}"
)
def on_resize(self, width, height, window: "ClientWindow"):

View File

@ -1,4 +1,4 @@
# -------------------------------
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved

View File

@ -49,7 +49,11 @@ class CommandText:
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:]
self.text = (
self.text[find + len(text) :]
if not self.text[find + len(text)] == " "
else self.text[find + len(text) + 1 :]
)
return True
return False
@ -64,9 +68,9 @@ class CommandText:
# 20230122 我现在也不知道为啥这么写了
# 果然使用正则表达式就是让一个问题变成两个问题
except IndexError:
self.text = self.text[finding.span()[1] + 1:]
self.text = self.text[finding.span()[1] + 1 :]
return True
if next_find == ' ':
if next_find == " ":
return True
# 将匹配到的字符串,和最后一个匹配字符后面的字符删除(相当暴力的操作)
return False
@ -74,17 +78,14 @@ class CommandText:
def int_value(self, name: Optional[str]):
...
def value(self,
name: str = None,
split: str = ' ',
middle: list = ('\'', '\"')):
def value(self, name: str = None, split: str = " ", middle: list = ("'", '"')):
pass
def get_all(self, value_name: str):
self.value_list.append(self.text)
if value_name:
self.value_dict[value_name] = self.text
self.text = ''
self.text = ""
return self.value_list[-1]
def get_value(self):

View File

@ -35,14 +35,28 @@ class CommandLineTextEntry(widgets.TextEntry):
基于 Text Entry 重写的 Command Line
"""
def __init__(self, x: int, y: int, width: int,
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):
super().__init__(x=x, y=y, width=width,
color=color, text_color=text_color, caret_color=caret_color,
batch=batch, group=group, text='')
def __init__(
self,
x: int,
y: int,
width: int,
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,
):
super().__init__(
x=x,
y=y,
width=width,
color=color,
text_color=text_color,
caret_color=caret_color,
batch=batch,
group=group,
text="",
)
...
@ -51,26 +65,28 @@ class CommandLine(widgets.WidgetBase):
command line show
"""
def __init__(self,
x: int,
y: int,
width: int,
height: int,
length: int,
batch: Batch,
group: Group = None,
command_text: str = '/',
font_size: int = 20):
def __init__(
self,
x: int,
y: int,
width: int,
height: int,
length: int,
batch: Batch,
group: Group = None,
command_text: str = "/",
font_size: int = 20,
):
super().__init__(x, y, width, height)
# normal values
self.length = length
self._command_list = ['' for _ in range(length)]
self._command_list = ["" for _ in range(length)]
self._command_text = command_text
self._text_position = 0
self._command_view = 0
self._value = 0
self._text = ''
self._text = ""
self.command_split = 25
self.command_distance = 20
@ -80,24 +96,45 @@ class CommandLine(widgets.WidgetBase):
fg_group = Group(order=1, parent=group)
# hidden value
self._text = ''
self._line = Label(x=x, y=y, batch=batch, text=self.text,
color=(100, 255, 255, 255),
anchor_x='left', anchor_y='bottom',
font_size=font_size, font_name=translate.微软等宽,
group=fg_group)
self._label = [Label(x=x + 10, y=y + self.command_distance + (line * self.command_split), batch=batch, text='a',
anchor_x='left', anchor_y='bottom',
font_size=font_size - 3, font_name=translate.鸿蒙简体,
group=bg_group)
for line in range(length)]
self._text = ""
self._line = Label(
x=x,
y=y,
batch=batch,
text=self.text,
color=(100, 255, 255, 255),
anchor_x="left",
anchor_y="bottom",
font_size=font_size,
font_name=translate.微软等宽,
group=fg_group,
)
self._label = [
Label(
x=x + 10,
y=y + self.command_distance + (line * self.command_split),
batch=batch,
text="a",
anchor_x="left",
anchor_y="bottom",
font_size=font_size - 3,
font_name=translate.鸿蒙简体,
group=bg_group,
)
for line in range(length)
]
# Rectangular outline with 5-pixel pad:
color = (100, 100, 100, 100)
self._pad = p = 5
self._outline = pyglet.shapes.Rectangle(x=x - p, y=y - p,
width=width + p, height=height + p,
color=color[:3],
batch=batch, group=fg_group)
self._outline = pyglet.shapes.Rectangle(
x=x - p,
y=y - p,
width=width + p,
height=height + p,
color=color[:3],
batch=batch,
group=fg_group,
)
self._outline.opacity = color[3]
self.editing = False
@ -125,7 +162,7 @@ class CommandLine(widgets.WidgetBase):
@text.setter
def text(self, value):
assert isinstance(value, str), 'CommandLine\'s text must be string!'
assert isinstance(value, str), "CommandLine's text must be string!"
self._text = value
self._line.text = value
@ -143,15 +180,19 @@ class CommandLine(widgets.WidgetBase):
0 ~ (self.length-1) -> 切换视角到对应的行数
实际上还有一个限制
"""
assert isinstance(value, int), 'Command View must be integer'
assert -self.length < value < self.length, f'Command View must be bigger than {-self.length} and smaller than {self.length}'
assert isinstance(value, int), "Command View must be integer"
assert (
-self.length < value < self.length
), f"Command View must be bigger than {-self.length} and smaller than {self.length}"
if value == -1: # flush command list
self._label.insert(0, self._label[-1])
self._label.pop(-1)
for line in range(self.length):
self._label[line].y = self.y + self.command_distance + (line * self.command_split)
self._label[line].y = (
self.y + self.command_distance + (line * self.command_split)
)
self._label[0].text = self.text
self.text = ''
self.text = ""
self._command_view = 0
self._text_position = 0
elif value == self._command_view: # not doing anything
@ -168,21 +209,25 @@ class CommandLine(widgets.WidgetBase):
@editing.setter
def editing(self, value):
assert isinstance(value, bool), 'Command editing must be bool!'
assert isinstance(value, bool), "Command editing must be bool!"
self._editing = value
self._line.visible = value
self._outline.visible = value
for label in self._label:
label.visible = value
@new_thread('command wait', daemon=True, log_thread=False)
@new_thread("command wait", daemon=True, log_thread=False)
def wait(self, wait: Union[float, int] = 0):
this = self._label[0]
self._label[0].visible = True
time.sleep(wait)
if self._label[0].visible and not self.editing:
while (self._label[0].opacity >= 30) and self._label[0].visible and (
self._label[0] is this) and not self.editing:
while (
(self._label[0].opacity >= 30)
and self._label[0].visible
and (self._label[0] is this)
and not self.editing
):
# (label 的透明度不是 0) and (label 还在显示) and (label 还是载入线程时候的那个label) and (现在不在输入新行)
self._label[0].opacity -= 2
time.sleep(0.01)
@ -206,45 +251,57 @@ class CommandLine(widgets.WidgetBase):
# 这里的大部分东西都会在最近被重写
# TODO 重写成基于新的 InputBox 的解析
if self.editing:
if text in ('\r', '\n'): # goto a new line
if text in ("\r", "\n"): # goto a new line
if not self.text:
pass
elif self.text[0] == self._command_text:
self.dispatch_event('on_command', CommandText(self.text[1:]))
self.dispatch_event("on_command", CommandText(self.text[1:]))
else:
self.dispatch_event('on_message', CommandText(self.text))
self.dispatch_event("on_message", CommandText(self.text))
# on_message 和 on_command 可能会覆盖 self.text 需要再次判定
if self.text:
self.command_view = -1
self.editing = False
self.wait()
else:
self.text = f'{self.text[:self._text_position]}{text}{self.text[self._text_position:]}' # 插入字符(简单粗暴)
self.text = f"{self.text[:self._text_position]}{text}{self.text[self._text_position:]}" # 插入字符(简单粗暴)
self._text_position += 1
elif text == 't': # open message line
elif text == "t": # open message line
self.editing = True
elif text == '/': # open command line
elif text == "/": # open command line
self.editing = True
self.text = '/'
self.text = "/"
self._text_position = 1
def on_text_motion(self, motion):
if self.editing:
# edit motion
if motion == key.MOTION_DELETE: # 确保不越界
self.text = f'{self.text[:self._text_position]}{self.text[self._text_position + 1:]}' # 简单粗暴的删除
elif motion == key.MOTION_BACKSPACE and self._text_position >= 1: # 确保不越界
self.text = f'{self.text[:self._text_position - 1]}{self.text[self._text_position:]}' # 简单粗暴的删除
self.text = f"{self.text[:self._text_position]}{self.text[self._text_position + 1:]}" # 简单粗暴的删除
elif (
motion == key.MOTION_BACKSPACE and self._text_position >= 1
): # 确保不越界
self.text = f"{self.text[:self._text_position - 1]}{self.text[self._text_position:]}" # 简单粗暴的删除
self._text_position -= 1 # 记得切换光标位置
# move motion
elif motion == key.MOTION_LEFT and self._text_position >= 0: # 确保不越界
self._text_position -= 1
elif motion == key.MOTION_RIGHT and self._text_position <= len(self.text): # 确保不越界
elif motion == key.MOTION_RIGHT and self._text_position <= len(
self.text
): # 确保不越界
self._text_position += 1
elif motion in (key.MOTION_BEGINNING_OF_LINE, key.MOTION_BEGINNING_OF_FILE, key.MOTION_PREVIOUS_PAGE):
elif motion in (
key.MOTION_BEGINNING_OF_LINE,
key.MOTION_BEGINNING_OF_FILE,
key.MOTION_PREVIOUS_PAGE,
):
self._text_position = 0
elif motion in (key.MOTION_END_OF_LINE, key.MOTION_END_OF_FILE, key.MOTION_NEXT_PAGE):
elif motion in (
key.MOTION_END_OF_LINE,
key.MOTION_END_OF_FILE,
key.MOTION_NEXT_PAGE,
):
self._text_position = len(self.text)
# view move motion
@ -288,5 +345,5 @@ class CommandLine(widgets.WidgetBase):
self.text = _text
CommandLine.register_event_type('on_command')
CommandLine.register_event_type('on_message')
CommandLine.register_event_type("on_command")
CommandLine.register_event_type("on_message")

View File

@ -54,31 +54,35 @@ all_process = [multiprocessing.current_process()]
def crash_info_handler(info: Optional[str] = None) -> str:
if not info:
info = traceback.format_exc().replace('<', '< ')
info = traceback.format_exc().replace("<", "< ")
format_info = f"<pre>\n{info}</pre>\n"
format_info.replace('<module>', '< module>')
format_info.replace("<module>", "< module>")
return format_info
def markdown_line_handler(string: Optional[Union[str, bool, int, float]], code: bool = False, level: int = 1,
end: str = '\n') -> str:
lvl = '- ' * level
def markdown_line_handler(
string: Optional[Union[str, bool, int, float]],
code: bool = False,
level: int = 1,
end: str = "\n",
) -> str:
lvl = "- " * level
f_string = string
if code:
f_string = f'`{f_string}`'
return f'{lvl}{f_string}{end}'
f_string = f"`{f_string}`"
return f"{lvl}{f_string}{end}"
def to_code(string: str):
return f'`{string}`'
return f"`{string}`"
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())
filename = f'crash-{date_time}.md'
if "crash_report" not in os.listdir("./"):
os.mkdir("./crash_report")
date_time = time.strftime("%Y-%m-%d %H-%M-%S", time.localtime())
filename = f"crash-{date_time}.md"
cache_stream = io.StringIO()
try:
write_cache(cache_stream, crash_info)
@ -86,13 +90,15 @@ def create_crash_report(info: Optional[str] = None) -> None:
finally:
get_cache = cache_stream.getvalue()
cache_stream.close()
with open(f'./crash_report/{filename}', 'w+', encoding='utf-8') as crash_file:
with open(f"./crash_report/{filename}", "w+", encoding="utf-8") as crash_file:
crash_file.write(get_cache)
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.localtime()))
)
# 崩溃信息
cache_stream.write(crash_info)
@ -101,47 +107,82 @@ def write_info_to_cache(cache_stream):
# 运行状态信息
from Difficult_Rocket import DR_status
from Difficult_Rocket.runtime import 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 language: {DR_runtime.language}', level=1))
cache_stream.write(markdown_line_handler(f'Running Dir: {Path(os.curdir).resolve()}', level=1))
cache_stream.write(
markdown_line_handler(f"DR Version: {Difficult_Rocket.sdk_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()}")
cache_stream.write(DR_configs)
cache_stream.write(f"\n{DR_status.as_markdown()}")
cache_stream.write(Process_message)
for process in all_process:
process: multiprocessing.Process
cache_stream.write(markdown_line_handler(f'{process.name}', code=True))
cache_stream.write(markdown_line_handler(f'Ident: {process.ident}', level=2))
cache_stream.write(markdown_line_handler(f'Running: {process.is_alive()}', level=2))
cache_stream.write(markdown_line_handler(f"{process.name}", code=True))
cache_stream.write(markdown_line_handler(f"Ident: {process.ident}", level=2))
cache_stream.write(
markdown_line_handler(f"Running: {process.is_alive()}", level=2)
)
# 运行线程信息
cache_stream.write(Thread_message)
for thread in all_thread:
thread: threading.Thread
cache_stream.write(markdown_line_handler(f'{thread.name}', code=True))
cache_stream.write(markdown_line_handler(f'order: {all_thread.index(thread)}', level=2))
cache_stream.write(markdown_line_handler(f'Ident: {thread.ident}', level=2))
cache_stream.write(markdown_line_handler(f'Daemon: {thread.daemon}', level=2))
cache_stream.write(markdown_line_handler(f'Running: {thread.is_alive()}', level=2))
cache_stream.write(markdown_line_handler(f"{thread.name}", code=True))
cache_stream.write(
markdown_line_handler(f"order: {all_thread.index(thread)}", level=2)
)
cache_stream.write(markdown_line_handler(f"Ident: {thread.ident}", level=2))
cache_stream.write(markdown_line_handler(f"Daemon: {thread.daemon}", level=2))
cache_stream.write(
markdown_line_handler(f"Running: {thread.is_alive()}", level=2)
)
# Python 信息
cache_stream.write(Python_message)
cache_stream.write(markdown_line_handler(f'Version: {to_code(platform.python_version())}', level=1))
cache_stream.write(markdown_line_handler(f'Branch: {to_code(platform.python_branch())}', level=1))
cache_stream.write(markdown_line_handler(f'Implementation: {to_code(platform.python_implementation())}', level=1))
cache_stream.write(markdown_line_handler(f'Compiler: {to_code(platform.python_compiler())}', level=1))
cache_stream.write(
markdown_line_handler(f"Version: {to_code(platform.python_version())}", level=1)
)
cache_stream.write(
markdown_line_handler(f"Branch: {to_code(platform.python_branch())}", level=1)
)
cache_stream.write(
markdown_line_handler(
f"Implementation: {to_code(platform.python_implementation())}", level=1
)
)
cache_stream.write(
markdown_line_handler(f"Compiler: {to_code(platform.python_compiler())}", level=1)
)
# 电脑系统信息
cache_stream.write(System_message)
cache_stream.write(markdown_line_handler(f'System: {to_code(platform.platform())}', level=1))
cache_stream.write(markdown_line_handler(f'Computer name: {to_code(platform.node())}', level=1))
cache_stream.write(markdown_line_handler(f'machine: {to_code(platform.machine())}', level=1))
cache_stream.write(markdown_line_handler(f'processor: {to_code(platform.processor())}', level=1))
cache_stream.write(markdown_line_handler(f'release: {to_code(platform.release())}', level=1))
cache_stream.write(markdown_line_handler(f'version: {to_code(platform.version())}', level=1))
cache_stream.write(
markdown_line_handler(f"System: {to_code(platform.platform())}", level=1)
)
cache_stream.write(
markdown_line_handler(f"Computer name: {to_code(platform.node())}", level=1)
)
cache_stream.write(
markdown_line_handler(f"machine: {to_code(platform.machine())}", level=1)
)
cache_stream.write(
markdown_line_handler(f"processor: {to_code(platform.processor())}", level=1)
)
cache_stream.write(
markdown_line_handler(f"release: {to_code(platform.release())}", level=1)
)
cache_stream.write(
markdown_line_handler(f"version: {to_code(platform.version())}", level=1)
)
if __name__ == '__main__':
os.chdir('../../')
if __name__ == "__main__":
os.chdir("../../")
try:
raise FileNotFoundError('abc')
raise FileNotFoundError("abc")
except FileNotFoundError:
create_crash_report()

View File

@ -19,7 +19,7 @@ __all__ = [
"CommandQMarkPosError",
"CommandQMarkConflict",
"CommandQMarkSufMissing",
"CommandQMarkPreMissing"
"CommandQMarkPreMissing",
]
@ -34,6 +34,7 @@ class CommandParseError(CommandError):
# QMark -> Quotation marks
# Pos -> Position
class CommandQMarkPosError(CommandParseError):
"""命令中,引号位置不正确
例如 /command "aabcc "awdawd"""
@ -41,12 +42,13 @@ class CommandQMarkPosError(CommandParseError):
class CommandQMarkMissing(CommandParseError):
"""命令中引号缺失
例如: /command "aawwdawda awdaw """
例如: /command "aawwdawda awdaw"""
class CommandQMarkConflict(CommandParseError):
"""命令中引号位置冲突
例如: /command "aaaa "aaaa aaaa"""
first_qmark_pos = None
conflict_qmark_pos = None
@ -54,10 +56,12 @@ class CommandQMarkConflict(CommandParseError):
class CommandQMarkPreMissing(CommandQMarkMissing):
"""命令中 前面的引号缺失
例如: /command aaaa" aaaaaa"""
suf_qmark_pos = None
class CommandQMarkSufMissing(CommandQMarkMissing):
"""命令中 后面的引号缺失(引号未闭合)
例如: /command "aaaawaa some command"""
pre_qmark_pos = None

View File

@ -6,10 +6,12 @@
from Difficult_Rocket.exception import BaseError
__all__ = ['LanguageNotFound',
'TranslateError',
'TranslateKeyNotFound',
'TranslateFileError']
__all__ = [
"LanguageNotFound",
"TranslateError",
"TranslateKeyNotFound",
"TranslateFileError",
]
class LanguageNotFound(BaseError):
@ -22,6 +24,7 @@ class TranslateError(BaseError):
class TranslateKeyNotFound(TranslateError):
"""语言文件某项缺失"""
def __init__(self, value: dict = None, item_names: list = None):
self.item_names: list = item_names
self.value: dict = value
@ -32,6 +35,3 @@ class TranslateKeyNotFound(TranslateError):
class TranslateFileError(TranslateError):
"""翻译文件错误"""

View File

@ -13,12 +13,12 @@ gitee: @shenjackyuanjie
from Difficult_Rocket.exception import BaseError, BaseRuntimeError
__all__ = [
'NoMoreJson5',
'Nope418ImATeapot',
'ThinkError',
'BrainError',
'BigBrainError',
'GrammarError'
"NoMoreJson5",
"Nope418ImATeapot",
"ThinkError",
"BrainError",
"BigBrainError",
"GrammarError",
]

View File

@ -11,8 +11,10 @@ import pyglet
from pyglet.text import Label
from pyglet.gui import widgets
from pyglet.window import mouse
# from pyglet.sprite import Sprite
from pyglet.shapes import Rectangle
# from pyglet.image import AbstractImage
from pyglet.graphics import Batch, Group
@ -28,11 +30,10 @@ class ButtonDrawTheme:
"""
直接绘制按钮的风格
"""
name = 'ButtonDrawTheme'
def __init__(self,
batch: Batch,
group: Group):
name = "ButtonDrawTheme"
def __init__(self, batch: Batch, group: Group):
self.batch = batch
self.group = group
a = (72, 73, 74)
@ -74,10 +75,10 @@ class ButtonDrawTheme:
"""
class ButtonThemeOptions(Options):
""" 基于 Options 写的 ButtonTheme """
name = 'ButtonTheme'
"""基于 Options 写的 ButtonTheme"""
name = "ButtonTheme"
untouched_color: RGBA = (39, 73, 114, 255)
touched_color: RGBA = (66, 150, 250, 255)
hit_color: RGBA = (15, 135, 250, 255)
@ -92,16 +93,17 @@ class PressTextButton(widgets.WidgetBase):
自带 字符 + 材质 的按钮就不用单独做材质了
"""
def __init__(self,
x: int,
y: int,
width: int,
height: int,
text: str,
batch: Optional[Batch] = None,
group: Optional[Group] = None,
theme: Optional[ButtonThemeOptions] = None,
):
def __init__(
self,
x: int,
y: int,
width: int,
height: int,
text: str,
batch: Optional[Batch] = None,
group: Optional[Group] = None,
theme: Optional[ButtonThemeOptions] = None,
):
super().__init__(x, y, width, height)
self.main_batch = batch or Batch()
self.user_batch = batch is not None
@ -117,20 +119,33 @@ class PressTextButton(widgets.WidgetBase):
# from ImGui
self.text = text
self.text_label = Label(font_name=self.theme.font_theme.font_name, font_size=self.theme.font_theme.font_size,
batch=self.main_batch, group=self.front_group,
x=self._x, y=self._y, width=self._width,
height=self._height,)
self.font = pyglet.font.load(self.theme.font_theme.font_name,
self.theme.font_theme.font_size,
bold=self.theme.font_theme.bold,
italic=self.theme.font_theme.italic,
stretch=self.theme.font_theme.stretch)
self.text_label = Label(
font_name=self.theme.font_theme.font_name,
font_size=self.theme.font_theme.font_size,
batch=self.main_batch,
group=self.front_group,
x=self._x,
y=self._y,
width=self._width,
height=self._height,
)
self.font = pyglet.font.load(
self.theme.font_theme.font_name,
self.theme.font_theme.font_size,
bold=self.theme.font_theme.bold,
italic=self.theme.font_theme.italic,
stretch=self.theme.font_theme.stretch,
)
self.font_height = self.font.ascent - self.font.descent
self.back_rec = Rectangle(x=self._x, y=self._y,
width=self._width, height=self._height,
color=self.untouched_color, # ImGui color
batch=self.main_batch, group=self.back_ground_group)
self.back_rec = Rectangle(
x=self._x,
y=self._y,
width=self._width,
height=self._height,
color=self.untouched_color, # ImGui color
batch=self.main_batch,
group=self.back_ground_group,
)
self.value = text # 重新分配一下高度和宽度的位置
@ -144,7 +159,9 @@ class PressTextButton(widgets.WidgetBase):
self.text_label.text = value
text_width = self.text_label.content_width
self.text_label.x = self._x + (self.width - text_width) // 2
self.text_label.y = self._y + (self.height - self.font_height) // 2 + (self.font_height * 0.2) # 修正一下位置
self.text_label.y = (
self._y + (self.height - self.font_height) // 2 + (self.font_height * 0.2)
) # 修正一下位置
def __contains__(self, item):
return item in self.back_rec
@ -163,7 +180,7 @@ class PressTextButton(widgets.WidgetBase):
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.dispatch_event("on_press", x, y)
self.pressed = True
return True
return False
@ -180,4 +197,4 @@ class PressTextButton(widgets.WidgetBase):
self.back_rec.height = self._height
PressTextButton.register_event_type('on_press')
PressTextButton.register_event_type("on_press")

View File

@ -13,7 +13,8 @@ class BaseTheme(dict):
"""
Base class of themes
"""
theme_name = 'BaseTheme'
theme_name = "BaseTheme"
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -22,10 +23,8 @@ class BaseTheme(dict):
setattr(self, k, v)
if TYPE_CHECKING:
def init(self,
batch: Batch,
group: Group,
**kwargs) -> None:
def init(self, batch: Batch, group: Group, **kwargs) -> None:
"""
Init theme
:param batch: batch
@ -39,12 +38,12 @@ class FontTheme(BaseTheme):
"""
Base class of font themes
"""
theme_name = 'FontTheme'
font_name: Optional[str] = 'Times New Roman'
theme_name = "FontTheme"
font_name: Optional[str] = "Times New Roman"
font_size: Optional[int] = 12
bold: Optional[bool] = False
italic: Optional[bool] = False
stretch: Optional[bool] = False
color: Optional[Tuple[int, int, int, int]] = (255, 255, 255, 255)
align: Optional[str] = 'center'
align: Optional[str] = "center"

View File

@ -20,7 +20,8 @@ class ButtonBaseTheme(BaseTheme):
按钮的基础主题
继承了 BaseTheme dict
"""
theme_name = 'Button Base Theme'
theme_name = "Button Base Theme"
def init(self, batch: Batch, group: Group, **kwargs) -> None:
"""
@ -39,10 +40,10 @@ class BlockTheme(ButtonBaseTheme):
"""
button theme: Block like button
"""
theme_name = 'Block Theme(button)'
theme_name = "Block Theme(button)"
main_color: _RGBA = (39, 73, 114, 255)
touch_color: _RGBA = (66, 150, 250, 255)
hit_color: _RGBA = (15, 135, 250, 255)
font_theme: FontTheme = FontTheme()

View File

@ -13,6 +13,7 @@ gitee: @shenjackyuanjie
# import time
import logging
# import traceback
import logging.config
import multiprocessing
@ -23,29 +24,31 @@ from typing import List, Optional, Dict
# 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
class Console(Options):
name = 'python stdin console'
name = "python stdin console"
running: bool = False
caches: List[str] = []
@new_thread('python console', daemon=True, log_thread=True)
@new_thread("python console", daemon=True, log_thread=True)
def main(self):
while self.running:
try:
get_str = input('>>>')
get_str = input(">>>")
except (EOFError, KeyboardInterrupt):
get_str = 'stop'
get_str = "stop"
self.caches.append(get_str)
if get_str == 'stop':
if get_str == "stop":
self.running = False
break
@ -64,7 +67,7 @@ class Console(Options):
class Game(Options):
name = 'MainGame'
name = "MainGame"
client: client.Client
server: server.Server
@ -82,11 +85,12 @@ class Game(Options):
def init_mods(self) -> None:
"""验证/加载 mod"""
from Difficult_Rocket.mod import loader as mod_loader
mod_loader.logger = logging.getLogger('mod_manager')
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.dispatch_mod_event("on_load", game=self)
def init_console(self) -> None:
self.console = self.console_class()
@ -96,7 +100,9 @@ class Game(Options):
self.server.run()
if DR_status.use_multiprocess:
try:
game_process = multiprocessing.Process(target=self.client.start, name='pyglet app')
game_process = multiprocessing.Process(
target=self.client.start, name="pyglet app"
)
game_process.start()
game_process.join()
except Exception:
@ -107,14 +113,14 @@ class Game(Options):
self.client.start()
def log_env(self) -> None:
self.logger.info(f'\n{self.as_markdown()}')
self.logger.info(f"\n{self.as_markdown()}")
def setup(self) -> None:
self.client = client.Client(game=self, net_mode='local')
self.server = server.Server(net_mode='local')
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.logger = logging.getLogger("main")
self.load_file()
self.setup()
self.log_env()

View File

@ -35,6 +35,7 @@ class ModInfo(Options):
"""
加载mod时候的参数
"""
"""基本信息"""
mod_id: str # mod id
name: str # mod 名称
@ -47,9 +48,17 @@ class ModInfo(Options):
info: str = "" # 其他信息 (可以很多很多)
"""版本相关信息"""
DR_version: RequireVersion = (DR_status.DR_version, DR_status.DR_version) # DR SDK 兼容版本
DR_Api_version: RequireVersion = (DR_status.API_version, DR_status.API_version) # DR Api版本
Mod_Require_version: List[Tuple[str, ForceRequire, RequireVersion]] = [] # mod 依赖版本
DR_version: RequireVersion = (
DR_status.DR_version,
DR_status.DR_version,
) # DR SDK 兼容版本
DR_Api_version: RequireVersion = (
DR_status.API_version,
DR_status.API_version,
) # DR Api版本
Mod_Require_version: List[
Tuple[str, ForceRequire, RequireVersion]
] = [] # mod 依赖版本
"""mod 状态"""
is_enable: bool = True # 是否启用
@ -61,34 +70,37 @@ class ModInfo(Options):
def __init__(self, **kwargs):
if not self.DR_version[0] <= DR_status.DR_version <= self.DR_version[1]:
warnings.warn(f"mod {self.mod_id} version {self.version} is not support by DR {DR_status.DR_version}\n"
f"DR {self.DR_version} is required")
warnings.warn(
f"mod {self.mod_id} version {self.version} is not support by DR {DR_status.DR_version}\n"
f"DR {self.DR_version} is required"
)
if not self.DR_Api_version[0] <= DR_status.API_version <= self.DR_Api_version[1]:
warnings.warn(f"mod {self.mod_id} version {self.version} is not support by DR {DR_status.API_version}\n"
f"DR {self.DR_Api_version} is required")
warnings.warn(
f"mod {self.mod_id} version {self.version} is not support by DR {DR_status.API_version}\n"
f"DR {self.DR_Api_version} is required"
)
super().__init__(**kwargs)
def on_load(self, game: Game, old_self: Optional["ModInfo"] = None) -> bool:
""" 加载时调用 """
"""加载时调用"""
return True
def on_client_start(self, game: Game, client: ClientWindow):
""" 客户端启动时调用 """
print(f'Mod {self.mod_id} client start')
"""客户端启动时调用"""
print(f"Mod {self.mod_id} client start")
def on_client_stop(self, game: Game, client: ClientWindow, source: str = 'window'):
""" 客户端停止时调用 """
print(f'Mod {self.mod_id} client stop')
def on_client_stop(self, game: Game, client: ClientWindow, source: str = "window"):
"""客户端停止时调用"""
print(f"Mod {self.mod_id} client stop")
def on_server_start(self, game: Game):
""" 服务器启动时调用 """
print(f'Mod {self.mod_id} server start')
"""服务器启动时调用"""
print(f"Mod {self.mod_id} server start")
def on_server_stop(self, game: Game):
""" 服务器停止时调用 """
print(f'Mod {self.mod_id} server stop')
"""服务器停止时调用"""
print(f"Mod {self.mod_id} server stop")
def on_unload(self, game: Game):
""" 卸载时调用 """
print(f'Mod {self.mod_id} unloaded')
"""卸载时调用"""
print(f"Mod {self.mod_id} unloaded")

View File

@ -16,11 +16,11 @@ from Difficult_Rocket.mod.api import ModInfo
from Difficult_Rocket.utils.translate import tr
from Difficult_Rocket.api.types import Options
Game = TypeVar('Game')
Game = TypeVar("Game")
logger = logging.getLogger('mod_manager')
ONE_FILE_SUFFIX = ('.py', '.pyc', '.pyd')
PACKAGE_SUFFIX = ('.pyz', '.zip', '.dr_mod')
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]):
@ -30,9 +30,9 @@ def _add_path_to_sys(paths: List[Path]):
class ModManager(Options):
name = 'Mod Manager'
name = "Mod Manager"
mods_path: List[Path] = [Path('./mods')]
mods_path: List[Path] = [Path("./mods")]
find_mod_paths: Dict[str, Path] = {}
loaded_mod_modules: Dict[str, ModInfo] = {}
@ -60,7 +60,11 @@ class ModManager(Options):
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()))
logger.error(
tr()
.mod.event.error()
.format(mod, event_name, e, traceback.format_exc())
)
def load_mod(self, mod_path: Path) -> Optional[type(ModInfo)]:
"""
@ -73,14 +77,20 @@ class ModManager(Options):
return None
_add_path_to_sys([mod_path.parent])
try:
if mod_path.name == '__pycache__':
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:
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):
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 类
@ -88,15 +98,21 @@ class ModManager(Options):
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()))
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]:
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 [])
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:
@ -104,18 +120,24 @@ class ModManager(Options):
path.mkdir(parents=True)
continue
for mod in path.iterdir():
if mod.name == '__pycache__':
if mod.name == "__pycache__":
# 忽略 __pycache__ 文件夹 (Python 编译文件)
continue
if mod.is_dir() or mod.suffix in PACKAGE_SUFFIX or mod.suffix in ONE_FILE_SUFFIX:
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)]:
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 路径
@ -154,7 +176,9 @@ class ModManager(Options):
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()))
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))
@ -165,7 +189,10 @@ class ModManager(Options):
: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:
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:
@ -174,7 +201,9 @@ class ModManager(Options):
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()))
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):
@ -203,4 +232,3 @@ class ModManager(Options):
if mod_id in self.loaded_mod_modules and mod_class is not None:
self.loaded_mod_modules[mod_id].on_load(game=game, old_self=mod_class)
logger.info(tr().mod.reload.success().format(mod_id))

View File

@ -15,19 +15,18 @@ from typing import Optional, List, Tuple
from Difficult_Rocket.api.types import Options, Version
__all__ = [
'DR_runtime'
]
__all__ = ["DR_runtime"]
class _DR_runtime(Options):
"""
DR 的运行时配置 / 状态
"""
name = 'DR Runtime'
language: str = 'zh-CN'
mod_path: str = './mods'
name = "DR Runtime"
language: str = "zh-CN"
mod_path: str = "./mods"
DR_Mod_List: List[Tuple[str, Version]] = [] # DR Mod 列表 (name, version)
# run status
@ -37,11 +36,12 @@ class _DR_runtime(Options):
def load_file(self) -> bool:
with contextlib.suppress(FileNotFoundError):
with open('./config/main.toml', 'r', encoding='utf-8') as f:
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']
self.language = config_file["runtime"]["language"]
self.mod_path = config_file["game"]["mods"]["path"]
return True
return False
@ -55,23 +55,25 @@ class _DR_runtime(Options):
sys.path.append(self.mod_path)
for mod_path in paths:
try:
if mod_path.is_dir() and mod_path.name != '__pycache__': # 处理文件夹 mod
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
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
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}')
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}')
print(f"ImportError when loading mod {mod_path}")
traceback.print_exc()
return mods

View File

@ -26,17 +26,17 @@ from Difficult_Rocket.utils.translate import tr
class Server:
def __init__(self, net_mode='local'):
def __init__(self, net_mode="local"):
start_time = time.time()
# logging
self.logger = logging.getLogger('server')
self.logger = logging.getLogger("server")
self.logger.info(tr().server.setup.start())
# value
self.process_id = os.getpid()
# os.set
self.process_name = 'server process'
self.process_name = "server process"
# config
self.config = tools.load_file('config/main.toml')
self.config = tools.load_file("config/main.toml")
# self.dev = Dev
# self.net_mode = net_mode
self.logger.info(tr().server.setup.use_time().format(time.time() - start_time))
@ -45,4 +45,4 @@ class Server:
self.logger.info(tr().server.os.pid_is().format(os.getpid(), os.getppid()))
def __repr__(self):
return f'<Server {self.process_name} {self.process_id}>'
return f"<Server {self.process_name} {self.process_id}>"

View File

@ -4,9 +4,4 @@
# All rights reserved
# -------------------------------
__all__ = [
'camera',
'thread',
'tools',
'translate'
]
__all__ = ["camera", "thread", "tools", "translate"]

View File

@ -14,27 +14,29 @@ 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:
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
@ -89,13 +91,13 @@ class Camera:
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()
@ -123,15 +125,17 @@ class GroupCamera(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):
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
@ -214,17 +218,19 @@ class CenterGroupFrame(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):
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
@ -247,7 +253,9 @@ class CenterGroupFrame(Group):
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.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)

View File

@ -20,19 +20,18 @@ from typing import Optional, Callable, Union, List
from Difficult_Rocket.exception.threading import LockTimeOutError
__all__ = [
'new_thread',
'FunctionThread',
'ThreadLock',
'record_thread',
'record_destination',
"new_thread",
"FunctionThread",
"ThreadLock",
"record_thread",
"record_destination",
]
record_thread = False
record_destination: List[Callable[['FunctionThread'], None]] = []
record_destination: List[Callable[["FunctionThread"], None]] = []
class ThreadLock:
def __init__(self, the_lock: Lock, time_out: Union[float, int] = 1 / 60) -> None:
self.lock = the_lock
self.time_out = time_out
@ -40,7 +39,7 @@ class ThreadLock:
def __enter__(self):
self.lock.acquire(timeout=self.time_out)
if not self.lock.locked():
raise LockTimeOutError(f'Lock time Out with {self.time_out}')
raise LockTimeOutError(f"Lock time Out with {self.time_out}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
@ -69,10 +68,13 @@ class FunctionThread(threading.Thread):
"""
A Thread subclass which is used in decorator :func:`new_thread` to wrap a synchronized function call
"""
__NONE = object()
def __init__(self, target, name, args, kwargs, daemon):
super().__init__(target=target, args=args, kwargs=kwargs, name=name, daemon=daemon)
super().__init__(
target=target, args=args, kwargs=kwargs, name=name, daemon=daemon
)
self.__return_value = self.__NONE
self.__error = None
@ -112,14 +114,16 @@ class FunctionThread(threading.Thread):
self.join(timeout)
if self.__return_value is self.__NONE:
if self.is_alive():
raise RuntimeError('The thread is still running')
raise RuntimeError("The thread is still running")
raise self.__error
return self.__return_value
def new_thread(arg: Optional[Union[str, Callable]] = None,
daemon: bool = False,
log_thread: bool = True):
def new_thread(
arg: Optional[Union[str, Callable]] = None,
daemon: bool = False,
log_thread: bool = True,
):
"""
This is a one line solution to make your function executes in parallels.
When decorated with this decorator, functions will be executed in a new daemon thread
@ -162,7 +166,9 @@ def new_thread(arg: Optional[Union[str, Callable]] = None,
def wrapper(func):
@functools.wraps(func) # to preserve the origin function information
def wrap(*args, **kwargs):
thread = FunctionThread(target=func, args=args, kwargs=kwargs, name=thread_name, daemon=daemon)
thread = FunctionThread(
target=func, args=args, kwargs=kwargs, name=thread_name, daemon=daemon
)
if record_thread:
for destination in record_destination:
destination(thread)
@ -192,8 +198,8 @@ if __name__ == "__main__":
test_lock = ThreadLock(thread_lock)
with test_lock:
print('do some thing')
print("do some thing")
...
with test_lock:
print('do some error')
raise TestError('ah lock test')
print("do some error")
raise TestError("ah lock test")

View File

@ -28,78 +28,91 @@ from defusedxml.ElementTree import parse
from Difficult_Rocket.exception.unsupport import NoMoreJson5
# logger
tools_logger = logging.getLogger('tools')
tools_logger = logging.getLogger("tools")
"""
file config
"""
file_error = {FileNotFoundError: 'no {filetype} file was founded!:\n file name: {filename}\n file_type: {filetype}\n stack: {stack}',
KeyError: 'no stack in {filetype} file {filename} was found! \n file type: {} \n file name: {} \n stack: {stack}',
Exception: 'get some {error_type} when read {filetype} file {filename}! \n file type: {} \n file name: {} \n stack: {stack}'}
file_error = {
FileNotFoundError: "no {filetype} file was founded!:\n file name: {filename}\n file_type: {filetype}\n stack: {stack}",
KeyError: "no stack in {filetype} file {filename} was found! \n file type: {} \n file name: {} \n stack: {stack}",
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]:
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)
f_type = file_name[file_name.rfind('.') + 1:] # 从最后一个.到末尾 (截取文件格式)
get_file = NotImplementedError('解析失败,请检查文件类型/文件内容/文件是否存在!')
f_type = file_name[file_name.rfind(".") + 1 :] # 从最后一个.到末尾 (截取文件格式)
get_file = NotImplementedError("解析失败,请检查文件类型/文件内容/文件是否存在!")
try:
if f_type == 'xml':
if f_type == "xml":
xml_load: ElementTree.ElementTree = parse(file_name)
if stack is not None:
get_file = xml_load.findall(stack)
elif (f_type == 'config') or (f_type == 'conf') or (f_type == 'ini'):
elif (f_type == "config") or (f_type == "conf") or (f_type == "ini"):
get_file = configparser.ConfigParser()
get_file.read(file_name)
if stack:
get_file = get_file[stack]
elif f_type == 'toml':
with open(file_name, mode='r', encoding=encoding) as file:
elif f_type == "toml":
with open(file_name, mode="r", encoding=encoding) as file:
get_file = rtoml.load(file)
if stack is not None:
get_file = get_file[stack]
elif f_type == 'json':
with open(file_name, mode='r', encoding=encoding) as file:
elif f_type == "json":
with open(file_name, mode="r", encoding=encoding) as file:
get_file = json.load(file)
if stack is not None:
get_file = get_file[stack]
elif f_type == 'json5':
elif f_type == "json5":
raise NoMoreJson5("我说什么也不用json5了喵的")
except Exception as exp:
error_type = type(exp)
if error_type in file_error:
tools_logger.error(file_error[error_type].format(filetype=f_type, filename=file_name, stack=stack))
tools_logger.error(
file_error[error_type].format(
filetype=f_type, filename=file_name, stack=stack
)
)
else:
tools_logger.error(file_error[Exception].format(error_type=error_type, filetype=f_type, filename=file_name, stack=stack))
tools_logger.error(
file_error[Exception].format(
error_type=error_type,
filetype=f_type,
filename=file_name,
stack=stack,
)
)
if raise_error:
raise exp from None
return get_file
def save_dict_file(file_name: str,
data: dict,
encoding: str = 'utf-8') -> bool:
f_type = file_name[file_name.rfind('.') + 1:] # 从最后一个.到末尾 (截取文件格式)
def save_dict_file(file_name: str, data: dict, encoding: str = "utf-8") -> bool:
f_type = file_name[file_name.rfind(".") + 1 :] # 从最后一个.到末尾 (截取文件格式)
try:
if (f_type == 'config') or (f_type == 'conf') or (f_type == 'ini'):
if (f_type == "config") or (f_type == "conf") or (f_type == "ini"):
return False
elif f_type == 'toml':
with open(file_name, mode='w', encoding=encoding) as file:
elif f_type == "toml":
with open(file_name, mode="w", encoding=encoding) as file:
rtoml.dump(data, file)
elif f_type == 'json':
with open(file_name, mode='w', encoding=encoding) as file:
elif f_type == "json":
with open(file_name, mode="w", encoding=encoding) as file:
json.dump(data, file)
elif f_type == 'json5':
elif f_type == "json5":
raise NoMoreJson5("我说什么也不用json5了喵的")
except Exception as exp:
raise exp
# main config
main_config_file = load_file('./config/main.toml')
main_config_file = load_file("./config/main.toml")
def get_At(name, in_xml, need_type=str):
@ -124,7 +137,9 @@ def get_At(name, in_xml, need_type=str):
else:
return None
else:
raise TypeError('only str and list type is ok but you give me a' + name_type + 'type')
raise TypeError(
"only str and list type is ok but you give me a" + name_type + "type"
)
return need_type(attr)
@ -134,11 +149,11 @@ def default_name_handler(name_: str) -> str:
just return one
"""
name = name_
name = name.replace('{time.time}', str(time.time()))
name = name.replace('{dir}', str(os.getcwd()))
name = name.replace('{py_v}', str(sys.version.split(' ')[0]))
name = name.replace('{version}', str(main_config_file['runtime']['version']))
name = name.replace('{write_v}', str(main_config_file['runtime']['write_py_v']))
name = name.replace("{time.time}", str(time.time()))
name = name.replace("{dir}", str(os.getcwd()))
name = name.replace("{py_v}", str(sys.version.split(" ")[0]))
name = name.replace("{version}", str(main_config_file["runtime"]["version"]))
name = name.replace("{write_v}", str(main_config_file["runtime"]["write_py_v"]))
return name
@ -148,11 +163,13 @@ def name_handler(name: str, formats: dict = None) -> str:
name = default_name_handler(name)
for need_replace in formats:
replace = formats[need_replace]
if need_replace == '{date}':
if '{date}' in formats:
replace = time.strftime(formats['{date}'], time.gmtime(time.time()))
if need_replace == "{date}":
if "{date}" in formats:
replace = time.strftime(formats["{date}"], time.gmtime(time.time()))
else:
replace = time.strftime(main_config_file['runtime']['date_fmt'], time.gmtime(time.time()))
replace = time.strftime(
main_config_file["runtime"]["date_fmt"], time.gmtime(time.time())
)
name = name.replace(need_replace, replace)
return name
@ -161,8 +178,8 @@ def name_handler(name: str, formats: dict = None) -> str:
some tools
"""
yes = ['true', '1', 1, 1.0, True]
no = ['false', '0', 0, 0.0, False, None]
yes = ["true", "1", 1, 1.0, True]
no = ["false", "0", 0, 0.0, False, None]
def format_bool(thing) -> bool:
@ -184,28 +201,39 @@ def format_bool(thing) -> bool:
raise TypeError("Need a 'like bool' not a {}".format(thing))
level_ = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL',
logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]
level_ = [
"DEBUG",
"INFO",
"WARNING",
"ERROR",
"CRITICAL",
logging.DEBUG,
logging.INFO,
logging.WARNING,
logging.ERROR,
logging.CRITICAL,
]
def log_level(level):
if level in level_:
if (level == 'DEBUG') or (level == logging.DEBUG):
if (level == "DEBUG") or (level == logging.DEBUG):
return logging.DEBUG
if (level == 'INFO') or (level == logging.INFO):
if (level == "INFO") or (level == logging.INFO):
return logging.INFO
if (level == 'WARNING') or (level == logging.WARNING):
if (level == "WARNING") or (level == logging.WARNING):
return logging.WARNING
if (level == 'ERROR') or (level == logging.ERROR):
if (level == "ERROR") or (level == logging.ERROR):
return logging.ERROR
if (level == 'CRITICAL') or (level == logging.CRITICAL):
if (level == "CRITICAL") or (level == logging.CRITICAL):
return logging.CRITICAL
else:
raise ValueError('Need a like logging.level thing not anything else')
raise ValueError("Need a like logging.level thing not anything else")
# linear_algebra
def C_R_P(position, degrees): # stand for calculation
"""
very thanks for lenny from pyglet developer
@ -215,5 +243,8 @@ def C_R_P(position, degrees): # stand for calculation
radians = degrees * (math.pi / 180)
cos = math.cos(radians)
sin = math.sin(radians)
rotated_pos = (position[0] * cos - position[1] * sin, position[0] * sin + position[1] * cos)
rotated_pos = (
position[0] * cos - position[1] * sin,
position[0] * sin + position[1] * cos,
)
return rotated_pos

View File

@ -21,8 +21,7 @@ from typing import Union, Tuple, Any, List, Dict, Hashable, Optional
from Difficult_Rocket import DR_status
from Difficult_Rocket.utils import tools
from Difficult_Rocket.runtime import DR_runtime
from Difficult_Rocket.exception.language import (LanguageNotFound,
TranslateKeyNotFound)
from Difficult_Rocket.exception.language import LanguageNotFound, TranslateKeyNotFound
@dataclass
@ -35,22 +34,26 @@ class TranslateConfig:
always_copy: bool = False # 是否一直新建 Translate (为 True 会降低性能)
source: Optional[Union["Tr", "Translates"]] = None # 翻译来源 (用于默认翻译)
def set(self, item: str, value: Union[bool, "Tr", "Translates"]) -> 'TranslateConfig':
assert getattr(self, item, None) is not None, f'Config {item} is not in TranslateConfig'
def set(self, item: str, value: Union[bool, "Tr", "Translates"]) -> "TranslateConfig":
assert (
getattr(self, item, None) is not None
), f"Config {item} is not in TranslateConfig"
assert isinstance(value, bool)
setattr(self, item, value)
return self
def __copy__(self) -> 'TranslateConfig':
return TranslateConfig(raise_error=self.raise_error,
crack_normal=self.crack_normal,
insert_crack=self.insert_crack,
is_final=self.is_final,
keep_get=self.keep_get,
always_copy=self.always_copy,
source=self.source)
def __copy__(self) -> "TranslateConfig":
return TranslateConfig(
raise_error=self.raise_error,
crack_normal=self.crack_normal,
insert_crack=self.insert_crack,
is_final=self.is_final,
keep_get=self.keep_get,
always_copy=self.always_copy,
source=self.source,
)
def copy(self) -> 'TranslateConfig':
def copy(self) -> "TranslateConfig":
return self.__copy__()
@ -58,11 +61,13 @@ key_type = Union[str, int, Hashable]
class Translates:
def __init__(self, value: Union[Dict[str, Any], list, tuple, str],
config: Optional[TranslateConfig] = None,
get_list: Optional[List[Tuple[bool, str]]] = None):
""" 一个用于翻译的东西
def __init__(
self,
value: Union[Dict[str, Any], list, tuple, str],
config: Optional[TranslateConfig] = None,
get_list: Optional[List[Tuple[bool, str]]] = None,
):
"""一个用于翻译的东西
:param value: 翻译键节点
:param config: 配置
:param get_list: 获取列表
@ -71,8 +76,11 @@ class Translates:
self._config = config or TranslateConfig()
self._get_list = get_list or []
def set_conf_(self, option: Union[str, TranslateConfig],
value: Optional[Union[bool, List[str]]] = None) -> 'Translates':
def set_conf_(
self,
option: Union[str, TranslateConfig],
value: Optional[Union[bool, List[str]]] = None,
) -> "Translates":
assert isinstance(option, (TranslateConfig, str))
if isinstance(option, TranslateConfig):
self._config = option
@ -82,26 +90,35 @@ class Translates:
def _raise_no_value(self, e: Exception, item: key_type):
if self._config.raise_error:
raise TranslateKeyNotFound(self._value, [x[1] for x in self._get_list]) from None
raise TranslateKeyNotFound(
self._value, [x[1] for x in self._get_list]
) from None
elif DR_status.report_translate_not_found:
frame = inspect.currentframe()
if frame is not None:
frame = frame.f_back.f_back
code_list = [self.__getitem__.__code__, self.__getattr__.__code__,
self.__copy__.__code__, self.copy.__code__,
Tr.lang.__code__, Tr.__getitem__.__code__,
Tr.__call__.__code__] # 调用堆栈上的不需要的东西
code_list = [
self.__getitem__.__code__,
self.__getattr__.__code__,
self.__copy__.__code__,
self.copy.__code__,
Tr.lang.__code__,
Tr.__getitem__.__code__,
Tr.__call__.__code__,
] # 调用堆栈上的不需要的东西
while True:
if frame.f_code not in code_list: # 直到调用堆栈不是不需要的东西
break
frame = frame.f_back # 继续向上寻找
frame = f'call at {frame.f_code.co_filename}:{frame.f_lineno}'
frame = f"call at {frame.f_code.co_filename}:{frame.f_lineno}"
else:
frame = 'but No Frame environment'
frame = "but No Frame environment"
raise_info = f"{self.name} Cause a error when getting {item} {frame}"
print(raise_info)
def __getitem__(self, item: Union[key_type, List[key_type], Tuple[key_type]]) -> "Translates":
def __getitem__(
self, item: Union[key_type, List[key_type], Tuple[key_type]]
) -> "Translates":
try:
if isinstance(item, (str, int, Hashable)):
cache_value = self._value[item]
@ -109,11 +126,19 @@ class Translates:
cache_value = self._value
for a_item in item:
cache_value = cache_value[a_item]
if isinstance(cache_value, (int, str,)):
if isinstance(
cache_value,
(
int,
str,
),
):
self._config.is_final = True
self._get_list.append((True, item))
if self._config.always_copy:
return Translates(value=cache_value, config=self._config, get_list=self._get_list)
return Translates(
value=cache_value, config=self._config, get_list=self._get_list
)
self._value = cache_value
except (KeyError, TypeError, AttributeError) as e:
self._get_list.append((False, item))
@ -128,11 +153,13 @@ class Translates:
def copy(self):
return self.__copy__()
def __copy__(self) -> 'Translates':
def __copy__(self) -> "Translates":
return Translates(value=self._value, config=self._config, get_list=self._get_list)
def __getattr__(self, item: key_type) -> "Translates":
if (self._config.is_final or any(x[0] for x in self._get_list)) and hasattr(self._value, item):
if (self._config.is_final or any(x[0] for x in self._get_list)) and hasattr(
self._value, item
):
return getattr(self._value, item)
# 实际上我这里完全不需要处理正常需求,因为 __getattribute__ 已经帮我处理过了
return self.__getitem__(item)
@ -153,10 +180,12 @@ 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,
lang_path: Optional[Path] = None,
):
"""
诶嘿我抄的MCDR
:param language: Tr 所使用的的语言
@ -164,11 +193,21 @@ class Tr:
: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.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())
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.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()
)
@property
def _language(self) -> str:
@ -187,15 +226,23 @@ class Tr:
# 首先判定是否存在对应的语言文件
if lang == self.language_name:
return False
if lang == ' ' or lang == '':
raise LanguageNotFound('Can not be empty')
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"{self.language_path}/{lang}.toml"):
print(
f"lang: {os.path.exists(f'{self.language_path}/{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_cache = Translates(value=self.translates, config=self.default_config.copy())
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_cache = Translates(
value=self.translates, config=self.default_config.copy()
)
self.language_name = lang
DR_runtime.language = self.language_name
return True

View File

@ -20,14 +20,16 @@ def ensure_cmd_readable(cmd: str) -> str:
:param cmd: 要格式化的命令行参数
:return: 格式化后的命令行参数
"""
if ' ' in str(cmd):
if " " in str(cmd):
return f'"{cmd}"'
return cmd
def format_cmd(arg_name: Optional[str] = None,
arg_value: Optional[Union[str, List[str]]] = None,
write: Optional[Any] = True) -> List[str]:
def format_cmd(
arg_name: Optional[str] = None,
arg_value: Optional[Union[str, List[str]]] = None,
write: Optional[Any] = True,
) -> List[str]:
"""
用来格式化输出命令行参数
:param arg_name: 类似 --show-memory 之类的主项
@ -42,10 +44,10 @@ def format_cmd(arg_name: Optional[str] = None,
if arg_value is None:
return [arg_name]
if isinstance(arg_value, list):
arg_value = ','.join([ensure_cmd_readable(value) for value in arg_value])
return [f'{arg_name}{arg_value}']
arg_value = ",".join([ensure_cmd_readable(value) for value in arg_value])
return [f"{arg_name}{arg_value}"]
arg_value = ensure_cmd_readable(arg_value)
return [f'{arg_name}{arg_value}']
return [f"{arg_name}{arg_value}"]
def _add_cmd(cmd: List[str], string: Optional[str]) -> List[str]:
@ -58,15 +60,18 @@ class CompilerHelper(Options):
"""
用于帮助生成 nuitka 构建脚本的类
Use to help generate nuitka build script
"""
name = 'Nuitka Compiler Helper'
name = "Nuitka Compiler Helper"
output_path: Path = Path("./build/nuitka")
src_file: Path = Path('DR.py')
src_file: Path = Path("DR.py")
python_cmd: str = 'python'
compat_nuitka_version: VersionRequirement = VersionRequirement("~1.8.1") # STATIC VERSION
python_cmd: str = "python"
compat_nuitka_version: VersionRequirement = VersionRequirement(
"~1.8.1"
) # STATIC VERSION
# 以下为 nuitka 的参数
use_lto: bool = False # --lto=yes (no is faster)
@ -75,7 +80,7 @@ class CompilerHelper(Options):
use_mingw: bool = False # --mingw64
onefile: bool = False # --onefile
onefile_tempdir: Optional[str] = '' # --onefile-tempdir-spec=
onefile_tempdir: Optional[str] = "" # --onefile-tempdir-spec=
standalone: bool = True # --standalone
use_ccache: bool = True # not --disable-ccache
enable_console: bool = True # --enable-console / --disable-console
@ -84,43 +89,59 @@ class CompilerHelper(Options):
show_memory: bool = False # --show-memory
remove_output: bool = True # --remove-output
save_xml: bool = False # --xml
xml_path: Path = Path('build/compile_data.xml')
xml_path: Path = Path("build/compile_data.xml")
save_report: bool = False # --report
report_path: Path = Path('build/compile_report.xml')
report_path: Path = Path("build/compile_report.xml")
download_confirm: bool = True # --assume-yes-for-download
run_after_build: bool = False # --run
company_name: str = 'tool-shenjack-workshop'
product_name: str = 'Difficult-Rocket'
company_name: str = "tool-shenjack-workshop"
product_name: str = "Difficult-Rocket"
file_version: Version
product_version: Version
file_description: str = 'Difficult Rocket' # --file-description
file_description: str = "Difficult Rocket" # --file-description
copy_right: str = 'Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com' # --copyright
copy_right: str = (
"Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com" # --copyright
)
icon_path: Path = Path('assets/textures/icon.png')
icon_path: Path = Path("assets/textures/icon.png")
follow_import: List[str] = ['pyglet']
no_follow_import: List[str] = ['objprint', 'pillow', 'PIL', 'cffi', 'pydoc', 'numpy', 'email', 'win32con',
'smtplib', 'win32evtlog', 'win32evtlogutil', 'win32api']
follow_import: List[str] = ["pyglet"]
no_follow_import: List[str] = [
"objprint",
"pillow",
"PIL",
"cffi",
"pydoc",
"numpy",
"email",
"win32con",
"smtplib",
"win32evtlog",
"win32evtlogutil",
"win32api",
]
include_data_dir: List[Tuple[str, str]] = [('./config', './config'),
('./assets', './assets')]
include_packages: List[str] = ['Difficult_Rocket.api']
include_data_dir: List[Tuple[str, str]] = [
("./config", "./config"),
("./assets", "./assets"),
]
include_packages: List[str] = ["Difficult_Rocket.api"]
enable_plugin: List[str] = [] # --enable-plugin=xxx,xxx
disable_plugin: List[str] = ['pyqt5', 'tk-inter'] # --disable-plugin=xxx,xxx
disable_plugin: List[str] = ["pyqt5", "tk-inter"] # --disable-plugin=xxx,xxx
def init(self, **kwargs) -> None:
if (compat_version := kwargs.get('compat_nuitka_version')) is not None:
if (compat_version := kwargs.get("compat_nuitka_version")) is not None:
if not self.compat_nuitka_version.accept(compat_version):
warnings.warn(
f"Nuitka version may not compat with {compat_version}\n"
f"requirement: {self.compat_nuitka_version}"
)
# 非 windows 平台不使用 msvc
if platform.system() != 'Windows':
if platform.system() != "Windows":
self.use_msvc = False
self.use_mingw = False
else:
@ -130,6 +151,7 @@ class CompilerHelper(Options):
def load_file(self) -> bool:
try:
from Difficult_Rocket import DR_status
self.product_version = DR_status.DR_version
self.file_version = DR_status.Build_version
return True
@ -144,10 +166,10 @@ class CompilerHelper(Options):
"""
输出编译器帮助信息
Output compiler help information
Args:
longest (Optional[int], optional):
输出信息的最大长度限制 The maximum length of output information.
longest (Optional[int], optional):
输出信息的最大长度限制 The maximum length of output information.
Defaults to None.
Returns:
@ -164,55 +186,89 @@ class CompilerHelper(Options):
Generate nuitka build script
Returns:
List[str]:
List[str]:
生成的 nuitka 构建脚本
Generated nuitka build script
"""
cmd_list = [self.python_cmd, '-m', 'nuitka']
cmd_list = [self.python_cmd, "-m", "nuitka"]
# macos 和 非 macos icon 参数不同
if platform.system() == 'Darwin':
cmd_list += format_cmd('--macos-app-version=', self.product_version, self.product_version) # noqa
cmd_list += format_cmd('--macos-app-icon=', self.icon_path.absolute(), self.icon_path) # noqa
elif platform.system() == 'Windows':
cmd_list += format_cmd('--windows-icon-from-ico=', self.icon_path.absolute(), self.icon_path) # noqa
elif platform.system() == 'Linux':
cmd_list += format_cmd('--linux-icon=', self.icon_path.absolute(), self.icon_path) # noqa
if platform.system() == "Darwin":
cmd_list += format_cmd(
"--macos-app-version=", self.product_version, self.product_version
) # noqa
cmd_list += format_cmd(
"--macos-app-icon=", self.icon_path.absolute(), self.icon_path
) # noqa
elif platform.system() == "Windows":
cmd_list += format_cmd(
"--windows-icon-from-ico=", self.icon_path.absolute(), self.icon_path
) # noqa
elif platform.system() == "Linux":
cmd_list += format_cmd(
"--linux-icon=", self.icon_path.absolute(), self.icon_path
) # noqa
cmd_list += format_cmd('--lto=', 'yes' if self.use_lto else 'no')
cmd_list += format_cmd('--clang' if self.use_clang else None)
cmd_list += format_cmd('--msvc=latest' if self.use_msvc else None)
cmd_list += format_cmd('--mingw64' if self.use_mingw else None)
cmd_list += format_cmd('--standalone' if self.standalone else None)
cmd_list += format_cmd('--onefile' if self.onefile else None)
cmd_list += format_cmd('--onefile-tempdir-spec=', self.onefile_tempdir, self.onefile_tempdir)
cmd_list += format_cmd("--lto=", "yes" if self.use_lto else "no")
cmd_list += format_cmd("--clang" if self.use_clang else None)
cmd_list += format_cmd("--msvc=latest" if self.use_msvc else None)
cmd_list += format_cmd("--mingw64" if self.use_mingw else None)
cmd_list += format_cmd("--standalone" if self.standalone else None)
cmd_list += format_cmd("--onefile" if self.onefile else None)
cmd_list += format_cmd(
"--onefile-tempdir-spec=", self.onefile_tempdir, self.onefile_tempdir
)
cmd_list += format_cmd('--disable-ccache' if not self.use_ccache else None)
cmd_list += format_cmd('--show-progress' if self.show_progress else None)
cmd_list += format_cmd('--show-memory' if self.show_memory else None)
cmd_list += format_cmd('--remove-output' if self.remove_output else None)
cmd_list += format_cmd('--assume-yes-for-download' if self.download_confirm else None)
cmd_list += format_cmd('--run' if self.run_after_build else None)
cmd_list += format_cmd('--enable-console' if self.enable_console else '--disable-console')
cmd_list += format_cmd("--disable-ccache" if not self.use_ccache else None)
cmd_list += format_cmd("--show-progress" if self.show_progress else None)
cmd_list += format_cmd("--show-memory" if self.show_memory else None)
cmd_list += format_cmd("--remove-output" if self.remove_output else None)
cmd_list += format_cmd(
"--assume-yes-for-download" if self.download_confirm else None
)
cmd_list += format_cmd("--run" if self.run_after_build else None)
cmd_list += format_cmd(
"--enable-console" if self.enable_console else "--disable-console"
)
cmd_list += format_cmd('--xml=', str(self.xml_path.absolute()), self.save_xml)
cmd_list += format_cmd('--report=', str(self.report_path.absolute()), self.save_report)
cmd_list += format_cmd('--output-dir=', str(self.output_path.absolute()), self.output_path)
cmd_list += format_cmd('--company-name=', self.company_name, self.company_name)
cmd_list += format_cmd('--product-name=', self.product_name, self.product_name)
cmd_list += format_cmd('--file-version=', str(self.file_version), self.file_version)
cmd_list += format_cmd('--product-version=', str(self.product_version), self.product_version)
cmd_list += format_cmd('--file-description=', self.file_description, self.file_description)
cmd_list += format_cmd('--copyright=', self.copy_right, self.copy_right)
cmd_list += format_cmd("--xml=", str(self.xml_path.absolute()), self.save_xml)
cmd_list += format_cmd(
"--report=", str(self.report_path.absolute()), self.save_report
)
cmd_list += format_cmd(
"--output-dir=", str(self.output_path.absolute()), self.output_path
)
cmd_list += format_cmd("--company-name=", self.company_name, self.company_name)
cmd_list += format_cmd("--product-name=", self.product_name, self.product_name)
cmd_list += format_cmd(
"--file-version=", str(self.file_version), self.file_version
)
cmd_list += format_cmd(
"--product-version=", str(self.product_version), self.product_version
)
cmd_list += format_cmd(
"--file-description=", self.file_description, self.file_description
)
cmd_list += format_cmd("--copyright=", self.copy_right, self.copy_right)
cmd_list += format_cmd('--follow-import-to=', self.follow_import, self.follow_import)
cmd_list += format_cmd('--nofollow-import-to=', self.no_follow_import, self.no_follow_import)
cmd_list += format_cmd('--enable-plugin=', self.enable_plugin, self.enable_plugin)
cmd_list += format_cmd('--disable-plugin=', self.disable_plugin, self.disable_plugin)
cmd_list += format_cmd(
"--follow-import-to=", self.follow_import, self.follow_import
)
cmd_list += format_cmd(
"--nofollow-import-to=", self.no_follow_import, self.no_follow_import
)
cmd_list += format_cmd("--enable-plugin=", self.enable_plugin, self.enable_plugin)
cmd_list += format_cmd(
"--disable-plugin=", self.disable_plugin, self.disable_plugin
)
if self.include_data_dir:
cmd_list += [f"--include-data-dir={src}={dst}" for src, dst in self.include_data_dir]
cmd_list += [
f"--include-data-dir={src}={dst}" for src, dst in self.include_data_dir
]
if self.include_packages:
cmd_list += [f"--include-package={package}" for package in self.include_packages]
cmd_list += [
f"--include-package={package}" for package in self.include_packages
]
cmd_list.append(f"--main={self.src_file}")
return cmd_list

View File

@ -4,8 +4,8 @@ import sys
import rtoml as toml
with open(sys.argv[1], encoding='utf-8', mode='r') as f:
if sys.argv[2] == 'parse':
with open(sys.argv[1], encoding="utf-8", mode="r") as f:
if sys.argv[2] == "parse":
a = toml.load(f)
b = json.dumps(a)
print(b)

View File

@ -9,26 +9,26 @@ from .lib import * # noqa: F403
from typing import TYPE_CHECKING, Dict, Tuple, Optional, List
if TYPE_CHECKING:
def test_call(py_obj) -> bool:
""" 这里展示的代码实际上就是实际的等效实现 """
"""这里展示的代码实际上就是实际的等效实现"""
py_obj.draw()
return True
def get_version_str() -> str:
"""
获取版本号
:return: 版本号
"""
def part_list_read_test(file_name: Optional[str] = "./assets/builtin/PartList.xml") -> None:
def part_list_read_test(
file_name: Optional[str] = "./assets/builtin/PartList.xml"
) -> None:
"""
PartList 读取测试
:param file_name:
"""
def read_ship_test(path: Optional[str] = "./assets/builtin/dock1.xml") -> None:
"""
飞船存档读取测试
@ -36,7 +36,6 @@ if TYPE_CHECKING:
:return:
"""
def map_ptype_textures(part_type: str) -> str:
"""
获取零件的贴图 (写死的)
@ -44,7 +43,6 @@ if TYPE_CHECKING:
:return:
"""
class SR1PartType_rs: # NOQA
"""
用于从 rust 中读取 SR1PartType
@ -54,51 +52,55 @@ if TYPE_CHECKING:
@property
def name(self) -> str:
""" 零件的名字 """
"""零件的名字"""
@property
def description(self) -> str:
""" 零件的描述 """
"""零件的描述"""
@property
def sprite(self) -> str:
""" 零件的贴图 """
"""零件的贴图"""
@property
def mass(self) -> float:
""" 零件的质量 """
"""零件的质量"""
@property
def width(self) -> int:
""" 零件的宽度 """
"""零件的宽度"""
@property
def height(self) -> int:
""" 零件的高度 """
"""零件的高度"""
@property
def friction(self) -> float:
""" 零件的摩擦系数 """
"""零件的摩擦系数"""
@property
def hidden(self) -> bool:
""" 零件是否隐藏 """
"""零件是否隐藏"""
@property
def type(self):
""" 零件的类型 """
"""零件的类型"""
class SR1PartList_rs: # NOQA
""" 用于从 rust 中读取 SR1PartList """
"""用于从 rust 中读取 SR1PartList"""
def __init__(self, file_name: Optional[str] = "./assets/builtin/PartList.xml",
list_name: Optional[str] = 'NewPartList'): ...
def __init__(
self,
file_name: Optional[str] = "./assets/builtin/PartList.xml",
list_name: Optional[str] = "NewPartList",
):
...
def as_dict(self) -> Dict[str, SR1PartType_rs]: ...
def get_part_type(self, name: str) -> SR1PartType_rs: ...
def as_dict(self) -> Dict[str, SR1PartType_rs]:
...
def get_part_type(self, name: str) -> SR1PartType_rs:
...
class SR1PartData_rs: # NOQA
"""
@ -106,53 +108,66 @@ if TYPE_CHECKING:
"""
@property
def id(self) -> int: ...
def id(self) -> int:
...
@property
def part_type_id(self) -> str: ...
def part_type_id(self) -> str:
...
@property
def pos(self) -> Tuple[float, float]: ...
def pos(self) -> Tuple[float, float]:
...
@property
def x(self) -> float: ...
def x(self) -> float:
...
@property
def y(self) -> float: ...
def y(self) -> float:
...
@property
def activate(self) -> bool: ...
def activate(self) -> bool:
...
@property
def angle(self) -> float: ...
def angle(self) -> float:
...
@property
def angle_r(self) -> float: ...
def angle_r(self) -> float:
...
@property
def angle_v(self) -> float: ...
def angle_v(self) -> float:
...
@property
def explode(self) -> bool: ...
def explode(self) -> bool:
...
@property
def flip_x(self) -> bool: ...
def flip_x(self) -> bool:
...
@property
def flip_y(self) -> bool: ...
def flip_y(self) -> bool:
...
class SaveStatus_rs: # NOQA
def __init__(self, save_default: Optional[bool] = False) -> None: ...
def __init__(self, save_default: Optional[bool] = False) -> None:
...
class SR1Ship_rs: # NOQA
""" 用于高效且省内存的读取 SR1Ship """
"""用于高效且省内存的读取 SR1Ship"""
def __init__(self,
file_path: Optional[str] = './assets/builtin/dock1.xml',
part_list: Optional[SR1PartList_rs] = None,
ship_name: Optional[str] = 'NewShip'):
def __init__(
self,
file_path: Optional[str] = "./assets/builtin/dock1.xml",
part_list: Optional[SR1PartList_rs] = None,
ship_name: Optional[str] = "NewShip",
):
"""
读取 SR1Ship
:raise ValueError: 读取失败
@ -162,30 +177,36 @@ if TYPE_CHECKING:
"""
@property
def name(self) -> str: ...
def name(self) -> str:
...
@property
def description(self) -> str: ...
def description(self) -> str:
...
@property
def lift_off(self) -> bool: ...
def lift_off(self) -> bool:
...
@property
def touch_ground(self) -> bool: ...
def touch_ground(self) -> bool:
...
@property
def mass(self) -> float:
""" 获取整搜船的质量 """
"""获取整搜船的质量"""
@property
def img_pos(self) -> Tuple[int, int, int, int]:
""" -x -y +x +y 左下右上 """
"""-x -y +x +y 左下右上"""
@property
def connection(self) -> List[Tuple[int, int, int, int]]:
"""获取所有连接信息"""
def get_part_box(self, part_id: int) -> Optional[Tuple[Tuple[int, int], Tuple[int, int]]]:
def get_part_box(
self, part_id: int
) -> Optional[Tuple[Tuple[int, int], Tuple[int, int]]]:
"""获取所有零件的盒子"""
def as_list(self) -> List[Tuple[SR1PartType_rs, SR1PartData_rs]]:
@ -194,16 +215,23 @@ if TYPE_CHECKING:
def as_dict(self) -> Dict[int, List[Tuple[SR1PartType_rs, SR1PartData_rs]]]:
"""用于返回一个包含所有已连接零件的字典"""
def save(self, file_path: str, save_status: Optional[SaveStatus_rs] = None) -> None: ...
def save(
self, file_path: str, save_status: Optional[SaveStatus_rs] = None
) -> None:
...
class Console_rs: # NOQA
def __init__(self) -> None: ...
def __init__(self) -> None:
...
def start(self) -> None: ...
def start(self) -> None:
...
def stop(self) -> bool: ...
def stop(self) -> bool:
...
def get_command(self) -> Optional[str]: ...
def get_command(self) -> Optional[str]:
...
def new_command(self) -> bool: ...
def new_command(self) -> bool:
...

View File

@ -9,30 +9,32 @@ import warnings
import traceback
from pathlib import Path
package_path = 'Difficult_Rocket_rs'
lib_path = Path('../lib').resolve()
build_path = 'build'
package_path = "Difficult_Rocket_rs"
lib_path = Path("../lib").resolve()
build_path = "build"
if not os.path.exists(lib_path):
os.mkdir(lib_path)
builds = os.listdir(build_path)
print(os.path.abspath('.'))
print(os.path.abspath("."))
try:
shutil.copy('src/__init__.py', os.path.join(lib_path, '__init__.py'))
shutil.copy("src/__init__.py", os.path.join(lib_path, "__init__.py"))
except shutil.SameFileError:
traceback.print_exc()
for build_dir in builds:
if not os.path.exists(os.path.join(build_path, build_dir, package_path)):
warnings.warn(f'package not found at {build_path}/{build_dir}')
warnings.warn(f"package not found at {build_path}/{build_dir}")
continue
for file in os.listdir(os.path.join(build_path, build_dir, package_path)):
file_name = os.path.join(lib_path, file)
shutil.rmtree(file_name, ignore_errors=True)
try:
shutil.copy(os.path.join(build_path, build_dir, package_path, file), file_name)
shutil.copy(
os.path.join(build_path, build_dir, package_path, file), file_name
)
except (shutil.SameFileError, PermissionError):
# print(os.path.exists(os.path))
print(os.listdir(lib_path))

View File

@ -8,23 +8,27 @@ import shutil
from setuptools import setup
from setuptools_rust import Binding, RustExtension, Strip
package_path = 'Difficult_Rocket_rs'
package_path = "Difficult_Rocket_rs"
setup(
name='Difficult_Rocket_rs',
name="Difficult_Rocket_rs",
version="0.2.21.0",
author='shenjackyuanjie',
author_email='3695888@qq.com',
rust_extensions=[RustExtension(target="Difficult_Rocket_rs.Difficult_Rocket_rs",
binding=Binding.PyO3,
strip=Strip.All)],
author="shenjackyuanjie",
author_email="3695888@qq.com",
rust_extensions=[
RustExtension(
target="Difficult_Rocket_rs.Difficult_Rocket_rs",
binding=Binding.PyO3,
strip=Strip.All,
)
],
zip_safe=False,
)
lib_path = '../lib'
build_path = 'build'
lib_path = "../lib"
build_path = "build"
if 'clean' in sys.argv:
if "clean" in sys.argv:
shutil.rmtree(build_path, ignore_errors=True)
shutil.rmtree(f'{package_path}.egg-info', ignore_errors=True)
shutil.rmtree(f"{package_path}.egg-info", ignore_errors=True)
sys.exit(0)

View File

@ -18,11 +18,11 @@ from Difficult_Rocket.api.types import Options, Version
DR_rust_version = Version("0.2.23.0") # DR_mod 的 Rust 编写部分的兼容版本
logger = logging.getLogger('client.dr_game')
logger = logging.getLogger("client.dr_game")
class _DR_mod_runtime(Options): # NOQA
name = 'DR mod runtime'
name = "DR mod runtime"
use_DR_rust: bool = True
DR_rust_available: bool = False
@ -32,13 +32,17 @@ class _DR_mod_runtime(Options): # NOQA
def init(self) -> None:
try:
from .Difficult_Rocket_rs import get_version_str
self.DR_rust_get_version = Version(get_version_str())
self.DR_rust_available = True
if self.DR_rust_get_version != self.DR_rust_version:
relationship = 'larger' if self.DR_rust_version > self.DR_rust_version else 'smaller'
relationship = (
"larger" if self.DR_rust_version > self.DR_rust_version else "smaller"
)
warnings.warn(
f'DR_rust builtin version is {self.DR_rust_version} but true version is {get_version_str()}.\n'
f'Builtin version {relationship} than true version')
f"DR_rust builtin version is {self.DR_rust_version} but true version is {get_version_str()}.\n"
f"Builtin version {relationship} than true version"
)
self.use_DR_rust = self.use_DR_rust and self.DR_rust_available
except Exception:
traceback.print_exc()
@ -77,6 +81,7 @@ class DR_mod(ModInfo): # NOQA
if old_self:
from .sr1_ship import SR1ShipRender
game.client.window.add_sub_screen("SR1_ship", SR1ShipRender)
else:
self.config.flush_option()
@ -86,8 +91,9 @@ class DR_mod(ModInfo): # NOQA
def on_client_start(self, game: Game, client: ClientWindow):
from .sr1_ship import SR1ShipRender
client.add_sub_screen("SR1_ship", SR1ShipRender)
logger.info('on_client_start added sub screen')
logger.info("on_client_start added sub screen")
def on_unload(self, game: Game):
game.client.window.screen_list.pop("SR1_ship")

View File

@ -6,7 +6,7 @@ if DR_mod_runtime.use_DR_rust:
class RustConsole(Console):
name = 'Rust stdin Console'
name = "Rust stdin Console"
running: bool = False
console: Console_rs

View File

@ -16,10 +16,10 @@ class Menu(BaseScreen):
"""
DR game 菜单
"""
name = 'DR_game_menu'
def __init__(self,
main_window: ClientWindow):
name = "DR_game_menu"
def __init__(self, main_window: ClientWindow):
super().__init__(main_window)
self.main_batch = Batch()
self.main_group = Group(parent=main_window.main_group, order=1)

View File

@ -33,14 +33,16 @@ from Difficult_Rocket.api.camera import CenterGroupCamera
from Difficult_Rocket.api.gui.widget import PressTextButton
if DR_mod_runtime.use_DR_rust:
from .Difficult_Rocket_rs import (SR1PartList_rs,
SR1Ship_rs,
SR1PartData_rs,
SR1PartType_rs)
from .Difficult_Rocket_rs import (
SR1PartList_rs,
SR1Ship_rs,
SR1PartData_rs,
SR1PartType_rs,
)
logger = logging.getLogger('client.dr_game_sr1_ship')
logger = logging.getLogger("client.dr_game_sr1_ship")
logger.level = logging.DEBUG
sr_tr = Tr(lang_path=Path(__file__).parent / 'lang')
sr_tr = Tr(lang_path=Path(__file__).parent / "lang")
class SR1ShipRenderStatus(Options): # NOQA
@ -66,10 +68,9 @@ class SR1ShipRenderStatus(Options): # NOQA
class SR1ShipRender(BaseScreen):
"""用于渲染 sr1 船的类"""
name = 'DR_game_sr1_ship_render'
name = "DR_game_sr1_ship_render"
def __init__(self,
main_window: ClientWindow):
def __init__(self, main_window: ClientWindow):
super().__init__(main_window)
self.logger = logger
logger.info(sr_tr().mod.info.setup.start())
@ -83,27 +84,56 @@ class SR1ShipRender(BaseScreen):
self.height = main_window.height
self.main_batch = Batch()
self.group_camera = CenterGroupCamera(window=main_window,
order=10,
parent=main_window.main_group,
min_zoom=(1 / 2) ** 10,
max_zoom=10)
self.group_camera = CenterGroupCamera(
window=main_window,
order=10,
parent=main_window.main_group,
min_zoom=(1 / 2) ** 10,
max_zoom=10,
)
self.part_group = Group(0, parent=self.group_camera)
self.debug_label = Label(x=20, y=main_window.height - 100, font_size=DR_status.std_font_size,
text='SR1 render!', font_name=Fonts.微软等宽无线,
width=main_window.width - 20, height=20,
anchor_x='left', anchor_y='top',
batch=self.main_batch, group=Group(5, parent=self.part_group))
self.render_d_line = Line(0, 0, 0, 0, width=5, color=(200, 200, 10, 255),
batch=self.main_batch, group=Group(5, parent=self.part_group))
self.debug_label = Label(
x=20,
y=main_window.height - 100,
font_size=DR_status.std_font_size,
text="SR1 render!",
font_name=Fonts.微软等宽无线,
width=main_window.width - 20,
height=20,
anchor_x="left",
anchor_y="top",
batch=self.main_batch,
group=Group(5, parent=self.part_group),
)
self.render_d_line = Line(
0,
0,
0,
0,
width=5,
color=(200, 200, 10, 255),
batch=self.main_batch,
group=Group(5, parent=self.part_group),
)
self.render_d_line.visible = self.status.draw_d_pos
self.render_d_label = Label('debug label NODATA', font_name=Fonts.微软等宽无线,
x=main_window.width / 2, y=main_window.height / 2)
self.render_d_label = Label(
"debug label NODATA",
font_name=Fonts.微软等宽无线,
x=main_window.width / 2,
y=main_window.height / 2,
)
self.render_d_label.visible = self.status.draw_d_pos
self.test_button = PressTextButton(x=100, y=100, width=150, height=30, text='test button',
batch=self.main_batch, group=Group(5, parent=main_window.main_group))
self.test_button = PressTextButton(
x=100,
y=100,
width=150,
height=30,
text="test button",
batch=self.main_batch,
group=Group(5, parent=main_window.main_group),
)
# self.test_button.push_handlers(main_window)
main_window.push_handlers(self.test_button)
@ -121,16 +151,22 @@ class SR1ShipRender(BaseScreen):
if DR_mod_runtime.use_DR_rust:
self.rust_parts = None
self.part_list_rs = SR1PartList_rs('assets/builtin/PartList.xml', 'builtin_part_list')
self.part_list_rs = SR1PartList_rs(
"assets/builtin/PartList.xml", "builtin_part_list"
)
self.load_xml('assets/builtin/dock1.xml')
self.load_xml("assets/builtin/dock1.xml")
load_end_time = time.time_ns()
logger.info(sr_tr().mod.info.setup.use_time().format((load_end_time - load_start_time) / 1000000000))
logger.info(
sr_tr()
.mod.info.setup.use_time()
.format((load_end_time - load_start_time) / 1000000000)
)
@property
def size(self) -> Tuple[int, int]:
""" 渲染器的渲染大小 """
"""渲染器的渲染大小"""
return self.width, self.height
@size.setter
@ -147,12 +183,15 @@ class SR1ShipRender(BaseScreen):
try:
start_time = time.time_ns()
logger.info(sr_tr().sr1.ship.xml.loading().format(file_path))
self.ship_name = file_path.split('/')[-1].split('.')[0]
self.ship_name = file_path.split("/")[-1].split(".")[0]
if DR_mod_runtime.use_DR_rust:
self.rust_ship = SR1Ship_rs(file_path, self.part_list_rs, 'a_new_ship')
self.rust_ship = SR1Ship_rs(file_path, self.part_list_rs, "a_new_ship")
logger.info(sr_tr().sr1.ship.xml.load_done())
logger.info(sr_tr().sr1.ship.xml.load_time().format(
(time.time_ns() - start_time) / 1000000000))
logger.info(
sr_tr()
.sr1.ship.xml.load_time()
.format((time.time_ns() - start_time) / 1000000000)
)
return True
except Exception:
traceback.print_exc()
@ -178,10 +217,17 @@ class SR1ShipRender(BaseScreen):
parts: List[Tuple[SR1PartType_rs, SR1PartData_rs]]
batch = []
for p_type, p_data in parts:
sprite_name = self.part_list_rs.get_part_type(p_data.part_type_id).sprite
part_sprite = Sprite(img=self.textures.get_texture(sprite_name),
x=p_data.x * 60, y=p_data.y * 60, z=random.random(),
batch=self.main_batch, group=part_group)
sprite_name = self.part_list_rs.get_part_type(
p_data.part_type_id
).sprite
part_sprite = Sprite(
img=self.textures.get_texture(sprite_name),
x=p_data.x * 60,
y=p_data.y * 60,
z=random.random(),
batch=self.main_batch,
group=part_group,
)
part_sprite.rotation = p_data.angle_r
part_sprite.scale_x = -1 if p_data.flip_x else 1
part_sprite.scale_y = -1 if p_data.flip_y else 1
@ -192,17 +238,61 @@ class SR1ShipRender(BaseScreen):
# 线框
part_line_box = []
width = 4
color = (random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255),
random.randrange(100, 200))
color = (
random.randrange(0, 255),
random.randrange(0, 255),
random.randrange(0, 255),
random.randrange(100, 200),
)
(x, y), (x2, y2) = part_box
part_line_box.append(Line(x=x * 30, y=y * 30, x2=x * 30, y2=y2 * 30,
batch=self.main_batch, width=width, color=color, group=line_box_group))
part_line_box.append(Line(x=x * 30, y=y2 * 30, x2=x2 * 30, y2=y2 * 30,
batch=self.main_batch, width=width, color=color, group=line_box_group))
part_line_box.append(Line(x=x2 * 30, y=y2 * 30, x2=x2 * 30, y2=y * 30,
batch=self.main_batch, width=width, color=color, group=line_box_group))
part_line_box.append(Line(x=x2 * 30, y=y * 30, x2=x * 30, y2=y * 30,
batch=self.main_batch, width=width, color=color, group=line_box_group))
part_line_box.append(
Line(
x=x * 30,
y=y * 30,
x2=x * 30,
y2=y2 * 30,
batch=self.main_batch,
width=width,
color=color,
group=line_box_group,
)
)
part_line_box.append(
Line(
x=x * 30,
y=y2 * 30,
x2=x2 * 30,
y2=y2 * 30,
batch=self.main_batch,
width=width,
color=color,
group=line_box_group,
)
)
part_line_box.append(
Line(
x=x2 * 30,
y=y2 * 30,
x2=x2 * 30,
y2=y * 30,
batch=self.main_batch,
width=width,
color=color,
group=line_box_group,
)
)
part_line_box.append(
Line(
x=x2 * 30,
y=y * 30,
x2=x * 30,
y2=y * 30,
batch=self.main_batch,
width=width,
color=color,
group=line_box_group,
)
)
# 直接用循环得了
self.part_line_box[p_id] = part_line_box
self.parts_sprite[p_id] = batch
@ -215,11 +305,24 @@ class SR1ShipRender(BaseScreen):
# 连接线
parent_part_data = cache[connect[2]][0][1]
child_part_data = cache[connect[3]][0][1]
color = (random.randrange(100, 255), random.randrange(0, 255), random.randrange(0, 255), 255)
self.part_line_list.append(Line(x=parent_part_data.x * 60, y=parent_part_data.y * 60,
x2=child_part_data.x * 60, y2=child_part_data.y * 60,
batch=self.main_batch, group=connect_line_group,
width=1, color=color))
color = (
random.randrange(100, 255),
random.randrange(0, 255),
random.randrange(0, 255),
255,
)
self.part_line_list.append(
Line(
x=parent_part_data.x * 60,
y=parent_part_data.y * 60,
x2=child_part_data.x * 60,
y2=child_part_data.y * 60,
batch=self.main_batch,
group=connect_line_group,
width=1,
color=color,
)
)
count += 1
if count >= each_count * 3:
count = 0
@ -274,24 +377,43 @@ class SR1ShipRender(BaseScreen):
full_mass = 0
if DR_mod_runtime.use_DR_rust:
full_mass = self.rust_ship.mass
logger.info(sr_tr().sr1.ship.ship.load_time().format(
(time.perf_counter_ns() - start_time) / 1000000000))
logger.info(sr_tr().sr1.ship.ship.info().format(
len(self.rust_ship.as_list()),
f'{full_mass}kg' if DR_mod_runtime.use_DR_rust else sr_tr().game.require_DR_rs()))
logger.info(
sr_tr()
.sr1.ship.ship.load_time()
.format((time.perf_counter_ns() - start_time) / 1000000000)
)
logger.info(
sr_tr()
.sr1.ship.ship.info()
.format(
len(self.rust_ship.as_list()),
f"{full_mass}kg"
if DR_mod_runtime.use_DR_rust
else sr_tr().game.require_DR_rs(),
)
)
def draw_batch(self, window: ClientWindow):
if self.status.draw_done:
self.render_d_label.text = f'x: {self.group_camera.view_x} y: {self.group_camera.view_y}'
self.render_d_label.position = self.group_camera.view_x + (
self.window_pointer.width / 2), self.group_camera.view_y + (
self.window_pointer.height / 2) + 10, 0 # 0 for z
self.render_d_label.text = (
f"x: {self.group_camera.view_x} y: {self.group_camera.view_y}"
)
self.render_d_label.position = (
self.group_camera.view_x + (self.window_pointer.width / 2),
self.group_camera.view_y + (self.window_pointer.height / 2) + 10,
0,
) # 0 for z
self.render_d_line.x2 = self.group_camera.view_x
self.render_d_line.y2 = self.group_camera.view_y
gl.glEnable(gl.GL_SCISSOR_TEST)
gl.glScissor(int(self.dx), int(self.dy), int(self.width), int(self.height))
gl.glViewport(int(self.dx), int(self.dy), self.window_pointer.width, self.window_pointer.height)
gl.glViewport(
int(self.dx),
int(self.dy),
self.window_pointer.width,
self.window_pointer.height,
)
self.main_batch.draw() # use group camera, no need to with
gl.glViewport(0, 0, self.window_pointer.width, self.window_pointer.height)
gl.glScissor(0, 0, self.window_pointer.width, self.window_pointer.height)
@ -321,7 +443,9 @@ class SR1ShipRender(BaseScreen):
self.render_d_line.y2 = height // 2
self.test_button._update_position()
def on_mouse_scroll(self, x: int, y: int, scroll_x: int, scroll_y: int, window: ClientWindow):
def on_mouse_scroll(
self, x: int, y: int, scroll_x: int, scroll_y: int, window: ClientWindow
):
if not self.status.draw_done:
return
if self.status.focus:
@ -334,7 +458,7 @@ class SR1ShipRender(BaseScreen):
if scroll_y == 0:
zoom_d = 1
else:
zoom_d = ((2 ** scroll_y) - 1) * 0.5 + 1
zoom_d = ((2**scroll_y) - 1) * 0.5 + 1
# 缩放的变换量
if not (self.group_camera.zoom == 10 and scroll_y > 0):
if self.group_camera.zoom * zoom_d >= 10:
@ -342,8 +466,8 @@ class SR1ShipRender(BaseScreen):
self.group_camera.zoom = 10
else:
self.group_camera.zoom *= zoom_d
mouse_dx_d *= (1 - zoom_d)
mouse_dy_d *= (1 - zoom_d)
mouse_dx_d *= 1 - zoom_d
mouse_dy_d *= 1 - zoom_d
self.group_camera.view_x += mouse_dx_d
self.group_camera.view_y += mouse_dy_d
elif self.status.moving:
@ -358,26 +482,26 @@ class SR1ShipRender(BaseScreen):
self.size = size_x, size_y
def on_command(self, command: CommandText, window: ClientWindow):
""" 解析命令 """
self.logger.info(f'command: {command}')
if command.find('render'):
if command.find('reset'):
"""解析命令"""
self.logger.info(f"command: {command}")
if command.find("render"):
if command.find("reset"):
self.group_camera.reset()
else:
self.status.draw_call = True
print('应该渲染飞船的')
print("应该渲染飞船的")
elif command.find('debug'):
if command.find('delta'):
elif command.find("debug"):
if command.find("delta"):
self.render_d_line.visible = not self.render_d_line.visible
self.status.draw_mouse_d_pos = self.render_d_line.visible
self.logger.info(f'sr1 mouse {self.status.draw_mouse_d_pos}')
elif command.find('ship'):
self.logger.info(f"sr1 mouse {self.status.draw_mouse_d_pos}")
elif command.find("ship"):
if self.status.draw_done:
for index, sprite in self.parts_sprite.items():
sprite.visible = not sprite.visible
elif command.find('get_buf'):
elif command.find("get_buf"):
def screenshot(window):
"""
@ -387,14 +511,19 @@ class SR1ShipRender(BaseScreen):
"""
from pyglet.gl import GLubyte, GL_RGBA, GL_UNSIGNED_BYTE, glReadPixels
import pyglet
format_str = "RGBA"
buf = (GLubyte * (len(format_str) * window.width * window.height))()
glReadPixels(0, 0, window.width, window.height, GL_RGBA, GL_UNSIGNED_BYTE, buf)
return pyglet.image.ImageData(window.width, window.height, format_str, buf)
glReadPixels(
0, 0, window.width, window.height, GL_RGBA, GL_UNSIGNED_BYTE, buf
)
return pyglet.image.ImageData(
window.width, window.height, format_str, buf
)
image_data = screenshot(self.window_pointer)
image_data.save('test.png')
elif command.find('gen_img'):
image_data.save("test.png")
elif command.find("gen_img"):
if not self.status.draw_done:
return
if not DR_mod_runtime.use_DR_rust:
@ -408,46 +537,66 @@ class SR1ShipRender(BaseScreen):
from PIL import Image
except ImportError:
traceback.print_exc()
print('PIL not found')
print("PIL not found")
return
img = Image.new('RGBA', img_size)
img = Image.new("RGBA", img_size)
part_data = self.rust_ship.as_dict()
for part, sprites in self.parts_sprite.items():
for index, sprite in enumerate(sprites):
sprite_img = sprite.image
print(
f"sprite_img: {sprite_img} {part_data[part][index][1].x * 60} {part_data[part][index][1].y * 60}")
f"sprite_img: {sprite_img} {part_data[part][index][1].x * 60} {part_data[part][index][1].y * 60}"
)
img_data = sprite_img.get_image_data()
fmt = img_data.format
if fmt != 'RGB':
fmt = 'RGBA'
if fmt != "RGB":
fmt = "RGBA"
pitch = -(img_data.width * len(fmt))
pil_image = Image.frombytes(fmt, (img_data.width, img_data.height), img_data.get_data(fmt, pitch))
pil_image = Image.frombytes(
fmt,
(img_data.width, img_data.height),
img_data.get_data(fmt, pitch),
)
pil_image = pil_image.rotate(-SR1Rotation.get_rotation(part_data[part][index][1].angle),
expand=True)
pil_image = pil_image.rotate(
-SR1Rotation.get_rotation(part_data[part][index][1].angle),
expand=True,
)
if part_data[part][index][1].flip_y:
pil_image.transpose(Image.FLIP_TOP_BOTTOM)
if part_data[part][index][1].flip_x:
pil_image.transpose(Image.FLIP_LEFT_RIGHT)
img.paste(pil_image, (
int(part_data[part][index][1].x * 60 + img_center[0]),
int(-part_data[part][index][1].y * 60 + img_center[1])))
img.paste(
pil_image,
(
int(part_data[part][index][1].x * 60 + img_center[0]),
int(-part_data[part][index][1].y * 60 + img_center[1]),
),
)
img.save(f'test{time.time()}.png', 'PNG')
img.save(f"test{time.time()}.png", "PNG")
elif command.find('test'):
if command.find('save'):
elif command.find("test"):
if command.find("save"):
if not self.status.draw_done:
return
if not DR_mod_runtime.use_DR_rust:
return
logger.info(sr_tr().sr1.ship.save.start().format(self.rust_ship))
self.rust_ship.save('./test-save.xml')
self.rust_ship.save("./test-save.xml")
def on_mouse_drag(self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int, window: ClientWindow):
def on_mouse_drag(
self,
x: int,
y: int,
dx: int,
dy: int,
buttons: int,
modifiers: int,
window: ClientWindow,
):
if self.status.focus:
self.group_camera.view_x += dx
self.group_camera.view_y += dy
@ -466,7 +615,7 @@ class SR1ShipRender(BaseScreen):
traceback.print_exc()
else:
if Path(paths[0]).is_dir():
for path in Path(paths[0]).glob('*.xml'):
for path in Path(paths[0]).glob("*.xml"):
try:
self.load_xml(str(path))
except ValueError:
@ -487,7 +636,7 @@ class SR1ShipRender(BaseScreen):
self.window_pointer.view = value
if __name__ == '__main__':
if __name__ == "__main__":
from objprint import op
op(SR1ShipRenderStatus())

View File

@ -15,10 +15,11 @@ from Difficult_Rocket.api.types import Options
class SR1Textures(Options):
""" 存储 sr1 的材质 img """
"""存储 sr1 的材质 img"""
def load_file(self, **kwargs):
for image_name in self.flush_option():
img = load(f'assets/textures/parts/{image_name}.png')
img = load(f"assets/textures/parts/{image_name}.png")
img.anchor_x = img.width // 2
img.anchor_y = img.height // 2
setattr(self, image_name, img)
@ -32,81 +33,83 @@ class SR1Textures(Options):
"""
if name in self.cached_options:
return self.cached_options.get(name)
elif name.split('.')[0] in self.cached_options:
return self.cached_options.get(name.split('.')[0])
elif name.split(".")[0] in self.cached_options:
return self.cached_options.get(name.split(".")[0])
else:
img = load(f'assets/textures/parts/{name}')
img = load(f"assets/textures/parts/{name}")
img.anchor_x = img.width // 2
img.anchor_y = img.height // 2
setattr(self, name, img)
return img
Battery: AbstractImage = None
Beam: AbstractImage = None
CoverBottom: AbstractImage = None
CoverStretch: AbstractImage = None
CoverTop: AbstractImage = None
DetacherRadial: AbstractImage = None
DetacherVertical: AbstractImage = None
DockingConnector: AbstractImage = None
DockingPort: AbstractImage = None
EngineIon: AbstractImage = None
EngineLarge: AbstractImage = None
EngineMedium: AbstractImage = None
EngineSmall: AbstractImage = None
EngineTiny: AbstractImage = None
Fuselage: AbstractImage = None
LanderLegJoint: AbstractImage = None
LanderLegLower: AbstractImage = None
LanderLegPreview: AbstractImage = None
LanderLegUpper: AbstractImage = None
NoseCone: AbstractImage = None
Parachute: AbstractImage = None
ParachuteCanister: AbstractImage = None
Battery: AbstractImage = None
Beam: AbstractImage = None
CoverBottom: AbstractImage = None
CoverStretch: AbstractImage = None
CoverTop: AbstractImage = None
DetacherRadial: AbstractImage = None
DetacherVertical: AbstractImage = None
DockingConnector: AbstractImage = None
DockingPort: AbstractImage = None
EngineIon: AbstractImage = None
EngineLarge: AbstractImage = None
EngineMedium: AbstractImage = None
EngineSmall: AbstractImage = None
EngineTiny: AbstractImage = None
Fuselage: AbstractImage = None
LanderLegJoint: AbstractImage = None
LanderLegLower: AbstractImage = None
LanderLegPreview: AbstractImage = None
LanderLegUpper: AbstractImage = None
NoseCone: AbstractImage = None
Parachute: AbstractImage = None
ParachuteCanister: AbstractImage = None
ParachuteCanisterSide: AbstractImage = None
Pod: AbstractImage = None
Puffy750: AbstractImage = None
RcsBlock: AbstractImage = None
SideTank: AbstractImage = None
SolarPanel: AbstractImage = None
SolarPanelBase: AbstractImage = None
SolidRocketBooster: AbstractImage = None
TankLarge: AbstractImage = None
TankMedium: AbstractImage = None
TankSmall: AbstractImage = None
TankTiny: AbstractImage = None
Wheel: AbstractImage = None
Wing: AbstractImage = None
Pod: AbstractImage = None
Puffy750: AbstractImage = None
RcsBlock: AbstractImage = None
SideTank: AbstractImage = None
SolarPanel: AbstractImage = None
SolarPanelBase: AbstractImage = None
SolidRocketBooster: AbstractImage = None
TankLarge: AbstractImage = None
TankMedium: AbstractImage = None
TankSmall: AbstractImage = None
TankTiny: AbstractImage = None
Wheel: AbstractImage = None
Wing: AbstractImage = None
class SR1PartTexture:
part_type_sprite: Dict[str, str] = {'pod-1': 'Pod',
'detacher-1': 'DetacherVertical',
'detacher-2': 'DetacherRadial',
'wheel-1': 'Wheel',
'wheel-2': 'Wheel',
'fuselage-1': 'Fuselage',
'strut-1': 'Beam',
'fueltank-0': 'TankTiny',
'fueltank-1': 'TankSmall',
'fueltank-2': 'TankMedium',
'fueltank-3': 'TankLarge',
'fueltank-4': 'Puffy750',
'fueltank-5': 'SideTank',
'engine-0': 'EngineTiny',
'engine-1': 'EngineSmall',
'engine-2': 'EngineMedium',
'engine-3': 'EngineLarge',
'engine-4': 'SolidRocketBooster',
'ion-0': 'EngineIon',
'parachute-1': 'ParachuteCanister',
'nosecone-1': 'NoseCone',
'rcs-1': 'RcsBlock',
'solar-1': 'SolarPanelBase',
'battery-0': 'Battery',
'dock-1': 'DockingConnector',
'port-1': 'DockingPort',
'lander-1': 'LanderLegPreview'}
part_type_sprite: Dict[str, str] = {
"pod-1": "Pod",
"detacher-1": "DetacherVertical",
"detacher-2": "DetacherRadial",
"wheel-1": "Wheel",
"wheel-2": "Wheel",
"fuselage-1": "Fuselage",
"strut-1": "Beam",
"fueltank-0": "TankTiny",
"fueltank-1": "TankSmall",
"fueltank-2": "TankMedium",
"fueltank-3": "TankLarge",
"fueltank-4": "Puffy750",
"fueltank-5": "SideTank",
"engine-0": "EngineTiny",
"engine-1": "EngineSmall",
"engine-2": "EngineMedium",
"engine-3": "EngineLarge",
"engine-4": "SolidRocketBooster",
"ion-0": "EngineIon",
"parachute-1": "ParachuteCanister",
"nosecone-1": "NoseCone",
"rcs-1": "RcsBlock",
"solar-1": "SolarPanelBase",
"battery-0": "Battery",
"dock-1": "DockingConnector",
"port-1": "DockingPort",
"lander-1": "LanderLegPreview",
}
@classmethod
def get_textures_from_type(cls, name: str) -> Union[None, str]:
@ -118,7 +121,7 @@ class SR1Rotation(Options):
0.0: 0,
1.570796: 270,
3.141593: 180,
4.712389: 90
4.712389: 90,
}
@classmethod
@ -131,4 +134,4 @@ class SR1Rotation(Options):
if radian in cls.radian_angle_map:
return cls.radian_angle_map[radian]
else:
return (radian / math.pi) * 180
return (radian / math.pi) * 180

View File

@ -16,20 +16,23 @@ import unittest
from Difficult_Rocket.client.guis.format import html
try_texts = [
'明天天气很好',
'从前有座山,山里有座庙, **is it**?',
'啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。阿巴巴巴',
'阿瓦达达瓦的aiwdhaihdwia.awdaiwhdahwido[12312](123131)',
'1231231dawdawd65ewwe56er56*awdadad*aaa**阿伟大的阿瓦打我的**',
'adwiuahiaa奥迪帮我auawuawdawdadw阿达达瓦aawd 2313',
'阿松大阿瓦达达娃啊aawadaawdawd阿瓦达达娃'
"明天天气很好",
"从前有座山,山里有座庙, **is it**?",
"啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。阿巴巴巴",
"阿瓦达达瓦的aiwdhaihdwia.awdaiwhdahwido[12312](123131)",
"1231231dawdawd65ewwe56er56*awdadad*aaa**阿伟大的阿瓦打我的**",
"adwiuahiaa奥迪帮我auawuawdawdadw阿达达瓦aawd 2313",
"阿松大阿瓦达达娃啊aawadaawdawd阿瓦达达娃",
]
class HtmlFormatTest(unittest.TestCase):
def test1_format_texts(self):
self.assertEqual(html.decode_text2HTML('明天天气很好'), '<font face="" color=white>明天天气很好</font>')
self.assertEqual(
html.decode_text2HTML("明天天气很好"),
'<font face="" color=white>明天天气很好</font>',
)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()