|
1 | 1 | import sys
|
2 | 2 | import nbtlib
|
3 | 3 | import os
|
4 |
| -import re |
5 |
| - |
6 |
| -OFFSETS = ( |
7 |
| - ("head", 0.0), ("right_arm", -1024.0), ("left_arm", -2048.0), |
8 |
| - ("torso", -3072.0), ("right_leg", -4096.0), ("left_leg", -5120.0) |
9 |
| -) |
10 |
| - |
11 |
| -SPLIT_OFFSETS = ( |
12 |
| - ("head", 0.0),("right_arm", -1024.0), ("right_forearm", -6144.0), ("left_arm", -2048.0), ("left_forearm", -7168.0), |
13 |
| - ("torso", -3072.0), ("waist", -8192.0), ("right_leg", -4096.0), ("lower_right_leg", -9216.0) ,("left_leg", -5120.0), ("lower_left_leg", -10240.0) |
14 |
| -) |
15 |
| - |
16 |
| -def parse_arguments(): |
17 |
| - """Parse command-line arguments.""" |
18 |
| - nArgs = len(sys.argv) |
19 |
| - if nArgs < 2: |
20 |
| - print("Usage: aj-convert.py [project] [optional:flags]") |
21 |
| - print("Available flags:") |
22 |
| - print("\t-pn=[playerName]\tPlayer skin to use. Default '' (no skin), must be set later in game") |
23 |
| - print("\t-split\tEnables split mode where the player model has extra joints (player_split.ajblueprint)") |
| 4 | + |
| 5 | +# Offset mappings for different player model modes |
| 6 | +DEFAULT_OFFSETS = { |
| 7 | + "head": 0.0, "right_arm": -1024.0, "left_arm": -2048.0, |
| 8 | + "torso": -3072.0, "right_leg": -4096.0, "left_leg": -5120.0 |
| 9 | +} |
| 10 | + |
| 11 | +SPLIT_OFFSETS = { |
| 12 | + "head": 0.0, "right_arm": -1024.0, "right_forearm": -6144.0, |
| 13 | + "left_arm": -2048.0, "left_forearm": -7168.0, "torso": -3072.0, |
| 14 | + "waist": -8192.0, "right_leg": -4096.0, "lower_right_leg": -9216.0, |
| 15 | + "left_leg": -5120.0, "lower_left_leg": -10240.0 |
| 16 | +} |
| 17 | + |
| 18 | +def parse_cli_arguments(): |
| 19 | + """Parse and validate command-line arguments.""" |
| 20 | + if len(sys.argv) < 2: |
| 21 | + print_usage() |
24 | 22 | sys.exit(1)
|
25 | 23 |
|
26 | 24 | project = sys.argv[1]
|
27 |
| - playerName = "" |
28 |
| - offsets = OFFSETS |
29 |
| - |
30 |
| - if nArgs > 2: |
31 |
| - for arg in sys.argv[2:]: |
32 |
| - if arg.startswith("-pn="): |
33 |
| - playerName = arg[4:] |
34 |
| - if arg.startswith("-split"): |
35 |
| - offsets = SPLIT_OFFSETS |
36 |
| - |
37 |
| - return project, playerName, offsets |
38 |
| - |
39 |
| -def read_and_modify_summon_function(summonPath, project, offsets, playerName): |
40 |
| - """Read and modify the summon.mcfunction file.""" |
41 |
| - summonResult = "" |
42 |
| - |
| 25 | + player_name = "" |
| 26 | + offsets = DEFAULT_OFFSETS |
| 27 | + |
| 28 | + for arg in sys.argv[2:]: |
| 29 | + if arg.startswith("-pn="): |
| 30 | + player_name = arg[4:] |
| 31 | + elif arg == "-split": |
| 32 | + offsets = SPLIT_OFFSETS |
| 33 | + |
| 34 | + return project, player_name, offsets |
| 35 | + |
| 36 | +def print_usage(): |
| 37 | + """Display usage instructions.""" |
| 38 | + print("Usage: aj-convert.py [project] [optional:flags]") |
| 39 | + print("Available flags:") |
| 40 | + print("\t-pn=[playerName]\tSpecify the player skin (default: none)") |
| 41 | + print("\t-split\tEnable split mode with extra joints") |
| 42 | + |
| 43 | +def modify_summon_file(filepath, project, offsets, player_name): |
| 44 | + """Modify the summon.mcfunction file with new offsets and player skin.""" |
43 | 45 | try:
|
44 |
| - with open(summonPath, "r") as f: |
45 |
| - for _ in range(3): |
46 |
| - summonResult += f.readline() |
| 46 | + with open(filepath, "r") as file: |
| 47 | + summon_content = [file.readline() for _ in range(3)] |
| 48 | + summon_data = file.readline() |
47 | 49 |
|
48 |
| - temp = f.readline() |
49 |
| - |
50 |
| - if temp[-4] == ',': |
51 |
| - temp = temp[:-4] + temp[-3:] # Remove malformed last comma |
| 50 | + if summon_data[-4] == ",": |
| 51 | + summon_data = summon_data[:-4] + summon_data[-3:] |
52 | 52 |
|
53 |
| - nbtStart = temp.index("~ ~ ~ ") + len("~ ~ ~ ") |
54 |
| - summonResult += temp[:nbtStart] |
55 |
| - nbt_data = temp[nbtStart:] |
| 53 | + nbt_start = summon_data.index("~ ~ ~ ") + len("~ ~ ~ ") |
| 54 | + summon_content.append(summon_data[:nbt_start]) |
| 55 | + nbt_data = summon_data[nbt_start:] |
56 | 56 |
|
57 |
| - nbtRoot = nbtlib.parse_nbt(nbt_data) |
58 |
| - modify_nbt_passengers(nbtRoot, project, offsets, playerName) |
| 57 | + nbt_root = nbtlib.parse_nbt(nbt_data) |
| 58 | + update_nbt_passengers(nbt_root, project, offsets, player_name) |
59 | 59 |
|
60 |
| - summonResult += nbtlib.serialize_tag(nbtRoot, compact=True) + "\n" |
61 |
| - summonResult += f.readline() |
| 60 | + summon_content.append(nbtlib.serialize_tag(nbt_root, compact=True) + "\n") |
| 61 | + summon_content.append(file.readline()) |
62 | 62 |
|
63 |
| - with open(summonPath, "w") as f: |
64 |
| - f.write(summonResult) |
| 63 | + with open(filepath, "w") as file: |
| 64 | + file.writelines(summon_content) |
65 | 65 |
|
66 |
| - print(f"Successfully modified {summonPath}") |
| 66 | + print(f"Successfully updated: {filepath}") |
67 | 67 |
|
68 | 68 | except Exception as e:
|
69 |
| - print(f"Error processing {summonPath}: {e}") |
| 69 | + print(f"Error updating {filepath}: {e}") |
70 | 70 | sys.exit(1)
|
71 | 71 |
|
72 |
| -def modify_nbt_passengers(nbtRoot, project, offsets, playerName): |
73 |
| - """Modify NBT data for passengers.""" |
74 |
| - for passenger in nbtRoot["Passengers"][1:]: # Skip First Marker |
75 |
| - for offsetPair in offsets: |
76 |
| - tag = f"aj.{project}.bone.{offsetPair[0]}" |
| 72 | +def update_nbt_passengers(nbt_root, project, offsets, player_name): |
| 73 | + """Apply offsets and player skin to NBT passenger data.""" |
| 74 | + for passenger in nbt_root["Passengers"][1:]: |
| 75 | + for bone_name, offset in offsets.items(): |
| 76 | + tag = f"aj.{project}.bone.{bone_name}" |
77 | 77 | if tag in passenger["Tags"]:
|
78 |
| - passenger["transformation"]["translation"][1] += offsetPair[1] |
| 78 | + passenger["transformation"]["translation"][1] += offset |
79 | 79 | passenger["item_display"] = nbtlib.tag.String("thirdperson_righthand")
|
80 |
| - |
81 |
| - if playerName: |
82 |
| - passenger["item"]["id"] = nbtlib.tag.String("minecraft:player_head") |
83 |
| - passenger["item"]["components"]["profile"] = nbtlib.tag.Compound({"name": nbtlib.tag.String(playerName)}) |
84 |
| - else: |
85 |
| - passenger["item"]["id"] = nbtlib.tag.String("minecraft:air") |
86 |
| - |
87 |
| -def process_pose_files(paths, project, offsets): |
88 |
| - """Process set_default_pose.mcfunction and apply_default_pose.mcfunction files.""" |
89 |
| - for path in paths: |
90 |
| - try: |
91 |
| - with open(path, "r") as f: |
92 |
| - lines = f.readlines() |
| 80 | + passenger["item"]["id"] = nbtlib.tag.String("minecraft:player_head" if player_name else "minecraft:air") |
| 81 | + if player_name: |
| 82 | + passenger["item"]["components"]["profile"] = nbtlib.tag.Compound({"name": nbtlib.tag.String(player_name)}) |
93 | 83 |
|
94 |
| - modifiedLines = [] |
95 |
| - for line in lines: |
96 |
| - if "merge entity @s" in line: |
97 |
| - modifiedLines.append(modify_merge_entity_line(line, project, offsets)) |
98 |
| - else: |
99 |
| - modifiedLines.append(line) |
| 84 | +def update_pose_files(filepaths, project, offsets): |
| 85 | + """Update set_default_pose.mcfunction and apply_default_pose.mcfunction files.""" |
| 86 | + for path in filepaths: |
| 87 | + try: |
| 88 | + with open(path, "r") as file: |
| 89 | + lines = [modify_pose_line(line, project, offsets) for line in file] |
100 | 90 |
|
101 |
| - with open(path, "w") as f: |
102 |
| - f.writelines(modifiedLines) |
| 91 | + with open(path, "w") as file: |
| 92 | + file.writelines(lines) |
103 | 93 |
|
104 |
| - print(f"Successfully modified {path}") |
| 94 | + print(f"Updated pose file: {path}") |
105 | 95 |
|
106 | 96 | except Exception as e:
|
107 | 97 | print(f"Error processing {path}: {e}")
|
108 | 98 | sys.exit(1)
|
109 | 99 |
|
110 |
| -def modify_merge_entity_line(line, project, offsets): |
111 |
| - """Modify a merge entity line with the appropriate offsets.""" |
112 |
| - for offsetPair in offsets: |
113 |
| - if f"[tag=aj.{project}.bone.{offsetPair[0]}]" in line: |
114 |
| - nbtStart = line.index("merge entity @s ") + len("merge entity @s ") |
115 |
| - tmpLine = line[:nbtStart] |
116 |
| - nbtRoot = nbtlib.parse_nbt(line[nbtStart:]) |
117 |
| - nbtRoot["transformation"][7] += offsetPair[1] |
118 |
| - return tmpLine + nbtlib.serialize_tag(nbtRoot, compact=True) + "\n" |
| 100 | +def modify_pose_line(line, project, offsets): |
| 101 | + """Modify a line with the appropriate transformation offset.""" |
| 102 | + for bone_name, offset in offsets.items(): |
| 103 | + tag = f"[tag=aj.{project}.bone.{bone_name}]" |
| 104 | + if tag in line: |
| 105 | + nbt_start = line.index("merge entity @s ") + len("merge entity @s ") |
| 106 | + tmp_line = line[:nbt_start] |
| 107 | + nbt_root = nbtlib.parse_nbt(line[nbt_start:]) |
| 108 | + nbt_root["transformation"][7] += offset |
| 109 | + return tmp_line + nbtlib.serialize_tag(nbt_root, compact=True) + "\n" |
119 | 110 | return line
|
120 | 111 |
|
121 |
| -def modify_animation_frames(rootpath, project, offsets): |
122 |
| - """Modify all animation frames in the specified project.""" |
| 112 | +def process_animation_frames(rootpath, offsets): |
| 113 | + """Modify all animation frames within the specified root directory.""" |
123 | 114 | try:
|
124 |
| - subfolders = [entry for entry in os.listdir(rootpath) if os.path.isdir(os.path.join(rootpath, entry))] |
125 |
| - |
126 |
| - for animation in subfolders: |
| 115 | + for animation in os.listdir(rootpath): |
127 | 116 | frames_path = os.path.join(rootpath, animation, "zzz", "frames")
|
128 |
| - frames = [entry for entry in os.listdir(frames_path) if os.path.isfile(os.path.join(frames_path, entry))] |
129 |
| - |
130 |
| - i = 0 |
131 |
| - for frame in frames: |
132 |
| - i+=1 |
133 |
| - sys.stdout.write(f"\rEditing {animation} frames: [{i}/{len(frames)}]") |
134 |
| - framepath = os.path.join(frames_path, frame) |
135 |
| - modify_frame_file(framepath, offsets) |
| 117 | + if not os.path.isdir(frames_path): |
| 118 | + continue |
| 119 | + |
| 120 | + frames = os.listdir(frames_path) |
| 121 | + for index, frame in enumerate(frames, start=1): |
| 122 | + frame_path = os.path.join(frames_path, frame) |
| 123 | + modify_frame_file(frame_path, offsets) |
| 124 | + sys.stdout.write(f"\rProcessing {animation} frames: [{index}/{len(frames)}]") |
136 | 125 | print()
|
137 | 126 |
|
138 | 127 | except Exception as e:
|
139 |
| - print(f"Error processing animations: {e}") |
| 128 | + print(f"Error processing animation frames: {e}") |
140 | 129 | sys.exit(1)
|
141 | 130 |
|
142 |
| -def modify_frame_file(framepath, offsets): |
143 |
| - """Modify a single frame file with the appropriate offsets.""" |
| 131 | +def modify_frame_file(filepath, offsets): |
| 132 | + """Modify an individual frame file by applying offsets.""" |
144 | 133 | try:
|
145 |
| - with open(framepath, "r") as f: |
146 |
| - lines = f.readlines() |
147 |
| - |
148 |
| - modifiedLines = [] |
149 |
| - for line in lines: |
150 |
| - if ") " in line: |
151 |
| - modifiedLines.append(modify_frame_line(line, offsets)) |
152 |
| - else: |
153 |
| - modifiedLines.append(line) |
| 134 | + with open(filepath, "r") as file: |
| 135 | + lines = [modify_frame_line(line, offsets) for line in file] |
154 | 136 |
|
155 |
| - with open(framepath, "w") as f: |
156 |
| - f.writelines(modifiedLines) |
| 137 | + with open(filepath, "w") as file: |
| 138 | + file.writelines(lines) |
157 | 139 |
|
158 | 140 | except Exception as e:
|
159 |
| - print(f"Error processing frame {framepath}: {e}") |
| 141 | + print(f"Error processing frame {filepath}: {e}") |
160 | 142 |
|
161 | 143 | def modify_frame_line(line, offsets):
|
162 |
| - """Modify a line in a frame file with the appropriate offsets.""" |
163 |
| - for offsetPair in offsets: |
164 |
| - # Construct the exact pattern to match |
165 |
| - target = f"$data merge entity $(bone_{offsetPair[0]})" |
| 144 | + """Modify a frame line by adjusting transformation values.""" |
| 145 | + for bone_name, offset in offsets.items(): |
| 146 | + target = f"$data merge entity $(bone_{bone_name})" |
166 | 147 | if target in line:
|
167 |
| - nbtStart = line.index(") ") + len(") ") |
168 |
| - tmpLine = line[:nbtStart] |
169 |
| - nbtRoot = nbtlib.parse_nbt(line[nbtStart:]) |
170 |
| - nbtRoot["transformation"][7] += offsetPair[1] |
171 |
| - return tmpLine + nbtlib.serialize_tag(nbtRoot, compact=True) + "\n" |
| 148 | + nbt_start = line.index(") ") + len(") ") |
| 149 | + tmp_line = line[:nbt_start] |
| 150 | + nbt_root = nbtlib.parse_nbt(line[nbt_start:]) |
| 151 | + nbt_root["transformation"][7] += offset |
| 152 | + return tmp_line + nbtlib.serialize_tag(nbt_root, compact=True) + "\n" |
172 | 153 | return line
|
173 | 154 |
|
174 | 155 | def main():
|
175 |
| - project, playerName, offsets = parse_arguments() |
| 156 | + project, player_name, offsets = parse_cli_arguments() |
176 | 157 |
|
177 |
| - print(f"Running aj-convert on project {project}") |
| 158 | + print(f"Starting aj-convert for project: {project}") |
178 | 159 |
|
179 |
| - summonPath = os.path.join(".","animated_java", "data", "animated_java", "function", project, "summon.mcfunction") |
180 |
| - read_and_modify_summon_function(summonPath, project, offsets, playerName) |
| 160 | + summon_filepath = os.path.join(".", "data", "animated_java", "function", project, "summon.mcfunction") |
| 161 | + modify_summon_file(summon_filepath, project, offsets, player_name) |
181 | 162 |
|
182 |
| - mcfunction_paths = [ |
183 |
| - os.path.join(".","animated_java", "data", "animated_java", "function", project, "set_default_pose.mcfunction"), |
184 |
| - os.path.join(".","animated_java", "data", "animated_java", "function", project, "apply_default_pose.mcfunction") |
| 163 | + pose_filepaths = [ |
| 164 | + os.path.join(".", "data", "animated_java", "function", project, "set_default_pose.mcfunction"), |
| 165 | + os.path.join(".", "data", "animated_java", "function", project, "apply_default_pose.mcfunction") |
185 | 166 | ]
|
186 |
| - process_pose_files(mcfunction_paths, project, offsets) |
| 167 | + update_pose_files(pose_filepaths, project, offsets) |
187 | 168 |
|
188 |
| - animations_rootpath = os.path.join(".","animated_java", "data", "animated_java", "function", project, "animations") |
189 |
| - modify_animation_frames(animations_rootpath, project, offsets) |
| 169 | + animation_rootpath = os.path.join(".", "data", "animated_java", "function", project, "animations") |
| 170 | + process_animation_frames(animation_rootpath, offsets) |
190 | 171 |
|
191 | 172 | if __name__ == "__main__":
|
192 | 173 | main()
|
0 commit comments