def get_model_preview_image(custom_model_name, prefix, selected_colors): custom_model_metadata = get_model_metadata(custom_model_name) if "preview_hero" not in custom_model_metadata: return None preview_image_path = custom_model_metadata["preview_%s" % prefix] if not os.path.isfile(preview_image_path): return None preview_image = Image.open(preview_image_path) custom_colors = custom_model_metadata.get(prefix + "_custom_colors", {}) for custom_color_basename, base_color in custom_colors.items(): custom_color = selected_colors.get(custom_color_basename, None) if custom_color is None: continue custom_color = tuple(custom_color) base_color = tuple(base_color) if custom_color == base_color: continue mask_path = custom_model_metadata[ "preview_" + prefix + "_color_mask_paths"][custom_color_basename] check_valid_mask_path(mask_path) preview_image = texture_utils.color_exchange( preview_image, base_color, custom_color, mask_path=mask_path, validate_mask_colors=False) return preview_image
def change_player_clothes_color(self): custom_model_name = self.options.get("custom_player_model", "Link") custom_model_metadata = get_model_metadata(custom_model_name) link_arc = self.get_arc("files/res/Object/Link.arc") link_main_model = link_arc.get_file("cl.bdl") if self.options.get("player_in_casual_clothes"): is_casual = True prefix = "casual" link_main_textures = [link_arc.get_file("linktexbci4.bti")] else: is_casual = False prefix = "hero" link_main_textures = link_main_model.tex1.textures_by_name[ "linktexS3TC"] first_texture = link_main_textures[0] link_main_image = first_texture.render() replaced_any = False custom_colors = custom_model_metadata.get(prefix + "_custom_colors", {}) has_colored_eyebrows = custom_model_metadata.get("has_colored_eyebrows", False) for custom_color_basename, base_color in custom_colors.items(): custom_color = self.options.get("custom_colors", {}).get(custom_color_basename, None) if custom_color is None: continue custom_color = tuple(custom_color) if custom_color == base_color: continue mask_path = custom_model_metadata[ prefix + "_color_mask_paths"][custom_color_basename] link_main_image = texture_utils.color_exchange(link_main_image, base_color, custom_color, mask_path=mask_path) replaced_any = True # Recolor the eyebrows. if has_colored_eyebrows and custom_color_basename == "Hair": for i in range(1, 6 + 1): textures = link_main_model.tex1.textures_by_name["mayuh.%d" % i] eyebrow_image = textures[0].render() eyebrow_image = texture_utils.color_exchange( eyebrow_image, base_color, custom_color) for texture in textures: texture.image_format = 6 texture.palette_format = 0 texture.replace_image(eyebrow_image) # Recolor the back hair for casual Link. if is_casual and custom_color_basename == "Hair": link_hair_model = link_arc.get_file("katsura.bdl") link_hair_textures = link_hair_model.tex1.textures_by_name[ "katsuraS3TC"] first_texture = link_hair_textures[0] back_hair_image = first_texture.render() back_hair_image.paste(custom_color, [0, 0, 8, 8]) for texture in link_hair_textures: if texture.image_format == 0xE: texture.image_format = 9 texture.palette_format = 1 texture.replace_image(back_hair_image) link_hair_model.save_changes() if not replaced_any: return for texture in link_main_textures: is_cmpr = (texture.image_format == 0xE) try: if is_cmpr: texture.image_format = 9 texture.palette_format = 1 texture.replace_image(link_main_image) except texture_utils.TooManyColorsError: if is_cmpr: texture.image_format = 4 texture.palette_format = 0 texture.replace_image(link_main_image) if is_casual: texture.save_changes() link_main_model.save_changes()
def change_player_custom_colors(self): custom_model_metadata = get_model_metadata(self.custom_model_name) disable_casual_clothes = custom_model_metadata.get( "disable_casual_clothes", False) sword_slash_trail_color = custom_model_metadata.get( "sword_slash_trail_color") elixir_soup_sword_trail_color = custom_model_metadata.get( "elixir_soup_sword_trail_color") parrying_sword_trail_color = custom_model_metadata.get( "parrying_sword_trail_color") boomerang_trail_color = custom_model_metadata.get("boomerang_trail_color") arrow_trail_color = custom_model_metadata.get("arrow_trail_color") if sword_slash_trail_color: self.dol.write_data(write_and_pack_bytes, 0x803F62AC, sword_slash_trail_color, "BBBB") if elixir_soup_sword_trail_color: self.dol.write_data(write_and_pack_bytes, 0x803F62B0, elixir_soup_sword_trail_color, "BBBB") if parrying_sword_trail_color: self.dol.write_data(write_and_pack_bytes, 0x803F62B4, parrying_sword_trail_color, "BBBB") if boomerang_trail_color: self.dol.write_data(write_and_pack_bytes, 0x803F6268, boomerang_trail_color, "BBBB") if arrow_trail_color: common_jpc = self.get_jpc("files/res/Particle/common.jpc") particle = common_jpc.particles_by_id[0x48] particle.bsp1.color_prm = tuple(arrow_trail_color) link_arc = self.get_arc("files/res/Object/Link.arc") link_main_model = link_arc.get_file("cl.bdl") if self.options.get( "player_in_casual_clothes") and not disable_casual_clothes: is_casual = True prefix = "casual" link_main_textures = [link_arc.get_file("linktexbci4.bti")] else: is_casual = False prefix = "hero" link_main_textures = link_main_model.tex1.textures_by_name[ "linktexS3TC"] first_texture = link_main_textures[0] link_main_image = first_texture.render() hitomi_textures = link_main_model.tex1.textures_by_name["hitomi"] hitomi_image = hitomi_textures[0].render() hands_model = link_arc.get_file("hands.bdl") hands_textures = hands_model.tex1.textures_by_name["handsS3TC"] hands_image = hands_textures[0].render() all_mouth_textures = OrderedDict() all_mouth_images = OrderedDict() replaced_any = False replaced_any_hands = False custom_colors = custom_model_metadata.get(prefix + "_custom_colors", {}) has_colored_eyebrows = custom_model_metadata.get("has_colored_eyebrows", False) hands_color_name = custom_model_metadata.get(prefix + "_hands_color_name", "Skin") mouth_color_name = custom_model_metadata.get(prefix + "_mouth_color_name", "Skin") hitomi_color_name = custom_model_metadata.get( prefix + "_hitomi_color_name", "Eyes") eyebrow_color_name = custom_model_metadata.get( prefix + "_eyebrow_color_name", "Hair") casual_hair_color_name = custom_model_metadata.get( "casual_hair_color_name", "Hair") # The "_color_name" fields will be completely ignored if that type of texture has even a single mask present. for custom_color_basename in custom_colors: for i in range(1, 9 + 1): mouth_mask_path = custom_model_metadata["mouth_color_mask_paths"][ i][custom_color_basename] if os.path.isfile(mouth_mask_path): mouth_color_name = None hands_mask_path = custom_model_metadata[ "hands_" + prefix + "_color_mask_paths"][custom_color_basename] if os.path.isfile(hands_mask_path): hands_color_name = None hitomi_mask_path = custom_model_metadata[ "hitomi_" + prefix + "_color_mask_paths"][custom_color_basename] if os.path.isfile(hitomi_mask_path): hitomi_color_name = None for custom_color_basename, base_color in custom_colors.items(): custom_color = self.options.get("custom_colors", {}).get(custom_color_basename, None) if custom_color is None: continue custom_color = tuple(custom_color) base_color = tuple(base_color) if custom_color == base_color: continue # Recolor the pupils. replaced_any_pupils_for_this_color = False hitomi_mask_path = custom_model_metadata[ "hitomi_" + prefix + "_color_mask_paths"][custom_color_basename] if os.path.isfile(hitomi_mask_path ) or custom_color_basename == hitomi_color_name: replaced_any_pupils_for_this_color = True if os.path.isfile(hitomi_mask_path): check_valid_mask_path(hitomi_mask_path) hitomi_image = texture_utils.color_exchange( hitomi_image, base_color, custom_color, mask_path=hitomi_mask_path) elif custom_color_basename == hitomi_color_name: hitomi_image = texture_utils.color_exchange(hitomi_image, base_color, custom_color, ignore_bright=True) for hitomi_texture in hitomi_textures: hitomi_texture.replace_image(hitomi_image) # Recolor the main player body texture. mask_path = custom_model_metadata[ prefix + "_color_mask_paths"][custom_color_basename] if not os.path.isfile( mask_path) and replaced_any_pupils_for_this_color: # Normally we throw an error for any color that doesn't have a mask for the main body texture, but if it's an eye color, we ignore it. pass else: check_valid_mask_path(mask_path) link_main_image = texture_utils.color_exchange(link_main_image, base_color, custom_color, mask_path=mask_path) replaced_any = True # Recolor the eyebrows. if has_colored_eyebrows and custom_color_basename == eyebrow_color_name: for i in range(1, 6 + 1): eyebrow_textures = link_main_model.tex1.textures_by_name[ "mayuh.%d" % i] eyebrow_image = eyebrow_textures[0].render() eyebrow_image = texture_utils.color_exchange( eyebrow_image, base_color, custom_color) for eyebrow_texture in eyebrow_textures: if eyebrow_texture.is_greyscale(): raise Exception( "Eyebrows use a greyscale image format, but metadata.txt specified the model should have colored eyebrows." ) eyebrow_texture.replace_image(eyebrow_image) # Recolor the back hair for casual Link. if is_casual and custom_color_basename == casual_hair_color_name: link_hair_model = link_arc.get_file("katsura.bdl") link_hair_textures = link_hair_model.tex1.textures_by_name[ "katsuraS3TC"] first_texture = link_hair_textures[0] back_hair_image = first_texture.render() back_hair_image.paste(custom_color, [0, 0, 8, 8]) for link_hair_texture in link_hair_textures: link_hair_texture.replace_image(back_hair_image) link_hair_model.save_changes() # Recolor the mouth. for i in range(1, 9 + 1): mouth_mask_path = custom_model_metadata["mouth_color_mask_paths"][ i][custom_color_basename] if os.path.isfile(mouth_mask_path ) or custom_color_basename == mouth_color_name: if i not in all_mouth_textures: all_mouth_textures[ i] = link_main_model.tex1.textures_by_name[ "mouthS3TC.%d" % i] all_mouth_images[i] = all_mouth_textures[i][0].render() if os.path.isfile(mouth_mask_path): check_valid_mask_path(mouth_mask_path) all_mouth_images[i] = texture_utils.color_exchange( all_mouth_images[i], base_color, custom_color, mask_path=mouth_mask_path) elif custom_color_basename == mouth_color_name: all_mouth_images[i] = texture_utils.color_exchange( all_mouth_images[i], base_color, custom_color) # Recolor the hands. hands_mask_path = custom_model_metadata[ "hands_" + prefix + "_color_mask_paths"][custom_color_basename] if os.path.isfile(hands_mask_path): check_valid_mask_path(hands_mask_path) hands_image = texture_utils.color_exchange( hands_image, base_color, custom_color, mask_path=hands_mask_path) replaced_any_hands = True elif custom_color_basename == hands_color_name: hands_image = texture_utils.color_exchange(hands_image, base_color, custom_color) replaced_any_hands = True if not replaced_any: return for texture in link_main_textures: if self.custom_model_name == "Link" and is_casual and texture.image_format == ImageFormat.C4: # Change the casual clothes texture to use C8 instead of C4 to increase the potential colors from 16 to 256. # This is only done for the vanilla Link model that comes with the game, not for custom models, since custom model creators could just change it themselves if they want to. texture.image_format = ImageFormat.C8 elif self.custom_model_name == "Link" and not is_casual and texture.image_format == ImageFormat.CMPR: # Change the hero's clothes texture to use C8 instead of CMPR to prevent the lossy compression from creating seams. # This is only done for the vanilla Link model that comes with the game, not for custom models, since custom model creators could just change it themselves if they want to. texture.image_format = ImageFormat.C8 texture.palette_format = PaletteFormat.RGB565 texture.replace_image(link_main_image) if is_casual: texture.save_changes() for i, mouth_textures in all_mouth_textures.items(): mouth_image = all_mouth_images[i] for mouth_texture in mouth_textures: mouth_texture.replace_image(mouth_image) if replaced_any_hands: for hands_texture in hands_textures: hands_texture.replace_image(hands_image) hands_model.save_changes() link_main_model.save_changes()
def change_player_clothes_color(self): custom_model_metadata = get_model_metadata(self.custom_model_name) disable_casual_clothes = custom_model_metadata.get( "disable_casual_clothes", False) link_arc = self.get_arc("files/res/Object/Link.arc") link_main_model = link_arc.get_file("cl.bdl") if self.options.get( "player_in_casual_clothes") and not disable_casual_clothes: is_casual = True prefix = "casual" link_main_textures = [link_arc.get_file("linktexbci4.bti")] else: is_casual = False prefix = "hero" link_main_textures = link_main_model.tex1.textures_by_name[ "linktexS3TC"] first_texture = link_main_textures[0] link_main_image = first_texture.render() replaced_any = False custom_colors = custom_model_metadata.get(prefix + "_custom_colors", {}) has_colored_eyebrows = custom_model_metadata.get("has_colored_eyebrows", False) hands_color_name = custom_model_metadata.get(prefix + "_hands_color_name", "Skin") mouth_color_name = custom_model_metadata.get(prefix + "_mouth_color_name", "Skin") eyebrow_color_name = custom_model_metadata.get( prefix + "_eyebrow_color_name", "Hair") casual_hair_color_name = custom_model_metadata.get( "casual_hair_color_name", "Hair") for custom_color_basename, base_color in custom_colors.items(): custom_color = self.options.get("custom_colors", {}).get(custom_color_basename, None) if custom_color is None: continue custom_color = tuple(custom_color) base_color = tuple(base_color) if custom_color == base_color: continue mask_path = custom_model_metadata[ prefix + "_color_mask_paths"][custom_color_basename] check_valid_mask_path(mask_path) link_main_image = texture_utils.color_exchange(link_main_image, base_color, custom_color, mask_path=mask_path) replaced_any = True # Recolor the eyebrows. if has_colored_eyebrows and custom_color_basename == eyebrow_color_name: for i in range(1, 6 + 1): eyebrow_textures = link_main_model.tex1.textures_by_name[ "mayuh.%d" % i] eyebrow_image = eyebrow_textures[0].render() eyebrow_image = texture_utils.color_exchange( eyebrow_image, base_color, custom_color) for eyebrow_texture in eyebrow_textures: eyebrow_texture.replace_image(eyebrow_image) # Recolor the back hair for casual Link. if is_casual and custom_color_basename == casual_hair_color_name: link_hair_model = link_arc.get_file("katsura.bdl") link_hair_textures = link_hair_model.tex1.textures_by_name[ "katsuraS3TC"] first_texture = link_hair_textures[0] back_hair_image = first_texture.render() back_hair_image.paste(custom_color, [0, 0, 8, 8]) for link_hair_texture in link_hair_textures: link_hair_texture.replace_image(back_hair_image) link_hair_model.save_changes() # Recolor the mouth. if custom_color_basename == mouth_color_name: for i in range(1, 9 + 1): mouth_textures = link_main_model.tex1.textures_by_name[ "mouthS3TC.%d" % i] mouth_image = mouth_textures[0].render() mouth_image = texture_utils.color_exchange( mouth_image, base_color, custom_color) for mouth_texture in mouth_textures: mouth_texture.replace_image(mouth_image) # Recolor the hands. if custom_color_basename == hands_color_name: hands_model = link_arc.get_file("hands.bdl") hands_textures = hands_model.tex1.textures_by_name["handsS3TC"] hands_image = hands_textures[0].render() hands_mask_path = custom_model_metadata["hands_" + prefix + "_color_mask_path"] if os.path.isfile(hands_mask_path): hands_image = texture_utils.color_exchange( hands_image, base_color, custom_color, mask_path=hands_mask_path) else: hands_image = texture_utils.color_exchange( hands_image, base_color, custom_color) for hands_texture in hands_textures: hands_texture.replace_image(hands_image) hands_model.save_changes() if not replaced_any: return for texture in link_main_textures: texture.replace_image(link_main_image) if is_casual: texture.save_changes() link_main_model.save_changes()
def change_player_clothes_color(self): custom_model_metadata = get_model_metadata(self.custom_model_name) disable_casual_clothes = custom_model_metadata.get( "disable_casual_clothes", False) link_arc = self.get_arc("files/res/Object/Link.arc") link_main_model = link_arc.get_file("cl.bdl") if self.options.get( "player_in_casual_clothes") and not disable_casual_clothes: is_casual = True prefix = "casual" link_main_textures = [link_arc.get_file("linktexbci4.bti")] else: is_casual = False prefix = "hero" link_main_textures = link_main_model.tex1.textures_by_name[ "linktexS3TC"] first_texture = link_main_textures[0] link_main_image = first_texture.render() hands_model = link_arc.get_file("hands.bdl") hands_textures = hands_model.tex1.textures_by_name["handsS3TC"] hands_image = hands_textures[0].render() replaced_any = False replaced_any_hands = False custom_colors = custom_model_metadata.get(prefix + "_custom_colors", {}) has_colored_eyebrows = custom_model_metadata.get("has_colored_eyebrows", False) hands_color_name = custom_model_metadata.get(prefix + "_hands_color_name", "Skin") mouth_color_name = custom_model_metadata.get(prefix + "_mouth_color_name", "Skin") eyebrow_color_name = custom_model_metadata.get( prefix + "_eyebrow_color_name", "Hair") casual_hair_color_name = custom_model_metadata.get( "casual_hair_color_name", "Hair") for custom_color_basename, base_color in custom_colors.items(): custom_color = self.options.get("custom_colors", {}).get(custom_color_basename, None) if custom_color is None: continue custom_color = tuple(custom_color) base_color = tuple(base_color) if custom_color == base_color: continue mask_path = custom_model_metadata[ prefix + "_color_mask_paths"][custom_color_basename] check_valid_mask_path(mask_path) link_main_image = texture_utils.color_exchange(link_main_image, base_color, custom_color, mask_path=mask_path) replaced_any = True # Recolor the eyebrows. if has_colored_eyebrows and custom_color_basename == eyebrow_color_name: for i in range(1, 6 + 1): eyebrow_textures = link_main_model.tex1.textures_by_name[ "mayuh.%d" % i] eyebrow_image = eyebrow_textures[0].render() eyebrow_image = texture_utils.color_exchange( eyebrow_image, base_color, custom_color) for eyebrow_texture in eyebrow_textures: if eyebrow_texture.is_greyscale(): raise Exception( "Eyebrows use a greyscale image format, but metadata.txt specified the model should have colored eyebrows." ) eyebrow_texture.replace_image(eyebrow_image) # Recolor the back hair for casual Link. if is_casual and custom_color_basename == casual_hair_color_name: link_hair_model = link_arc.get_file("katsura.bdl") link_hair_textures = link_hair_model.tex1.textures_by_name[ "katsuraS3TC"] first_texture = link_hair_textures[0] back_hair_image = first_texture.render() back_hair_image.paste(custom_color, [0, 0, 8, 8]) for link_hair_texture in link_hair_textures: link_hair_texture.replace_image(back_hair_image) link_hair_model.save_changes() # Recolor the mouth. if custom_color_basename == mouth_color_name: for i in range(1, 9 + 1): mouth_textures = link_main_model.tex1.textures_by_name[ "mouthS3TC.%d" % i] mouth_image = mouth_textures[0].render() mouth_image = texture_utils.color_exchange( mouth_image, base_color, custom_color) for mouth_texture in mouth_textures: mouth_texture.replace_image(mouth_image) # Recolor the hands. hands_mask_path = custom_model_metadata[ "hands_" + prefix + "_color_mask_paths"][custom_color_basename] if os.path.isfile(hands_mask_path): hands_image = texture_utils.color_exchange( hands_image, base_color, custom_color, mask_path=hands_mask_path) replaced_any_hands = True elif custom_color_basename == hands_color_name: hands_image = texture_utils.color_exchange(hands_image, base_color, custom_color) replaced_any_hands = True if not replaced_any: return for texture in link_main_textures: if self.custom_model_name == "Link" and is_casual and texture.image_format == ImageFormat.C4: # Change the casual clothes texture to use C8 instead of C4 to increase the potential colors from 16 to 256. # This is only done for the vanilla Link model that comes with the game, not for custom models, since custom model creators could just change it themselves if they want to. texture.image_format = ImageFormat.C8 texture.replace_image(link_main_image) if is_casual: texture.save_changes() if replaced_any_hands: for hands_texture in hands_textures: hands_texture.replace_image(hands_image) hands_model.save_changes() link_main_model.save_changes()