Merge pull request #16 from shenjackyuanjie/feature/dr-sdk

This commit is contained in:
shenjack 2023-05-03 00:40:53 +08:00 committed by GitHub
commit 0a7bfe0a94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 972 additions and 689 deletions

View File

@ -5,9 +5,7 @@ name: Build
on: on:
# 触发条件 # 触发条件
push: push:
branches: ["main"]
pull_request: pull_request:
branches: ["main"]
workflow_dispatch: workflow_dispatch:
# 主任务 # 主任务
@ -80,18 +78,6 @@ jobs:
Write-Output $infos >> $env:GITHUB_ENV Write-Output $infos >> $env:GITHUB_ENV
python .github/workflows/get_info.py python .github/workflows/get_info.py
# 这堆构建脚本全都是我自己写的
- name: DR-rs build
shell: pwsh
run: |
cd libs/Difficult_Rocket_rs/src
python setup.py build
python post_build.py
python setup.py clean
cd ..
cd ..
cd ..
# 还是得我自己写脚本 # 还是得我自己写脚本
- name: Build on Windows - name: Build on Windows
if: runner.os == 'Windows' if: runner.os == 'Windows'
@ -165,7 +151,6 @@ jobs:
Copy-Item libs/fonts build\DR.dist\libs\fonts -Recurse Copy-Item libs/fonts build\DR.dist\libs\fonts -Recurse
# Copy-Item libs\pyglet\ build\DR.dist -Recurse # Copy-Item libs\pyglet\ build\DR.dist -Recurse
# Copy-Item -Path "libs\Difficult_Rocket_rs\lib\*" -Destination "build\DR.dist\libs\Difficult_Rocket_rs\lib" -Recurse
Rename-Item build/DR.dist Difficult-Rocket Rename-Item build/DR.dist Difficult-Rocket
python ./.github/workflows/post_compile.py python ./.github/workflows/post_compile.py

View File

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

23
DR.py
View File

@ -7,6 +7,7 @@ import sys
import time import time
import cProfile import cProfile
import traceback import traceback
import threading
from io import StringIO from io import StringIO
@ -39,7 +40,7 @@ def print_path() -> None:
# 输出一遍大部分文件位置相关信息 以后可能会加到logs里 # 输出一遍大部分文件位置相关信息 以后可能会加到logs里
def main() -> None: def main() -> int:
print(hi) # hi print(hi) # hi
start_time_ns = time.time_ns() start_time_ns = time.time_ns()
start_time_perf_ns = time.perf_counter_ns() start_time_perf_ns = time.perf_counter_ns()
@ -57,7 +58,7 @@ def main() -> None:
print('pyglet_rs available:', get_version_str()) print('pyglet_rs available:', get_version_str())
print('trying to patch pyglet_rs') print('trying to patch pyglet_rs')
patch_vector() patch_vector()
except ImportError as e: except ImportError:
print('pyglet_rs import error') print('pyglet_rs import error')
traceback.print_exc() traceback.print_exc()
try: try:
@ -82,7 +83,8 @@ def main() -> None:
if DR_option.crash_report_test: if DR_option.crash_report_test:
raise TestError('debugging') # debug 嘛试试crash raise TestError('debugging') # debug 嘛试试crash
except Exception as exp: # 出毛病了 except Exception as exp: # 出毛病了
print(error_format['error.happen']) # # 解析错误信息
print(error_format['error.happen'])
error = traceback.format_exc() error = traceback.format_exc()
name = type(exp).__name__ name = type(exp).__name__
if name in error_format: if name in error_format:
@ -90,6 +92,7 @@ def main() -> None:
else: else:
print(error_format['error.unknown']) print(error_format['error.unknown'])
print(error) print(error)
# 输出 crash 信息
crash.create_crash_report(error) crash.create_crash_report(error)
cache_steam = StringIO() cache_steam = StringIO()
crash.write_info_to_cache(cache_steam) crash.write_info_to_cache(cache_steam)
@ -99,8 +102,20 @@ def main() -> None:
crash.record_thread = False crash.record_thread = False
print(crash.all_thread) print(crash.all_thread)
print(crash.all_process) print(crash.all_process)
# join all thread
for thread in threading.enumerate():
print(thread)
if thread.name == 'MainThread' or thread == threading.main_thread() or thread == threading.current_thread():
continue
if thread.daemon:
continue
thread.join()
# stop pyglet
import pyglet
pyglet.app.exit()
print("Difficult_Rocket 已关闭") print("Difficult_Rocket 已关闭")
return 0
if __name__ == '__main__': if __name__ == '__main__':
main() sys.exit(main())

View File

@ -4,7 +4,6 @@
# All rights reserved # All rights reserved
# ------------------------------- # -------------------------------
import os
import sys import sys
import warnings import warnings
import importlib import importlib
@ -15,22 +14,19 @@ from pathlib import Path
from typing import Optional, List, Tuple from typing import Optional, List, Tuple
from Difficult_Rocket.api.types import Options from Difficult_Rocket.api.types import Options
from Difficult_Rocket.utils.new_thread import new_thread
from libs.MCDR.version import Version from libs.MCDR.version import Version
game_version = Version("0.7.2.2") # 游戏版本 game_version = Version("0.7.2.2") # 游戏版本
build_version = Version("1.2.1.0") # 编译文件版本(与游戏本体无关) build_version = Version("1.2.1.0") # 编译文件版本(与游戏本体无关)
Api_version = Version("0.0.2.0") # API 版本 Api_version = Version("0.1.0.0") # API 版本
__version__ = game_version __version__ = game_version
# TODO 解耦 DR SDK 与 DR_mod 和 DR_rs long_version: int = 15
DR_rust_version = Version("0.2.6.1") # DR 的 Rust 编写部分的版本
# 后面会移除的 DR_rs 相关信息
# DR_rs和 DR_mod 的部分正在和 DR SDK 解耦
long_version: int = 14
""" """
long_version: 一个用于标记内部协议的整数 long_version: 一个用于标记内部协议的整数
15: 完全移除 DR_rust 相关内容 解耦完成
14: BaseScreen 的每一个函数都添加了一个参数: window: "ClientWindow" 14: BaseScreen 的每一个函数都添加了一个参数: window: "ClientWindow"
13: DR_runtime 添加 API_version 13: DR_runtime 添加 API_version
12: 去除 DR_runtime global_logger 12: 去除 DR_runtime global_logger
@ -66,36 +62,15 @@ class _DR_option(Options):
DR_rust_available: bool = False DR_rust_available: bool = False
use_cProfile: bool = False use_cProfile: bool = False
use_local_logging: bool = False use_local_logging: bool = False
use_DR_rust: bool = True
# tests # tests
playing: bool = False playing: bool = False
debugging: bool = False debugging: bool = False
crash_report_test: bool = True crash_report_test: bool = False
# window option # window option
gui_scale: float = 1.0 # default 1.0 2.0 -> 2x 3 -> 3x gui_scale: float = 1.0 # default 1.0 2.0 -> 2x 3 -> 3x
def init(self, **kwargs):
try:
from libs.Difficult_Rocket_rs import test_call, get_version_str
test_call(self)
print(f'DR_rust available: {get_version_str()}')
except ImportError:
if __name__ != '__main__':
traceback.print_exc()
self.DR_rust_available = False
self.use_DR_rust = self.use_DR_rust and self.DR_rust_available
self.flush_option()
def test_rust(self):
if self.DR_rust_available:
from libs.Difficult_Rocket_rs import part_list_read_test
part_list_read_test("./configs/PartList.xml")
def draw(self):
self.DR_rust_available = True
@property @property
def std_font_size(self) -> int: def std_font_size(self) -> int:
return round(12 * self.gui_scale) return round(12 * self.gui_scale)
@ -114,9 +89,6 @@ class _DR_runtime(Options):
DR_long_version: int = long_version # DR SDK 内部协议版本 (不要问我为什么不用 Version我也在考虑 DR_long_version: int = long_version # DR SDK 内部协议版本 (不要问我为什么不用 Version我也在考虑
DR_Mod_List: List[Tuple[str, Version]] = [] # DR Mod 列表 (name, version) DR_Mod_List: List[Tuple[str, Version]] = [] # DR Mod 列表 (name, version)
DR_Rust_version: Version = DR_rust_version # 后面要去掉的 DR_rs 版本
DR_Rust_get_version: Optional[Version] = None # 后面也要去掉的 DR_rs 版本
# run status # run status
running: bool = False running: bool = False
@ -132,15 +104,6 @@ class _DR_runtime(Options):
language: str = 'zh-CN' language: str = 'zh-CN'
default_language: str = 'zh-CN' default_language: str = 'zh-CN'
def init(self, **kwargs) -> None:
with contextlib.suppress(ImportError):
from libs.Difficult_Rocket_rs import get_version_str
self.DR_Rust_get_version = Version(get_version_str())
if self.DR_Rust_get_version != self.DR_Rust_version:
relationship = 'larger' if self.DR_Rust_version > self.DR_Rust_get_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')
def load_file(self) -> bool: def load_file(self) -> bool:
with contextlib.suppress(FileNotFoundError): with contextlib.suppress(FileNotFoundError):
with open('./configs/main.toml', 'r', encoding='utf-8') as f: with open('./configs/main.toml', 'r', encoding='utf-8') as f:
@ -151,33 +114,34 @@ class _DR_runtime(Options):
return True return True
return False return False
def load_mods(self) -> None:
mod_list = self.find_mods()
def find_mods(self) -> List[str]: def find_mods(self) -> List[str]:
mods = [] mods = []
paths = Path(self.mod_path).iterdir() mod_path = Path(self.mod_path)
if not mod_path.exists():
mod_path.mkdir()
return []
paths = mod_path.iterdir()
sys.path.append(self.mod_path) sys.path.append(self.mod_path)
for mod_path in paths: for mod_path in paths:
try: 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: if importlib.util.find_spec(mod_path.name) is not None:
module = importlib.import_module(mod_path.name)
mods.append(mod_path.name) mods.append(mod_path.name)
else: else:
print(f'can not import mod {mod_path} because importlib can not find spec') print(f'can not import mod {mod_path} because importlib can not find spec')
elif mod_path.suffix in ('.pyz', '.zip'): # 处理压缩包 mod elif mod_path.suffix in ('.pyz', '.zip'): # 处理压缩包 mod
if importlib.util.find_spec(mod_path.name) is not None: if importlib.util.find_spec(mod_path.name) is not None:
module = importlib.import_module(mod_path.name) mods.append(mod_path.name)
elif mod_path.suffix == '.pyd': # pyd 扩展 mod
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name) mods.append(mod_path.name)
elif mod_path.suffix == '.py': # 处理单文件 mod elif mod_path.suffix == '.py': # 处理单文件 mod
print(f'importing mod {mod_path=} {mod_path.stem}') print(f'importing mod {mod_path=} {mod_path.stem}')
module = importlib.import_module(mod_path.stem) if importlib.util.find_spec(mod_path.stem) is not None:
mods.append(mod_path.stem) mods.append(mod_path.stem)
except ImportError: except ImportError:
print(f'ImportError when loading mod {mod_path}') print(f'ImportError when loading mod {mod_path}')
traceback.print_exc() traceback.print_exc()
self.DR_Mod_List = [(mod, Version('0.0.0-unknown')) for mod in mods]
return mods return mods
@ -185,9 +149,6 @@ DR_option = _DR_option()
DR_runtime = _DR_runtime() DR_runtime = _DR_runtime()
if DR_option.playing: if DR_option.playing:
from Difficult_Rocket.utils import new_thread
def think_it(something): def think_it(something):
return something return something

