pyscreeps-arena 0.5.8.8__py3-none-any.whl → 0.5.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyscreeps_arena/compiler.py +110 -17
- pyscreeps_arena/core/const.py +1 -1
- pyscreeps_arena/project.7z +0 -0
- pyscreeps_arena/ui/project_ui.py +695 -75
- {pyscreeps_arena-0.5.8.8.dist-info → pyscreeps_arena-0.5.9.0.dist-info}/METADATA +1 -1
- {pyscreeps_arena-0.5.8.8.dist-info → pyscreeps_arena-0.5.9.0.dist-info}/RECORD +9 -9
- {pyscreeps_arena-0.5.8.8.dist-info → pyscreeps_arena-0.5.9.0.dist-info}/WHEEL +0 -0
- {pyscreeps_arena-0.5.8.8.dist-info → pyscreeps_arena-0.5.9.0.dist-info}/entry_points.txt +0 -0
- {pyscreeps_arena-0.5.8.8.dist-info → pyscreeps_arena-0.5.9.0.dist-info}/top_level.txt +0 -0
pyscreeps_arena/compiler.py
CHANGED
|
@@ -8,6 +8,7 @@ import shutil
|
|
|
8
8
|
import chardet
|
|
9
9
|
import subprocess
|
|
10
10
|
import pyperclip
|
|
11
|
+
import datetime
|
|
11
12
|
from colorama import Fore
|
|
12
13
|
from typing import List, Optional, Tuple, Union
|
|
13
14
|
|
|
@@ -52,6 +53,7 @@ def replace_src_prefix(file_list):
|
|
|
52
53
|
# """
|
|
53
54
|
# return PYFILE_PRAGMA_INSERTS + "\n" + content
|
|
54
55
|
class Compiler_Const:
|
|
56
|
+
CALLED_FUNCTIONS = ['behavior', 'sequence', 'selector', 'parallel', 'listen']
|
|
55
57
|
PROTO_DEFINES_DIRS = ["builtin", "library"]
|
|
56
58
|
FILE_STRONG_REPLACE = {
|
|
57
59
|
"std": {
|
|
@@ -108,14 +110,14 @@ export var loop = function () {
|
|
|
108
110
|
if (know.now === 1) {
|
|
109
111
|
std.show_welcome();
|
|
110
112
|
init (know);
|
|
111
|
-
|
|
113
|
+
|
|
112
114
|
}
|
|
113
115
|
step (know);
|
|
114
116
|
timeLine = get.cpu_us();
|
|
115
117
|
if (get._SCH_FLAG) sch.handle();
|
|
116
118
|
stepCost = get.cpu_us() - timeLine;
|
|
117
119
|
std.show_usage ();
|
|
118
|
-
|
|
120
|
+
print("knowCost:", knowCost, "monitorCost:", monitorCost, "stepCost:", stepCost);
|
|
119
121
|
if (know.draw) know.draw();
|
|
120
122
|
};
|
|
121
123
|
"""
|
|
@@ -648,6 +650,82 @@ class Compiler_Utils(Compiler_Const):
|
|
|
648
650
|
|
|
649
651
|
return result
|
|
650
652
|
|
|
653
|
+
@staticmethod
|
|
654
|
+
def stage_called_replace(caller_name: str, content: str) -> str:
|
|
655
|
+
"""
|
|
656
|
+
移除 '@<caller_name>(...)' 装饰器行,并在文末添加对应的 _<caller_name>Login 调用。
|
|
657
|
+
|
|
658
|
+
对于类方法: _<caller_name>Login("ClassName", "method_name", a, b, ...)
|
|
659
|
+
对于普通函数: _<caller_name>Login("", "function_name", a, b, ...)
|
|
660
|
+
"""
|
|
661
|
+
calls_to_add = []
|
|
662
|
+
deletions = []
|
|
663
|
+
|
|
664
|
+
# 1. 收集所有类定义的位置和缩进
|
|
665
|
+
class_pattern = re.compile(r'^(\s*)class\s+(\w+)', re.MULTILINE)
|
|
666
|
+
classes = [(m.start(), len(m.group(1)), m.group(2))
|
|
667
|
+
for m in class_pattern.finditer(content)]
|
|
668
|
+
|
|
669
|
+
# 2. 查找所有 @<caller_name>(...) 装饰器(支持多行参数)
|
|
670
|
+
decorator_pattern = re.compile(
|
|
671
|
+
r'^\s*@\s*' + re.escape(caller_name) + r'\s*\((.*?)\)\s*$\n?',
|
|
672
|
+
re.MULTILINE | re.DOTALL
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
for dec_match in decorator_pattern.finditer(content):
|
|
676
|
+
dec_start = dec_match.start()
|
|
677
|
+
dec_end = dec_match.end()
|
|
678
|
+
# 提取装饰器的参数
|
|
679
|
+
params_str = dec_match.group(1).strip()
|
|
680
|
+
|
|
681
|
+
# 查找接下来的函数定义(跳过可能的空行)
|
|
682
|
+
after_decorator = content[dec_end:]
|
|
683
|
+
func_match = re.search(r'^(\s*)def\s+([^\s\(]+)', after_decorator, re.MULTILINE)
|
|
684
|
+
|
|
685
|
+
if not func_match:
|
|
686
|
+
continue
|
|
687
|
+
|
|
688
|
+
func_indent_len = len(func_match.group(1))
|
|
689
|
+
func_name = func_match.group(2)
|
|
690
|
+
|
|
691
|
+
# 3. 确定类名:查找装饰器前最近的、缩进小于函数缩进的类
|
|
692
|
+
class_name = ""
|
|
693
|
+
for cls_pos, cls_indent_len, cls_name in reversed(classes):
|
|
694
|
+
if cls_pos < dec_match.start() and func_indent_len > cls_indent_len:
|
|
695
|
+
class_name = cls_name
|
|
696
|
+
break
|
|
697
|
+
|
|
698
|
+
# 4. 处理参数,保持参数的格式
|
|
699
|
+
# 移除参数中的换行和多余空格,保持参数列表的格式
|
|
700
|
+
params = []
|
|
701
|
+
if params_str:
|
|
702
|
+
# 简单处理参数,保持引号内的内容不变
|
|
703
|
+
# 这里可以根据需要进行更复杂的参数解析
|
|
704
|
+
params = [p.strip() for p in params_str.split(',') if p.strip()]
|
|
705
|
+
|
|
706
|
+
# 构建参数部分的字符串
|
|
707
|
+
params_part = ""
|
|
708
|
+
if params:
|
|
709
|
+
params_part = ", " + ", ".join(params)
|
|
710
|
+
|
|
711
|
+
# 5. 记录删除位置和调用信息
|
|
712
|
+
deletions.append((dec_start, dec_end))
|
|
713
|
+
calls_to_add.append(f'_{caller_name}Login("{class_name}", "{func_name}"{params_part})')
|
|
714
|
+
|
|
715
|
+
# 6. 应用删除(倒序避免位置偏移)
|
|
716
|
+
if not deletions:
|
|
717
|
+
return content
|
|
718
|
+
|
|
719
|
+
result = content
|
|
720
|
+
for start, end in sorted(deletions, key=lambda x: x[0], reverse=True):
|
|
721
|
+
result = result[:start] + result[end:]
|
|
722
|
+
|
|
723
|
+
# 7. 在文末添加调用
|
|
724
|
+
if calls_to_add:
|
|
725
|
+
result = '\n'.join(calls_to_add) + '\n' + result
|
|
726
|
+
|
|
727
|
+
return result
|
|
728
|
+
|
|
651
729
|
@staticmethod
|
|
652
730
|
def process_mate_code(code):
|
|
653
731
|
# 用于存储匹配到的信息
|
|
@@ -921,6 +999,8 @@ class Compiler(CompilerBase):
|
|
|
921
999
|
|
|
922
1000
|
# 将PYFILE_PRAGMA_INSERTS.replace("\t", "").replace(" ", "")插入到文件开头
|
|
923
1001
|
content = self.auto_read(fpath)
|
|
1002
|
+
# 移除"""$..."""代码块
|
|
1003
|
+
content = re.sub(r'"""\$[\s\S]*?"""', '', content)
|
|
924
1004
|
content = self.PYFILE_PRAGMA_INSERTS.replace("\t", "").replace(" ", "") + content
|
|
925
1005
|
# content = self.remove_long_docstring(content) # 移除长注释 | remove long docstring
|
|
926
1006
|
|
|
@@ -1077,10 +1157,13 @@ class Compiler(CompilerBase):
|
|
|
1077
1157
|
with open(fpath, 'w', encoding='utf-8') as f:
|
|
1078
1158
|
f.write(new_content)
|
|
1079
1159
|
|
|
1080
|
-
# ------------------------------------ 自定义:调用stage_recursive_replace ------------------------------------ #
|
|
1160
|
+
# ------------------------------------ 自定义:调用stage_recursive_replace和stage_called_replace ------------------------------------ #
|
|
1081
1161
|
for fpath in py_fpath:
|
|
1082
1162
|
content = self.auto_read(fpath)
|
|
1083
1163
|
content = self.stage_recursive_replace(content) # 调用stage_recursive_replace
|
|
1164
|
+
# 调用stage_called_replace处理四个装饰器
|
|
1165
|
+
for caller in self.CALLED_FUNCTIONS:
|
|
1166
|
+
content = self.stage_called_replace(caller, content)
|
|
1084
1167
|
with open(fpath, 'w', encoding='utf-8') as f:
|
|
1085
1168
|
f.write(content)
|
|
1086
1169
|
|
|
@@ -1217,12 +1300,22 @@ class Compiler(CompilerBase):
|
|
|
1217
1300
|
:param min_js_files: list[str] # .min.js文件路径列表
|
|
1218
1301
|
:return: str
|
|
1219
1302
|
"""
|
|
1220
|
-
arena_name = const.ARENA_NAMES.get(config.arena, const.ARENA_NAMES[
|
|
1303
|
+
arena_name = const.ARENA_NAMES.get(config.arena, const.ARENA_NAMES["green"]) # like green -> spawn_and_swamp
|
|
1221
1304
|
self.TOTAL_INSERT_AT_HEAD += self.ARENA_IMPORTS_GETTER[arena_name]() # add arena imports
|
|
1222
|
-
|
|
1223
|
-
|
|
1305
|
+
current_time = datetime.datetime.now()
|
|
1306
|
+
timestamp_ms = int(current_time.timestamp() * 1000)
|
|
1307
|
+
timestring = current_time.strftime("%Y-%m-%d %H:%M")
|
|
1308
|
+
|
|
1309
|
+
total_js = f"""const __VERSION__ = '{const.VERSION}';
|
|
1310
|
+
const __PYTHON_VERSION__ = '{python_version_info}';""" + self.TOTAL_INSERT_AT_HEAD + f"""
|
|
1311
|
+
export var LANGUAGE = '{config.language}';
|
|
1312
|
+
"""
|
|
1313
|
+
|
|
1314
|
+
total_js += f"export var TIMESTAMP = {timestamp_ms};\n"
|
|
1315
|
+
total_js += f"export var TIMESTRING = '{timestring}';\n"
|
|
1316
|
+
total_js += f"""const __AUTHOR__ = '{const.AUTHOR}';
|
|
1317
|
+
const __AUTHOR_CN__ = '{const.BILIBILI_NAME}';"""
|
|
1224
1318
|
|
|
1225
|
-
# 添加.min.js文件的import语句
|
|
1226
1319
|
if min_js_files:
|
|
1227
1320
|
for min_js_path in min_js_files:
|
|
1228
1321
|
min_js_filename = os.path.basename(min_js_path)
|
|
@@ -1342,14 +1435,6 @@ class Compiler(CompilerBase):
|
|
|
1342
1435
|
dir_path = os.path.dirname(mjs_path)
|
|
1343
1436
|
build_dir_path = os.path.dirname(build_main_mjs)
|
|
1344
1437
|
|
|
1345
|
-
# 复制.min.js文件到目标目录
|
|
1346
|
-
for min_js_path in min_js_files:
|
|
1347
|
-
min_js_filename = os.path.basename(min_js_path)
|
|
1348
|
-
# 复制到build目录
|
|
1349
|
-
shutil.copy(min_js_path, os.path.join(build_dir_path, min_js_filename))
|
|
1350
|
-
# 复制到最终导出目录
|
|
1351
|
-
shutil.copy(min_js_path, os.path.join(dir_path, min_js_filename))
|
|
1352
|
-
|
|
1353
1438
|
# 生成total_js,传入.min.js文件列表
|
|
1354
1439
|
total_js = imports + "\n" + self.generate_total_js(
|
|
1355
1440
|
replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps, min_js_files
|
|
@@ -1365,6 +1450,14 @@ class Compiler(CompilerBase):
|
|
|
1365
1450
|
with open(mjs_path, 'w', encoding='utf-8') as f:
|
|
1366
1451
|
f.write(total_js)
|
|
1367
1452
|
|
|
1453
|
+
# 复制.min.js文件到目标目录
|
|
1454
|
+
for min_js_path in min_js_files:
|
|
1455
|
+
min_js_filename = os.path.basename(min_js_path)
|
|
1456
|
+
# 复制到build目录
|
|
1457
|
+
shutil.copy(min_js_path, os.path.join(build_dir_path, min_js_filename))
|
|
1458
|
+
# 复制到最终导出目录
|
|
1459
|
+
shutil.copy(min_js_path, os.path.join(dir_path, min_js_filename))
|
|
1460
|
+
|
|
1368
1461
|
core.lprint(GREEN.format('[6/6]'), LOC_DONE, " ", LOC_EXPORTING_TOTAL_MAIN_JS_FINISH, sep="", head="\r", ln=config.language)
|
|
1369
1462
|
|
|
1370
1463
|
if mjs_path != build_main_mjs:
|
|
@@ -1422,7 +1515,7 @@ if __name__ == '__main__':
|
|
|
1422
1515
|
# compiler.compile()
|
|
1423
1516
|
# compiler.clean()
|
|
1424
1517
|
test = """
|
|
1425
|
-
|
|
1518
|
+
|
|
1426
1519
|
def patrolling(self, c: Creep):
|
|
1427
1520
|
e = self.center.nearest(k.civilian.enemies, 5)
|
|
1428
1521
|
if e:
|
|
@@ -1432,6 +1525,6 @@ def patrolling(self, c: Creep):
|
|
|
1432
1525
|
):
|
|
1433
1526
|
case True: c.move(e, SWAMP_MOTION)
|
|
1434
1527
|
case False: c.move(self.bpos, SWAMP_MOTION)
|
|
1435
|
-
|
|
1528
|
+
|
|
1436
1529
|
"""
|
|
1437
1530
|
print(f"res=\n{Compiler.convert_match_to_if(test)}")
|
pyscreeps_arena/core/const.py
CHANGED
pyscreeps_arena/project.7z
CHANGED
|
Binary file
|
pyscreeps_arena/ui/project_ui.py
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
"""
|
|
5
5
|
import sys
|
|
6
6
|
import os
|
|
7
|
+
import json
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
|
9
10
|
QHBoxLayout, QLabel, QLineEdit, QPushButton,
|
|
10
|
-
QFileDialog, QMessageBox, QComboBox)
|
|
11
|
-
from
|
|
11
|
+
QFileDialog, QMessageBox, QComboBox, QStackedWidget)
|
|
12
|
+
from pyscreeps_arena.ui.qprefabs.qprefabs import QPrefabsManager
|
|
13
|
+
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot, pyqtProperty
|
|
12
14
|
from PyQt6.QtGui import QFont
|
|
13
15
|
from PyQt6.QtGui import QIcon
|
|
14
16
|
from pyscreeps_arena.core import const
|
|
@@ -16,36 +18,307 @@ from pyscreeps_arena.ui.rs_icon import get_pixmap
|
|
|
16
18
|
from pyscreeps_arena.afters import ToConfigAfter, ToEmptyAfter, ToPrefabAfter, ToCustomAfter
|
|
17
19
|
from PyQt6.QtWidgets import QRadioButton, QGroupBox
|
|
18
20
|
|
|
21
|
+
# Language mapping
|
|
22
|
+
LANG = {
|
|
23
|
+
'cn': {
|
|
24
|
+
'window_title': 'PyScreeps Arena - 项目创建器',
|
|
25
|
+
'title': 'PyScreeps Arena',
|
|
26
|
+
'version': '版本: {0}',
|
|
27
|
+
'author': '作者: {0}',
|
|
28
|
+
'github': 'GitHub: {0}',
|
|
29
|
+
'project_name': '项目名称:',
|
|
30
|
+
'name_placeholder': '输入项目名称...',
|
|
31
|
+
'save_location': '保存位置:',
|
|
32
|
+
'path_placeholder': '选择项目保存位置...',
|
|
33
|
+
'browse': '浏览...',
|
|
34
|
+
'language': '语 言:',
|
|
35
|
+
'arena': '竞技场:',
|
|
36
|
+
'difficulty': '难 度:',
|
|
37
|
+
'empty': '空白 (Empty)',
|
|
38
|
+
'basic': '基础 (Basic)',
|
|
39
|
+
'prefab': '预设 (Prefab)',
|
|
40
|
+
'pi': '预设继承 (P&&I)',
|
|
41
|
+
'create_project': '创建项目',
|
|
42
|
+
'cancel': '取消',
|
|
43
|
+
'path_exists': '路径已存在',
|
|
44
|
+
'path_exists_message': "路径 '{0}' 已存在。\n是否继续?",
|
|
45
|
+
'success': '成功',
|
|
46
|
+
'success_message': "项目 '{0}' 创建成功!\n路径: {1}",
|
|
47
|
+
'error': '错误',
|
|
48
|
+
'error_message': '项目创建失败:\n{0}',
|
|
49
|
+
'select_location': '选择项目保存位置',
|
|
50
|
+
'next_page': '下一页',
|
|
51
|
+
'previous_page': '上一页',
|
|
52
|
+
},
|
|
53
|
+
'en': {
|
|
54
|
+
'window_title': 'PyScreeps Arena - Project Creator',
|
|
55
|
+
'title': 'PyScreeps Arena',
|
|
56
|
+
'version': 'Version: {0}',
|
|
57
|
+
'author': 'Author: {0}',
|
|
58
|
+
'github': 'GitHub: {0}',
|
|
59
|
+
'project_name': 'Project Name:',
|
|
60
|
+
'name_placeholder': 'Enter project name...',
|
|
61
|
+
'save_location': 'Save Location:',
|
|
62
|
+
'path_placeholder': 'Select project save location...',
|
|
63
|
+
'browse': 'Browse...',
|
|
64
|
+
'language': 'Language:',
|
|
65
|
+
'arena': 'Arena:',
|
|
66
|
+
'difficulty': 'Difficulty:',
|
|
67
|
+
'empty': 'Empty',
|
|
68
|
+
'basic': 'Basic',
|
|
69
|
+
'prefab': 'Prefab',
|
|
70
|
+
'pi': 'Prefab & Inherit',
|
|
71
|
+
'create_project': 'Create Project',
|
|
72
|
+
'cancel': 'Cancel',
|
|
73
|
+
'path_exists': 'Path Exists',
|
|
74
|
+
'path_exists_message': "Path '{0}' already exists.\nContinue anyway?",
|
|
75
|
+
'success': 'Success',
|
|
76
|
+
'success_message': "Project '{0}' created successfully!\nPath: {1}",
|
|
77
|
+
'error': 'Error',
|
|
78
|
+
'error_message': 'Failed to create project:\n{0}',
|
|
79
|
+
'select_location': 'Select Project Save Location',
|
|
80
|
+
'next_page': 'Next Page',
|
|
81
|
+
'previous_page': 'Previous',
|
|
82
|
+
}
|
|
83
|
+
}
|
|
19
84
|
|
|
20
85
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
86
|
+
|
|
87
|
+
class QProjectBody(QWidget):
|
|
88
|
+
"""Project creation body widget"""
|
|
89
|
+
|
|
90
|
+
# Signals
|
|
91
|
+
projectCreated = pyqtSignal(str, str) # (project_name, project_path)
|
|
92
|
+
projectCancelled = pyqtSignal()
|
|
93
|
+
languageChanged = pyqtSignal(str) # (language_code)
|
|
94
|
+
|
|
95
|
+
def __init__(self, parent=None):
|
|
96
|
+
super().__init__(parent)
|
|
24
97
|
self._proj_name = ""
|
|
25
98
|
self._proj_path = ""
|
|
26
99
|
self._init_ui()
|
|
100
|
+
|
|
101
|
+
def lang(self, key, *args):
|
|
102
|
+
"""Get translation for the current language"""
|
|
103
|
+
# Get language code from combo box data
|
|
104
|
+
lang = self._lang_combo.currentData() or 'cn'
|
|
105
|
+
|
|
106
|
+
if lang not in LANG:
|
|
107
|
+
lang = 'cn'
|
|
108
|
+
return LANG[lang][key].format(*args)
|
|
109
|
+
|
|
110
|
+
def _get_settings_path(self):
|
|
111
|
+
"""Get cross-platform settings file path"""
|
|
112
|
+
# Get user's home directory
|
|
113
|
+
home_dir = os.path.expanduser("~")
|
|
114
|
+
|
|
115
|
+
# Create settings directory if it doesn't exist
|
|
116
|
+
settings_dir = os.path.join(home_dir, ".psaui")
|
|
117
|
+
os.makedirs(settings_dir, exist_ok=True)
|
|
118
|
+
|
|
119
|
+
# Return settings file path
|
|
120
|
+
return os.path.join(settings_dir, "psaui.json")
|
|
121
|
+
|
|
122
|
+
def _save_settings(self):
|
|
123
|
+
"""Save settings to psaui.json"""
|
|
124
|
+
settings = {
|
|
125
|
+
"language": self._lang_combo.currentIndex(),
|
|
126
|
+
"path": self._path_input.text()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
settings_path = self._get_settings_path()
|
|
131
|
+
with open(settings_path, 'w', encoding='utf-8') as f:
|
|
132
|
+
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f"Error saving settings: {e}")
|
|
135
|
+
|
|
136
|
+
def _load_settings(self):
|
|
137
|
+
"""Load settings from psaui.json if it exists"""
|
|
138
|
+
try:
|
|
139
|
+
settings_path = self._get_settings_path()
|
|
140
|
+
if os.path.exists(settings_path):
|
|
141
|
+
with open(settings_path, 'r', encoding='utf-8') as f:
|
|
142
|
+
settings = json.load(f)
|
|
143
|
+
|
|
144
|
+
# Load language setting
|
|
145
|
+
if "language" in settings:
|
|
146
|
+
language_index = settings["language"]
|
|
147
|
+
if 0 <= language_index < self._lang_combo.count():
|
|
148
|
+
self._lang_combo.setCurrentIndex(language_index)
|
|
149
|
+
|
|
150
|
+
# Load path setting
|
|
151
|
+
if "path" in settings and settings["path"]:
|
|
152
|
+
self._path_input.setText(settings["path"])
|
|
153
|
+
self._proj_path = settings["path"]
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print(f"Error loading settings: {e}")
|
|
156
|
+
|
|
157
|
+
def _update_language_options(self):
|
|
158
|
+
"""Update language combo box options based on current language"""
|
|
159
|
+
# Get current language
|
|
160
|
+
lang_text = self._lang_combo.currentText()
|
|
161
|
+
current_lang = lang_text.split('(')[1].strip(')') if '(' in lang_text else 'cn'
|
|
162
|
+
|
|
163
|
+
# Save current index
|
|
164
|
+
current_index = self._lang_combo.currentIndex()
|
|
165
|
+
|
|
166
|
+
# Disconnect signal to avoid recursion
|
|
167
|
+
self._lang_combo.currentTextChanged.disconnect(self._update_language)
|
|
168
|
+
|
|
169
|
+
# Clear existing items
|
|
170
|
+
self._lang_combo.clear()
|
|
171
|
+
|
|
172
|
+
# Add items based on current language
|
|
173
|
+
if current_lang == 'en':
|
|
174
|
+
# In English mode, show only language codes
|
|
175
|
+
self._lang_combo.addItems(["cn", "en"])
|
|
176
|
+
else:
|
|
177
|
+
# In Chinese mode, show full language names
|
|
178
|
+
self._lang_combo.addItems(["中文 (cn)", "英文 (en)"])
|
|
179
|
+
|
|
180
|
+
# Restore current index
|
|
181
|
+
if current_index < self._lang_combo.count():
|
|
182
|
+
self._lang_combo.setCurrentIndex(current_index)
|
|
183
|
+
|
|
184
|
+
# Reconnect signal
|
|
185
|
+
self._lang_combo.currentTextChanged.connect(self._update_language)
|
|
186
|
+
|
|
187
|
+
def _update_language(self):
|
|
188
|
+
"""Update all UI elements to use the current language"""
|
|
189
|
+
# Get current language code
|
|
190
|
+
current_lang = self._lang_combo.currentData() or 'cn'
|
|
191
|
+
|
|
192
|
+
# Update window title
|
|
193
|
+
self.setWindowTitle(self.lang('window_title'))
|
|
194
|
+
|
|
195
|
+
# Update title
|
|
196
|
+
self._title_label.setText(self.lang('title'))
|
|
197
|
+
|
|
198
|
+
# Update info labels
|
|
199
|
+
self._version_label.setText(self.lang('version', const.VERSION))
|
|
200
|
+
self._author_label.setText(self.lang('author', const.AUTHOR))
|
|
201
|
+
self._github_label.setText(self.lang('github', const.GITHUB_NAME))
|
|
202
|
+
|
|
203
|
+
# Update input labels and placeholders
|
|
204
|
+
current_lang = self._lang_combo.currentData() or 'cn'
|
|
205
|
+
if current_lang == 'en':
|
|
206
|
+
self._name_label.setText('Name:')
|
|
207
|
+
else:
|
|
208
|
+
self._name_label.setText(self.lang('project_name'))
|
|
209
|
+
self._name_input.setPlaceholderText(self.lang('name_placeholder'))
|
|
210
|
+
self._path_label.setText(self.lang('save_location'))
|
|
211
|
+
self._path_input.setPlaceholderText(self.lang('path_placeholder'))
|
|
212
|
+
|
|
213
|
+
# Update buttons
|
|
214
|
+
self._browse_btn.setText(self.lang('browse'))
|
|
215
|
+
if hasattr(self, '_create_btn'):
|
|
216
|
+
self._create_btn.setText(self.lang('create_project'))
|
|
217
|
+
if hasattr(self, '_cancel_btn'):
|
|
218
|
+
self._cancel_btn.setText(self.lang('cancel'))
|
|
219
|
+
|
|
220
|
+
# Update config labels
|
|
221
|
+
self._lang_label.setText(self.lang('language'))
|
|
222
|
+
self._arena_label.setText(self.lang('arena'))
|
|
223
|
+
self._level_label.setText(self.lang('difficulty'))
|
|
224
|
+
|
|
225
|
+
# Update radio buttons
|
|
226
|
+
self._empty_radio.setText(self.lang('empty'))
|
|
227
|
+
self._basic_radio.setText(self.lang('basic'))
|
|
228
|
+
self._prefab_radio.setText(self.lang('prefab'))
|
|
229
|
+
self._pi_radio.setText(self.lang('pi'))
|
|
230
|
+
|
|
231
|
+
# Update language combo box items based on current language
|
|
232
|
+
if current_lang == 'en':
|
|
233
|
+
# In English mode, show only language codes
|
|
234
|
+
self._lang_combo.blockSignals(True)
|
|
235
|
+
current_index = self._lang_combo.currentIndex()
|
|
236
|
+
self._lang_combo.clear()
|
|
237
|
+
self._lang_combo.addItem("cn", "cn")
|
|
238
|
+
self._lang_combo.addItem("en", "en")
|
|
239
|
+
self._lang_combo.setCurrentIndex(current_index)
|
|
240
|
+
self._lang_combo.blockSignals(False)
|
|
241
|
+
|
|
242
|
+
# Update arena combo box items
|
|
243
|
+
self._arena_combo.blockSignals(True)
|
|
244
|
+
current_arena_index = self._arena_combo.currentIndex()
|
|
245
|
+
self._arena_combo.clear()
|
|
246
|
+
self._arena_combo.addItem("gray", "gray")
|
|
247
|
+
self._arena_combo.addItem("green", "green")
|
|
248
|
+
self._arena_combo.addItem("blue", "blue")
|
|
249
|
+
self._arena_combo.addItem("red", "red")
|
|
250
|
+
self._arena_combo.setCurrentIndex(current_arena_index)
|
|
251
|
+
self._arena_combo.blockSignals(False)
|
|
252
|
+
|
|
253
|
+
# Update level combo box items
|
|
254
|
+
self._level_combo.blockSignals(True)
|
|
255
|
+
current_level_index = self._level_combo.currentIndex()
|
|
256
|
+
self._level_combo.clear()
|
|
257
|
+
self._level_combo.addItem("basic", "basic")
|
|
258
|
+
self._level_combo.addItem("advanced", "advanced")
|
|
259
|
+
self._level_combo.setCurrentIndex(current_level_index)
|
|
260
|
+
self._level_combo.blockSignals(False)
|
|
261
|
+
else:
|
|
262
|
+
# In Chinese mode, show full language names
|
|
263
|
+
self._lang_combo.blockSignals(True)
|
|
264
|
+
current_index = self._lang_combo.currentIndex()
|
|
265
|
+
self._lang_combo.clear()
|
|
266
|
+
self._lang_combo.addItem("中文 (cn)", "cn")
|
|
267
|
+
self._lang_combo.addItem("英文 (en)", "en")
|
|
268
|
+
self._lang_combo.setCurrentIndex(current_index)
|
|
269
|
+
self._lang_combo.blockSignals(False)
|
|
270
|
+
|
|
271
|
+
# Update arena combo box items
|
|
272
|
+
self._arena_combo.blockSignals(True)
|
|
273
|
+
current_arena_index = self._arena_combo.currentIndex()
|
|
274
|
+
self._arena_combo.clear()
|
|
275
|
+
self._arena_combo.addItem("灰色 (gray)", "gray")
|
|
276
|
+
self._arena_combo.addItem("绿色 (green)", "green")
|
|
277
|
+
self._arena_combo.addItem("蓝色 (blue)", "blue")
|
|
278
|
+
self._arena_combo.addItem("红色 (red)", "red")
|
|
279
|
+
self._arena_combo.setCurrentIndex(current_arena_index)
|
|
280
|
+
self._arena_combo.blockSignals(False)
|
|
281
|
+
|
|
282
|
+
# Update level combo box items
|
|
283
|
+
self._level_combo.blockSignals(True)
|
|
284
|
+
current_level_index = self._level_combo.currentIndex()
|
|
285
|
+
self._level_combo.clear()
|
|
286
|
+
self._level_combo.addItem("基础 (basic)", "basic")
|
|
287
|
+
self._level_combo.addItem("高级 (advanced)", "advanced")
|
|
288
|
+
self._level_combo.setCurrentIndex(current_level_index)
|
|
289
|
+
self._level_combo.blockSignals(False)
|
|
290
|
+
|
|
291
|
+
# Save settings after language change
|
|
292
|
+
self._save_settings()
|
|
27
293
|
|
|
28
294
|
def _init_ui(self):
|
|
29
295
|
"""初始化UI界面"""
|
|
30
|
-
self.setWindowTitle("PyScreeps Arena - 项目创建器")
|
|
31
|
-
self.setWindowIcon(QIcon(get_pixmap()))
|
|
32
296
|
self.setFixedSize(500, 580)
|
|
33
297
|
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
self.setCentralWidget(central_widget)
|
|
37
|
-
layout = QVBoxLayout(central_widget)
|
|
298
|
+
# 主布局
|
|
299
|
+
layout = QVBoxLayout(self)
|
|
38
300
|
layout.setSpacing(15)
|
|
39
|
-
|
|
301
|
+
# 减小左右边距,保持上下边距
|
|
302
|
+
layout.setContentsMargins(15, 15, 15, 30)
|
|
303
|
+
|
|
304
|
+
# 创建项目创建页面
|
|
305
|
+
self._project_page = QWidget()
|
|
306
|
+
self._project_layout = QVBoxLayout(self._project_page)
|
|
307
|
+
self._project_layout.setSpacing(15)
|
|
308
|
+
self._project_layout.setContentsMargins(0, 0, 0, 0)
|
|
309
|
+
|
|
310
|
+
# 创建堆叠容器
|
|
311
|
+
self._stacked_widget = QStackedWidget()
|
|
312
|
+
layout.addWidget(self._stacked_widget)
|
|
40
313
|
|
|
41
314
|
# 标题
|
|
42
|
-
|
|
43
|
-
|
|
315
|
+
self._title_label = QLabel("PyScreeps Arena")
|
|
316
|
+
self._title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
44
317
|
title_font = QFont()
|
|
45
318
|
title_font.setPointSize(18)
|
|
46
319
|
title_font.setBold(True)
|
|
47
|
-
|
|
48
|
-
|
|
320
|
+
self._title_label.setFont(title_font)
|
|
321
|
+
self._project_layout.addWidget(self._title_label)
|
|
49
322
|
|
|
50
323
|
# 项目信息
|
|
51
324
|
info_widget = QWidget()
|
|
@@ -53,27 +326,27 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
53
326
|
info_layout.setSpacing(8)
|
|
54
327
|
|
|
55
328
|
# 版本信息
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
info_layout.addWidget(
|
|
329
|
+
self._version_label = QLabel(f"版本: {const.VERSION}")
|
|
330
|
+
self._version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
331
|
+
info_layout.addWidget(self._version_label)
|
|
59
332
|
|
|
60
333
|
# 作者信息
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
info_layout.addWidget(
|
|
334
|
+
self._author_label = QLabel(f"作者: {const.AUTHOR}")
|
|
335
|
+
self._author_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
336
|
+
info_layout.addWidget(self._author_label)
|
|
64
337
|
|
|
65
338
|
# GitHub信息
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
info_layout.addWidget(
|
|
339
|
+
self._github_label = QLabel(f"GitHub: {const.GITHUB_NAME}")
|
|
340
|
+
self._github_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
341
|
+
info_layout.addWidget(self._github_label)
|
|
69
342
|
|
|
70
|
-
|
|
343
|
+
self._project_layout.addWidget(info_widget)
|
|
71
344
|
|
|
72
345
|
# 分隔线
|
|
73
346
|
separator = QLabel("─" * 50)
|
|
74
347
|
separator.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
75
348
|
separator.setStyleSheet("color: #ccc;")
|
|
76
|
-
|
|
349
|
+
self._project_layout.addWidget(separator)
|
|
77
350
|
|
|
78
351
|
# 项目输入区域
|
|
79
352
|
input_widget = QWidget()
|
|
@@ -82,20 +355,20 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
82
355
|
|
|
83
356
|
# 项目名称输入
|
|
84
357
|
name_layout = QHBoxLayout()
|
|
85
|
-
|
|
86
|
-
|
|
358
|
+
self._name_label = QLabel("项目名称:")
|
|
359
|
+
self._name_label.setFixedWidth(80)
|
|
87
360
|
self._name_input = QLineEdit()
|
|
88
361
|
self._name_input.setPlaceholderText("输入项目名称...")
|
|
89
362
|
self._name_input.textChanged.connect(self._on_name_changed)
|
|
90
363
|
self._name_input.returnPressed.connect(self._create_project)
|
|
91
|
-
name_layout.addWidget(
|
|
364
|
+
name_layout.addWidget(self._name_label)
|
|
92
365
|
name_layout.addWidget(self._name_input)
|
|
93
366
|
input_layout.addLayout(name_layout)
|
|
94
367
|
|
|
95
368
|
# 项目路径输入
|
|
96
369
|
path_layout = QHBoxLayout()
|
|
97
|
-
|
|
98
|
-
|
|
370
|
+
self._path_label = QLabel("保存位置:")
|
|
371
|
+
self._path_label.setFixedWidth(80)
|
|
99
372
|
self._path_input = QLineEdit()
|
|
100
373
|
self._path_input.setPlaceholderText("选择项目保存位置...")
|
|
101
374
|
self._path_input.setReadOnly(True)
|
|
@@ -104,23 +377,23 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
104
377
|
if os.path.exists(desktop_path):
|
|
105
378
|
self._path_input.setText(desktop_path)
|
|
106
379
|
self._proj_path = desktop_path
|
|
107
|
-
path_layout.addWidget(
|
|
380
|
+
path_layout.addWidget(self._path_label)
|
|
108
381
|
path_layout.addWidget(self._path_input)
|
|
109
382
|
|
|
110
383
|
# 浏览按钮
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
path_layout.addWidget(
|
|
384
|
+
self._browse_btn = QPushButton("浏览...")
|
|
385
|
+
self._browse_btn.setFixedWidth(80)
|
|
386
|
+
self._browse_btn.clicked.connect(self._browse_path)
|
|
387
|
+
path_layout.addWidget(self._browse_btn)
|
|
115
388
|
|
|
116
389
|
input_layout.addLayout(path_layout)
|
|
117
|
-
|
|
390
|
+
self._project_layout.addWidget(input_widget)
|
|
118
391
|
|
|
119
392
|
# 分隔线
|
|
120
393
|
separator2 = QLabel("─" * 50)
|
|
121
394
|
separator2.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
122
395
|
separator2.setStyleSheet("color: #ccc;")
|
|
123
|
-
|
|
396
|
+
self._project_layout.addWidget(separator2)
|
|
124
397
|
|
|
125
398
|
# 配置选项区域(左右布局)
|
|
126
399
|
config_container = QWidget()
|
|
@@ -134,34 +407,43 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
134
407
|
|
|
135
408
|
# 语言选择
|
|
136
409
|
lang_layout = QHBoxLayout()
|
|
137
|
-
|
|
138
|
-
|
|
410
|
+
self._lang_label = QLabel("语 言:")
|
|
411
|
+
self._lang_label.setFixedWidth(80)
|
|
139
412
|
self._lang_combo = QComboBox()
|
|
140
|
-
|
|
413
|
+
# 语言选项
|
|
414
|
+
self._lang_combo.addItem("中文 (cn)", "cn")
|
|
415
|
+
self._lang_combo.addItem("英文 (en)", "en")
|
|
141
416
|
self._lang_combo.setCurrentIndex(0)
|
|
142
|
-
|
|
417
|
+
self._lang_combo.currentIndexChanged.connect(self._update_language)
|
|
418
|
+
lang_layout.addWidget(self._lang_label)
|
|
143
419
|
lang_layout.addWidget(self._lang_combo)
|
|
144
420
|
left_config_layout.addLayout(lang_layout)
|
|
145
421
|
|
|
146
422
|
# 竞技场选择
|
|
147
423
|
arena_layout = QHBoxLayout()
|
|
148
|
-
|
|
149
|
-
|
|
424
|
+
self._arena_label = QLabel("竞技场:")
|
|
425
|
+
self._arena_label.setFixedWidth(80)
|
|
150
426
|
self._arena_combo = QComboBox()
|
|
151
|
-
|
|
427
|
+
# 竞技场选项
|
|
428
|
+
self._arena_combo.addItem("灰色 (gray)", "gray")
|
|
429
|
+
self._arena_combo.addItem("绿色 (green)", "green")
|
|
430
|
+
self._arena_combo.addItem("蓝色 (blue)", "blue")
|
|
431
|
+
self._arena_combo.addItem("红色 (red)", "red")
|
|
152
432
|
self._arena_combo.setCurrentIndex(0)
|
|
153
|
-
arena_layout.addWidget(
|
|
433
|
+
arena_layout.addWidget(self._arena_label)
|
|
154
434
|
arena_layout.addWidget(self._arena_combo)
|
|
155
435
|
left_config_layout.addLayout(arena_layout)
|
|
156
436
|
|
|
157
437
|
# 难度级别选择
|
|
158
438
|
level_layout = QHBoxLayout()
|
|
159
|
-
|
|
160
|
-
|
|
439
|
+
self._level_label = QLabel("难 度:")
|
|
440
|
+
self._level_label.setFixedWidth(80)
|
|
161
441
|
self._level_combo = QComboBox()
|
|
162
|
-
|
|
442
|
+
# 难度选项
|
|
443
|
+
self._level_combo.addItem("基础 (basic)", "basic")
|
|
444
|
+
self._level_combo.addItem("高级 (advanced)", "advanced")
|
|
163
445
|
self._level_combo.setCurrentIndex(0)
|
|
164
|
-
level_layout.addWidget(
|
|
446
|
+
level_layout.addWidget(self._level_label)
|
|
165
447
|
level_layout.addWidget(self._level_combo)
|
|
166
448
|
left_config_layout.addLayout(level_layout)
|
|
167
449
|
|
|
@@ -194,7 +476,22 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
194
476
|
|
|
195
477
|
config_container_layout.addWidget(right_config_widget)
|
|
196
478
|
|
|
197
|
-
|
|
479
|
+
self._project_layout.addWidget(config_container)
|
|
480
|
+
|
|
481
|
+
self._project_layout.addStretch()
|
|
482
|
+
|
|
483
|
+
# 加载设置
|
|
484
|
+
self._load_settings()
|
|
485
|
+
|
|
486
|
+
# 创建预制件管理器页面
|
|
487
|
+
self._prefabs_page = QPrefabsManager()
|
|
488
|
+
|
|
489
|
+
# 添加页面到堆叠容器
|
|
490
|
+
self._stacked_widget.addWidget(self._project_page)
|
|
491
|
+
self._stacked_widget.addWidget(self._prefabs_page)
|
|
492
|
+
|
|
493
|
+
# 连接堆叠容器的页面变化信号
|
|
494
|
+
self._stacked_widget.currentChanged.connect(self._on_page_changed)
|
|
198
495
|
|
|
199
496
|
# 按钮区域
|
|
200
497
|
button_layout = QHBoxLayout()
|
|
@@ -202,36 +499,95 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
202
499
|
|
|
203
500
|
# 创建按钮
|
|
204
501
|
self._create_btn = QPushButton("创建项目")
|
|
205
|
-
self._create_btn.setFixedSize(
|
|
206
|
-
self._create_btn.clicked.connect(self.
|
|
502
|
+
self._create_btn.setFixedSize(120, 35)
|
|
503
|
+
self._create_btn.clicked.connect(self._on_next_or_create)
|
|
207
504
|
self._create_btn.setEnabled(False)
|
|
208
505
|
self._create_btn.setDefault(True)
|
|
209
506
|
button_layout.addWidget(self._create_btn)
|
|
210
507
|
|
|
211
508
|
# 取消按钮
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
button_layout.addWidget(
|
|
509
|
+
self._cancel_btn = QPushButton("取消")
|
|
510
|
+
self._cancel_btn.setFixedSize(80, 35)
|
|
511
|
+
self._cancel_btn.clicked.connect(self._on_previous_or_cancel)
|
|
512
|
+
button_layout.addWidget(self._cancel_btn)
|
|
216
513
|
|
|
514
|
+
# 添加按钮区域到主布局
|
|
217
515
|
layout.addLayout(button_layout)
|
|
218
|
-
|
|
516
|
+
|
|
517
|
+
# 初始更新语言
|
|
518
|
+
# 延迟调用,确保所有UI元素都已创建
|
|
519
|
+
self._update_language()
|
|
520
|
+
|
|
521
|
+
# 初始更新按钮文本
|
|
522
|
+
self._update_button_texts()
|
|
219
523
|
|
|
220
524
|
def _on_name_changed(self, text):
|
|
221
525
|
"""项目名称改变时的处理"""
|
|
222
526
|
self._proj_name = text.strip()
|
|
223
527
|
self._create_btn.setEnabled(bool(self._proj_name and self._proj_path))
|
|
528
|
+
|
|
529
|
+
def _on_cancel(self):
|
|
530
|
+
"""Handle cancel button click"""
|
|
531
|
+
self.projectCancelled.emit()
|
|
532
|
+
# Exit the application when cancel is clicked
|
|
533
|
+
QApplication.quit()
|
|
534
|
+
|
|
535
|
+
def _on_next_or_create(self):
|
|
536
|
+
"""Handle next or create button click"""
|
|
537
|
+
current_index = self._stacked_widget.currentIndex()
|
|
538
|
+
max_index = self._stacked_widget.count() - 1
|
|
539
|
+
|
|
540
|
+
if current_index < max_index:
|
|
541
|
+
# Go to next page
|
|
542
|
+
self._stacked_widget.setCurrentIndex(current_index + 1)
|
|
543
|
+
else:
|
|
544
|
+
# Create project
|
|
545
|
+
self._create_project()
|
|
546
|
+
|
|
547
|
+
def _on_previous_or_cancel(self):
|
|
548
|
+
"""Handle previous or cancel button click"""
|
|
549
|
+
current_index = self._stacked_widget.currentIndex()
|
|
550
|
+
|
|
551
|
+
if current_index > 0:
|
|
552
|
+
# Go to previous page
|
|
553
|
+
self._stacked_widget.setCurrentIndex(current_index - 1)
|
|
554
|
+
else:
|
|
555
|
+
# Cancel
|
|
556
|
+
self._on_cancel()
|
|
557
|
+
|
|
558
|
+
def _on_page_changed(self, index):
|
|
559
|
+
"""Handle page changed signal"""
|
|
560
|
+
self._update_button_texts()
|
|
561
|
+
|
|
562
|
+
def _update_button_texts(self):
|
|
563
|
+
"""Update button texts based on current page index"""
|
|
564
|
+
current_index = self._stacked_widget.currentIndex()
|
|
565
|
+
max_index = self._stacked_widget.count() - 1
|
|
566
|
+
|
|
567
|
+
# Update create/next button
|
|
568
|
+
if current_index < max_index:
|
|
569
|
+
self._create_btn.setText(self.lang('next_page'))
|
|
570
|
+
else:
|
|
571
|
+
self._create_btn.setText(self.lang('create_project'))
|
|
572
|
+
|
|
573
|
+
# Update cancel/previous button
|
|
574
|
+
if current_index == 0:
|
|
575
|
+
self._cancel_btn.setText(self.lang('cancel'))
|
|
576
|
+
else:
|
|
577
|
+
self._cancel_btn.setText(self.lang('previous_page'))
|
|
224
578
|
|
|
225
579
|
def _browse_path(self):
|
|
226
580
|
"""浏览选择路径"""
|
|
227
581
|
current_path = self._path_input.text() or os.path.expanduser("~")
|
|
228
582
|
path = QFileDialog.getExistingDirectory(
|
|
229
|
-
self,
|
|
583
|
+
self, self.lang('select_location'), current_path
|
|
230
584
|
)
|
|
231
585
|
if path:
|
|
232
586
|
self._path_input.setText(path)
|
|
233
587
|
self._proj_path = path
|
|
234
588
|
self._create_btn.setEnabled(bool(self._proj_name and self._proj_path))
|
|
589
|
+
# Save settings after path change
|
|
590
|
+
self._save_settings()
|
|
235
591
|
|
|
236
592
|
def _create_project(self):
|
|
237
593
|
"""创建项目"""
|
|
@@ -244,8 +600,8 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
244
600
|
# 检查路径是否已存在
|
|
245
601
|
if os.path.exists(full_path):
|
|
246
602
|
reply = QMessageBox.question(
|
|
247
|
-
self,
|
|
248
|
-
|
|
603
|
+
self, self.lang('path_exists'),
|
|
604
|
+
self.lang('path_exists_message', full_path),
|
|
249
605
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
250
606
|
)
|
|
251
607
|
if reply != QMessageBox.StandardButton.Yes:
|
|
@@ -256,15 +612,22 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
256
612
|
self._extract_project_template(full_path)
|
|
257
613
|
|
|
258
614
|
QMessageBox.information(
|
|
259
|
-
self,
|
|
260
|
-
|
|
615
|
+
self, self.lang('success'),
|
|
616
|
+
self.lang('success_message', self._proj_name, full_path)
|
|
261
617
|
)
|
|
262
|
-
|
|
618
|
+
|
|
619
|
+
# 处理预制件
|
|
620
|
+
self._process_prefabs(full_path)
|
|
621
|
+
|
|
622
|
+
self.projectCreated.emit(self._proj_name, full_path)
|
|
623
|
+
|
|
624
|
+
# 成功创建项目后关闭程序
|
|
625
|
+
QApplication.quit()
|
|
263
626
|
|
|
264
627
|
except Exception as e:
|
|
265
628
|
QMessageBox.critical(
|
|
266
|
-
self,
|
|
267
|
-
|
|
629
|
+
self, self.lang('error'),
|
|
630
|
+
self.lang('error_message', str(e))
|
|
268
631
|
)
|
|
269
632
|
|
|
270
633
|
def _extract_project_template(self, target_path):
|
|
@@ -287,14 +650,9 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
287
650
|
print(f"[DEBUG] 项目模板已解压到: {target_path}") # 调试输出
|
|
288
651
|
|
|
289
652
|
# 获取用户选择的配置
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
arena_text = self._arena_combo.currentText()
|
|
294
|
-
arena = arena_text.split('(')[1].strip(')')
|
|
295
|
-
|
|
296
|
-
level_text = self._level_combo.currentText()
|
|
297
|
-
level = level_text.split('(')[1].strip(')')
|
|
653
|
+
lang = self._lang_combo.currentData() or 'cn'
|
|
654
|
+
arena = self._arena_combo.currentData() or 'gray'
|
|
655
|
+
level = self._level_combo.currentData() or 'basic'
|
|
298
656
|
|
|
299
657
|
# 调用配置方法
|
|
300
658
|
ToConfigAfter(path=target_path, language=lang, arena=arena, level=level)
|
|
@@ -314,6 +672,268 @@ class ProjectCreatorUI(QMainWindow):
|
|
|
314
672
|
print(f"[DEBUG] 执行预设继承模式配置")
|
|
315
673
|
ToPrefabAfter(path=target_path)
|
|
316
674
|
ToCustomAfter(path=target_path)
|
|
675
|
+
|
|
676
|
+
# Properties
|
|
677
|
+
@pyqtProperty(str, constant=False)
|
|
678
|
+
def projectName(self) -> str:
|
|
679
|
+
"""Get project name"""
|
|
680
|
+
return self._proj_name
|
|
681
|
+
|
|
682
|
+
@projectName.setter
|
|
683
|
+
def projectName(self, value: str):
|
|
684
|
+
"""Set project name"""
|
|
685
|
+
self._proj_name = value.strip()
|
|
686
|
+
self._name_input.setText(self._proj_name)
|
|
687
|
+
self._create_btn.setEnabled(bool(self._proj_name and self._proj_path))
|
|
688
|
+
|
|
689
|
+
@pyqtProperty(str, constant=False)
|
|
690
|
+
def projectPath(self) -> str:
|
|
691
|
+
"""Get project path"""
|
|
692
|
+
return self._proj_path
|
|
693
|
+
|
|
694
|
+
@projectPath.setter
|
|
695
|
+
def projectPath(self, value: str):
|
|
696
|
+
"""Set project path"""
|
|
697
|
+
self._proj_path = value
|
|
698
|
+
self._path_input.setText(self._proj_path)
|
|
699
|
+
self._create_btn.setEnabled(bool(self._proj_name and self._proj_path))
|
|
700
|
+
|
|
701
|
+
@pyqtProperty(str, constant=False)
|
|
702
|
+
def language(self) -> str:
|
|
703
|
+
"""Get current language code"""
|
|
704
|
+
return self._lang_combo.currentData() or 'cn'
|
|
705
|
+
|
|
706
|
+
@language.setter
|
|
707
|
+
def language(self, value: str):
|
|
708
|
+
"""Set language code"""
|
|
709
|
+
if value == 'cn':
|
|
710
|
+
self._lang_combo.setCurrentIndex(0)
|
|
711
|
+
elif value == 'en':
|
|
712
|
+
self._lang_combo.setCurrentIndex(1)
|
|
713
|
+
|
|
714
|
+
def _process_prefabs(self, project_path: str):
|
|
715
|
+
"""
|
|
716
|
+
Process selected prefabs: copy them to src directory, handle naming conflicts,
|
|
717
|
+
and add import statements to main.py
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
project_path: Path to the newly created project
|
|
721
|
+
"""
|
|
722
|
+
print(f"Processing prefabs for project: {project_path}")
|
|
723
|
+
|
|
724
|
+
# Check if QPrefabsManager exists and has selected prefabs
|
|
725
|
+
if hasattr(self, '_prefabs_page'):
|
|
726
|
+
# Get selected prefabs as list
|
|
727
|
+
selected_prefabs = self._prefabs_page.selectedPrefabs
|
|
728
|
+
print(f"Selected prefabs: {selected_prefabs}")
|
|
729
|
+
|
|
730
|
+
if selected_prefabs:
|
|
731
|
+
# Convert to dict format using list_to_dict method
|
|
732
|
+
prefabs_dict = self._prefabs_page.list_to_dict(selected_prefabs)
|
|
733
|
+
print(f"Prefabs dict: {prefabs_dict}")
|
|
734
|
+
|
|
735
|
+
# Create src directory if it doesn't exist
|
|
736
|
+
src_dir = os.path.join(project_path, 'src')
|
|
737
|
+
os.makedirs(src_dir, exist_ok=True)
|
|
738
|
+
print(f"Src directory: {src_dir}")
|
|
739
|
+
|
|
740
|
+
# Get existing directory names in src
|
|
741
|
+
existing_dirs = [d for d in os.listdir(src_dir) if os.path.isdir(os.path.join(src_dir, d))]
|
|
742
|
+
print(f"Existing dirs in src: {existing_dirs}")
|
|
743
|
+
|
|
744
|
+
# Process each prefab
|
|
745
|
+
imported_modules = []
|
|
746
|
+
|
|
747
|
+
for name, path in prefabs_dict.items():
|
|
748
|
+
# Clean directory name: replace non-alphanumeric and non-underscore characters with underscore
|
|
749
|
+
clean_name = ''.join(c if c.isalnum() or c == '_' else '_' for c in name)
|
|
750
|
+
|
|
751
|
+
# Handle naming conflicts
|
|
752
|
+
final_name = clean_name
|
|
753
|
+
i = 2
|
|
754
|
+
while final_name in existing_dirs:
|
|
755
|
+
final_name = f"{clean_name}_{i}"
|
|
756
|
+
i += 1
|
|
757
|
+
|
|
758
|
+
# Add to existing_dirs to avoid conflicts with subsequent prefabs
|
|
759
|
+
existing_dirs.append(final_name)
|
|
760
|
+
|
|
761
|
+
# Target directory path
|
|
762
|
+
target_dir = os.path.join(src_dir, final_name)
|
|
763
|
+
|
|
764
|
+
# Copy directory from path to target_dir
|
|
765
|
+
import shutil
|
|
766
|
+
try:
|
|
767
|
+
shutil.copytree(path, target_dir)
|
|
768
|
+
imported_modules.append(final_name)
|
|
769
|
+
print(f"Copied prefab {name} to {target_dir}")
|
|
770
|
+
except Exception as e:
|
|
771
|
+
print(f"Error copying prefab {name}: {e}")
|
|
772
|
+
|
|
773
|
+
# Add import statements to main.py
|
|
774
|
+
if imported_modules:
|
|
775
|
+
main_py_path = os.path.join(project_path, 'src', 'main.py')
|
|
776
|
+
print(f"Main.py path: {main_py_path}")
|
|
777
|
+
if os.path.exists(main_py_path):
|
|
778
|
+
# Read main.py content
|
|
779
|
+
with open(main_py_path, 'r', encoding='utf-8') as f:
|
|
780
|
+
content = f.read()
|
|
781
|
+
print(f"Main.py content:\n{content}")
|
|
782
|
+
|
|
783
|
+
# Find the end of import statements
|
|
784
|
+
import_end = 0
|
|
785
|
+
lines = content.split('\n')
|
|
786
|
+
# Iterate through all lines to find the last import statement
|
|
787
|
+
for i, line in enumerate(lines):
|
|
788
|
+
if line.strip().startswith('from ') or line.strip().startswith('import '):
|
|
789
|
+
import_end = i + 1
|
|
790
|
+
elif import_end > 0 and line.strip() == '':
|
|
791
|
+
# Keep moving past empty lines after imports
|
|
792
|
+
import_end = i + 1
|
|
793
|
+
elif import_end > 0 and line.strip() != '':
|
|
794
|
+
# Stop when we reach non-empty, non-import line
|
|
795
|
+
break
|
|
796
|
+
# If no imports found, add at the beginning
|
|
797
|
+
print(f"Import end position: {import_end}")
|
|
798
|
+
|
|
799
|
+
# Add import statements
|
|
800
|
+
import_lines = []
|
|
801
|
+
for module in imported_modules:
|
|
802
|
+
import_lines.append(f"from {module} import *")
|
|
803
|
+
print(f"Import lines to add: {import_lines}")
|
|
804
|
+
|
|
805
|
+
# Process the content to properly format imports
|
|
806
|
+
# First, separate the content into import section and code section
|
|
807
|
+
import_section = []
|
|
808
|
+
code_section = []
|
|
809
|
+
in_imports = True
|
|
810
|
+
|
|
811
|
+
for line in lines:
|
|
812
|
+
if in_imports:
|
|
813
|
+
if line.strip().startswith('from ') or line.strip().startswith('import '):
|
|
814
|
+
import_section.append(line.strip())
|
|
815
|
+
elif line.strip() == '':
|
|
816
|
+
# Skip empty lines in import section
|
|
817
|
+
pass
|
|
818
|
+
else:
|
|
819
|
+
# Reached end of imports
|
|
820
|
+
in_imports = False
|
|
821
|
+
code_section.append(line)
|
|
822
|
+
else:
|
|
823
|
+
# Add all other lines to code section
|
|
824
|
+
code_section.append(line)
|
|
825
|
+
|
|
826
|
+
# Add new imports to import section
|
|
827
|
+
import_section.extend(import_lines)
|
|
828
|
+
|
|
829
|
+
# Create properly formatted content
|
|
830
|
+
# Join imports without empty lines between them
|
|
831
|
+
import_text = '\n'.join(import_section)
|
|
832
|
+
# Join code section as is
|
|
833
|
+
code_text = '\n'.join(code_section)
|
|
834
|
+
# Combine with exactly two empty lines between imports and code
|
|
835
|
+
new_content = import_text + '\n\n\n' + code_text
|
|
836
|
+
print(f"New main.py content:\n{new_content}")
|
|
837
|
+
|
|
838
|
+
# Write back to main.py
|
|
839
|
+
with open(main_py_path, 'w', encoding='utf-8') as f:
|
|
840
|
+
f.write(new_content)
|
|
841
|
+
|
|
842
|
+
print(f"Added import statements for {len(imported_modules)} modules to main.py")
|
|
843
|
+
|
|
844
|
+
# Verify the changes
|
|
845
|
+
with open(main_py_path, 'r', encoding='utf-8') as f:
|
|
846
|
+
verify_content = f.read()
|
|
847
|
+
print(f"Verified main.py content:\n{verify_content}")
|
|
848
|
+
else:
|
|
849
|
+
print(f"Main.py not found at {main_py_path}")
|
|
850
|
+
else:
|
|
851
|
+
print("No modules to import")
|
|
852
|
+
else:
|
|
853
|
+
print("No selected prefabs")
|
|
854
|
+
else:
|
|
855
|
+
print("No QPrefabsManager found")
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
class ProjectCreatorUI(QMainWindow):
|
|
859
|
+
"""Project creator main window"""
|
|
860
|
+
|
|
861
|
+
def __init__(self):
|
|
862
|
+
super().__init__()
|
|
863
|
+
self._init_ui()
|
|
864
|
+
|
|
865
|
+
def _init_ui(self):
|
|
866
|
+
"""Initialize main window"""
|
|
867
|
+
self.setWindowTitle("PyScreeps Arena - Project Wizard")
|
|
868
|
+
self.setWindowIcon(QIcon(get_pixmap()))
|
|
869
|
+
|
|
870
|
+
# Create central widget
|
|
871
|
+
central_widget = QWidget()
|
|
872
|
+
self.setCentralWidget(central_widget)
|
|
873
|
+
|
|
874
|
+
# Create main layout
|
|
875
|
+
layout = QVBoxLayout(central_widget)
|
|
876
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
|
877
|
+
|
|
878
|
+
# Create project body widget
|
|
879
|
+
self._project_body = QProjectBody()
|
|
880
|
+
|
|
881
|
+
# Connect signals
|
|
882
|
+
self._project_body.projectCreated.connect(self._on_project_created)
|
|
883
|
+
self._project_body.projectCancelled.connect(self._on_project_cancelled)
|
|
884
|
+
self._project_body.languageChanged.connect(self._on_language_changed)
|
|
885
|
+
|
|
886
|
+
# Add project body to layout
|
|
887
|
+
layout.addWidget(self._project_body)
|
|
888
|
+
|
|
889
|
+
# Set window size
|
|
890
|
+
self.setFixedSize(550, 650)
|
|
891
|
+
|
|
892
|
+
def _on_project_created(self, project_name: str, project_path: str):
|
|
893
|
+
"""Handle project created signal"""
|
|
894
|
+
print(f"Project created: {project_name} at {project_path}")
|
|
895
|
+
# You can add additional handling here if needed
|
|
896
|
+
|
|
897
|
+
def _on_project_cancelled(self):
|
|
898
|
+
"""Handle project cancelled signal"""
|
|
899
|
+
print("Project creation cancelled")
|
|
900
|
+
# You can add additional handling here if needed
|
|
901
|
+
|
|
902
|
+
def _on_language_changed(self, language_code: str):
|
|
903
|
+
"""Handle language changed signal"""
|
|
904
|
+
print(f"Language changed to: {language_code}")
|
|
905
|
+
# You can add additional handling here if needed
|
|
906
|
+
|
|
907
|
+
# Properties to expose project body properties
|
|
908
|
+
@pyqtProperty(str, constant=False)
|
|
909
|
+
def projectName(self) -> str:
|
|
910
|
+
"""Get project name"""
|
|
911
|
+
return self._project_body.projectName
|
|
912
|
+
|
|
913
|
+
@projectName.setter
|
|
914
|
+
def projectName(self, value: str):
|
|
915
|
+
"""Set project name"""
|
|
916
|
+
self._project_body.projectName = value
|
|
917
|
+
|
|
918
|
+
@pyqtProperty(str, constant=False)
|
|
919
|
+
def projectPath(self) -> str:
|
|
920
|
+
"""Get project path"""
|
|
921
|
+
return self._project_body.projectPath
|
|
922
|
+
|
|
923
|
+
@projectPath.setter
|
|
924
|
+
def projectPath(self, value: str):
|
|
925
|
+
"""Set project path"""
|
|
926
|
+
self._project_body.projectPath = value
|
|
927
|
+
|
|
928
|
+
@pyqtProperty(str, constant=False)
|
|
929
|
+
def language(self) -> str:
|
|
930
|
+
"""Get current language code"""
|
|
931
|
+
return self._project_body.language
|
|
932
|
+
|
|
933
|
+
@language.setter
|
|
934
|
+
def language(self, value: str):
|
|
935
|
+
"""Set language code"""
|
|
936
|
+
self._project_body.language = value
|
|
317
937
|
|
|
318
938
|
|
|
319
939
|
def run_project_creator():
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
pyscreeps_arena/__init__.py,sha256=GWrCpZnrzPwbBzhNqk8gTvV4VSwkP2DbxK8lR0E_SzY,3223
|
|
2
2
|
pyscreeps_arena/build.py,sha256=DQeGLnID2FjpsWTbnqwt2cOp28olWK68U67bfbFJSOU,177
|
|
3
|
-
pyscreeps_arena/compiler.py,sha256=
|
|
3
|
+
pyscreeps_arena/compiler.py,sha256=sddY_09Nvq0Ts86lmjLlsKOT0b-EWnhsKK04oIRvnDc,70671
|
|
4
4
|
pyscreeps_arena/localization.py,sha256=Dr0G6n8DvT6syfC0urqwjccYpEPEfcwYyJx3f9s-6a8,6031
|
|
5
|
-
pyscreeps_arena/project.7z,sha256=
|
|
5
|
+
pyscreeps_arena/project.7z,sha256=PSWXjm7FpLRWzlx_dMUaVXRtJ8gFFXQkaQKUO6RueFw,580860
|
|
6
6
|
pyscreeps_arena/afters/__init__.py,sha256=Ze8NKQoMEzIJ3WlyR8Jhhdq21gSXJUtGoINzB8iaQyE,254
|
|
7
7
|
pyscreeps_arena/afters/after_config.py,sha256=Tw8Qry0KAM3hDasmXArKoS1crPuwfiAfWiEeAcKrPdo,2455
|
|
8
8
|
pyscreeps_arena/afters/after_custom.py,sha256=pgI5xkY4X7w_NHFufQ5PUGxpCrv9Ayw6VWFwVJCHLFw,3226
|
|
@@ -11,7 +11,7 @@ pyscreeps_arena/afters/after_prefab.py,sha256=H_2LJbr_7e2yCtxWStxze259-py3TXQlyP
|
|
|
11
11
|
pyscreeps_arena/core/__init__.py,sha256=qoP_rx1TpbDLJoTm5via4XPwEPaV1FXr1SYvoVoHGms,41
|
|
12
12
|
pyscreeps_arena/core/basic.py,sha256=DFvyDTsTXf2bQtnS9s254TrkshvRwajaHcvTyVvJyqw,2790
|
|
13
13
|
pyscreeps_arena/core/config.py,sha256=x_JhVHlVZqB3qA7UyACVnwZjg2gZU-BIs49UxZzwCoE,637
|
|
14
|
-
pyscreeps_arena/core/const.py,sha256=
|
|
14
|
+
pyscreeps_arena/core/const.py,sha256=XyypqtmbRI4bXv4NviZ-G-9UegCpPUgjPKKAWbgWGmA,590
|
|
15
15
|
pyscreeps_arena/core/core.py,sha256=3Nty8eLKPNgwnYk_sVNBPrWuKxBXI2od8nfEezsEAZQ,5157
|
|
16
16
|
pyscreeps_arena/core/main.py,sha256=-FNSOEjksNlDfCbUqsjtPSUW8vT3qxEdfzXqT5Tdsik,170
|
|
17
17
|
pyscreeps_arena/core/utils.py,sha256=N9OOkORvrqnJakayaFp9qyS0apWhB9lBK4xyyYkhFdo,215
|
|
@@ -20,7 +20,7 @@ pyscreeps_arena/ui/__init__.py,sha256=rxSx5ZPxMNUVokvYgOmGkxHInFUVVYh9InSbbvkgo2
|
|
|
20
20
|
pyscreeps_arena/ui/creeplogic_edit.py,sha256=XlO4aoH48UyWhQQAejyLY57yukpn9BzS0w9QXMMOeeg,314
|
|
21
21
|
pyscreeps_arena/ui/map_render.py,sha256=dS7iL5ywEwlhGEOiif0G9up3bpPI4p20o07mbnDaSD8,30414
|
|
22
22
|
pyscreeps_arena/ui/mapviewer.py,sha256=AWv9loznosIJaQC5L5szRms_zDetDiHXIsY2EEpRNB8,293
|
|
23
|
-
pyscreeps_arena/ui/project_ui.py,sha256=
|
|
23
|
+
pyscreeps_arena/ui/project_ui.py,sha256=eQMngywySlFbSHOMbi0mrsanGzI2lNZiWg_92_fChxQ,39188
|
|
24
24
|
pyscreeps_arena/ui/rs_icon.py,sha256=5v8YdTv2dds4iAp9x3MXpqB_5XIOYd0uPqaq83ZNySM,22496
|
|
25
25
|
pyscreeps_arena/ui/qcreeplogic/__init__.py,sha256=FVuJ6TZyDh5WnkYlHjCWjEKGPBWl27LzqIAab-4aeuI,75
|
|
26
26
|
pyscreeps_arena/ui/qcreeplogic/model.py,sha256=_Ba5jbHcFsKQ1el36UcCnXGUimvvWSRHgzo979SpVzs,2833
|
|
@@ -45,8 +45,8 @@ pyscreeps_arena/ui/qmapv/test_simple_array.py,sha256=hPnLXcFrn6I1X4JXFM3RVpBPhU_
|
|
|
45
45
|
pyscreeps_arena/ui/qrecipe/__init__.py,sha256=2Guvr9k5kGkZboiVC0aNF4u48LRbmcCm2dqOhEF52Tw,59
|
|
46
46
|
pyscreeps_arena/ui/qrecipe/model.py,sha256=s3lr_DXtsBgt8bVg1_wLz-dX88QKi77mNkqM5VJsGwE,13200
|
|
47
47
|
pyscreeps_arena/ui/qrecipe/qrecipe.py,sha256=z57VLmlpMripdpGtVCkKR0csJQhw5-WpocZK5l2xTVg,39398
|
|
48
|
-
pyscreeps_arena-0.5.
|
|
49
|
-
pyscreeps_arena-0.5.
|
|
50
|
-
pyscreeps_arena-0.5.
|
|
51
|
-
pyscreeps_arena-0.5.
|
|
52
|
-
pyscreeps_arena-0.5.
|
|
48
|
+
pyscreeps_arena-0.5.9.0.dist-info/METADATA,sha256=YVlVorfH88HdKb0Hl1BxtqpUD66dYBk1LXRtbL6lW0Y,2356
|
|
49
|
+
pyscreeps_arena-0.5.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
50
|
+
pyscreeps_arena-0.5.9.0.dist-info/entry_points.txt,sha256=pnpuPPadwQsxQPaR1rXzUo0fUvhOcC7HTHlf7TYXr7M,141
|
|
51
|
+
pyscreeps_arena-0.5.9.0.dist-info/top_level.txt,sha256=l4uLyMR2NOy41ngBMh795jOHTFk3tgYKy64-9cgjVng,16
|
|
52
|
+
pyscreeps_arena-0.5.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|