2 # -*- coding: utf-8 -*-
4 # Converts Minecraft resource packs to Minetest texture packs.
8 __license__
= "MIT License"
9 __status__
= "Development"
11 import shutil
, csv
, os
, tempfile
, sys
, getopt
14 home
= os
.environ
["HOME"]
15 mineclone2_path
= home
+ "/.minetest/games/mineclone2"
16 working_dir
= os
.getcwd()
17 appname
= "Texture_Converter.py"
20 output_dir
= working_dir
24 # If True, will only make console output but not convert anything.
27 # If True, textures will be put into a texture pack directory structure.
28 # If False, textures will be put into MineClone 2 directories.
29 make_texture_pack
= True
31 # If True, prints all copying actions
36 syntax_help
= appname
+""" -i <input dir> [-o <output dir>] [-d] [-v|-q] [-h]
39 Directory of Minecraft resource pack to convert
43 Specify the size (in pixels) of the original textures (default: 16)
45 Directory in which to put the resulting Minetest texture pack
46 (default: working directory)
48 Just pretend to convert textures and just print output, but do not actually
51 Print out all copying actions
53 Show this help and exit"""
55 opts
, args
= getopt
.getopt(sys
.argv
[1:],"hi:o:p:dv")
56 except getopt
.GetoptError
:
58 """ERROR! The options you gave me make no sense!
60 Here's the syntax reference:""")
66 """This is the official MineClone 2 Texture Converter.
67 This will convert textures from Minecraft resource packs to
68 a Minetest texture pack.
70 Supported Minecraft version: 1.12 (Java Edition)
88 """ERROR: You didn't tell me the path to the Minecraft resource pack.
89 Mind-reading has not been implemented yet.
92 """+appname
+""" -i <path to resource pack> -p <texture size>
94 For the full help, use:
95 """+appname
+""" -h""")
98 ### END OF SETTINGS ###
100 tex_dir
= base_dir
+ "/assets/minecraft/textures"
102 # Get texture pack name (from directory name)
103 bdir_split
= base_dir
.split("/")
104 output_dir_name
= bdir_split
[-1]
105 if len(output_dir_name
) == 0:
106 if len(bdir_split
) >= 2:
107 output_dir_name
= base_dir
.split("/")[-2]
110 output_dir_name
= "New_MineClone_2_Texture_Pack"
112 # FUNCTION DEFINITIONS
113 def colorize(colormap
, source
, colormap_pixel
, texture_size
, destination
):
114 os
.system("convert "+colormap
+" -crop 1x1+"+colormap_pixel
+" -depth 8 -resize "+texture_size
+"x"+texture_size
+" "+tempfile1
.name
)
115 os
.system("composite -compose Multiply "+tempfile1
.name
+" "+source
+" "+destination
)
117 def colorize_alpha(colormap
, source
, colormap_pixel
, texture_size
, destination
):
118 colorize(colormap
, source
, colormap_pixel
, texture_size
, tempfile2
.name
)
119 os
.system("composite -compose Dst_In "+source
+" "+tempfile2
.name
+" -alpha Set "+destination
)
121 # This function is unused atm.
122 # TODO: Implemnt colormap extraction
123 def extract_colormap(colormap
, colormap_pixel
, positions
):
124 os
.system("convert -size 16x16 canvas:black "+tempfile1
.name
)
128 os
.system("convert "+colormap
+" -crop 1x1+"+colormap_pixel
+" -depth 8 "+tempfile2
.name
)
129 os
.system("composite -geometry 16x16+"+x
+"+"+y
+" "+tempfile2
.name
)
132 def target_dir(directory
):
133 if make_texture_pack
:
134 return output_dir
+ "/" + output_dir_name
136 return mineclone2_path
+ directory
139 def convert_textures():
140 failed_conversions
= 0
141 print("Texture conversion BEGINS NOW!")
142 with
open("Conversion_Table.csv", newline
="") as csvfile
:
143 reader
= csv
.reader(csvfile
, delimiter
=",", quotechar
='"')
152 src_filename
= row
[1]
154 dst_filename
= row
[3]
164 blacklisted
= row
[10]
166 if blacklisted
== "y":
167 # Skip blacklisted files
170 if make_texture_pack
== False and dst_dir
== "":
171 # If destination dir is empty, this texture is not supposed to be used in MCL2
172 # (but maybe an external mod). It should only be used in texture packs.
173 # Otherwise, it must be ignored.
174 # Example: textures for mcl_supplemental
177 src_file
= base_dir
+ src_dir
+ "/" + src_filename
# source file
178 src_file_exists
= os
.path
.isfile(src_file
)
179 dst_file
= target_dir(dst_dir
) + "/" + dst_filename
# destination file
181 if src_file_exists
== False:
182 print("WARNING: Source file does not exist: "+src_file
)
183 failed_conversions
= failed_conversions
+ 1
187 # Crop and copy images
189 os
.system("convert "+src_file
+" -crop "+xl
+"x"+yl
+"+"+xs
+"+"+ys
+" "+dst_file
)
191 print(src_file
+ " → " + dst_file
)
193 # Copy image verbatim
195 shutil
.copy2(src_file
, dst_file
)
197 print(src_file
+ " → " + dst_file
)
199 # Convert chest textures (requires ImageMagick)
201 [ tex_dir
+ "/entity/chest/normal.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "default_chest_top.png", "mcl_chests_chest_bottom.png", "default_chest_front.png", "mcl_chests_chest_left.png", "mcl_chests_chest_right.png", "mcl_chests_chest_back.png" ],
202 [ tex_dir
+ "/entity/chest/trapped.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_chest_trapped_top.png", "mcl_chests_chest_trapped_bottom.png", "mcl_chests_chest_trapped_front.png", "mcl_chests_chest_trapped_left.png", "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_back.png" ],
203 [ tex_dir
+ "/entity/chest/ender.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_ender_chest_top.png", "mcl_chests_ender_chest_bottom.png", "mcl_chests_ender_chest_front.png", "mcl_chests_ender_chest_left.png", "mcl_chests_ender_chest_right.png", "mcl_chests_ender_chest_back.png" ]
206 for c
in chest_files
:
208 if os
.path
.isfile(chest_file
):
210 CHPX
= (PPX
* 14) # Chest width
211 LIDPX
= (PPX
* 5) # Lid height
212 LIDLOW
= (PPX
* 10) # Lower lid section height
213 LOCKW
= (PPX
* 6) # Lock width
214 LOCKH
= (PPX
* 5) # Lock height
217 top
= cdir
+ "/" + c
[2]
218 bottom
= cdir
+ "/" + c
[3]
219 front
= cdir
+ "/" + c
[4]
220 left
= cdir
+ "/" + c
[5]
221 right
= cdir
+ "/" + c
[6]
222 back
= cdir
+ "/" + c
[7]
224 os
.system("convert " + chest_file
+ " \
225 \( -clone 0 -crop "+str(CHPX
)+"x"+str(CHPX
)+"+"+str(CHPX
)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+top
)
227 os
.system("convert " + chest_file
+ " \
228 \( -clone 0 -crop "+str(CHPX
)+"x"+str(CHPX
)+"+"+str(CHPX
*2)+"+"+str(CHPX
+LIDPX
)+" \) -geometry +0+0 -composite -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+bottom
)
230 os
.system("convert " + chest_file
+ " \
231 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(CHPX
)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
232 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(CHPX
)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
233 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+front
)
236 # Left, right back (use same texture, we're lazy
237 files
= [ left
, right
, back
]
239 os
.system("convert " + chest_file
+ " \
240 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(0)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
241 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(0)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
242 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+f
)
247 [ tex_dir
+ "/entity/chest/normal_double.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "default_chest_front_big.png", "default_chest_top_big.png", "default_chest_side_big.png" ],
248 [ tex_dir
+ "/entity/chest/trapped_double.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_chest_trapped_front_big.png", "mcl_chests_chest_trapped_top_big.png", "mcl_chests_chest_trapped_side_big.png" ]
250 for c
in chest_files
:
252 if os
.path
.isfile(chest_file
):
254 CHPX
= (PPX
* 14) # Chest width (short side)
255 CHPX2
= (PPX
* 15) # Chest width (long side)
256 LIDPX
= (PPX
* 5) # Lid height
257 LIDLOW
= (PPX
* 10) # Lower lid section height
258 LOCKW
= (PPX
* 6) # Lock width
259 LOCKH
= (PPX
* 5) # Lock height
262 front
= cdir
+ "/" + c
[2]
263 top
= cdir
+ "/" + c
[3]
264 side
= cdir
+ "/" + c
[4]
266 os
.system("convert " + chest_file
+ " \
267 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(CHPX
)+"+"+str(CHPX
)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX2
)+"x"+str(CHPX
)+" "+top
)
270 os
.system("convert " + chest_file
+ " \
271 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(LIDPX
)+"+"+str(CHPX
)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
272 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(LIDLOW
)+"+"+str(CHPX
)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
273 -extent "+str(CHPX2
)+"x"+str(CHPX
)+" "+front
)
275 os
.system("convert " + chest_file
+ " \
276 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(0)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
277 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(0)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
278 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+side
)
281 # Generate railway crossings and t-junctions. Note: They may look strange.
282 # Note: these may be only a temporary solution, as crossings and t-junctions do not occour in MC.
285 # (Straigt src, curved src, t-junction dest, crossing dest)
286 ("rail_normal.png", "rail_normal_turned.png", "default_rail_t_junction.png", "default_rail_crossing.png"),
287 ("rail_golden.png", "rail_normal_turned.png", "carts_rail_t_junction_pwr.png", "carts_rail_crossing_pwr.png"),
288 ("rail_golden_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"),
289 ("rail_detector.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"),
290 ("rail_detector_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"),
291 ("rail_activator.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"),
292 ("rail_activator_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_d_t_junction.png", "mcl_minecarts_rail_activator_powered_crossing.png"),
295 os
.system("composite -compose Dst_Over "+tex_dir
+"/blocks/"+r
[0]+" "+tex_dir
+"/blocks/"+r
[1]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r
[2])
296 os
.system("convert "+tex_dir
+"/blocks/"+r
[0]+" -rotate 90 "+tempfile1
.name
)
297 os
.system("composite -compose Dst_Over "+tempfile1
.name
+" "+tex_dir
+"/blocks/"+r
[0]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r
[3])
299 # Convert banner overlays
315 "half_horizontal_bottom",
318 "half_vertical_right",
332 "square_bottom_left",
333 "square_bottom_right",
342 orig
= tex_dir
+ "/entity/banner/" + o
+ ".png"
343 if os
.path
.isfile(orig
):
346 dest
= target_dir("/mods/ITEMS/mcl_banners/textures")+"/"+"mcl_banners_"+o
+".png"
347 os
.system("convert "+orig
+" -transparent-color white -background black -alpha remove -alpha copy -channel RGB -white-threshold 0 "+dest
)
350 grass_file
= tex_dir
+ "/blocks/grass_top.png"
351 if os
.path
.isfile(grass_file
):
352 FOLIAG
= tex_dir
+"/colormap/foliage.png"
353 GRASS
= tex_dir
+"/colormap/grass.png"
357 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_oak.png", "116+143", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_leaves.png")
358 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_big_oak.png", "158+177", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_big_oak.png")
359 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_acacia.png", "40+255", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_acacia_leaves.png")
360 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_spruce.png", "226+230", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_spruce.png")
361 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_birch.png", "141+186", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_birch.png")
362 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_jungle.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_jungleleaves.png")
365 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/waterlily.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/flowers_waterlily.png")
368 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/vine.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_vine.png")
370 # Tall grass, fern (inventory images)
371 pcol
= "49+172" # Plains grass color
372 colorize_alpha(GRASS
, tex_dir
+"/blocks/tallgrass.png", pcol
, str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_tallgrass_inv.png")
373 colorize_alpha(GRASS
, tex_dir
+"/blocks/fern.png", pcol
, str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_fern_inv.png")
374 colorize_alpha(GRASS
, tex_dir
+"/blocks/double_plant_fern_top.png", pcol
, str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_double_plant_fern_inv.png")
375 colorize_alpha(GRASS
, tex_dir
+"/blocks/double_plant_grass_top.png", pcol
, str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_double_plant_grass_inv.png")
377 # TODO: Convert grass palette
380 [ pcol
, "", "grass" ], # Default grass: Plains
383 colorize(GRASS
, tex_dir
+"/blocks/grass_top.png", o
[0], str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o
[2]+".png")
384 colorize_alpha(GRASS
, tex_dir
+"/blocks/grass_side_overlay.png", o
[0], str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o
[2]+"_side.png")
387 if make_texture_pack
:
388 # Create description file
389 description
= "Texture pack for MineClone 2. Automatically converted from a Minecraft resource pack by the MineClone 2 Texture Converter. Size: "+str(PXSIZE
)+"×"+str(PXSIZE
)
390 description_file
= open(target_dir("/") + "/description.txt", "w")
391 description_file
.write(description
)
392 description_file
.close()
394 # Create preview image (screenshot.png)
395 os
.system("convert -size 300x200 canvas:transparent "+target_dir("/") + "/screenshot.png")
396 os
.system("composite "+base_dir
+"/pack.png "+target_dir("/") + "/screenshot.png -gravity center "+target_dir("/") + "/screenshot.png")
398 print("Textures conversion COMPLETE!")
399 if failed_conversions
> 0:
400 print("WARNING: Number of missing files in original resource pack: "+str(failed_conversions
))
401 print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
402 if make_texture_pack
:
403 print("You can now retrieve the texture pack in "+output_dir
+"/"+output_dir_name
+"/")
406 if make_texture_pack
and not os
.path
.isdir(output_dir
+"/"+output_dir_name
):
407 os
.mkdir(output_dir
+"/"+output_dir_name
)
409 tempfile1
= tempfile
.NamedTemporaryFile()
410 tempfile2
= tempfile
.NamedTemporaryFile()