View File

@ -11,7 +11,7 @@ github: @shenjackyuanjie
gitee: @shenjackyuanjie gitee: @shenjackyuanjie
""" """
# 单独导入的(或者就这一个有用的)
# from .delivery import Delivery
# lazy之后之前全部导入的(太多了写不动__all__了 # from Difficult_Rocket.api import screen, mod, exception
__all__ = ['screen', 'mod', 'exception']

View File

@ -1,28 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
class Delivery:
def __init__(self):
# bool
self.using = False
self.read = False
self.key_board_get = False
# dic
self.ship_info = {}
self.planet_system = {}
self.main_ship_parts = {}
self.this_planet_info = {}
self.back_ground_element = {}
# value
self.back_ground_image = ''

View File

@ -10,3 +10,10 @@ mail: 3695888@qq.com
github: @shenjackyuanjie github: @shenjackyuanjie
gitee: @shenjackyuanjie gitee: @shenjackyuanjie
""" """
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']

View File

@ -0,0 +1,81 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
# system function
from typing import Tuple, List, Optional, TypeVar, TYPE_CHECKING
# from libs
from libs.MCDR.version import Version
# from DR
if TYPE_CHECKING:
from Difficult_Rocket.main import Game
from Difficult_Rocket.client import ClientWindow
else:
Game = TypeVar("Game")
ClientWindow = TypeVar("ClientWindow")
from Difficult_Rocket import DR_runtime
from ..types import Options
RequireVersion = Tuple[Version, Version]
# 第一个是最低兼容版本,第二个是最高兼容版本
# 例如: ("1.0.0", "1.1.0") 表示从1.0.0版本开始兼容,到1.1.0版本结束兼容
ForceRequire = bool
class ModInfo(Options):
"""
加载mod时候的参数
"""
"""基本信息"""
mod_id: str # mod id
name: str # mod 名称
version: Version # mod 版本
"""作者、描述"""
writer: str # 作者
link: str = "" # 作者链接
description: str = "" # 描述 (务必简洁明了)
info: str = "" # 其他信息 (可以很多很多)
"""版本相关信息"""
DR_version: RequireVersion = (DR_runtime.DR_version, DR_runtime.DR_version) # DR SDK 兼容版本
DR_Api_version: RequireVersion = (DR_runtime.API_version, DR_runtime.API_version) # DR Api版本
Mod_Require_version: List[Tuple[str, ForceRequire, RequireVersion]] = [] # mod 依赖版本
"""mod 状态"""
is_enable: bool = True # 是否启用
is_loaded: bool = False # 是否加载
"""mod 配置"""
config: Options = Options() # mod 配置存储
old_mod: Optional["ModInfo"] = None # 旧的mod实例
def on_load(self, game: Game, old_self: Optional["ModInfo"] = None) -> bool:
""" 加载时调用 """
print(f'Mod {self.mod_id} loaded')
return True
def on_client_start(self, game: Game, client: ClientWindow):
""" 客户端启动时调用 """
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_server_start(self, game: Game):
""" 服务器启动时调用 """
print(f'Mod {self.mod_id} server start')
def on_server_stop(self, game: Game):
""" 服务器停止时调用 """
print(f'Mod {self.mod_id} server stop')
def on_unload(self, game: Game):
""" 卸载时调用 """
print(f'Mod {self.mod_id} unloaded')

View File

@ -5,15 +5,14 @@
# ------------------------------- # -------------------------------
import math import math
from typing import Dict, Union, List, Optional from typing import Dict, Union, Optional
from dataclasses import dataclass from dataclasses import dataclass
# pyglet # pyglet
# import pyglet
from pyglet.image import load, AbstractImage from pyglet.image import load, AbstractImage
# Difficult Rocket # Difficult Rocket
from Difficult_Rocket.api.types import Options from Difficult_Rocket.utils.options import Options
@dataclass @dataclass
@ -30,7 +29,6 @@ class SR1PartData:
flip_y: bool flip_y: bool
explode: bool explode: bool
textures: Optional[str] = None textures: Optional[str] = None
connections: Optional[List[int]] = None
class SR1Textures(Options): class SR1Textures(Options):
@ -145,23 +143,3 @@ def xml_bool(bool_like: Union[str, int, bool, None]) -> bool:
if isinstance(bool_like, int): if isinstance(bool_like, int):
return bool_like != 0 return bool_like != 0
return False if bool_like == '0' else bool_like.lower() != 'false' return False if bool_like == '0' else bool_like.lower() != 'false'
#
#
# from xml.etree.ElementTree import Element, ElementTree
# from defusedxml.ElementTree import parse
#
# part_list = parse("../../../../textures/PartList.xml")
# part_list_root: Element = part_list.getroot()
# print(part_list_root.tag, part_list_root.attrib)
#
# part_types = part_list_root.find('PartTypes')
#
# for x in list(part_list_root):
# print(f'tag: {x.tag} attr: {x.attrib}')
#
# for part_type in list(part_list_root):
# part_type: Element
# print(f'\'{part_type.attrib.get("id")}\': \'{part_type.attrib.get("sprite")}\'')
#
#

View File

