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 output_dir_name
= "New_MineClone_2_Texture_Pack"
18 appname
= "Texture_Converter.py"
21 output_dir
= working_dir
25 # If True, will only make console output but not convert anything.
28 # If True, textures will be put into a texture pack directory structure.
29 # If False, textures will be put into MineClone 2 directories.
30 make_texture_pack
= True
32 # If True, prints all copying actions
37 syntax_help
= appname
+""" -i <input dir> [-o <output dir>] [-d] [-v|-q] [-h]
40 Directory of Minecraft resource pack to convert
44 Specify the size (in pixels) of the original textures (default: 16)
46 Directory in which to put the resulting Minetest texture pack
47 (default: working directory)
49 Just pretend to convert textures and just print output, but do not actually
52 Print out all copying actions
54 Show this help and exit"""
56 opts
, args
= getopt
.getopt(sys
.argv
[1:],"hi:o:p:dv")
57 except getopt
.GetoptError
:
59 """ERROR! The options you gave me make no sense!
61 Here's the syntax reference:""")
67 """This is the official MineClone 2 Texture Converter.
68 This will convert textures from Minecraft resource packs to
69 a Minetest texture pack.
71 Supported Minecraft version: 1.12 (Java Edition)
89 """ERROR: You didn't tell me the path to the Minecraft resource pack.
90 Mind-reading has not been implemented yet.
93 """+appname
+""" -i <path to resource pack> -p <texture size>
95 For the full help, use:
96 """+appname
+""" -h""")
99 ### END OF SETTINGS ###
101 tex_dir
= base_dir
+ "/assets/minecraft/textures"
103 # FUNCTION DEFINITIONS
104 def colorize(colormap
, source
, colormap_pixel
, texture_size
, destination
):
105 os
.system("convert "+colormap
+" -crop 1x1+"+colormap_pixel
+" -depth 8 -resize "+texture_size
+"x"+texture_size
+" "+tempfile1
.name
)
106 os
.system("composite -compose Multiply "+tempfile1
.name
+" "+source
+" "+destination
)
108 def colorize_alpha(colormap
, source
, colormap_pixel
, texture_size
, destination
):
109 colorize(colormap
, source
, colormap_pixel
, texture_size
, tempfile2
.name
)
110 os
.system("composite -compose Dst_In "+source
+" "+tempfile2
.name
+" -alpha Set "+destination
)
112 # This function is unused atm.
113 # TODO: Implemnt colormap extraction
114 def extract_colormap(colormap
, colormap_pixel
, positions
):
115 os
.system("convert -size 16x16 canvas:black "+tempfile1
.name
)
119 os
.system("convert "+colormap
+" -crop 1x1+"+colormap_pixel
+" -depth 8 "+tempfile2
.name
)
120 os
.system("composite -geometry 16x16+"+x
+"+"+y
+" "+tempfile2
.name
)
123 def target_dir(directory
):
124 if make_texture_pack
:
125 return output_dir
+ "/" + output_dir_name
127 return mineclone2_path
+ directory
130 def convert_textures():
131 failed_conversions
= 0
132 print("Texture conversion BEGINS NOW!")
133 with
open("Conversion_Table.csv", newline
="") as csvfile
:
134 reader
= csv
.reader(csvfile
, delimiter
=",", quotechar
='"')
143 src_filename
= row
[1]
145 dst_filename
= row
[3]
155 blacklisted
= row
[10]
157 if blacklisted
== "y":
158 # Skip blacklisted files
161 if make_texture_pack
== False and dst_dir
== "":
162 # If destination dir is empty, this texture is not supposed to be used in MCL2
163 # (but maybe an external mod). It should only be used in texture packs.
164 # Otherwise, it must be ignored.
165 # Example: textures for mcl_supplemental
168 src_file
= base_dir
+ src_dir
+ "/" + src_filename
# source file
169 src_file_exists
= os
.path
.isfile(src_file
)
170 dst_file
= target_dir(dst_dir
) + "/" + dst_filename
# destination file
172 if src_file_exists
== False:
173 print("WARNING: Source file does not exist: "+src_file
)
174 failed_conversions
= failed_conversions
+ 1
178 # Crop and copy images
180 os
.system("convert "+src_file
+" -crop "+xl
+"x"+yl
+"+"+xs
+"+"+ys
+" "+dst_file
)
182 print(src_file
+ " → " + dst_file
)
184 # Copy image verbatim
186 shutil
.copy2(src_file
, dst_file
)
188 print(src_file
+ " → " + dst_file
)
190 # Convert chest textures (requires ImageMagick)
192 [ 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" ],
193 [ 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" ],
194 [ 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" ]
197 for c
in chest_files
:
199 if os
.path
.isfile(chest_file
):
201 CHPX
= (PPX
* 14) # Chest width
202 LIDPX
= (PPX
* 5) # Lid height
203 LIDLOW
= (PPX
* 10) # Lower lid section height
204 LOCKW
= (PPX
* 6) # Lock width
205 LOCKH
= (PPX
* 5) # Lock height
208 top
= cdir
+ "/" + c
[2]
209 bottom
= cdir
+ "/" + c
[3]
210 front
= cdir
+ "/" + c
[4]
211 left
= cdir
+ "/" + c
[5]
212 right
= cdir
+ "/" + c
[6]
213 back
= cdir
+ "/" + c
[7]
215 os
.system("convert " + chest_file
+ " \
216 \( -clone 0 -crop "+str(CHPX
)+"x"+str(CHPX
)+"+"+str(CHPX
)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+top
)
218 os
.system("convert " + chest_file
+ " \
219 \( -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
)
221 os
.system("convert " + chest_file
+ " \
222 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(CHPX
)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
223 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(CHPX
)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
224 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+front
)
227 # Left, right back (use same texture, we're lazy
228 files
= [ left
, right
, back
]
230 os
.system("convert " + chest_file
+ " \
231 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(0)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
232 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(0)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
233 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+f
)
238 [ 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" ],
239 [ 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" ]
241 for c
in chest_files
:
243 if os
.path
.isfile(chest_file
):
245 CHPX
= (PPX
* 14) # Chest width (short side)
246 CHPX2
= (PPX
* 15) # Chest width (long side)
247 LIDPX
= (PPX
* 5) # Lid height
248 LIDLOW
= (PPX
* 10) # Lower lid section height
249 LOCKW
= (PPX
* 6) # Lock width
250 LOCKH
= (PPX
* 5) # Lock height
253 front
= cdir
+ "/" + c
[2]
254 top
= cdir
+ "/" + c
[3]
255 side
= cdir
+ "/" + c
[4]
257 os
.system("convert " + chest_file
+ " \
258 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(CHPX
)+"+"+str(CHPX
)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX2
)+"x"+str(CHPX
)+" "+top
)
261 os
.system("convert " + chest_file
+ " \
262 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(LIDPX
)+"+"+str(CHPX
)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
263 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(LIDLOW
)+"+"+str(CHPX
)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
264 -extent "+str(CHPX2
)+"x"+str(CHPX
)+" "+front
)
266 os
.system("convert " + chest_file
+ " \
267 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(0)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
268 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(0)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
269 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+side
)
272 # Generate railway crossings and t-junctions. Note: They may look strange.
273 # Note: these may be only a temporary solution, as crossings and t-junctions do not occour in MC.
276 # (Straigt src, curved src, t-junction dest, crossing dest)
277 ("rail_normal.png", "rail_normal_turned.png", "default_rail_t_junction.png", "default_rail_crossing.png"),
278 ("rail_golden.png", "rail_normal_turned.png", "carts_rail_t_junction_pwr.png", "carts_rail_crossing_pwr.png"),
279 ("rail_golden_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"),
280 ("rail_detector.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"),
281 ("rail_detector_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"),
282 ("rail_activator.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"),
283 ("rail_activator_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_d_t_junction.png", "mcl_minecarts_rail_activator_powered_crossing.png"),
286 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])
287 os
.system("convert "+tex_dir
+"/blocks/"+r
[0]+" -rotate 90 "+tempfile1
.name
)
288 os
.system("composite -compose Dst_Over "+tempfile1
.name
+" "+tex_dir
+"/blocks/"+r
[0]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r
[3])
290 # Convert banner overlays
306 "half_horizontal_bottom",
309 "half_vertical_right",
323 "square_bottom_left",
324 "square_bottom_right",
333 orig
= tex_dir
+ "/entity/banner/" + o
+ ".png"
334 if os
.path
.isfile(orig
):
337 dest
= target_dir("/mods/ITEMS/mcl_banners/textures")+"/"+"mcl_banners_"+o
+".png"
338 os
.system("convert "+orig
+" -transparent-color white -background black -alpha remove -alpha copy -channel RGB -white-threshold 0 "+dest
)
341 grass_file
= tex_dir
+ "/blocks/grass_top.png"
342 if os
.path
.isfile(grass_file
):
343 FOLIAG
= tex_dir
+"/colormap/foliage.png"
344 GRASS
= tex_dir
+"/colormap/grass.png"
348 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_oak.png", "116+143", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_leaves.png")
349 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")
350 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_acacia.png", "40+255", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_acacia_leaves.png")
351 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")
352 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")
353 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_jungle.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_jungleleaves.png")
356 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/waterlily.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/flowers_waterlily.png")
359 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/vine.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_vine.png")
361 # Tall grass, fern (inventory images)
362 pcol
= "49+172" # Plains grass color
363 colorize_alpha(GRASS
, tex_dir
+"/blocks/tallgrass.png", pcol
, str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_tallgrass_inv.png")
364 colorize_alpha(GRASS
, tex_dir
+"/blocks/fern.png", pcol
, str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_fern_inv.png")
365 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")
366 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")
368 # TODO: Convert grass palette
371 [ pcol
, "", "grass" ], # Default grass: Plains
372 [ "40+255", "_dry", "dry_grass" ], # Dry grass: Savanna, Mesa Plateau F, Nether, …
375 colorize(GRASS
, tex_dir
+"/blocks/grass_top.png", o
[0], str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o
[2]+".png")
376 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")
379 print("Textures conversion COMPLETE!")
380 if failed_conversions
> 0:
381 print("WARNING: Number of missing files in original resource pack: "+str(failed_conversions
))
382 print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
383 if make_texture_pack
:
384 print("You can now retrieve the texture pack in "+output_dir
+"/"+output_dir_name
+"/")
387 if make_texture_pack
and not os
.path
.isdir(output_dir
+"/"+output_dir_name
):
388 os
.mkdir(output_dir
+"/"+output_dir_name
)
390 tempfile1
= tempfile
.NamedTemporaryFile()
391 tempfile2
= tempfile
.NamedTemporaryFile()