3 # Converts Minecraft resource packs to Minetest texture packs.
7 __license__
= "MIT License"
8 __status__
= "Development"
10 import shutil
, csv
, os
, tempfile
, sys
, getopt
13 home
= os
.environ
["HOME"]
14 mineclone2_path
= home
+ "/.minetest/games/mineclone2"
15 working_dir
= os
.getcwd()
16 output_dir_name
= "New_MineClone_2_Texture_Pack"
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 of the original textures (default: 16)
45 Directory in which to put the resulting MineClone 2 texture pack
46 (default: working directory)
48 The script will only pretend to convert textures by writing
49 to the console only, but not changing any files.
51 Prints out all copying actions
53 Shows this help an exits"""
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 MineClone 2 texture pack.
70 Supported Minecraft version: 1.12 (Java Edition)
88 """ERROR: You forgot to tell me the path to the Minecraft resource pack.
89 Mind-reading has not been implemented yet.
92 """+appname
+""" -i <path to resource pack>
94 For the full help, use:
95 """+appname
+""" -h""")
98 ### END OF SETTINGS ###
100 tex_dir
= base_dir
+ "/assets/minecraft/textures"
102 # FUNCTION DEFINITIONS
103 def colorize(colormap
, source
, colormap_pixel
, texture_size
, destination
):
104 os
.system("convert "+colormap
+" -crop 1x1+"+colormap_pixel
+" -depth 8 -resize "+texture_size
+"x"+texture_size
+" "+tempfile1
.name
)
105 os
.system("composite -compose Multiply "+tempfile1
.name
+" "+source
+" "+destination
)
107 def colorize_alpha(colormap
, source
, colormap_pixel
, texture_size
, destination
):
108 colorize(colormap
, source
, colormap_pixel
, texture_size
, tempfile2
.name
)
109 os
.system("composite -compose Dst_In "+source
+" "+tempfile2
.name
+" -alpha Set "+destination
)
111 # This function is unused atm.
112 # TODO: Implemnt colormap extraction
113 def extract_colormap(colormap
, colormap_pixel
, positions
):
114 os
.system("convert -size 16x16 canvas:black "+tempfile1
.name
)
118 os
.system("convert "+colormap
+" -crop 1x1+"+colormap_pixel
+" -depth 8 "+tempfile2
.name
)
119 os
.system("composite -geometry 16x16+"+x
+"+"+y
+" "+tempfile2
.name
)
122 def target_dir(directory
):
123 if make_texture_pack
:
124 return output_dir
+ "/" + output_dir_name
126 return mineclone2_path
+ directory
129 def convert_textures():
130 failed_conversions
= 0
131 print("Texture conversion BEGINS NOW!")
132 with
open("Conversion_Table.csv", newline
="") as csvfile
:
133 reader
= csv
.reader(csvfile
, delimiter
=",", quotechar
='"')
142 src_filename
= row
[1]
144 dst_filename
= row
[3]
154 blacklisted
= row
[10]
156 if blacklisted
== "y":
157 # Skip blacklisted files
160 if make_texture_pack
== False and dst_dir
== "":
161 # If destination dir is empty, this texture is not supposed to be used in MCL2
162 # (but maybe an external mod). It should only be used in texture packs.
163 # Otherwise, it must be ignored.
164 # Example: textures for mcl_supplemental
167 src_file
= base_dir
+ src_dir
+ "/" + src_filename
# source file
168 src_file_exists
= os
.path
.isfile(src_file
)
169 dst_file
= target_dir(dst_dir
) + "/" + dst_filename
# destination file
171 if src_file_exists
== False:
172 print("WARNING: Source file does not exist: "+src_file
)
173 failed_conversions
= failed_conversions
+ 1
177 # Crop and copy images
179 os
.system("convert "+src_file
+" -crop "+xl
+"x"+yl
+"+"+xs
+"+"+ys
+" "+dst_file
)
181 print(src_file
+ " → " + dst_file
)
183 # Copy image verbatim
185 shutil
.copy2(src_file
, dst_file
)
187 print(src_file
+ " → " + dst_file
)
189 # Convert chest textures (requires ImageMagick)
191 [ 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" ],
192 [ 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" ],
193 [ 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" ]
196 for c
in chest_files
:
198 if os
.path
.isfile(chest_file
):
200 CHPX
= (PPX
* 14) # Chest width
201 LIDPX
= (PPX
* 5) # Lid height
202 LIDLOW
= (PPX
* 10) # Lower lid section height
203 LOCKW
= (PPX
* 6) # Lock width
204 LOCKH
= (PPX
* 5) # Lock height
207 top
= cdir
+ "/" + c
[2]
208 bottom
= cdir
+ "/" + c
[3]
209 front
= cdir
+ "/" + c
[4]
210 left
= cdir
+ "/" + c
[5]
211 right
= cdir
+ "/" + c
[6]
212 back
= cdir
+ "/" + c
[7]
214 os
.system("convert " + chest_file
+ " \
215 \( -clone 0 -crop "+str(CHPX
)+"x"+str(CHPX
)+"+"+str(CHPX
)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+top
)
217 os
.system("convert " + chest_file
+ " \
218 \( -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
)
220 os
.system("convert " + chest_file
+ " \
221 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(CHPX
)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
222 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(CHPX
)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
223 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+front
)
226 # Left, right back (use same texture, we're lazy
227 files
= [ left
, right
, back
]
229 os
.system("convert " + chest_file
+ " \
230 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(0)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
231 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(0)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
232 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+f
)
237 [ 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" ],
238 [ 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" ]
240 for c
in chest_files
:
242 if os
.path
.isfile(chest_file
):
244 CHPX
= (PPX
* 14) # Chest width (short side)
245 CHPX2
= (PPX
* 15) # Chest width (long side)
246 LIDPX
= (PPX
* 5) # Lid height
247 LIDLOW
= (PPX
* 10) # Lower lid section height
248 LOCKW
= (PPX
* 6) # Lock width
249 LOCKH
= (PPX
* 5) # Lock height
252 front
= cdir
+ "/" + c
[2]
253 top
= cdir
+ "/" + c
[3]
254 side
= cdir
+ "/" + c
[4]
256 os
.system("convert " + chest_file
+ " \
257 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(CHPX
)+"+"+str(CHPX
)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX2
)+"x"+str(CHPX
)+" "+top
)
260 os
.system("convert " + chest_file
+ " \
261 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(LIDPX
)+"+"+str(CHPX
)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
262 \( -clone 0 -crop "+str(CHPX2
)+"x"+str(LIDLOW
)+"+"+str(CHPX
)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
263 -extent "+str(CHPX2
)+"x"+str(CHPX
)+" "+front
)
265 os
.system("convert " + chest_file
+ " \
266 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDPX
)+"+"+str(0)+"+"+str(CHPX
)+" \) -geometry +0+0 -composite \
267 \( -clone 0 -crop "+str(CHPX
)+"x"+str(LIDLOW
)+"+"+str(0)+"+"+str(CHPX
*2+LIDPX
)+" \) -geometry +0+"+str(LIDPX
-PPX
)+" -composite \
268 -extent "+str(CHPX
)+"x"+str(CHPX
)+" "+side
)
271 # Generate railway crossings and t-junctions. Note: They may look strange.
272 # Note: these may be only a temporary solution, as crossings and t-junctions do not occour in MC.
275 # (Straigt src, curved src, t-junction dest, crossing dest)
276 ("rail_normal.png", "rail_normal_turned.png", "default_rail_t_junction.png", "default_rail_crossing.png"),
277 ("rail_golden.png", "rail_normal_turned.png", "carts_rail_t_junction_pwr.png", "carts_rail_crossing_pwr.png"),
278 ("rail_golden_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"),
279 ("rail_detector.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"),
280 ("rail_detector_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"),
281 ("rail_activator.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"),
282 ("rail_activator_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_d_t_junction.png", "mcl_minecarts_rail_activator_powered_crossing.png"),
285 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])
286 os
.system("convert "+tex_dir
+"/blocks/"+r
[0]+" -rotate 90 "+tempfile1
.name
)
287 os
.system("composite -compose Dst_Over "+tempfile1
.name
+" "+tex_dir
+"/blocks/"+r
[0]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r
[3])
289 # Convert banner overlays
305 "half_horizontal_bottom",
308 "half_vertical_right",
322 "square_bottom_left",
323 "square_bottom_right",
332 orig
= tex_dir
+ "/entity/banner/" + o
+ ".png"
333 if os
.path
.isfile(orig
):
336 dest
= target_dir("/mods/ITEMS/mcl_banners/textures")+"/"+"mcl_banners_"+o
+".png"
337 os
.system("convert "+orig
+" -transparent-color white -background black -alpha remove -alpha copy -channel RGB -white-threshold 0 "+dest
)
340 grass_file
= tex_dir
+ "/blocks/grass_top.png"
341 if os
.path
.isfile(grass_file
):
342 FOLIAG
= tex_dir
+"/colormap/foliage.png"
343 GRASS
= tex_dir
+"/colormap/grass.png"
347 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_oak.png", "116+143", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_leaves.png")
348 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")
349 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_acacia.png", "40+255", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_acacia_leaves.png")
350 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")
351 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")
352 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/leaves_jungle.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_jungleleaves.png")
355 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/waterlily.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/flowers_waterlily.png")
358 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/vine.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_vine.png")
360 # Tall grass, fern (inventory images)
361 pcol
= "49+172" # Plains grass color
362 colorize_alpha(GRASS
, tex_dir
+"/blocks/tallgrass.png", pcol
, str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_tallgrass_inv.png")
363 colorize_alpha(GRASS
, tex_dir
+"/blocks/fern.png", pcol
, str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_fern_inv.png")
364 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")
365 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")
367 # TODO: Convert grass palette
370 [ pcol
, "", "grass" ], # Default grass: Plains
371 [ "40+255", "_dry", "dry_grass" ], # Dry grass: Savanna, Mesa Plateau F, Nether, …
374 colorize(GRASS
, tex_dir
+"/blocks/grass_top.png", o
[0], str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o
[2]+".png")
375 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")
378 print("Textures conversion COMPLETE!")
379 if failed_conversions
> 0:
380 print("WARNING: Number of missing files in original resource pack: "+str(failed_conversions
))
381 print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
382 if make_texture_pack
:
383 print("You can now retrieve the texture pack in "+output_dir
+"/"+output_dir_name
+"/")
386 if make_texture_pack
and not os
.path
.isdir(output_dir
+"/"+output_dir_name
):
387 os
.mkdir(output_dir
+"/"+output_dir_name
)
389 tempfile1
= tempfile
.NamedTemporaryFile()
390 tempfile2
= tempfile
.NamedTemporaryFile()