@ -4,195 +4,24 @@
# All rights reserved # All rights reserved
# ------------------------------- # -------------------------------
""" from Difficult_Rocket.utils.options import Options, FontData, Fonts, \
writen by shenjackyuanjie OptionsError, OptionNameNotDefined, OptionNotFound, \
mail: 3695888@qq.com get_type_hints_
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
import traceback __all__ = [
from dataclasses import dataclass # main class
from typing import get_type_hints, Type, List, Union, Dict, Any, Callable, Tuple, Optional, TYPE_CHECKING 'Options',
# from Difficult Rocket # data class
'FontData',
'Fonts',
__all__ = ['get_type_hints_', # exception
'Options', 'OptionsError',
'Fonts', 'OptionNameNotDefined',
'FontData', 'OptionNotFound',
'OptionsError',
'OptionNotFound',
'OptionNameNotDefined']
# other
'get_type_hints_',
]
def get_type_hints_(cls: Type):
try:
return get_type_hints(cls)
except ValueError:
return get_type_hints(cls, globalns={})
class OptionsError(Exception):
""" option 的错误基类"""
class OptionNameNotDefined(OptionsError):
""" 向初始化的 option 里添加了一个不存在于选项里的选项 """
class OptionNotFound(OptionsError):
""" 某个选项没有找到 """
class Options:
"""
Difficult Rocket 的游戏配置的存储基类
"""
name = 'Option Base'
cached_options: Dict[str, Union[str, Any]] = {}
def __init__(self, **kwargs):
"""
创建一个新的 Options 的时候的配置
如果存在 init 方法 会在设置完 kwargs 之后运行子类的 init 方法
:param kwargs:
"""
self.flush_option()
for option, value in kwargs.items():
if option not in self.cached_options:
raise OptionNameNotDefined(f"option: {option} with value: {value} is not defined")
setattr(self, option, value)
if hasattr(self, 'init'):
self.init(**kwargs)
if hasattr(self, 'load_file'):
try:
self.load_file()
except Exception:
traceback.print_exc()
self.flush_option()
if TYPE_CHECKING:
def init(self, **kwargs) -> None:
""" 如果子类定义了这个函数,则会在 __init__ 之后调用这个函数 """
def load_file(self) -> bool:
"""如果子类定义了这个函数,则会在 __init__ 和 init 之后再调用这个函数
请注意这个函数请尽量使用 try 包裹住可能出现错误的部分
否则会在控制台输出你的报错"""
def option(self) -> Dict[str, Any]:
"""
获取配置类的所有配置
:return: 自己的所有配置
"""
values = {}
for ann in self.__annotations__: # 获取类型注释
values[ann] = getattr(self, ann, None)
if values[ann] is None:
values[ann] = self.__annotations__[ann]
if not hasattr(self, 'options'):
self.options: Dict[str, Union[Callable, object]] = {}
for option, a_fun in self.options.items(): # 获取额外内容
values[option] = a_fun
for option, a_fun in values.items(): # 检查是否为 property
if a_fun is bool and getattr(self, option, None) is not None:
values[option] = False
if isinstance(a_fun, property):
try:
values[option] = getattr(self, option)
except AttributeError:
raise OptionNotFound(f'Option {option} is not found in {self.name}') from None
return values
def format(self, text: str) -> str:
"""
使用自己的选项给输入的字符串替换内容
:param text: 想替换的内容
:return: 替换之后的内容
"""
cache_option = self.flush_option()
for option, value in cache_option.items():
text = text.replace(f'{{{option}}}', str(value))
return text
def flush_option(self) -> Dict[str, Any]:
"""
刷新缓存 options 的内容
:return: 刷新过的 options
"""
self.cached_options = self.option()
return self.cached_options
def option_with_len(self) -> List[Union[List[Tuple[str, Any, Any]], int, Any]]:
options = self.flush_option()
max_len_key = 1
max_len_value = 1
max_len_value_t = 1
option_list = []
for key, value in options.items():
value_t = value if isinstance(value, Type) else type(value)
max_len_key = max(max_len_key, len(key))
max_len_value = max(max_len_value, len(str(value)))
max_len_value_t = max(max_len_value_t, len(str(value_t)))
option_list.append((key, value, value_t))
return [option_list, max_len_key, max_len_value, max_len_value_t]
@classmethod
def add_option(cls, name: str, value: Union[Callable, object]) -> Dict:
if not hasattr(cls, 'options'):
cls.options: Dict[str, Union[Callable, object]] = {}
cls.options[name] = value
return cls.options
@staticmethod
def init_option(options_class: 'Options'.__class__, init_value: Optional[dict] = None) -> 'Options':
return options_class(**init_value if init_value is not None else {})
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'
鸿蒙字体: 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'
微软等宽: str = CC
微软等宽无线: str = CM
微软等宽带电线: str = CCPL
微软等宽带电线无线: str = CMPL
得意黑: str = '得意黑'
# SS = smiley-sans
SS: str = 得意黑
@dataclass
class FontData:
""" 用于保存字体的信息 """
font_name: str = Fonts.鸿蒙简体
font_size: int = 13
bold: bool = False
italic: bool = False
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)

View File

@ -1,30 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
# system function
import ctypes
# Difficult_Rocket function
# libs function
# from MCDR.serializer import Serializable
"""
DR 内部数据传输格式类型
"""
class datas(ctypes.Structure):
_fields_: list = [("name", ctypes.c_char_p),
("uuid", ctypes.c_char_p),
]

View File

@ -5,21 +5,15 @@
# All rights reserved # All rights reserved
# ------------------------------- # -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
import inspect
# system function
import os import os
import sys
import time import time
import logging import logging
import inspect
import functools import functools
import traceback import traceback
from typing import List, Callable from typing import Callable, Dict, List, TYPE_CHECKING
from decimal import Decimal from decimal import Decimal
# third function # third function
@ -32,6 +26,8 @@ from pyglet.window import Window
from pyglet.window import key, mouse from pyglet.window import key, mouse
# Difficult_Rocket function # Difficult_Rocket function
if TYPE_CHECKING:
from Difficult_Rocket.main import Game
from Difficult_Rocket.utils import tools from Difficult_Rocket.utils import tools
from Difficult_Rocket.api.types import Options from Difficult_Rocket.api.types import Options
from Difficult_Rocket.command import line, tree from Difficult_Rocket.command import line, tree
@ -43,7 +39,6 @@ from Difficult_Rocket.client.fps.fps_log import FpsLogger
from Difficult_Rocket.client.guis.widgets import InputBox from Difficult_Rocket.client.guis.widgets import InputBox
from Difficult_Rocket.exception.command import CommandError from Difficult_Rocket.exception.command import CommandError
from Difficult_Rocket.exception.language import LanguageNotFound from Difficult_Rocket.exception.language import LanguageNotFound
from Difficult_Rocket.client.render.sr1_ship import SR1ShipRender
from Difficult_Rocket.client.screen import DRScreen, DRDEBUGScreen from Difficult_Rocket.client.screen import DRScreen, DRDEBUGScreen
@ -59,7 +54,7 @@ class ClientOption(Options):
caption: str = "Difficult Rocket v{DR_version}|DR_rs v{DR_Rust_get_version}" caption: str = "Difficult Rocket v{DR_version}|DR_rs v{DR_Rust_get_version}"
def load_file(self) -> None: def load_file(self) -> None:
file = tools.load_file('./configs/main.toml') file: dict = tools.load_file('./configs/main.toml')
self.fps = int(file['runtime']['fps']) self.fps = int(file['runtime']['fps'])
self.width = int(file['window']['width']) self.width = int(file['window']['width'])
self.height = int(file['window']['height']) self.height = int(file['window']['height'])
@ -70,7 +65,7 @@ class ClientOption(Options):
class Client: class Client:
def __init__(self, net_mode='local'): def __init__(self, game: "Game", net_mode='local'):
start_time = time.time_ns() start_time = time.time_ns()
# logging # logging
self.logger = logging.getLogger('client') self.logger = logging.getLogger('client')
@ -82,20 +77,14 @@ class Client:
self.process_name = 'Client process' self.process_name = 'Client process'
self.process_pid = os.getpid() self.process_pid = os.getpid()
self.net_mode = net_mode self.net_mode = net_mode
file_drop = bool( self.game = game
pyglet.compat_platform != 'darwin' or DR_option.pyglet_macosx_dev_test self.window = ClientWindow(game=game, net_mode=self.net_mode,
) width=self.config.width, height=self.config.height,
self.window = ClientWindow(net_mode=self.net_mode, width=self.config.width, height=self.config.height,
fullscreen=self.config.fullscreen, caption=self.config.caption, fullscreen=self.config.fullscreen, caption=self.config.caption,
resizable=self.config.resizeable, visible=self.config.visible, resizable=self.config.resizeable, visible=self.config.visible,
file_drops=file_drop) file_drops=True)
end_time = time.time_ns() end_time = time.time_ns()
self.use_time = end_time - start_time self.use_time = end_time - start_time
if DR_option.use_DR_rust:
from libs.Difficult_Rocket_rs import read_ship_test, part_list_read_test
# part_list_read_test()
# read_ship_test()
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)) self.logger.debug(tr().client.setup.use_time_ns().format(self.use_time))
@ -119,7 +108,7 @@ def _call_screen_after(func: Callable) -> Callable:
@functools.wraps(func) @functools.wraps(func)
def warped(self: "ClientWindow", *args, **kwargs): def warped(self: "ClientWindow", *args, **kwargs):
result = func(self, *args, **kwargs) result = func(self, *args, **kwargs)
for a_screen in self.screen_list: for title, a_screen in self.screen_list.items():
a_screen.window_pointer = self a_screen.window_pointer = self
# 提前帮子窗口设置好指针 # 提前帮子窗口设置好指针
if hasattr(a_screen, func.__name__): if hasattr(a_screen, func.__name__):
@ -136,7 +125,7 @@ def _call_screen_after(func: Callable) -> Callable:
def _call_screen_before(func: Callable) -> Callable: def _call_screen_before(func: Callable) -> Callable:
@functools.wraps(func) @functools.wraps(func)
def warped(self: "ClientWindow", *args, **kwargs): def warped(self: "ClientWindow", *args, **kwargs):
for a_screen in self.screen_list: for title, a_screen in self.screen_list.items():
a_screen.window_pointer = self a_screen.window_pointer = self
# 提前帮子窗口设置好指针 # 提前帮子窗口设置好指针
if hasattr(a_screen, func.__name__): if hasattr(a_screen, func.__name__):
@ -153,7 +142,7 @@ def _call_screen_before(func: Callable) -> Callable:
class ClientWindow(Window): class ClientWindow(Window):
def __init__(self, net_mode='local', *args, **kwargs): def __init__(self, game: "Game", net_mode='local', *args, **kwargs):
""" """
@param net_mode: @param net_mode:
@ -166,8 +155,10 @@ class ClientWindow(Window):
self.logger = logging.getLogger('client') self.logger = logging.getLogger('client')
self.logger.info(tr().window.setup.start()) self.logger.info(tr().window.setup.start())
# value # value
self.game = game
self.net_mode = net_mode self.net_mode = net_mode
self.run_input = False self.run_input = False
self.command_list: List[str] = []
# configs # configs
self.main_config = tools.load_file('./configs/main.toml') self.main_config = tools.load_file('./configs/main.toml')
self.game_config = tools.load_file('./configs/game.config') self.game_config = tools.load_file('./configs/game.config')
@ -181,7 +172,7 @@ class ClientWindow(Window):
# frame # frame
self.frame = pyglet.gui.Frame(self, order=20) self.frame = pyglet.gui.Frame(self, order=20)
self.M_frame = pyglet.gui.MovableFrame(self, modifier=key.LCTRL) self.M_frame = pyglet.gui.MovableFrame(self, modifier=key.LCTRL)
self.screen_list: List[BaseScreen] = [] self.screen_list: Dict[str, BaseScreen] = {}
# setup # setup
self.setup() self.setup()
# 命令显示 # 命令显示
@ -194,7 +185,7 @@ class ClientWindow(Window):
self.set_handlers(self.input_box) self.set_handlers(self.input_box)
self.input_box.enabled = True self.input_box.enabled = True
# 设置刷新率 # 设置刷新率
pyglet.clock.schedule_interval(self.draw_update, float(self.SPF)) # pyglet.clock.schedule_interval(self.draw_update, float(self.SPF))
# 完成设置后的信息输出 # 完成设置后的信息输出
self.logger.info(tr().window.os.pid_is().format(os.getpid(), os.getppid())) self.logger.info(tr().window.os.pid_is().format(os.getpid(), os.getppid()))
end_time = time.time_ns() end_time = time.time_ns()
@ -208,9 +199,9 @@ class ClientWindow(Window):
self.set_icon(pyglet.image.load('./textures/icon.png')) self.set_icon(pyglet.image.load('./textures/icon.png'))
self.load_fonts() self.load_fonts()
# TODO 读取配置文件,加载不同的屏幕,解耦 # TODO 读取配置文件,加载不同的屏幕,解耦
self.screen_list.append(DRDEBUGScreen(self)) self.screen_list['DR_debug'] = DRDEBUGScreen(self)
self.screen_list.append(DRScreen(self)) self.screen_list['DR_main'] = DRScreen(self)
self.screen_list.append(SR1ShipRender(self)) self.game.dispatch_event('on_client_start', game=self.game, client=self)
def load_fonts(self) -> None: def load_fonts(self) -> None:
fonts_folder_path = self.main_config['runtime']['fonts_folder'] fonts_folder_path = self.main_config['runtime']['fonts_folder']
@ -222,27 +213,35 @@ class ClientWindow(Window):
self.set_icon(pyglet.image.load('./textures/icon.png')) self.set_icon(pyglet.image.load('./textures/icon.png'))
self.run_input = True self.run_input = True
self.read_input() self.read_input()
pyglet.app.event_loop.run(1 / self.main_config['runtime']['fps']) try:
pyglet.app.event_loop.run(1 / self.main_config['runtime']['fps'])
except KeyboardInterrupt:
print("==========client stop. KeyboardInterrupt info==========")
traceback.print_exc()
print("==========client stop. KeyboardInterrupt info end==========")
self.dispatch_event("on_close")
sys.exit(0)
@new_thread('window read_input', daemon=True) @new_thread('window read_input', daemon=True)
def read_input(self): def read_input(self):
self.logger.debug('read_input start') self.logger.debug('read_input start')
while self.run_input: while self.run_input:
get = input(">") try:
get = input(">")
except (EOFError, KeyboardInterrupt):
self.run_input = False
break
if get in ('', ' ', '\n', '\r'): if get in ('', ' ', '\n', '\r'):
continue continue
if get == 'stop': if get == 'stop':
self.run_input = False self.run_input = False
try: self.command_list.append(get)
self.on_command(line.CommandText(get))
except CommandError:
self.logger.error(traceback.format_exc())
self.logger.debug('read_input end') self.logger.debug('read_input end')
@new_thread('window save_info') @new_thread('window save_info')
def save_info(self): def save_info(self):
self.logger.info(tr().client.config.save.start()) self.logger.info(tr().client.config.save.start())
config_file = tools.load_file('./configs/main.toml') config_file: dict = tools.load_file('./configs/main.toml')
config_file['window']['width'] = self.width config_file['window']['width'] = self.width
config_file['window']['height'] = self.height config_file['window']['height'] = self.height
config_file['runtime']['language'] = DR_runtime.language config_file['runtime']['language'] = DR_runtime.language
@ -253,8 +252,8 @@ class ClientWindow(Window):
client api client api
""" """
def add_sub_screen(self, sub_screen: BaseScreen.__class__): def add_sub_screen(self, title: str, sub_screen: type(BaseScreen)):
self.screen_list.append(sub_screen(self)) self.screen_list[title] = sub_screen(self)
""" """
draws and some event draws and some event
@ -268,9 +267,13 @@ class ClientWindow(Window):
@_call_screen_after @_call_screen_after
def on_draw(self, *dt): def on_draw(self, *dt):
# self.logger.debug('on_draw call dt: {}'.format(dt)) if self.command_list:
for command in self.command_list:
self.on_command(line.CommandText(command))
self.command_list.pop(0)
pyglet.gl.glClearColor(0.1, 0, 0, 0.0) pyglet.gl.glClearColor(0.1, 0, 0, 0.0)
self.clear() self.clear()
self.draw_update(float(self.SPF))
self.draw_batch() self.draw_batch()
@_call_screen_after @_call_screen_after
@ -434,6 +437,7 @@ class ClientWindow(Window):
@_call_screen_before @_call_screen_before
def on_close(self, source: str = 'window') -> None: def on_close(self, source: str = 'window') -> None:
self.game.dispatch_event('on_close', game=self.game, client=self, source=source)
self.logger.info(tr().window.game.stop_get().format(tr().game[source]())) self.logger.info(tr().window.game.stop_get().format(tr().game[source]()))
self.logger.info(tr().window.game.stop()) self.logger.info(tr().window.game.stop())
self.fps_log.check_list = False self.fps_log.check_list = False

View File

@ -15,17 +15,22 @@ import os
import sys import sys
import time import time
import logging import logging
import importlib
import importlib.util
import logging.config import logging.config
import multiprocessing import multiprocessing
from io import StringIO from io import StringIO
from pathlib import Path
from typing import TYPE_CHECKING
if __name__ == '__main__': # been start will not run this if __name__ == '__main__': # been start will not run this
sys.path.append('/bin/libs') sys.path.append('/bin/libs')
sys.path.append('/bin') sys.path.append('/bin')
from Difficult_Rocket import client, server, DR_option from Difficult_Rocket import client, server, DR_option, DR_runtime
if TYPE_CHECKING:
from Difficult_Rocket.api.mod import ModInfo
from Difficult_Rocket.crash import write_info_to_cache from Difficult_Rocket.crash import write_info_to_cache
from Difficult_Rocket.utils import tools from Difficult_Rocket.utils import tools
from Difficult_Rocket.utils.translate import tr from Difficult_Rocket.utils.translate import tr
@ -58,6 +63,9 @@ class Game:
# version check # version check
self.log_env() self.log_env()
self.python_version_check() self.python_version_check()
self.loaded_mods = []
# self.client = client.Client
# self.server = server.Server
self.setup() self.setup()
def log_env(self) -> None: def log_env(self) -> None:
@ -66,8 +74,67 @@ class Game:
text = cache_steam.getvalue() text = cache_steam.getvalue()
self.logger.info(text) self.logger.info(text)
def load_mods(self) -> None:
mods = []
mod_path = Path(DR_runtime.mod_path)
if not mod_path.exists():
self.logger.info(tr().main.mod.find.faild.no_mod_folder())
return
# 寻找有效 mod
paths = mod_path.iterdir()
sys.path.append(DR_runtime.mod_path)
for mod_path in paths:
try:
if mod_path.name == '__pycache__':
continue
self.logger.info(tr().main.mod.find.start().format(mod_path))
if mod_path.is_dir():
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
else:
self.logger.warning(tr().main.mod.load.faild.info().format(mod_path.name, tr().main.mod.find.faild.no_spec()))
elif mod_path.suffix in ('.pyz', '.zip', '.pyd', '.py'):
if importlib.util.find_spec(mod_path.name) is not None:
mods.append(mod_path.name)
except ImportError as e:
self.logger.warning(tr().main.mod.find.faild().format(mod_path, e))
self.logger.info(tr().main.mod.find.done())
# 加载有效 mod
module = []
for mod in mods:
try:
self.logger.info(tr().main.mod.load.start().format(mod))
mod_module = importlib.import_module(mod)
if not hasattr(mod_module, "mod_class"):
self.logger.warning(tr().main.mod.load.faild.info().format(mod, tr().main.mod.load.faild.no_mod_class()))
del mod_module # 释放内存
continue
mod_class: type(ModInfo) = mod_module.mod_class
mod_class = mod_class()
module.append(mod_class)
self.logger.info(tr().main.mod.load.info().format(mod_class.mod_id, mod_class.version))
except ImportError as e:
self.logger.warning(tr().main.mod.load.faild().format(mod, e))
self.logger.info(tr().main.mod.load.done())
self.loaded_mods = module
mod_list = []
for mod in module:
mod_list.append((mod.mod_id, mod.version))
# 调用 on_load
self.dispatch_event('on_load', game=self)
DR_runtime.DR_Mod_List = mod_list
def dispatch_event(self, event_name: str, *args, **kwargs) -> None:
for mod in self.loaded_mods:
if hasattr(mod, event_name):
try:
getattr(mod, event_name)(*args, **kwargs)
except Exception as e:
self.logger.error(tr().main.mod.event.error().format(event_name, e, mod.mod_id))
def setup(self) -> None: def setup(self) -> None:
self.client = client.Client(net_mode='local') self.load_mods()
self.client = client.Client(game=self, net_mode='local')
self.server = server.Server(net_mode='local') self.server = server.Server(net_mode='local')
def python_version_check(self) -> None: # best 3.8+ and write at 3.8.10 def python_version_check(self) -> None: # best 3.8+ and write at 3.8.10

View File

@ -4,68 +4,3 @@
# All rights reserved # All rights reserved
# ------------------------------- # -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
# system function
from typing import Tuple, List
# from libs
from MCDR.version import Version
from MCDR.serializer import Serializable
# from DR
from Difficult_Rocket import DR_runtime, Options
"""
mod系统参数
"""
MOD_loader_version = "0.1.0.0" # mod系统版本 版本号遵守 semver ++
semver_loader_version = Version(MOD_loader_version)
"""
加载mod时会更改的参数
这里的只是范例,实际加载时会根据mod配置修改
"""
RequireVersion = Tuple[Version, Version]
# 第一个是最低兼容版本,第二个是最高兼容版本
# 例如: ("1.0.0", "1.1.0") 表示从1.0.0版本开始兼容,到1.1.0版本结束兼容
ForceRequire = bool
# TODO 完善中
class MODInfo(Serializable):
"""
加载mod时候的参数
"""
"""基本信息"""
name: str # mod 名称
version: Version # mod 版本
"""作者、描述"""
writer: str # 作者
link: str = "" # 作者链接
description: str = "" # 描述 (务必简洁明了)
info: str = "" # 其他信息 (可以很多很多)
"""版本相关信息"""
DR_version: RequireVersion = (DR_runtime.DR_version, DR_runtime.DR_version) # DR SDK 兼容版本
DR_Api_version: RequireVersion = (DR_runtime.API_version, DR_runtime.API_version) # DR Api版本
Mod_Require_version: List[Tuple[str, ForceRequire, RequireVersion]] = [] # mod 依赖版本
"""mod 状态"""
is_enable: bool = True # 是否启用
is_loaded: bool = False # 是否加载
"""mod 配置"""
config: Options = Options() # mod 配置存储
"""
一些重置用函数
"""

View File

@ -4,9 +4,8 @@
# All rights reserved # All rights reserved
# ------------------------------- # -------------------------------
""" from Difficult_Rocket.api.screen import BaseScreen
writen by shenjackyuanjie from Difficult_Rocket import DR_option, DR_runtime
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""

View File

@ -4,17 +4,3 @@
# All rights reserved # All rights reserved
# ------------------------------- # -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .new_thread import new_thread
__all__ = ['new_thread']

View File

@ -7,8 +7,7 @@
import functools import functools
import inspect import inspect
import threading import threading
from Difficult_Rocket import crash, DR_option from typing import Optional, Callable, Union, List
from typing import Optional, Callable
""" """
This part of code come from MCDReforged(https://github.com/Fallen-Breath/MCDReforged) This part of code come from MCDReforged(https://github.com/Fallen-Breath/MCDReforged)
@ -17,6 +16,15 @@ GNU Lesser General Public License v3.0GNU LGPL v3)
(have some changes) (have some changes)
""" """
__all__ = [
'new_thread',
'FunctionThread'
]
record_thread = False
record_destination: List[Callable[['FunctionThread'], None]] = []
def copy_signature(target: Callable, origin: Callable) -> Callable: def copy_signature(target: Callable, origin: Callable) -> Callable:
""" """
@ -28,10 +36,13 @@ def copy_signature(target: Callable, origin: Callable) -> Callable:
class FunctionThread(threading.Thread): class FunctionThread(threading.Thread):
"""
A Thread subclass which is used in decorator :func:`new_thread` to wrap a synchronized function call
"""
__NONE = object() __NONE = object()
def __init__(self, target, name, args, kwargs): def __init__(self, target, name, args, kwargs, daemon):
super().__init__(target=target, args=args, kwargs=kwargs, name=name) super().__init__(target=target, args=args, kwargs=kwargs, name=name, daemon=daemon)
self.__return_value = self.__NONE self.__return_value = self.__NONE
self.__error = None self.__error = None
@ -40,12 +51,33 @@ class FunctionThread(threading.Thread):
self.__return_value = target(*args_, **kwargs_) self.__return_value = target(*args_, **kwargs_)
except Exception as e: except Exception as e:
self.__error = e self.__error = e
print(e)
raise e from None raise e from None
self._target = wrapped_target self._target = wrapped_target
def get_return_value(self, block: bool = False, timeout: Optional[float] = None): def get_return_value(self, block: bool = False, timeout: Optional[float] = None):
"""
Get the return value of the original function
If an exception has occurred during the original function call, the exception will be risen again here
Examples::
>>> import time
>>> @new_thread
... def do_something(text: str):
... time.sleep(1)
... return text
>>> do_something('task').get_return_value(block=True)
'task'
:param block: If it should join the thread before getting the return value to make sure the function invocation finishes
:param timeout: The maximum timeout for the thread join
:raise RuntimeError: If the thread is still alive when getting return value. Might be caused by ``block=False``
while the thread is still running, or thread join operation times out
:return: The return value of the original function
"""
if block: if block:
self.join(timeout) self.join(timeout)
if self.__return_value is self.__NONE: if self.__return_value is self.__NONE:
@ -54,30 +86,57 @@ class FunctionThread(threading.Thread):
raise self.__error raise self.__error
return self.__return_value return self.__return_value
def join(self, timeout: Optional[float] = None) -> None:
super().join(timeout)
def start(self) -> None: def new_thread(arg: Optional[Union[str, Callable]] = None,
super().start()
def new_thread(thread_name: Optional[str or Callable] = None,
daemon: bool = False, daemon: bool = False,
log_thread: bool = True): log_thread: bool = True):
""" """
Use a new thread to execute the decorated function This is a one line solution to make your function executes in parallels.
The function return value will be set to the thread instance that executes this function When decorated with this decorator, functions will be executed in a new daemon thread
The name of the thread can be specified in parameter
This decorator only changes the return value of the function to the created ``Thread`` object.
Beside the return value, it reserves all signatures of the decorated function,
so you can safely use the decorated function as if there's no decorating at all
It's also a simple compatible upgrade method for old MCDR 0.x plugins
The return value of the decorated function is changed to the ``Thread`` object that executes this function
The decorated function has 1 extra field:
* ``original`` field: The original undecorated function
Examples::
>>> import time
>>> @new_thread('My Plugin Thread')
... def do_something(text: str):
... time.sleep(1)
... print(threading.current_thread().name)
>>> callable(do_something.original)
True
>>> t = do_something('foo')
>>> isinstance(t, FunctionThread)
True
>>> t.join()
My Plugin Thread
:param arg: A :class:`str`, the name of the thread. It's recommend to specify the thread name, so when you
log something by ``server.logger``, a meaningful thread name will be displayed
instead of a plain and meaningless ``Thread-3``
:param daemon: If the thread should be a daemon thread
:param log_thread: If the thread should be logged to callback defined in record_destination
""" """
def wrapper(func): def wrapper(func):
@functools.wraps(func) # to preserve the origin function information @functools.wraps(func) # to preserve the origin function information
def wrap(*args, **kwargs): def wrap(*args, **kwargs):
thread = FunctionThread(target=func, args=args, kwargs=kwargs, name=thread_name) thread = FunctionThread(target=func, args=args, kwargs=kwargs, name=thread_name, daemon=daemon)
thread.daemon = daemon if record_thread:
for destination in record_destination:
destination(thread)
thread.start() thread.start()
if log_thread and DR_option.record_threads:
crash.all_thread.append(thread)
return thread return thread
# bring the signature of the func to the wrap function # bring the signature of the func to the wrap function
@ -86,10 +145,11 @@ def new_thread(thread_name: Optional[str or Callable] = None,
wrap.original = func # access this field to get the original function wrap.original = func # access this field to get the original function
return wrap return wrap
# Directly use @on_new_thread without ending brackets case # Directly use @new_thread without ending brackets case, e.g. @new_thread
if isinstance(thread_name, Callable): if isinstance(arg, Callable):
this_is_a_function = thread_name
thread_name = None thread_name = None
return wrapper(this_is_a_function) return wrapper(arg)
# Use @on_new_thread with ending brackets case # Use @new_thread with ending brackets case, e.g. @new_thread('A'), @new_thread()
return wrapper else:
thread_name = arg
return wrapper

View File

@ -0,0 +1,194 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import traceback
from dataclasses import dataclass
from typing import get_type_hints, Type, List, Union, Dict, Any, Callable, Tuple, Optional, TYPE_CHECKING
__all__ = ['get_type_hints_',
'Options',
'Fonts',
'FontData',
'OptionsError',
'OptionNotFound',
'OptionNameNotDefined']
def get_type_hints_(cls: Type):
try:
return get_type_hints(cls)
except ValueError:
return get_type_hints(cls, globalns={})
class OptionsError(Exception):
""" option 的错误基类"""
class OptionNameNotDefined(OptionsError):
""" 向初始化的 option 里添加了一个不存在于选项里的选项 """
class OptionNotFound(OptionsError):
""" 某个选项没有找到 """
class Options:
"""
Difficult Rocket 的游戏配置的存储基类
"""
name = 'Option Base'
cached_options: Dict[str, Union[str, Any]] = {}
def __init__(self, **kwargs):
"""
创建一个新的 Options 的时候的配置
如果存在 init 方法 会在设置完 kwargs 之后运行子类的 init 方法
:param kwargs:
"""
if TYPE_CHECKING:
self.options: Dict[str, Union[Callable, object]] = {}
self.flush_option()
for option, value in kwargs.items():
if option not in self.cached_options:
raise OptionNameNotDefined(f"option: {option} with value: {value} is not defined")
setattr(self, option, value)
if hasattr(self, 'init'):
self.init(**kwargs)
if hasattr(self, 'load_file'):
try:
self.load_file()
except Exception:
traceback.print_exc()
self.flush_option()
if TYPE_CHECKING:
options: Dict[str, Union[Callable, object]] = {}
def init(self, **kwargs) -> None:
""" 如果子类定义了这个函数,则会在 __init__ 之后调用这个函数 """
def load_file(self) -> bool:
"""如果子类定义了这个函数,则会在 __init__ 和 init 之后再调用这个函数
请注意这个函数请尽量使用 try 包裹住可能出现错误的部分
否则会在控制台输出你的报错"""
return True
def option(self) -> Dict[str, Any]:
"""
获取配置类的所有配置
:return: 自己的所有配置
"""
values = {}
for ann in self.__annotations__: # 获取类型注释
values[ann] = getattr(self, ann, None)
if values[ann] is None:
values[ann] = self.__annotations__[ann]
if not hasattr(self, 'options'):
self.options: Dict[str, Union[Callable, object]] = {}
for option, a_fun in self.options.items(): # 获取额外内容
values[option] = a_fun
for option, a_fun in values.items(): # 检查是否为 property
if a_fun is bool and getattr(self, option, None) is not None:
values[option] = False
if isinstance(a_fun, property):
try:
values[option] = getattr(self, option)
except AttributeError:
raise OptionNotFound(f'Option {option} is not found in {self.name}') from None
return values
def format(self, text: str) -> str:
"""
使用自己的选项给输入的字符串替换内容
:param text: 想替换的内容
:return: 替换之后的内容
"""
cache_option = self.flush_option()
for option, value in cache_option.items():
text = text.replace(f'{{{option}}}', str(value))
return text
def flush_option(self) -> Dict[str, Any]:
"""
刷新缓存 options 的内容
:return: 刷新过的 options
"""
self.cached_options = self.option()
return self.cached_options
def option_with_len(self) -> List[Union[List[Tuple[str, Any, Any]], int, Any]]:
options = self.flush_option()
max_len_key = 1
max_len_value = 1
max_len_value_t = 1
option_list = []
for key, value in options.items():
value_t = value if isinstance(value, Type) else type(value)
max_len_key = max(max_len_key, len(key))
max_len_value = max(max_len_value, len(str(value)))
max_len_value_t = max(max_len_value_t, len(str(value_t)))
option_list.append((key, value, value_t))
return [option_list, max_len_key, max_len_value, max_len_value_t]
@classmethod
def add_option(cls, name: str, value: Union[Callable, object]) -> Dict:
if not hasattr(cls, 'options'):
cls.options: Dict[str, Union[Callable, object]] = {}
cls.options[name] = value
return cls.options
@staticmethod
def init_option(options_class: Type['Options'], init_value: Optional[dict] = None) -> 'Options':
return options_class(**init_value if init_value is not None else {})
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'
鸿蒙字体: 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'
微软等宽: str = CC
微软等宽无线: str = CM
微软等宽带电线: str = CCPL
微软等宽带电线无线: str = CMPL
得意黑: str = '得意黑'
# SS = smiley-sans
SS: str = 得意黑
@dataclass
class FontData:
""" 用于保存字体的信息 """
font_name: str = Fonts.鸿蒙简体
font_size: int = 13
bold: bool = False
italic: bool = False
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)

View File

@ -121,8 +121,8 @@ class Translates:
self._config.is_final = True self._config.is_final = True
return self return self
def __call__(self, *args, **kwargs) -> Union[dict, list, int, str]: def __call__(self, *args, **kwargs) -> str:
return self.__str__() return str(self)
def copy(self): def copy(self):
return self.__copy__() return self.__copy__()

20
biggit.ps1 Normal file
View File

@ -0,0 +1,20 @@
$objects = git verify-pack -v .git/objects/pack/pack-aba4bfc55979194c86dbd466c86e57d8199ae7ad.idx | Select-String -Pattern 'chain' -NotMatch | ForEach-Object {$_.Line} | Sort-Object -Property @{Expression={$_.Split(" ")[2]}; Ascending=$false} | Select-Object -First 50
Write-Output "All sizes are in kB. The pack column is the size of the object, compressed, inside the pack file."
$output = "size,pack,SHA,location"
foreach ($y in $objects) {
# extract the size in bytes
$size = [int]($y.Split(" ")[4]/1024)
# extract the compressed size in bytes
$compressedSize = [int]($y.Split(" ")[5]/1024)
# extract the SHA
$sha = $y.Split(" ")[0]
# find the objects location in the repository tree
$other = git rev-list --all --objects | Select-String $sha
$output += "`n${size},${compressedSize},${other}"
}
Write-Output $output | ConvertFrom-Csv -Delimiter "," | Format-Table
Pause

View File

@ -1,4 +1,4 @@
Set-Location .\libs\Difficult_Rocket_rs\src Set-Location .\mods\dr_game\Difficult_Rocket_rs\src
Write-Output $args[0] Write-Output $args[0]
@ -23,6 +23,6 @@ if ($do -or "311" -in $args) {
python3.11 setup.py build python3.11 setup.py build
} }
python3 post_build.py python3.8 post_build.py
Set-Location ..\..\..\ Set-Location ..\..\..\..\

View File

@ -1,4 +0,0 @@
[command]
log = 1000
show = 20
size = 12

View File

@ -20,7 +20,16 @@ logger.logfile_name = "Log file name : "
logger.logfile_level = "Log file record level : " logger.logfile_level = "Log file record level : "
logger.logfile_fmt = "Log file record format : " logger.logfile_fmt = "Log file record format : "
logger.logfile_datefmt = "Log file date format : " logger.logfile_datefmt = "Log file date format : "
game_start.at = "The main thread of the game starts with : " game_start.at = "Game MainThread start at: {}"
mod.find.start = "Checking Mod: {}"
mod.find.faild.no_spec = "importlib can't find spec"
mod.find.faild.no_mod_folder = "Can't find mod folder"
mod.find.done = "All Mod checked"
mod.load.start = "Loading Mod: {}"
mod.load.info = "mod id: {} version: {}"
mod.load.faild.info = "Mod load failed: {} error info: {}"
mod.load.faild.no_mod_class = "Can't find Mod class"
mod.load.done = "All Mod loaded"
[client] [client]
setup.start = "Client start loading" setup.start = "Client start loading"

View File

@ -21,6 +21,16 @@ logger.logfile_level = "日志文件记录级别:"
logger.logfile_fmt = "日志文件记录格式:" logger.logfile_fmt = "日志文件记录格式:"
logger.logfile_datefmt = "日志文件日期格式:" logger.logfile_datefmt = "日志文件日期格式:"
game_start.at = "游戏主线程开始于:" game_start.at = "游戏主线程开始于:"
mod.find.start = "正在校验 Mod: {}"
mod.find.faild.no_spec = "importlib 无法找到 spec"
mod.find.faild.no_mod_folder = "没有找到 Mod 文件夹"
mod.find.done = "所有 Mod 校验完成"
mod.load.start = "正在加载 Mod: {}"
mod.load.info = "mod id: {} 版本号: {}"
mod.load.faild.info = "Mod 加载失败: {} 错误信息: {}"
mod.load.faild.no_mod_class = "没有找到 Mod 类"
mod.load.done = "所有 Mod 加载完成"
mod.event.error = "Mod 事件 {} 发生错误 {} Mod: {}"
[client] [client]
setup.start = "客户端加载开始" setup.start = "客户端加载开始"

View File

@ -7,8 +7,8 @@ fonts_folder = "libs/fonts"
[window] [window]
style = "None" style = "None"
width = 1406 width = 2571
height = 900 height = 1510
visible = true visible = true
gui_scale = 1 gui_scale = 1
caption = "Difficult Rocket v{DR_version}|DR_rs v{DR_Rust_get_version}" caption = "Difficult Rocket v{DR_version}|DR_rs v{DR_Rust_get_version}"
@ -19,3 +19,5 @@ full_screen = false
width = 1024 width = 1024
height = 768 height = 768
gui_scale = 1 gui_scale = 1
[game.mods]
path = "mods"

View File

@ -22,15 +22,54 @@
- [![Readme-gitee](https://img.shields.io/badge/Readme-中文(点我!)-blue.svg?style=flat-square)](../../README.md) - [![Readme-gitee](https://img.shields.io/badge/Readme-中文(点我!)-blue.svg?style=flat-square)](../../README.md)
- Using [SemVer 2.0.0](https://semver.org/) to manage version - Using [SemVer 2.0.0](https://semver.org/) to manage version
## 202305 DR `0.8.0.0` + DR_api `0.1.0.0` ## 202305 DR `0.8.0.0` + DR_api `0.1.0.0` + DR_rs `0.2.7.0` + 15
> 啊哈! mod 加载来啦! > 啊哈! mod 加载来啦!
> 啊啊啊啊啊 大重构 api
### DR_api `0.1.0.0`
- 大概是一个可用的版本了
- `ModInfo`
- `on_load(game: Game, old_self: Optional[ModInfo]) -> bool`
- `game`: Game 对象 用于存储 DR SDK 的信息
- `old_self`: 旧的 ModInfo 对象, 可以用于从上次加载中恢复信息
- 返回值: 是否加载成功
- `on_client_start(game: Game, client: ClientWindow) -> None`
- `game`: Game 对象 用于存储 DR SDK 的信息
- `client`: ClientWindow 对象 用于传递客户端状态
- `on_client_stop(game: Game, client: ClientWindow, source: str = 'window')`
- `game`: Game 对象 用于存储 DR SDK 的信息
- `client`: ClientWindow 对象 用于传递客户端状态
- `source`: 关闭调用的来源
### DR_rs `0.2.7.0`
- `__init__.py`
- 添加了 `SR1Ship_rs` 的 typing
- `name`
- `description`
- `lift_off`
- `touch_ground`
- `img_pos() -> Tuple[int, int, int, int]`
- 导出了 `SR1Ship_rs`
- Exported `SR1Ship_rs`
- `types::SR1PartData`
- `get_box(&self, part_type: &SR1PartType) -> (f64, f64, f64, f64)`
- `types::SR1Ship`
- `from_file`
### Remove ### Remove
- `game.config` - `game.config`
- 已删除 - 已删除
- Removed - Removed
- `DR_option` & `DR_runtime`(`long_version` `15`)
- 完全移除 `DR_rust` 部分
- Completely removed the `DR_rust` part
- 现在 `client` 不会在 `setup()` 中调用 `DR_runtime``find_mods()` 方法
- Now `client` will not call the `find_mods()` method of `DR_runtime` in `setup()`
### Changes ### Changes
@ -53,6 +92,14 @@
- 现在游戏崩溃时会自动在 stdio 中输出崩溃日志 内容跟 crash report 中的基本相同 - 现在游戏崩溃时会自动在 stdio 中输出崩溃日志 内容跟 crash report 中的基本相同
- Now when the game crashes, it will automatically output the crash log in stdio - Now when the game crashes, it will automatically output the crash log in stdio
- The content of the crash log is basically the same as the crash report - The content of the crash log is basically the same as the crash report
- `utils.new_thread`
- 跟随 MCDR 的更新
- 将记录线程的方式改成 函数回调
- Follow the update of MCDR
- Change the way to record threads to function callbacks
- `Difficult_Rocket.api`
- 大重构,移除定义,改为引用
- Big refactoring, remove definition, change to reference
### Docs ### Docs
@ -67,6 +114,13 @@
### Mod Loader ### Mod Loader
- `ModInfo` - `ModInfo`
- `on_load(game: Game, old_self: Optional[ModInfo]) -> bool`
- `game`: Game 对象 用于存储 DR SDK 的信息
- `old_self`: 旧的 ModInfo 对象, 可以用于从上次加载中恢复信息
- 返回值: 是否加载成功
- `game`: Game object used to store information about the DR SDK
- `old_self`: Old ModInfo object, can be used to restore information from the last load
- Return value: Whether the load is successful
## 20230422 DR `0.7.2.2` + DR_rs `0.2.6.1` + DR_api `0.0.2.0` + 14 ## 20230422 DR `0.7.2.2` + DR_rs `0.2.6.1` + DR_api `0.0.2.0` + 14
@ -147,6 +201,13 @@
- 修改了中文语言的翻译,在中英混杂的中间加入空格 - 修改了中文语言的翻译,在中英混杂的中间加入空格
- Modified the Chinese translation, and added a space in the middle of Chinese and English - Modified the Chinese translation, and added a space in the middle of Chinese and English
### Fix
- `mods` 路径不存在时 游戏会崩溃的 bug
- Bug that the game will crash when the `mods` path does not exist
- 游戏崩溃 / 从控制台退出时 `WerFault.exe` 会跳出来卡住程序的 bug
- Bug that `WerFault.exe` will pop up and block the program when the game crashes / exits from the console
## 20230405 DR `0.7.2.1` ## 20230405 DR `0.7.2.1`
### Changes ### Changes

Binary file not shown.

View File

@ -3,7 +3,6 @@ from .base import Display, Screen, ScreenMode, Canvas
from pyglet.libs.win32 import _user32 from pyglet.libs.win32 import _user32
from pyglet.libs.win32.constants import * from pyglet.libs.win32.constants import *
from pyglet.libs.win32.types import * from pyglet.libs.win32.types import *
from pyglet.libs.win32.context_managers import device_context
class Win32Display(Display): class Win32Display(Display):
@ -31,13 +30,13 @@ class Win32Screen(Screen):
self._handle = handle self._handle = handle
def get_matching_configs(self, template): def get_matching_configs(self, template):
with device_context(None) as hdc: hdc = _user32.GetDC(0)
canvas = Win32Canvas(self.display, 0, hdc) canvas = Win32Canvas(self.display, 0, hdc)
configs = template.match(canvas) configs = template.match(canvas)
# XXX deprecate config's being screen-specific # XXX deprecate config's being screen-specific
for config in configs: for config in configs:
config.screen = self config.screen = self
_user32.ReleaseDC(0, hdc)
return configs return configs
def get_device_name(self): def get_device_name(self):

View File

@ -9,7 +9,6 @@ from pyglet.font import base
from pyglet.font import win32query from pyglet.font import win32query
import pyglet.image import pyglet.image
from pyglet.libs.win32.constants import * from pyglet.libs.win32.constants import *
from pyglet.libs.win32.context_managers import device_context
from pyglet.libs.win32.types import * from pyglet.libs.win32.types import *
from pyglet.libs.win32 import _gdi32 as gdi32, _user32 as user32 from pyglet.libs.win32 import _gdi32 as gdi32, _user32 as user32
from pyglet.libs.win32 import _kernel32 as kernel32 from pyglet.libs.win32 import _kernel32 as kernel32
@ -196,13 +195,14 @@ class Win32Font(base.Font):
self.hfont = gdi32.CreateFontIndirectW(byref(self.logfont)) self.hfont = gdi32.CreateFontIndirectW(byref(self.logfont))
# Create a dummy DC for coordinate mapping # Create a dummy DC for coordinate mapping
with device_context(None) as dc: dc = user32.GetDC(0)
metrics = TEXTMETRIC() metrics = TEXTMETRIC()
gdi32.SelectObject(dc, self.hfont) gdi32.SelectObject(dc, self.hfont)
gdi32.GetTextMetricsA(dc, byref(metrics)) gdi32.GetTextMetricsA(dc, byref(metrics))
self.ascent = metrics.tmAscent self.ascent = metrics.tmAscent
self.descent = -metrics.tmDescent self.descent = -metrics.tmDescent
self.max_glyph_width = metrics.tmMaxCharWidth self.max_glyph_width = metrics.tmMaxCharWidth
user32.ReleaseDC(0, dc)
def __del__(self): def __del__(self):
gdi32.DeleteObject(self.hfont) gdi32.DeleteObject(self.hfont)
@ -210,22 +210,22 @@ class Win32Font(base.Font):
@staticmethod @staticmethod
def get_logfont(name, size, bold, italic, dpi): def get_logfont(name, size, bold, italic, dpi):
# Create a dummy DC for coordinate mapping # Create a dummy DC for coordinate mapping
with device_context(None) as dc: dc = user32.GetDC(0)
if dpi is None: if dpi is None:
dpi = 96 dpi = 96
logpixelsy = dpi logpixelsy = dpi
logfont = LOGFONTW()
# Conversion of point size to device pixels
logfont.lfHeight = int(-size * logpixelsy // 72)
if bold:
logfont.lfWeight = FW_BOLD
else:
logfont.lfWeight = FW_NORMAL
logfont.lfItalic = italic
logfont.lfFaceName = name
logfont.lfQuality = ANTIALIASED_QUALITY
logfont = LOGFONTW()
# Conversion of point size to device pixels
logfont.lfHeight = int(-size * logpixelsy // 72)
if bold:
logfont.lfWeight = FW_BOLD
else:
logfont.lfWeight = FW_NORMAL
logfont.lfItalic = italic
logfont.lfFaceName = name
logfont.lfQuality = ANTIALIASED_QUALITY
user32.ReleaseDC(0, dc)
return logfont return logfont
@classmethod @classmethod

View File

@ -71,7 +71,6 @@ appropriate typeface name and create the font using CreateFont or
CreateFontIndirect. CreateFontIndirect.
""" """
from pyglet.libs.win32.context_managers import device_context
DEBUG = False DEBUG = False
@ -386,33 +385,36 @@ def query(charset=DEFAULT_CHARSET):
global FONTDB global FONTDB
# 1. Get device context of the entire screen # 1. Get device context of the entire screen
with device_context(None) as hdc: hdc = user32.GetDC(None)
# 2. Call EnumFontFamiliesExA (ANSI version) # 2. Call EnumFontFamiliesExA (ANSI version)
# 2a. Call with empty font name to query all available fonts # 2a. Call with empty font name to query all available fonts
# (or fonts for the specified charset) # (or fonts for the specified charset)
# #
# NOTES: # NOTES:
# #
# * there are fonts that don't support ANSI charset # * there are fonts that don't support ANSI charset
# * for DEFAULT_CHARSET font is passed to callback function as # * for DEFAULT_CHARSET font is passed to callback function as
# many times as charsets it supports # many times as charsets it supports
# [ ] font name should be less than 32 symbols with terminating \0 # [ ] font name should be less than 32 symbols with terminating \0
# [ ] check double purpose - enumerate all available font names # [ ] check double purpose - enumerate all available font names
# - enumerate all available charsets for a single font # - enumerate all available charsets for a single font
# - other params? # - other params?
logfont = LOGFONTW(0, 0, 0, 0, 0, 0, 0, 0, charset, 0, 0, 0, 0, '') logfont = LOGFONTW(0, 0, 0, 0, 0, 0, 0, 0, charset, 0, 0, 0, 0, '')
FONTDB = [] # clear cached FONTDB for enum_font_names callback FONTDB = [] # clear cached FONTDB for enum_font_names callback
res = gdi32.EnumFontFamiliesExW( res = gdi32.EnumFontFamiliesExW(
hdc, # handle to device context hdc, # handle to device context
ctypes.byref(logfont), ctypes.byref(logfont),
enum_font_names, # pointer to callback function enum_font_names, # pointer to callback function
0, # lParam - application-supplied data 0, # lParam - application-supplied data
0) # dwFlags - reserved = 0 0) # dwFlags - reserved = 0
# res here is the last value returned by callback function # res here is the last value returned by callback function
# 3. Release DC
user32.ReleaseDC(None, hdc)
return FONTDB return FONTDB

View File

@ -1,57 +0,0 @@
"""
Win32 resources as handy context managers.
These are intended to help keep loader code clean & stable at the price
of a tiny bit of execution speed. Performance-critical code should avoid
using these helpers in favor of direct win32 API calls.
Before::
def loader_function(arg):
context_handle = user32.GetResource(None)
result = calculation(arg)
# Easily forgotten!
user32.ReleaseResource(context_handle)
return result
After::
def loader_function(arg):
with resource_context() as context_handle:
result = calculation(arg)
return result
"""
from contextlib import contextmanager
from typing import Optional, Generator
from ctypes.wintypes import HANDLE
from ctypes import WinError
from pyglet.libs.win32 import _user32 as user32
@contextmanager
def device_context(window_handle: Optional[int] = None) -> Generator[HANDLE, None, None]:
"""
A Windows device context wrapped as a context manager.
Args:
window_handle: A specific window handle to use, if any.
Raises:
WinError: Raises if a device context cannot be acquired or released
Yields:
HANDLE: the managed drawing context handle to auto-close.
"""
if not (_dc := user32.GetDC(window_handle)):
raise WinError()
try:
yield _dc
finally:
if not user32.ReleaseDC(None, _dc):
raise WinError()

View File

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

View File

@ -40,22 +40,16 @@ if TYPE_CHECKING:
@property @property
def dx(self) -> float: ... def dx(self) -> float: ...
@property @property
def dy(self) -> float: ... def dy(self) -> float: ...
@property @property
def zoom(self) -> float: ... def zoom(self) -> float: ...
@property @property
def position(self) -> Tuple[float, float]: ... def position(self) -> Tuple[float, float]: ...
@dx.setter @dx.setter
def dx(self, value: float) -> None: ... def dx(self, value: float) -> None: ...
@dy.setter @dy.setter
def dy(self, value: float) -> None: ... def dy(self, value: float) -> None: ...
@zoom.setter @zoom.setter
def zoom(self, value: float) -> None: ... def zoom(self, value: float) -> None: ...
@ -93,3 +87,17 @@ if TYPE_CHECKING:
def as_dict(self) -> Dict[str, SR1PartType_rs]: ... def as_dict(self) -> Dict[str, SR1PartType_rs]: ...
def get_part_type(self, name: str) -> SR1PartType_rs: ... def get_part_type(self, name: str) -> SR1PartType_rs: ...
class SR1Ship_rs:
""" 用于高效且省内存的读取 SR1Ship """
@property
def name(self) -> str: ...
@property
def description(self) -> str: ...
@property
def lift_off(self) -> bool: ...
@property
def touch_ground(self) -> bool: ...
@property
def img_pos(self) -> Tuple[int, int, int, int]: ...
""" -x -y +x +y 左下右上 """

View File

@ -3,22 +3,16 @@
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com # Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved # All rights reserved
# ------------------------------- # -------------------------------
import os
import sys import sys
import shutil import shutil
from setuptools import setup from setuptools import setup
from setuptools_rust import Binding, RustExtension from setuptools_rust import Binding, RustExtension
sys.path.append('../../../')
sys.path.append(os.curdir)
if '../../../' in sys.path:
from Difficult_Rocket import DR_runtime
package_path = 'Difficult_Rocket_rs' package_path = 'Difficult_Rocket_rs'
setup( setup(
name='Difficult_Rocket_rs', name='Difficult_Rocket_rs',
version=DR_runtime.DR_Rust_version.__str__(), version="0.2.7.0",
author='shenjackyuanjie', author='shenjackyuanjie',
author_email='3695888@qq.com', author_email='3695888@qq.com',
rust_extensions=[RustExtension(target="Difficult_Rocket_rs.Difficult_Rocket_rs", rust_extensions=[RustExtension(target="Difficult_Rocket_rs.Difficult_Rocket_rs",

View File

@ -17,7 +17,7 @@ mod types;
use pyo3::prelude::*; use pyo3::prelude::*;
#[pyfunction] #[pyfunction]
fn get_version_str() -> String { "0.2.6.1".to_string() } fn get_version_str() -> String { "0.2.7.0".to_string() }
#[pyfunction] #[pyfunction]
fn test_call(py_obj: &PyAny) -> PyResult<bool> { fn test_call(py_obj: &PyAny) -> PyResult<bool> {
@ -39,6 +39,7 @@ fn module_init(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<render::camera::CameraRs>()?; m.add_class::<render::camera::CameraRs>()?;
m.add_class::<render::camera::CenterCameraRs>()?; m.add_class::<render::camera::CenterCameraRs>()?;
m.add_class::<render::screen::PartFrame>()?; m.add_class::<render::screen::PartFrame>()?;
m.add_class::<python::data::PySR1Ship>()?;
m.add_class::<python::data::PySR1PartList>()?; m.add_class::<python::data::PySR1PartList>()?;
m.add_class::<python::data::PySR1PartType>()?; m.add_class::<python::data::PySR1PartType>()?;
Ok(()) Ok(())

View File

@ -15,11 +15,10 @@
pub mod plugin_trait { pub mod plugin_trait {
pub struct ModInfo { pub struct ModInfo {
pub name: String, pub name: String,
pub version: String pub version: String,
} }
pub trait ModInfoTrait { pub trait ModInfoTrait {
fn info() -> ModInfo; fn info() -> ModInfo;
} }
} }

View File

@ -13,8 +13,8 @@ pub mod data {
use crate::sr1_data::part_list::RawPartList; use crate::sr1_data::part_list::RawPartList;
use crate::sr1_data::ship::RawShip; use crate::sr1_data::ship::RawShip;
use crate::types::sr1::{SR1PartData, SR1PartListTrait};
use crate::types::sr1::{SR1PartList, SR1PartType, SR1Ship}; use crate::types::sr1::{SR1PartList, SR1PartType, SR1Ship};
use crate::types::sr1::{SR1PartListTrait, SR1ShipTrait};
#[pyclass] #[pyclass]
#[pyo3(name = "SR1PartType_rs")] #[pyo3(name = "SR1PartType_rs")]
@ -70,6 +70,12 @@ pub mod data {
} }
} }
#[pyclass]
#[pyo3(name = "SR1PartData_rs")]
pub struct PySR1PartData {
pub data: SR1PartData,
}
#[pyclass] #[pyclass]
#[pyo3(name = "SR1Ship_rs")] #[pyo3(name = "SR1Ship_rs")]
#[pyo3(text_signature = "(file_path = './configs/dock1.xml', part_list = './configs/PartList.xml', ship_name = 'NewShip')")] #[pyo3(text_signature = "(file_path = './configs/dock1.xml', part_list = './configs/PartList.xml', ship_name = 'NewShip')")]
@ -82,11 +88,29 @@ pub mod data {
impl PySR1Ship { impl PySR1Ship {
#[new] #[new]
fn new(file_path: String, part_list: String, ship_name: String) -> Self { fn new(file_path: String, part_list: String, ship_name: String) -> Self {
let raw_ship: RawShip = RawShip::from_file(file_path).unwrap(); let ship = SR1Ship::from_file(file_path, Some(ship_name)).unwrap();
let ship = raw_ship.to_sr_ship(Some(ship_name));
let part_list = SR1PartList::from_file(part_list).unwrap(); let part_list = SR1PartList::from_file(part_list).unwrap();
Self { ship, part_list } Self { ship, part_list }
} }
fn get_img_pos(&self) -> (i64, i64, i64, i64) {
let mut img_pos = (0, 0, 0, 0);
// -x, -y, +x, +y
// 左下角,右上角
for part in self.ship.types.iter() {
// let part_box = part
todo!("get_img_pos")
}
img_pos
}
fn get_name(&self) -> String { self.ship.name.clone() }
fn get_description(&self) -> String { self.ship.description.clone() }
fn get_lift_off(&self) -> bool { self.ship.lift_off }
fn get_touch_ground(&self) -> bool { self.ship.touch_ground }
} }
} }
@ -94,6 +118,12 @@ pub mod translate {
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::PyDict; use pyo3::types::PyDict;
#[derive(Clone)]
pub enum BoolString {
Bool(bool),
String(String),
}
#[pyclass] #[pyclass]
#[pyo3(name = "TranslateConfig_rs")] #[pyo3(name = "TranslateConfig_rs")]
#[pyo3(text_signature = "(language, raise_error = False, replace_normal = False, add_error = False, is_result = False, keep_get = False)")] #[pyo3(text_signature = "(language, raise_error = False, replace_normal = False, add_error = False, is_result = False, keep_get = False)")]
@ -121,6 +151,13 @@ pub mod translate {
language: language.unwrap_or(default_language), language: language.unwrap_or(default_language),
} }
} }
// fn set(&self, py_: Python, item: String, value: BoolString) -> &Self {
// match item.as_str() {
// "raise_error" => self,
// _ => self,
// }
// }
} }
#[pyclass] #[pyclass]

View File

@ -9,10 +9,9 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use rapier2d_f64::prelude::*; use rapier2d_f64::prelude::*;
#[pyfunction] #[pyfunction]
#[pyo3(name = "simluation")] #[pyo3(name = "simluation")]
pub fn simluation() -> PyResult<()> { pub fn simluation() -> () {
let mut rigid_body_set = RigidBodySet::new(); let mut rigid_body_set = RigidBodySet::new();
let mut collider_set = ColliderSet::new(); let mut collider_set = ColliderSet::new();
@ -62,6 +61,4 @@ pub fn simluation() -> PyResult<()> {
let ball_body = &rigid_body_set[ball_body_handle]; let ball_body = &rigid_body_set[ball_body_handle];
println!("Ball altitude: {} {}", ball_body.translation().x, ball_body.translation().y); println!("Ball altitude: {} {}", ball_body.translation().x, ball_body.translation().y);
} }
Ok(())
} }

View File

@ -8,7 +8,9 @@
pub mod sr1 { pub mod sr1 {
use std::collections::HashMap; use std::collections::HashMap;
use std::fs;
use super::math::{Edge, Shape};
use crate::sr1_data::part_list::Damage as RawDamage; use crate::sr1_data::part_list::Damage as RawDamage;
use crate::sr1_data::part_list::{AttachPoint, AttachPoints, Engine, Lander, Rcs, Shape as RawShape, Solar, Tank}; use crate::sr1_data::part_list::{AttachPoint, AttachPoints, Engine, Lander, Rcs, Shape as RawShape, Solar, Tank};
use crate::sr1_data::part_list::{RawPartList, RawPartType, SR1PartTypeEnum}; use crate::sr1_data::part_list::{RawPartList, RawPartType, SR1PartTypeEnum};
@ -179,31 +181,49 @@ pub mod sr1 {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SR1PartList { pub struct SR1PartList {
pub types: Vec<SR1PartType>, pub types: Vec<SR1PartType>,
pub cache: Option<HashMap<String, SR1PartType>>, pub cache: HashMap<String, SR1PartType>,
pub name: String, pub name: String,
} }
impl SR1PartList { impl SR1PartList {
#[inline]
pub fn new(name: String, types: Vec<SR1PartType>) -> SR1PartList {
let mut map = HashMap::new();
for part in types.iter() {
map.insert(part.id.clone(), part.clone());
}
SR1PartList {
types,
cache: map,
name,
}
}
#[inline] #[inline]
pub fn from_file(file_name: String) -> Option<SR1PartList> { pub fn from_file(file_name: String) -> Option<SR1PartList> {
if let Some(raw_list) = RawPartList::from_file(file_name) { if let Some(raw_list) = RawPartList::from_file(file_name) {
return Some(raw_list.to_sr_part_list(None)); let sr_list = raw_list.to_sr_part_list(None);
let mut map = HashMap::new();
for part in sr_list.types.iter() {
map.insert(part.id.clone(), part.clone());
}
} }
None None
} }
#[inline] #[inline]
pub fn get_hash_map(&mut self) -> HashMap<String, SR1PartType> { pub fn get_part_type(self, type_name: String) -> Option<SR1PartType> {
if let Some(map) = &self.cache { if let Some(part) = self.cache.get(&type_name) {
return map.clone(); return Some(part.clone());
} }
let mut map = HashMap::new(); None
for part in self.types.iter() {
map.insert(part.id.clone(), part.clone());
}
self.cache = Some(map.clone());
map
} }
pub fn part_types_new(part_types: Vec<SR1PartType>, name: Option<String>) -> Self {
SR1PartList::new(name.unwrap_or("NewPartList".to_string()), part_types)
}
pub fn insert_part(&mut self, part: SR1PartType) -> () { self.types.insert(0, part); }
} }
pub trait SR1PartTypeData { pub trait SR1PartTypeData {
@ -226,17 +246,6 @@ pub mod sr1 {
fn to_raw_ship(&self) -> RawShip; fn to_raw_ship(&self) -> RawShip;
} }
impl SR1PartList {
#[inline]
pub fn new(name: String, types: Vec<SR1PartType>) -> Self { SR1PartList { name, cache: None, types } }
pub fn part_types_new(part_types: Vec<SR1PartType>, name: Option<String>) -> Self {
SR1PartList::new(name.unwrap_or("NewPartList".to_string()), part_types)
}
pub fn insert_part(&mut self, part: SR1PartType) -> () { self.types.insert(0, part); }
}
impl SR1PartListTrait for SR1PartList { impl SR1PartListTrait for SR1PartList {
fn to_sr_part_list(&self, name: Option<String>) -> SR1PartList { fn to_sr_part_list(&self, name: Option<String>) -> SR1PartList {
return if let Some(name) = name { return if let Some(name) = name {
@ -491,6 +500,32 @@ pub mod sr1 {
pub explode: bool, pub explode: bool,
} }
impl SR1PartData {
pub fn get_box(&self, part_type: &SR1PartType) -> (f64, f64, f64, f64) {
let width = part_type.width;
let height = part_type.height;
let radius = self.angle;
let mut shape = Shape::new_width_height(width as f64, height as f64, Some(radius));
shape.move_xy(Some(self.x), Some(self.y));
let mut pos_box = (0_f64, 0_f64, 0_f64, 0_f64);
match shape.bounds[0] {
Edge::OneTimeLine(line) => {
pos_box.0 = line.start.x;
pos_box.1 = line.start.y;
}
_ => {}
}
match shape.bounds[2] {
Edge::OneTimeLine(line) => {
pos_box.2 = line.start.x;
pos_box.3 = line.start.y;
}
_ => {}
}
pos_box
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SR1PartDataAttr { pub enum SR1PartDataAttr {
Tank { Tank {
@ -532,6 +567,18 @@ pub mod sr1 {
pub disconnected: Option<Vec<(Vec<SR1PartData>, Option<Vec<Connection>>)>>, pub disconnected: Option<Vec<(Vec<SR1PartData>, Option<Vec<Connection>>)>>,
} }
impl SR1Ship {
pub fn from_file(file_name: String, ship_name: Option<String>) -> Option<Self> {
// 首先验证文件是否存在 不存在则返回None
if !std::path::Path::new(&file_name).exists() {
return None;
}
// 解析为 RawShip
let ship: RawShip = RawShip::from_file(file_name).unwrap();
Some(ship.to_sr_ship(ship_name))
}
}
impl SR1ShipTrait for SR1Ship { impl SR1ShipTrait for SR1Ship {
#[inline] #[inline]
fn to_sr_ship(&self, name: Option<String>) -> SR1Ship { fn to_sr_ship(&self, name: Option<String>) -> SR1Ship {
@ -582,6 +629,14 @@ pub mod sr1 {
} }
} }
} }
pub fn get_max_box(parts: &Vec<SR1PartData>, part_list: &SR1PartList) -> (f64, f64, f64, f64) {
let mut max_box = (0_f64, 0_f64, 0_f64, 0_f64);
for part in parts.iter() {
let part_type = part_list.get_part_type(part.part_type);
}
todo!("get_max_box")
}
} }
#[allow(unused)] #[allow(unused)]
@ -686,7 +741,7 @@ pub mod math {
} }
} }
pub fn new_width_height(width: f64, height: f64, angle: Option<f64>) -> Self { pub fn new_width_height(width: f64, height: f64, radius: Option<f64>) -> Self {
let d_width = width / 2.0; let d_width = width / 2.0;
let d_height = height / 2.0; let d_height = height / 2.0;
let mut edges: Vec<Edge> = vec![ let mut edges: Vec<Edge> = vec![
@ -695,17 +750,17 @@ pub mod math {
Edge::OneTimeLine(OneTimeLine::pos_new(d_width, d_height, -d_width, d_height)), Edge::OneTimeLine(OneTimeLine::pos_new(d_width, d_height, -d_width, d_height)),
Edge::OneTimeLine(OneTimeLine::pos_new(-d_width, d_height, -d_width, -d_height)), Edge::OneTimeLine(OneTimeLine::pos_new(-d_width, d_height, -d_width, -d_height)),
]; ];
if let Some(angle) = angle { if let Some(radius) = radius {
edges = edges edges = edges
.iter() .iter()
.map(|edge| match edge { .map(|edge| match edge {
Edge::OneTimeLine(line) => { Edge::OneTimeLine(line) => {
let start = line.start.rotate(angle); let start = line.start.rotate_radius(radius);
let end = line.end.rotate(angle); let end = line.end.rotate_radius(radius);
Edge::OneTimeLine(OneTimeLine::point_new(&start, &end)) Edge::OneTimeLine(OneTimeLine::point_new(&start, &end))
} }
Edge::CircularArc(arc) => { Edge::CircularArc(arc) => {
let pos = arc.pos.rotate(angle); let pos = arc.pos.rotate_radius(radius);
Edge::CircularArc(CircularArc { Edge::CircularArc(CircularArc {
r: arc.r, r: arc.r,
pos, pos,
@ -722,6 +777,25 @@ pub mod math {
bounds: edges, bounds: edges,
} }
} }
pub fn move_xy(&mut self, x: Option<f64>, y: Option<f64>) {
let x = x.unwrap_or(0.0);
let y = y.unwrap_or(0.0);
for edge in self.bounds.iter() {
match edge {
Edge::OneTimeLine(mut line) => {
line.start.x += x;
line.start.y += y;
line.end.x += x;
line.end.y += y;
}
Edge::CircularArc(mut arc) => {
arc.pos.x += x;
arc.pos.y += y;
}
}
}
}
} }
impl OneTimeLine { impl OneTimeLine {

84
mods/dr_game/__init__.py Normal file
View File

@ -0,0 +1,84 @@
# -------------------------------
# Difficult Rocket Mod
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import warnings
import traceback
from typing import Optional
from libs.MCDR.version import Version
from Difficult_Rocket.main import Game
from Difficult_Rocket.api.mod import ModInfo
from Difficult_Rocket.api.types import Options
from Difficult_Rocket.client import ClientWindow
DR_rust_version = Version("0.2.6.2") # DR_mod 的 Rust 编写部分的兼容版本
class _DR_mod_runtime(Options):
use_DR_rust: bool = True
DR_rust_available: bool = False
DR_rust_version: Version = DR_rust_version
DR_rust_get_version: Optional[Version] = None
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'
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')
self.use_DR_rust = self.use_DR_rust and self.DR_rust_available
except Exception:
traceback.print_exc()
self.DR_rust_available = False
self.use_DR_rust = False
self.flush_option()
DR_mod_runtime = _DR_mod_runtime()
class DR_mod(ModInfo):
mod_id = "difficult_rocket_mod"
name = "Difficult Rocket mod"
version = Version("0.7.2.2")
writer = "shenjackyuanjie"
link = "shenjack.top"
description = "Difficult Rocket mod (where the game implement)"
info = "Difficult Rocket mod (where the game implement)"
config = DR_mod_runtime
# DR_version = # DR SDK 兼容版本
# 反正是内置 mod 跟着最新版本的 DR 走就行了
# DR_Api_version = # DR Api版本
# 同理 不管 API 版本 这东西要是不兼容了才是大问题
def on_load(self, game: Game, old_self: Optional["DR_mod"] = None) -> bool:
if not DR_mod_runtime.DR_rust_available:
return False
if old_self:
game.client.window.add_sub_screen("SR1_ship", old_self.screen)
else:
self.config.flush_option()
print("DR_mod: on_load")
return True
def on_client_start(self, game: Game, client: ClientWindow):
from .sr1_ship import SR1ShipRender
self.screen = SR1ShipRender
print('DR_mod: on_client_start')
client.add_sub_screen("SR1_ship", SR1ShipRender)
mod_class = DR_mod

View File

@ -19,8 +19,10 @@ from pyglet.math import Vec4
from pyglet.text import Label from pyglet.text import Label
from pyglet.shapes import Line from pyglet.shapes import Line
from pyglet.sprite import Sprite from pyglet.sprite import Sprite
from pyglet.image import Texture
from pyglet.graphics import Batch, Group from pyglet.graphics import Batch, Group
from pyglet.image import load as load_image
from . import DR_mod_runtime
# Difficult Rocket # Difficult Rocket
from Difficult_Rocket import DR_option from Difficult_Rocket import DR_option
@ -33,8 +35,8 @@ from Difficult_Rocket.api.types.SR1 import SR1Textures, SR1PartTexture, SR1PartD
if TYPE_CHECKING: if TYPE_CHECKING:
from Difficult_Rocket.client import ClientWindow from Difficult_Rocket.client import ClientWindow
if DR_option.use_DR_rust: if DR_mod_runtime.use_DR_rust:
from libs.Difficult_Rocket_rs import CenterCamera_rs, SR1PartList_rs from .Difficult_Rocket_rs import CenterCamera_rs, SR1PartList_rs
logger = logging.getLogger('client') logger = logging.getLogger('client')
@ -128,7 +130,7 @@ class SR1ShipRender(BaseScreen):
load_end_time = time.time_ns() load_end_time = time.time_ns()
logger.info(tr().client.sr1_render.setup.use_time().format( logger.info(tr().client.sr1_render.setup.use_time().format(
(load_end_time - load_start_time) / 1000000000)) (load_end_time - load_start_time) / 1000000000))
if DR_option.use_DR_rust: if DR_mod_runtime.use_DR_rust:
self.camera_rs = CenterCamera_rs(main_window, self.camera_rs = CenterCamera_rs(main_window,
min_zoom=(1 / 2) ** 10, max_zoom=10) min_zoom=(1 / 2) ** 10, max_zoom=10)
self.rust_parts = None self.rust_parts = None
@ -194,7 +196,7 @@ class SR1ShipRender(BaseScreen):
self.part_data: Dict[int, SR1PartData] = {} self.part_data: Dict[int, SR1PartData] = {}
self.parts_sprite: Dict[int, Sprite] = {} self.parts_sprite: Dict[int, Sprite] = {}
self.camera_rs.zoom = 1.0 self.camera_rs.zoom = 1.0
if DR_option.use_DR_rust: if DR_mod_runtime.use_DR_rust:
self.camera_rs.dx = 0 self.camera_rs.dx = 0
self.camera_rs.dy = 0 self.camera_rs.dy = 0
parts = self.xml_root.find('Parts') parts = self.xml_root.find('Parts')
@ -213,13 +215,13 @@ class SR1ShipRender(BaseScreen):
self.drawing = False self.drawing = False
self.need_draw = False self.need_draw = False
full_mass = 0 full_mass = 0
if DR_option.use_DR_rust: if DR_mod_runtime.use_DR_rust:
for part in self.part_data: for part in self.part_data:
full_mass += self.part_list_rs.get_part_type(self.part_data[part].p_type).mass * 500 full_mass += self.part_list_rs.get_part_type(self.part_data[part].p_type).mass * 500
logger.info(tr().client.sr1_render.ship.load_time().format( logger.info(tr().client.sr1_render.ship.load_time().format(
(time.perf_counter_ns() - start_time) / 1000000000)) (time.perf_counter_ns() - start_time) / 1000000000))
logger.info(tr().client.sr1_render.ship.info().format( logger.info(tr().client.sr1_render.ship.info().format(
len(self.part_data), f'{full_mass}kg' if DR_option.use_DR_rust else tr().game.require_DR_rs())) len(self.part_data), f'{full_mass}kg' if DR_mod_runtime.use_DR_rust else tr().game.require_DR_rs()))
self.rendered = True self.rendered = True
def get_ship_size(self) -> (int, int): def get_ship_size(self) -> (int, int):
@ -359,6 +361,12 @@ class SR1ShipRender(BaseScreen):
image_data = screenshot(self.window_pointer) image_data = screenshot(self.window_pointer)
image_data.save('test.png') image_data.save('test.png')
elif command.re_match('gen_img'):
if not self.rendered:
return
# ship_size = self.ship.size
base_textures = Texture.create(100, 100)
...
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 not self.focus: if not self.focus: