diff --git a/base-hack/src/misc/krusha.c b/base-hack/src/misc/krusha.c index 1c6d23600..d13d26ce4 100644 --- a/base-hack/src/misc/krusha.c +++ b/base-hack/src/misc/krusha.c @@ -319,26 +319,29 @@ typedef struct projectile_extra { /* 0x014 */ float unk14; } projectile_extra; +typedef struct KrushaProjectileColorStruct { + unsigned char pellet; + unsigned char red; + unsigned char green; + unsigned char blue; +} KrushaProjectileColorStruct; + +static KrushaProjectileColorStruct krusha_projectile_colors[] = { + {.pellet = 48, .red = 0xC0, .green = 0xFF, .blue = 0x00}, + {.pellet = 36, .red = 0xFF, .green = 0x40, .blue = 0x00}, + {.pellet = 42, .red = 0x18, .green = 0x18, .blue = 0x00}, + {.pellet = 43, .red = 0x80, .green = 0x00, .blue = 0x00}, + {.pellet = 38, .red = 0x00, .green = 0xFF, .blue = 0x00}, +}; + void setKrushaAmmoColor(void) { int currentPellet = CurrentActorPointer_0->actorType; - switch (currentPellet) { - case 48: - changeActorColor(0xC0, 0xFF, 0, 0xFF); - break; - case 36: - changeActorColor(0xFF, 0x40, 0x40, 0xFF); - break; - case 42: - changeActorColor(0x18, 0x18, 0xFF, 0xFF); - break; - case 43: - changeActorColor(0x80, 0, 0xFF, 0xFF); - break; - case 38: - changeActorColor(0, 0xFF, 0, 0xFF); - break; - default: - changeActorColor(0, 0xFF, 0, 0xFF); + for (int i = 0; i < 5; i++) { + KrushaProjectileColorStruct *data = &krusha_projectile_colors[i]; + if ((currentPellet == data->pellet) || (i == 4)) { + changeActorColor(data->red, data->green, data->blue, 0xFF); + return; + } } } diff --git a/randomizer/Lists/DoorLocations.py b/randomizer/Lists/DoorLocations.py index a586e99a8..37117ff8b 100644 --- a/randomizer/Lists/DoorLocations.py +++ b/randomizer/Lists/DoorLocations.py @@ -2852,6 +2852,7 @@ def isBarrierRemoved(spoiler, barrier_id: RemovedBarriersSelected): kong_lst=[Kongs.tiny, Kongs.chunky], group=11, moveless=False, + door_type=[DoorType.boss, DoorType.wrinkly], ), # might be accessible by all kongs post-punch? DoorData( name="Winch Room - on the Winch", @@ -2954,6 +2955,7 @@ def isBarrierRemoved(spoiler, barrier_id: RemovedBarriersSelected): logicregion=Regions.MushroomLankyMushroomsRoom, location=[447, 0, 368, 239.50], group=20, + door_type=[DoorType.dk_portal, DoorType.wrinkly], ), DoorData( name="DK's Barn - Second floor", diff --git a/randomizer/Patching/ASMPatcher.py b/randomizer/Patching/ASMPatcher.py index 8b49e57a4..12f518c2e 100644 --- a/randomizer/Patching/ASMPatcher.py +++ b/randomizer/Patching/ASMPatcher.py @@ -266,8 +266,6 @@ def getROMAddress(address: int, overlay: Overlay, offset_dict: dict) -> int: raise Exception(f"Seeking out of bounds for this overlay. Attempted to seek to {hex(address)} in overlay {overlay.name}") if address < rdram_start: raise Exception(f"Seeking out of bounds for this overlay. Attempted to seek to {hex(address)} in overlay {overlay.name}") - if overlay == Overlay.Boot: - print(hex(rdram_start), hex(overlay_start), hex(overlay_start + (address - rdram_start))) return overlay_start + (address - rdram_start) @@ -664,7 +662,23 @@ def patchAssemblyCosmetic(ROM_COPY: ROM, settings: Settings, has_dom: bool = Tru elif holiday == Holidays.Anniv25: # Change barrel base sprite writeValue(ROM_COPY, 0x80721458, Overlay.Static, getBonusSkinOffset(ExtraTextures.Anniv25Barrel), offset_dict) - + # Smoother Camera + if settings.smoother_camera: + camera_change_cooldown = 5 + writeValue(ROM_COPY, 0x806EA238, Overlay.Static, 0, offset_dict, 4) # Disable it requiring a new input + writeValue(ROM_COPY, 0x806EA2A4, Overlay.Static, 0, offset_dict, 4) # Disable it requiring a new input + camera_change_amount = 5 * (camera_change_cooldown - 2) + addr = getROMAddress(0x806EA25E, Overlay.Static, offset_dict) + ROM_COPY.seek(addr) + val = int.from_bytes(ROM_COPY.readBytes(2), "big") + if (val & 0x8000) == 0: # Is Mirror Mode + camera_change_amount = -camera_change_amount + writeValue(ROM_COPY, 0x806EA256, Overlay.Static, camera_change_cooldown, offset_dict) + writeValue(ROM_COPY, 0x806EA25E, Overlay.Static, -camera_change_amount, offset_dict, 2, True) + writeValue(ROM_COPY, 0x806EA2C2, Overlay.Static, camera_change_cooldown, offset_dict) + writeValue(ROM_COPY, 0x806EA2CA, Overlay.Static, camera_change_amount, offset_dict, 2, True) + + # Crosshair if settings.colorblind_mode != ColorblindMode.off: writeValue(ROM_COPY, 0x8069E974, Overlay.Static, 0x1000, offset_dict) # Force first option writeValue(ROM_COPY, 0x8069E9B0, Overlay.Static, 0, offset_dict, 4) # Prevent write diff --git a/randomizer/Patching/ApplyLocal.py b/randomizer/Patching/ApplyLocal.py index fbd2a84d2..eeff17ec6 100644 --- a/randomizer/Patching/ApplyLocal.py +++ b/randomizer/Patching/ApplyLocal.py @@ -15,12 +15,12 @@ from randomizer.Enums.Models import Model, ModelNames, HeadResizeImmune from randomizer.Enums.Settings import RandomModels, BigHeadMode from randomizer.Lists.Songs import ExcludedSongsSelector +from randomizer.Patching.Cosmetics.TextRando import writeCrownNames +from randomizer.Patching.Cosmetics.Holiday import applyHolidayMode +from randomizer.Patching.Cosmetics.EnemyColors import writeMiscCosmeticChanges from randomizer.Patching.CosmeticColors import ( apply_cosmetic_colors, - applyHolidayMode, overwrite_object_colors, - writeMiscCosmeticChanges, - writeCrownNames, darkenDPad, darkenPauseBubble, ) diff --git a/randomizer/Patching/ApplyRandomizer.py b/randomizer/Patching/ApplyRandomizer.py index 6d4d57e23..e49838102 100644 --- a/randomizer/Patching/ApplyRandomizer.py +++ b/randomizer/Patching/ApplyRandomizer.py @@ -43,13 +43,11 @@ from randomizer.Patching.BananaPortRando import randomize_bananaport, move_bananaports from randomizer.Patching.BarrelRando import randomize_barrels from randomizer.Patching.CoinPlacer import randomize_coins +from randomizer.Patching.Cosmetics.TextRando import writeBootMessages +from randomizer.Patching.Cosmetics.Puzzles import updateMillLeverTexture, updateCryptLeverTexture, updateDiddyDoors from randomizer.Patching.CosmeticColors import ( applyHelmDoorCosmetics, applyKongModelSwaps, - updateCryptLeverTexture, - updateMillLeverTexture, - writeBootMessages, - updateDiddyDoors, showWinCondition, ) from randomizer.Patching.CratePlacer import randomize_melon_crate diff --git a/randomizer/Patching/CosmeticColors.py b/randomizer/Patching/CosmeticColors.py index c6bfcdb23..b1a22b98f 100644 --- a/randomizer/Patching/CosmeticColors.py +++ b/randomizer/Patching/CosmeticColors.py @@ -4,45 +4,56 @@ import gzip import random -import zlib -import math -from random import randint from typing import TYPE_CHECKING, List, Tuple -from enum import IntEnum, auto from io import BytesIO from PIL import Image, ImageDraw, ImageEnhance import js from randomizer.Enums.Kongs import Kongs -from randomizer.Enums.Settings import CharacterColors, ColorblindMode, RandomModels, KongModels, WinConditionComplex -from randomizer.Enums.Models import Model, Sprite +from randomizer.Enums.Settings import CharacterColors, ColorblindMode, KongModels, WinConditionComplex from randomizer.Enums.Maps import Maps from randomizer.Enums.Types import BarrierItems -from randomizer.Patching.generate_kong_color_images import convertColors +from randomizer.Patching.Cosmetics.CustomTextures import writeTransition, writeCustomPaintings, writeCustomPortal +from randomizer.Patching.Cosmetics.Krusha import placeKrushaHead, fixBaboonBlasts, kong_index_mapping, fixModelSmallKongCollision +from randomizer.Patching.Cosmetics.Colorblind import ( + recolorKlaptraps, + writeWhiteKasplatHairColorToROM, + recolorBells, + recolorWrinklyDoors, + recolorSlamSwitches, + recolorBlueprintModelTwo, + recolorPotions, + recolorMushrooms, + writeKasplatHairColorToROM, + maskBlueprintImage, + maskLaserImage, + recolorKRoolShipSwitch, + recolorRotatingRoomTiles, +) from randomizer.Patching.Lib import ( - float_to_hex, - getObjectAddress, - int_to_list, - intf_to_float, - PaletteFillType, - SpawnerChange, - applyCharacterSpawnerChanges, compatible_background_textures, - grabText, - writeText, TableNames, - getRawFile, - writeRawFile, - Holidays, - getHoliday, ) -from randomizer.Patching.LibImage import getImageFile, TextureFormat, getRandomHueShift, hueShift, ExtraTextures, imageToCI, getBonusSkinOffset +from randomizer.Patching.LibImage import ( + getImageFile, + TextureFormat, + ExtraTextures, + getBonusSkinOffset, + writeColorImageToROM, + numberToImage, + maskImage, + maskImageWithColor, + getKongItemColor, + hueShiftColor, +) from randomizer.Patching.Patcher import ROM, LocalROM from randomizer.Settings import Settings - -if TYPE_CHECKING: - from PIL.Image import Image +from randomizer.Patching.Cosmetics.ModelSwaps import ( + model_mapping, + applyCosmeticModelSwaps, +) +from randomizer.Patching.Cosmetics.KongColor import writeKongColors, changeModelTextures class HelmDoorSetting: @@ -77,442 +88,9 @@ def __init__( self.format = format -turtle_models = [ - Model.Diddy, # Diddy - Model.DK, # DK - Model.Lanky, # Lanky - Model.Tiny, # Tiny - Model.Chunky, # Regular Chunky - Model.ChunkyDisco, # Disco Chunky - Model.Cranky, # Cranky - Model.Funky, # Funky - Model.Candy, # Candy - Model.Seal, # Seal - Model.Enguarde, # Enguarde - Model.BeaverBlue_LowPoly, # Beaver - Model.Squawks_28, # Squawks - Model.KlaptrapGreen, # Klaptrap Green - Model.KlaptrapPurple, # Klaptrap Purple - Model.KlaptrapRed, # Klaptrap Red - Model.KlaptrapTeeth, # Klaptrap Teeth - Model.SirDomino, # Sir Domino - Model.MrDice_41, # Mr Dice - Model.Beetle, # Beetle - Model.NintendoLogo, # N64 Logo - Model.MechanicalFish, # Mech Fish - Model.ToyCar, # Toy Car - Model.BananaFairy, # Fairy - Model.Shuri, # Starfish - Model.Gimpfish, # Gimpfish - Model.Spider, # Spider - Model.Rabbit, # Rabbit - Model.KRoolCutscene, # K Rool - Model.SkeletonHead, # Skeleton Head - Model.Vulture_76, # Vulture - Model.Vulture_77, # Racing Vulture - Model.Tomato, # Tomato - Model.Fly, # Fly - Model.SpotlightFish, # Spotlight Fish - Model.Puftup, # Pufftup - Model.CuckooBird, # Cuckoo Bird - Model.IceTomato, # Ice Tomato - Model.Boombox, # Boombox - Model.KRoolFight, # K Rool (Boxing) - Model.Microphone, # Microbuffer - Model.DeskKRool, # K Rool's Desk - Model.Bell, # Bell - Model.BonusBarrel, # Bonus Barrel - Model.HunkyChunkyBarrel, # HC Barrel - Model.MiniMonkeyBarrel, # MM Barrel - Model.TNTBarrel, # TNT Barrel - Model.Rocketbarrel, # RB Barrel - Model.StrongKongBarrel, # SK Barrel - Model.OrangstandSprintBarrel, # OSS Barrel - Model.BBBSlot_143, # BBB Slot - Model.PlayerCar, # Tiny Car - Model.Boulder, # Boulder - Model.Boat_158, # Boat - Model.Potion, # Potion - Model.ArmyDilloMissle, # AD Missile - Model.TagBarrel, # Tag Barrel - Model.QuestionMark, # Question Mark - Model.Krusha, # Krusha - Model.BananaPeel, # Banana Peel - Model.Butterfly, # Butterfly - Model.FunkyGun, # Funky's Gun -] - -panic_models = [ - Model.Diddy, # Diddy - Model.DK, # DK - Model.Lanky, # Lanky - Model.Tiny, # Tiny - Model.Chunky, # Regular Chunky - Model.ChunkyDisco, # Disco Chunky - Model.Cranky, # Cranky - Model.Funky, # Funky - Model.Candy, # Candy - Model.Seal, # Seal - Model.Enguarde, # Enguarde - Model.BeaverBlue_LowPoly, # Beaver - Model.Squawks_28, # Squawks - Model.KlaptrapGreen, # Klaptrap Green - Model.KlaptrapPurple, # Klaptrap Purple - Model.KlaptrapRed, # Klaptrap Red - Model.MadJack, # Mad Jack - Model.Troff, # Troff - Model.SirDomino, # Sir Domino - Model.MrDice_41, # Mr Dice - Model.RoboKremling, # Robo Kremling - Model.Scoff, # Scoff - Model.Beetle, # Beetle - Model.NintendoLogo, # N64 Logo - Model.MechanicalFish, # Mech Fish - Model.ToyCar, # Toy Car - Model.Klump, # Klump - Model.Dogadon, # Dogadon - Model.BananaFairy, # Fairy - Model.Guard, # Guard - Model.Shuri, # Starfish - Model.Gimpfish, # Gimpfish - Model.KLumsy, # K Lumsy - Model.Spider, # Spider - Model.Rabbit, # Rabbit - # Model.Beanstalk, # Beanstalk - Model.KRoolCutscene, # K Rool - Model.SkeletonHead, # Skeleton Head - Model.Vulture_76, # Vulture - Model.Vulture_77, # Racing Vulture - Model.Ghost, # Ghost - Model.Fly, # Fly - Model.FlySwatter_83, # Fly Swatter - Model.Owl, # Owl - Model.Book, # Book - Model.SpotlightFish, # Spotlight Fish - Model.Puftup, # Pufftup - Model.Mermaid, # Mermaid - Model.Mushroom, # Mushroom Man - Model.Worm, # Worm - Model.EscapeShip, # Escape Ship - Model.KRoolFight, # K Rool (Boxing) - Model.Microphone, # Microbuffer - Model.BonusBarrel, # Bonus Barrel - Model.HunkyChunkyBarrel, # HC Barrel - Model.MiniMonkeyBarrel, # MM Barrel - Model.TNTBarrel, # TNT Barrel - Model.Rocketbarrel, # RB Barrel - Model.StrongKongBarrel, # SK Barrel - Model.OrangstandSprintBarrel, # OSS Barrel - Model.PlayerCar, # Tiny Car - Model.Boulder, # Boulder - Model.VaseCircle, # Vase - Model.VaseColon, # Vase - Model.VaseTriangle, # Vase - Model.VasePlus, # Vase - Model.ArmyDilloMissle, # AD Missile - Model.TagBarrel, # Tag Barrel - Model.QuestionMark, # Question Mark - Model.Krusha, # Krusha - Model.Light, # Light - Model.BananaPeel, # Banana Peel - Model.FunkyGun, # Funky's Gun -] - -bother_models = [ - Model.BeaverBlue_LowPoly, # Beaver - Model.Klobber, # Klobber - Model.Kaboom, # Kaboom - Model.KlaptrapGreen, # Green Klap - Model.KlaptrapPurple, # Purple Klap - Model.KlaptrapRed, # Red Klap - Model.KlaptrapTeeth, # Klap Teeth - Model.Krash, # Krash - Model.Troff, # Troff - Model.NintendoLogo, # N64 Logo - Model.MechanicalFish, # Mech Fish - Model.Krossbones, # Krossbones - Model.Rabbit, # Rabbit - Model.SkeletonHead, # Minecart Skeleton Head - Model.Tomato, # Tomato - Model.IceTomato, # Ice Tomato - Model.GoldenBanana_104, # Golden Banana - Model.Microphone, # Microbuffer - Model.Bell, # Bell - Model.Missile, # Missile (Car Race) - Model.Buoy, # Red Buoy - Model.BuoyGreen, # Green Buoy - Model.RarewareLogo, # Rareware Logo -] - -piano_models = [ - Model.Krash, - Model.RoboKremling, - Model.KoshKremling, - Model.KoshKremlingRed, - Model.Kasplat, - Model.Guard, - Model.Krossbones, - Model.Mermaid, - Model.Mushroom, - Model.GoldenBanana_104, - Model.FlySwatter_83, - Model.Ruler, -] -piano_extreme_model = [ - Model.SkeletonHead, - Model.Owl, - Model.Kosha, - # Model.Beanstalk, -] - -spotlight_fish_models = [ - # Model.Turtle, # Lighting Bug - Model.Seal, - Model.BeaverBlue, - Model.BeaverGold, - Model.Zinger, - Model.Squawks_28, - Model.Klobber, - Model.Kaboom, - Model.KlaptrapGreen, - Model.KlaptrapPurple, - Model.KlaptrapRed, - Model.Krash, - # Model.SirDomino, # Lighting issue - # Model.MrDice_41, # Lighting issue - # Model.Ruler, # Lighting issue - # Model.RoboKremling, # Lighting issue - Model.NintendoLogo, - Model.MechanicalFish, - Model.ToyCar, - Model.Kasplat, - Model.BananaFairy, - Model.Guard, - Model.Gimpfish, - # Model.Shuri, # Lighting issue - Model.Spider, - Model.Rabbit, - Model.KRoolCutscene, - Model.KRoolFight, - # Model.SkeletonHead, # Lighting bug - # Model.Vulture_76, # Lighting bug - # Model.Vulture_77, # Lighting bug - # Model.Bat, # Lighting bug - # Model.Tomato, # Lighting bug - # Model.IceTomato, # Lighting bug - # Model.FlySwatter_83, # Lighting bug - Model.SpotlightFish, - Model.Microphone, - # Model.Rocketbarrel, # Model too big, obstructs view - # Model.StrongKongBarrel, # Model too big, obstructs view - # Model.OrangstandSprintBarrel, # Model too big, obstructs view - # Model.MiniMonkeyBarrel, # Model too big, obstructs view - # Model.HunkyChunkyBarrel, # Model too big, obstructs view -] -candy_cutscene_models = [ - Model.Cranky, - # Model.Funky, # Disappears with collision - Model.Candy, - Model.Snide, - Model.Seal, - Model.BeaverBlue, - Model.BeaverGold, - Model.Klobber, - Model.Kaboom, - Model.Krash, - Model.Troff, - Model.Scoff, - Model.RoboKremling, - Model.Beetle, - Model.MrDice_41, - Model.MrDice_56, - Model.BananaFairy, - Model.Rabbit, - Model.KRoolCutscene, - Model.KRoolFight, - Model.Vulture_76, - Model.Vulture_77, - Model.Tomato, - Model.IceTomato, - Model.FlySwatter_83, - Model.Microphone, - Model.StrongKongBarrel, - Model.Rocketbarrel, - Model.OrangstandSprintBarrel, - Model.MiniMonkeyBarrel, - Model.HunkyChunkyBarrel, - Model.RambiCrate, - Model.EnguardeCrate, - Model.Boulder, - Model.SteelKeg, - Model.GoldenBanana_104, -] - -funky_cutscene_models = [ - Model.Cranky, - Model.Candy, - Model.Funky, - Model.Troff, - Model.Scoff, - Model.Ruler, - Model.RoboKremling, - Model.KRoolCutscene, - Model.KRoolFight, - Model.Microphone, -] - -# Not holding gun -funky_cutscene_models_extreme = [ - Model.BeaverBlue, - Model.BeaverGold, - Model.Klobber, - Model.Kaboom, - Model.SirDomino, - Model.MechanicalFish, - Model.BananaFairy, - Model.SkeletonHand, - Model.IceTomato, - Model.Tomato, -] - -boot_cutscene_models = [ - Model.Turtle, - Model.Enguarde, - Model.BeaverBlue, - Model.BeaverGold, - Model.Zinger, - Model.Squawks_28, - Model.KlaptrapGreen, - Model.KlaptrapPurple, - Model.KlaptrapRed, - Model.BananaFairy, - Model.Spider, - Model.Bat, - Model.KRoolGlove, -] - -melon_random_sprites = [ - Sprite.BouncingMelon, - Sprite.BouncingOrange, - Sprite.Coconut, - Sprite.Peanut, - Sprite.Grape, - Sprite.Feather, - Sprite.Pineapple, - Sprite.CrystalCoconut0, - Sprite.DKCoin, - Sprite.DiddyCoin, - Sprite.LankyCoin, - Sprite.TinyCoin, - Sprite.ChunkyCoin, - Sprite.Fairy, - Sprite.RaceCoin, -] - -model_mapping = { - KongModels.default: 0, - KongModels.disco_chunky: 6, - KongModels.krusha: 7, - KongModels.krool_cutscene: 9, - KongModels.krool_fight: 8, - KongModels.cranky: 10, - KongModels.candy: 11, - KongModels.funky: 12, -} -krusha_texture_replacement = { - # Textures Krusha can use when he replaces various kongs (Main color, belt color) - Kongs.donkey: (3724, 0x177D), - Kongs.diddy: (4971, 4966), - Kongs.lanky: (3689, 0xE9A), - Kongs.tiny: (6014, 0xE68), - Kongs.chunky: (3687, 3778), -} -model_texture_sections = { - KongModels.krusha: { - "skin": [0x4738, 0x2E96, 0x3A5E], - "kong": [0x3126, 0x354E, 0x37FE, 0x41E6], - }, - KongModels.krool_fight: { - "skin": [ - 0x61D6, - 0x63FE, - 0x6786, - 0x7DD6, - 0x7E8E, - 0x7F3E, - 0x7FEE, - 0x5626, - 0x56E6, - 0x5A86, - 0x5BAE, - 0x5D46, - 0x5E2E, - 0x5FAE, - 0x69BE, - 0x735E, - 0x7C5E, - 0x7E4E, - 0x7EF6, - 0x7FA6, - 0x8056, - ], - "kong": [0x607E, 0x7446, 0x7D46, 0x80FE], - }, - # KongModels.krool_cutscene: { - # "skin": [0x4A6E, 0x4CBE, 0x52AE, 0x55BE, 0x567E, 0x57E6, 0x5946, 0x5AA6, 0x5E06, 0x5EC6, 0x6020, 0x618E, 0x62F6, 0x6946, 0x6A6E, 0x6C5E, 0x6D86, 0x6F76, 0x702E, 0x70DE, 0x718E, 0x72FE, 0x4FBE, 0x51FE, 0x5C26, 0x6476, 0x6826, 0x6B26, 0x6E3E, 0x6FE6, 0x7096, 0x7146, 0x71F6, 0x733E, 0x743E], - # "kong": [], - # } -} - - -class KongPalette: - """Class to store information regarding a kong palette.""" - - def __init__(self, name: str, image: int, fill_type: PaletteFillType, alt_name: str = None): - """Initialize with given parameters.""" - self.name = name - self.image = image - self.fill_type = fill_type - self.alt_name = alt_name - if alt_name is None: - self.alt_name = name - - -class KongPaletteSetting: - """Class to store information regarding the kong palette setting.""" - - def __init__(self, kong: str, kong_index: int, palettes: list[KongPalette]): - """Initialize with given parameters.""" - self.kong = kong - self.kong_index = kong_index - self.palettes = palettes.copy() - self.setting_kong = kong - - -def getKongColor(settings: Settings, index: int): - """Get color index for a kong.""" - kong_colors = ["#ffd700", "#ff0000", "#1699ff", "#B045ff", "#41ff25"] - mode = settings.colorblind_mode - if mode != ColorblindMode.off and settings.override_cosmetics: - if mode == ColorblindMode.prot: - kong_colors = ["#000000", "#0072FF", "#766D5A", "#FFFFFF", "#FDE400"] - elif mode == ColorblindMode.deut: - kong_colors = ["#000000", "#318DFF", "#7F6D59", "#FFFFFF", "#E3A900"] - elif mode == ColorblindMode.trit: - kong_colors = ["#000000", "#C72020", "#13C4D8", "#FFFFFF", "#FFA4A4"] - return kong_colors[index] - - -DEFAULT_COLOR = "#000000" -KLAPTRAPS = [Model.KlaptrapGreen, Model.KlaptrapPurple, Model.KlaptrapRed] RECOLOR_MEDAL_RIM = False -def getRandomKlaptrapModel() -> Model: - """Get random klaptrap model.""" - return random.choice(KLAPTRAPS) - - def changePatchFace(settings: Settings): """Change the top of the dirt patch image.""" if not settings.better_dirt_patch_cosmetic: @@ -535,103 +113,13 @@ def changePatchFace(settings: Settings): def apply_cosmetic_colors(settings: Settings): """Apply cosmetic skins to kongs.""" - bother_model_index = Model.KlaptrapGreen - panic_fairy_model_index = Model.BananaFairy - panic_klap_model_index = Model.KlaptrapGreen - turtle_model_index = Model.Turtle - sseek_klap_model_index = Model.KlaptrapGreen - fungi_tomato_model_index = Model.Tomato - caves_tomato_model_index = Model.IceTomato - racer_beetle = Model.Beetle - racer_rabbit = Model.Rabbit - piano_burper = Model.KoshKremlingRed - spotlight_fish_model_index = Model.SpotlightFish - candy_model_index = Model.Candy - funky_model_index = Model.Funky - boot_model_index = Model.Boot - melon_sprite = Sprite.BouncingMelon - swap_bitfield = 0 - ROM_COPY = ROM() sav = settings.rom_data + applyCosmeticModelSwaps(settings, ROM_COPY) changePatchFace(settings) + writeKongColors(settings) - model_inverse_mapping = {} - for model in model_mapping: - val = model_mapping[model] - model_inverse_mapping[val] = model - ROM_COPY.seek(settings.rom_data + 0x1B8) - settings.kong_model_dk = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] - settings.kong_model_diddy = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] - settings.kong_model_lanky = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] - settings.kong_model_tiny = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] - settings.kong_model_chunky = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] - if settings.override_cosmetics: - model_setting = RandomModels[js.document.getElementById("random_models").value] - else: - model_setting = settings.random_models - if model_setting == RandomModels.random: - bother_model_index = getRandomKlaptrapModel() - elif model_setting == RandomModels.extreme: - bother_model_index = random.choice(bother_models) - racer_beetle = random.choice([Model.Beetle, Model.Rabbit]) - racer_rabbit = random.choice([Model.Beetle, Model.Rabbit]) - if racer_rabbit == Model.Beetle: - spawner_changes = [] - # Fungi - rabbit_race_fungi_change = SpawnerChange(Maps.FungiForest, 2) - rabbit_race_fungi_change.new_scale = 50 - rabbit_race_fungi_change.new_speed_0 = 70 - rabbit_race_fungi_change.new_speed_1 = 136 - spawner_changes.append(rabbit_race_fungi_change) - # Caves - rabbit_caves_change = SpawnerChange(Maps.CavesChunkyIgloo, 1) - rabbit_caves_change.new_scale = 40 - spawner_changes.append(rabbit_caves_change) - applyCharacterSpawnerChanges(spawner_changes) - if model_setting != RandomModels.off: - panic_fairy_model_index = random.choice(panic_models) - turtle_model_index = random.choice(turtle_models) - panic_klap_model_index = getRandomKlaptrapModel() - sseek_klap_model_index = getRandomKlaptrapModel() - fungi_tomato_model_index = random.choice([Model.Tomato, Model.IceTomato]) - caves_tomato_model_index = random.choice([Model.Tomato, Model.IceTomato]) - referenced_piano_models = piano_models.copy() - referenced_funky_models = funky_cutscene_models.copy() - if model_setting == RandomModels.extreme: - referenced_piano_models.extend(piano_extreme_model) - spotlight_fish_model_index = random.choice(spotlight_fish_models) - referenced_funky_models.extend(funky_cutscene_models_extreme) - boot_model_index = random.choice(boot_cutscene_models) - piano_burper = random.choice(referenced_piano_models) - candy_model_index = random.choice(candy_cutscene_models) - funky_model_index = random.choice(funky_cutscene_models) - settings.bother_klaptrap_model = bother_model_index - settings.beetle_model = racer_beetle - settings.rabbit_model = racer_rabbit - settings.panic_fairy_model = panic_fairy_model_index - settings.turtle_model = turtle_model_index - settings.panic_klaptrap_model = panic_klap_model_index - settings.seek_klaptrap_model = sseek_klap_model_index - settings.fungi_tomato_model = fungi_tomato_model_index - settings.caves_tomato_model = caves_tomato_model_index - settings.piano_burp_model = piano_burper - settings.spotlight_fish_model = spotlight_fish_model_index - settings.candy_cutscene_model = candy_model_index - settings.funky_cutscene_model = funky_model_index - settings.boot_cutscene_model = boot_model_index - settings.wrinkly_rgb = [255, 255, 255] - # Compute swap bitfield - swap_bitfield |= 0x10 if settings.rabbit_model == Model.Beetle else 0 - swap_bitfield |= 0x20 if settings.beetle_model == Model.Rabbit else 0 - swap_bitfield |= 0x40 if settings.fungi_tomato_model == Model.IceTomato else 0 - swap_bitfield |= 0x80 if settings.caves_tomato_model == Model.Tomato else 0 - # Write Models - ROM_COPY.seek(sav + 0x1B5) - ROM_COPY.writeMultipleBytes(settings.panic_fairy_model + 1, 1) # Still needed for end seq fairy swap - ROM_COPY.seek(sav + 0x1E2) - ROM_COPY.write(swap_bitfield) settings.jetman_color = [0xFF, 0xFF, 0xFF] if settings.misc_cosmetics and settings.override_cosmetics: ROM_COPY.seek(sav + 0x196) @@ -655,185 +143,17 @@ def apply_cosmetic_colors(settings: Settings): value = random.randint(brightness_threshold, 0xFF) jetman_color[channel] = value settings.jetman_color = jetman_color.copy() - melon_sprite = random.choice(melon_random_sprites) - settings.minigame_melon_sprite = melon_sprite - color_palettes = [] - color_obj = {} - colors_dict = {} - kong_settings = [ - KongPaletteSetting( - "dk", - 0, - [ - KongPalette("fur", 3724, PaletteFillType.block), - KongPalette("tie", 0x177D, PaletteFillType.block), - KongPalette("tie", 0xE8D, PaletteFillType.patch), - ], - ), - KongPaletteSetting( - "diddy", - 1, - [ - KongPalette("clothes", 3686, PaletteFillType.block), - ], - ), - KongPaletteSetting( - "lanky", - 2, - [ - KongPalette("clothes", 3689, PaletteFillType.block), - KongPalette("clothes", 3734, PaletteFillType.patch), - KongPalette("fur", 0xE9A, PaletteFillType.block), - KongPalette("fur", 0xE94, PaletteFillType.block), - ], - ), - KongPaletteSetting( - "tiny", - 3, - [ - KongPalette("clothes", 6014, PaletteFillType.block), - KongPalette("hair", 0xE68, PaletteFillType.block), - ], - ), - KongPaletteSetting( - "chunky", - 4, - [ - KongPalette("main", 3769, PaletteFillType.checkered, "other"), - KongPalette("main", 3687, PaletteFillType.block), - ], - ), - KongPaletteSetting( - "rambi", - 5, - [ - KongPalette("skin", 3826, PaletteFillType.block), - ], - ), - KongPaletteSetting( - "enguarde", - 6, - [ - KongPalette("skin", 3847, PaletteFillType.block), - ], - ), - ] - - KONG_ZONES = { - "DK": ["Fur", "Tie"], - "Diddy": ["Clothes"], - "Lanky": ["Clothes", "Fur"], - "Tiny": ["Clothes", "Hair"], - "Chunky": ["Main", "Other"], - "Rambi": ["Skin"], - "Enguarde": ["Skin"], - } if js.document.getElementById("override_cosmetics").checked or True: writeTransition(settings) writeCustomPortal(settings) writeCustomPaintings(settings) # randomizePlants(ROM_COPY, settings) # Not sure how much I like how this feels - if js.document.getElementById("random_colors").checked: - for kong in KONG_ZONES: - for zone in KONG_ZONES[kong]: - settings.__setattr__(f"{kong.lower()}_{zone.lower()}_colors", CharacterColors.randomized) - else: - for kong in KONG_ZONES: - for zone in KONG_ZONES[kong]: - settings.__setattr__( - f"{kong.lower()}_{zone.lower()}_colors", - CharacterColors[js.document.getElementById(f"{kong.lower()}_{zone.lower()}_colors").value], - ) - settings.__setattr__( - f"{kong.lower()}_{zone.lower()}_custom_color", - js.document.getElementById(f"{kong.lower()}_{zone.lower()}_custom_color").value, - ) settings.gb_colors = CharacterColors[js.document.getElementById("gb_colors").value] settings.gb_custom_color = js.document.getElementById("gb_custom_color").value else: - if settings.random_colors: - for kong in KONG_ZONES: - for zone in KONG_ZONES[kong]: - settings.__setattr__(f"{kong.lower()}_{zone.lower()}_colors", CharacterColors.randomized) settings.gb_colors = CharacterColors.randomized - colors_dict = {} - for kong in KONG_ZONES: - for zone in KONG_ZONES[kong]: - colors_dict[f"{kong.lower()}_{zone.lower()}_colors"] = settings.__getattribute__(f"{kong.lower()}_{zone.lower()}_colors") - colors_dict[f"{kong.lower()}_{zone.lower()}_custom_color"] = settings.__getattribute__(f"{kong.lower()}_{zone.lower()}_custom_color") - for kong in kong_settings: - if kong.kong_index == 4: - if settings.kong_model_chunky == KongModels.disco_chunky: - kong.palettes = [ - KongPalette("main", 3777, PaletteFillType.sparkle), - KongPalette("other", 3778, PaletteFillType.sparkle), - ] - settings_values = [ - settings.kong_model_dk, - settings.kong_model_diddy, - settings.kong_model_lanky, - settings.kong_model_tiny, - settings.kong_model_chunky, - ] - if kong.kong_index >= 0 and kong.kong_index < len(settings_values): - if settings_values[kong.kong_index] in model_texture_sections: - base_setting = kong.palettes[0].name - kong.palettes = [ - KongPalette(base_setting, krusha_texture_replacement[kong.kong_index][0], PaletteFillType.block), # krusha_skin - KongPalette(base_setting, krusha_texture_replacement[kong.kong_index][1], PaletteFillType.kong), # krusha_indicator - ] - base_obj = {"kong": kong.kong, "zones": []} - zone_to_colors = {} - for palette in kong.palettes: - arr = [DEFAULT_COLOR] - if palette.fill_type == PaletteFillType.checkered: - arr = ["#FFFF00", "#00FF00"] - elif palette.fill_type == PaletteFillType.kong: - arr = [getKongColor(settings, kong.kong_index)] - zone_data = { - "zone": palette.name, - "image": palette.image, - "fill_type": palette.fill_type, - "colors": arr, - } - for index in range(len(arr)): - base_setting = f"{kong.kong}_{palette.name}_colors" - custom_setting = f"{kong.kong}_{palette.name}_custom_color" - if index == 1: # IS THE CHECKERED PATTERN - base_setting = f"{kong.kong}_{palette.alt_name}_colors" - custom_setting = f"{kong.kong}_{palette.alt_name}_custom_color" - if (settings.override_cosmetics and colors_dict[base_setting] != CharacterColors.vanilla) or (palette.fill_type == PaletteFillType.kong): - color = None - # if this palette color is randomized, and isn't krusha's kong indicator: - if colors_dict[base_setting] == CharacterColors.randomized and palette.fill_type != PaletteFillType.kong: - if base_setting in zone_to_colors: - color = zone_to_colors[base_setting] - else: - color = f"#{format(randint(0, 0xFFFFFF), '06x')}" - zone_to_colors[base_setting] = color - # if this palette color is not randomized (but might be a custom color) and isn't krusha's kong indicator: - elif palette.fill_type != PaletteFillType.kong: - color = colors_dict[custom_setting] - if not color: - color = DEFAULT_COLOR - # if this is krusha's kong indicator: - else: - color = getKongColor(settings, kong.kong_index) - if color is not None: - zone_data["colors"][index] = color - base_obj["zones"].append(zone_data) - color_palettes.append(base_obj) - color_obj[f"{kong.kong} {palette.name}"] = color - settings.colors = color_obj - if len(color_palettes) > 0: - # this is just to prune the duplicates that appear. someone should probably fix the root of the dupe issue tbh - new_color_palettes = [] - for pal in color_palettes: - if pal not in new_color_palettes: - new_color_palettes.append(pal) - convertColors(new_color_palettes) # GB Shine if settings.override_cosmetics and settings.gb_colors != CharacterColors.vanilla: channels = [] @@ -893,154 +213,9 @@ def apply_cosmetic_colors(settings: Settings): writeColorImageToROM(gb_shine_img, 25, tex, width, height, False, TextureFormat.RGBA5551) -color_bases = [] balloon_single_frames = [(4, 38), (5, 38), (5, 38), (5, 38), (5, 38), (5, 38), (4, 38), (4, 38)] -def getRGBFromHash(hash: str): - """Convert hash RGB code to rgb array.""" - red = int(hash[1:3], 16) - green = int(hash[3:5], 16) - blue = int(hash[5:7], 16) - return [red, green, blue] - - -def maskImageWithColor(im_f: Image, mask: tuple): - """Apply rgb mask to image using a rgb color tuple.""" - w, h = im_f.size - converter = ImageEnhance.Color(im_f) - im_f = converter.enhance(0) - im_dupe = im_f.copy() - brightener = ImageEnhance.Brightness(im_dupe) - im_dupe = brightener.enhance(2) - im_f.paste(im_dupe, (0, 0), im_dupe) - pix = im_f.load() - w, h = im_f.size - for x in range(w): - for y in range(h): - base = list(pix[x, y]) - if base[3] > 0: - for channel in range(3): - base[channel] = int(mask[channel] * (base[channel] / 255)) - pix[x, y] = (base[0], base[1], base[2], base[3]) - return im_f - - -def maskImage(im_f, base_index, min_y, keep_dark=False): - """Apply RGB mask to image.""" - w, h = im_f.size - converter = ImageEnhance.Color(im_f) - im_f = converter.enhance(0) - im_dupe = im_f.crop((0, min_y, w, h)) - if keep_dark is False: - brightener = ImageEnhance.Brightness(im_dupe) - im_dupe = brightener.enhance(2) - im_f.paste(im_dupe, (0, min_y), im_dupe) - pix = im_f.load() - mask = getRGBFromHash(color_bases[base_index]) - w, h = im_f.size - for x in range(w): - for y in range(min_y, h): - base = list(pix[x, y]) - if base[3] > 0: - for channel in range(3): - base[channel] = int(mask[channel] * (base[channel] / 255)) - pix[x, y] = (base[0], base[1], base[2], base[3]) - return im_f - - -def maskMushroomImage(im_f, reference_image, color, side_2=False): - """Apply RGB mask to mushroom image.""" - w, h = im_f.size - pixels_to_mask = [] - pix_ref = reference_image.load() - for x in range(w): - for y in range(h): - base_ref = list(pix_ref[x, y]) - # Filter out the white dots that won't get filtered out correctly with the below conditions - if not (max(abs(base_ref[0] - base_ref[2]), abs(base_ref[1] - base_ref[2])) < 41 and abs(base_ref[0] - base_ref[1]) < 11): - # Filter out that one lone pixel that is technically blue AND gets through the above filter, but should REALLY not be blue - if not (side_2 is True and x == 51 and y == 21): - # Select the exact pixels to mask, which is all the "blue" pixels, filtering out the white spots - if base_ref[2] > base_ref[0] and base_ref[2] > base_ref[1] and int(base_ref[0] + base_ref[1]) < 200: - pixels_to_mask.append([x, y]) - # Select the darker blue pixels as well - elif base_ref[2] > int(base_ref[0] + base_ref[1]): - pixels_to_mask.append([x, y]) - pix = im_f.load() - mask = getRGBFromHash(color) - for channel in range(3): - mask[channel] = max(1, mask[channel]) # Absolute black is bad - for x in range(w): - for y in range(h): - base = list(pix[x, y]) - if base[3] > 0 and [x, y] in pixels_to_mask: - average_light = int((base[0] + base[1] + base[2]) / 3) - for channel in range(3): - base[channel] = int(mask[channel] * (average_light / 255)) - pix[x, y] = (base[0], base[1], base[2], base[3]) - return im_f - - -def recolorRotatingRoomTiles(): - """Determine how to recolor the tiles rom the memory game in Donkey's Rotating Room in Caves.""" - question_mark_tiles = [900, 901, 892, 893, 896, 897, 890, 891, 898, 899, 894, 895] - face_tiles = [ - 874, - 878, - 875, - 879, - 876, - 886, - 877, - 885, - 880, - 887, - 881, - 888, - 870, - 872, - 871, - 873, - 866, - 882, - 867, - 883, - 868, - 889, - 869, - 884, - ] - question_mark_tile_masks = [508, 509] - face_tile_masks = [636, 635, 633, 634, 631, 632, 630, 629, 627, 628, 5478, 5478] - question_mark_resize = [17, 37] - face_resize = [[32, 64], [32, 64], [32, 64], [32, 64], [32, 64], [71, 66]] - question_mark_offsets = [[16, 14], [0, 14]] - face_offsets = [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [-5, -1], [-38, -1]] - - for tile in range(len(question_mark_tiles)): - tile_image = getImageFile(7, question_mark_tiles[tile], False, 32, 64, TextureFormat.RGBA5551) - mask = getImageFile(7, question_mark_tile_masks[(tile % 2)], False, 32, 64, TextureFormat.RGBA5551) - resize = question_mark_resize - mask = mask.resize((resize[0], resize[1])) - masked_tile = maskImageRotatingRoomTile(tile_image, mask, question_mark_offsets[(tile % 2)], int(tile / 2), (tile % 2)) - writeColorImageToROM(masked_tile, 7, question_mark_tiles[tile], 32, 64, False, TextureFormat.RGBA5551) - for tile in range(len(face_tiles)): - face_index = int(tile / 4) - if face_index < 5: - width = 32 - height = 64 - else: - width = 44 - height = 44 - mask = getImageFile(25, face_tile_masks[int(tile / 2)], True, width, height, TextureFormat.RGBA5551) - resize = face_resize[face_index] - mask = mask.resize((resize[0], resize[1])) - tile_image = getImageFile(7, face_tiles[tile], False, 32, 64, TextureFormat.RGBA5551) - masked_tile = maskImageRotatingRoomTile(tile_image, mask, face_offsets[int(tile / 2)], face_index, (int(tile / 2) % 2)) - writeColorImageToROM(masked_tile, 7, face_tiles[tile], 32, 64, False, TextureFormat.RGBA5551) - - def getSpinPixels() -> dict: """Get pixels that shouldn't be affected by the mask.""" spin_lengths = { @@ -1123,124 +298,6 @@ def maskImageGBSpin(im_f, color: tuple, image_index: int): return masked_im -def maskImageRotatingRoomTile(im_f, im_mask, paste_coords, image_color_index, tile_side): - """Apply RGB mask to image of a Rotating Room Memory Tile.""" - w, h = im_f.size - im_original = im_f - pix_original = im_original.load() - pixels_original = [] - for x in range(w): - pixels_original.append([]) - for y in range(h): - pixels_original[x].append(list(pix_original[x, y]).copy()) - converter = ImageEnhance.Color(im_f) - im_f = converter.enhance(0) - brightener = ImageEnhance.Brightness(im_f) - im_f = brightener.enhance(2) - pix = im_f.load() - pix_mask = im_mask.load() - w2, h2 = im_mask.size - mask_coords = [] - for x in range(w2): - for y in range(h2): - coord = list(pix_mask[x, y]) - if coord[3] > 0: - mask_coords.append([(x + paste_coords[0]), (y + paste_coords[1])]) - if image_color_index < 5: - mask = getRGBFromHash(color_bases[image_color_index]) - for channel in range(3): - mask[channel] = max(39, mask[channel]) # Too dark looks bad - else: - mask = getRGBFromHash(color_bases[2]) - mask2 = getRGBFromHash("#000000") - if image_color_index == 0: - mask2 = getRGBFromHash("#FFFFFF") - for x in range(w): - for y in range(h): - base = list(pix[x, y]) - base_original = list(pixels_original[x][y]) - if [x, y] not in mask_coords: - if image_color_index in [1, 2, 4]: # Diddy, Lanky and Chunky don't get any special features - for channel in range(3): - base[channel] = int(mask[channel] * (base[channel] / 255)) - elif image_color_index in [0, 3]: # Donkey and Tiny get a diamond-shape frame - side = w - if tile_side == 1: - side = 0 - if abs(abs(side - x) - y) < 2 or abs(abs(side - x) - abs(h - y)) < 2: - for channel in range(3): - base[channel] = int(mask2[channel] * (base[channel] / 255)) - else: - for channel in range(3): - base[channel] = int(mask[channel] * (base[channel] / 255)) - else: # Golden Banana gets a block-pattern - if (int(x / 8) + int(y / 8)) % 2 == 0: - for channel in range(3): - base[channel] = int(mask[channel] * (base[channel] / 255)) - else: - for channel in range(3): - base[channel] = int(mask2[channel] * (base[channel] / 255)) - else: - for channel in range(3): - base[channel] = base_original[channel] - pix[x, y] = (base[0], base[1], base[2], base[3]) - return im_f - - -def hueShiftColor(color: tuple, amount: int, head_ratio: int = None) -> tuple: - """Apply a hue shift to a color.""" - # RGB -> HSV Conversion - red_ratio = color[0] / 255 - green_ratio = color[1] / 255 - blue_ratio = color[2] / 255 - color_max = max(red_ratio, green_ratio, blue_ratio) - color_min = min(red_ratio, green_ratio, blue_ratio) - color_delta = color_max - color_min - hue = 0 - if color_delta != 0: - if color_max == red_ratio: - hue = 60 * (((green_ratio - blue_ratio) / color_delta) % 6) - elif color_max == green_ratio: - hue = 60 * (((blue_ratio - red_ratio) / color_delta) + 2) - else: - hue = 60 * (((red_ratio - green_ratio) / color_delta) + 4) - sat = 0 if color_max == 0 else color_delta / color_max - val = color_max - # Adjust Hue - if head_ratio is not None and sat != 0: - amount = head_ratio / (sat * 100) - hue = (hue + amount) % 360 - # HSV -> RGB Conversion - c = val * sat - x = c * (1 - abs(((hue / 60) % 2) - 1)) - m = val - c - if hue < 60: - red_ratio = c - green_ratio = x - blue_ratio = 0 - elif hue < 120: - red_ratio = x - green_ratio = c - blue_ratio = 0 - elif hue < 180: - red_ratio = 0 - green_ratio = c - blue_ratio = x - elif hue < 240: - red_ratio = 0 - green_ratio = x - blue_ratio = c - elif hue < 300: - red_ratio = x - green_ratio = 0 - blue_ratio = c - else: - red_ratio = c - green_ratio = 0 - blue_ratio = x - return (int((red_ratio + m) * 255), int((green_ratio + m) * 255), int((blue_ratio + m) * 255)) - - def maskImageWithOutline(im_f, base_index, min_y, colorblind_mode, type=""): """Apply RGB mask to image with an Outline in a different color.""" w, h = im_f.size @@ -1252,12 +309,11 @@ def maskImageWithOutline(im_f, base_index, min_y, colorblind_mode, type=""): im_dupe = brightener.enhance(2) im_f.paste(im_dupe, (0, min_y), im_dupe) pix = im_f.load() - mask = getRGBFromHash(color_bases[base_index]) + mask = getKongItemColor(colorblind_mode, base_index, True) if base_index == 2 or (base_index == 0 and colorblind_mode == ColorblindMode.trit): # lanky or (DK in tritanopia mode) - border_color = color_bases[4] + mask2 = getKongItemColor(colorblind_mode, Kongs.chunky, True) else: - border_color = color_bases[1] - mask2 = getRGBFromHash(border_color) + mask2 = getKongItemColor(colorblind_mode, Kongs.diddy, True) contrast = False if base_index == 0: contrast = True @@ -1293,830 +349,17 @@ def maskImageWithOutline(im_f, base_index, min_y, colorblind_mode, type=""): return im_f -def writeColorImageToROM( - im_f: PIL.Image.Image, - table_index: int, - file_index: int, - width: int, - height: int, - transparent_border: bool, - format: TextureFormat, -) -> None: - """Write texture to ROM.""" - file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] - file_end = js.pointer_addresses[table_index]["entries"][file_index + 1]["pointing_to"] - file_size = file_end - file_start - try: - LocalROM().seek(file_start) - except Exception: - ROM().seek(file_start) - pix = im_f.load() - width, height = im_f.size - bytes_array = [] - border = 1 - right_border = 3 - for y in range(height): - for x in range(width): - if transparent_border: - if ((x < border) or (y < border) or (x >= (width - border)) or (y >= (height - border))) or (x == (width - right_border)): - pix_data = [0, 0, 0, 0] - else: - pix_data = list(pix[x, y]) - else: - pix_data = list(pix[x, y]) - if format == TextureFormat.RGBA32: - bytes_array.extend(pix_data) - elif format == TextureFormat.RGBA5551: - red = int((pix_data[0] >> 3) << 11) - green = int((pix_data[1] >> 3) << 6) - blue = int((pix_data[2] >> 3) << 1) - alpha = int(pix_data[3] != 0) - value = red | green | blue | alpha - bytes_array.extend([(value >> 8) & 0xFF, value & 0xFF]) - elif format == TextureFormat.IA4: - intensity = pix_data[0] >> 5 - alpha = 0 if pix_data[3] == 0 else 1 - data = ((intensity << 1) | alpha) & 0xF - bytes_array.append(data) - bytes_per_px = 2 - if format == TextureFormat.IA4: - temp_ba = bytes_array.copy() - bytes_array = [] - value_storage = 0 - bytes_per_px = 0.5 - for idx, val in enumerate(temp_ba): - polarity = idx % 2 - if polarity == 0: - value_storage = val << 4 - else: - value_storage |= val - bytes_array.append(value_storage) - data = bytearray(bytes_array) - if format == TextureFormat.RGBA32: - bytes_per_px = 4 - if len(data) > (bytes_per_px * width * height): - print(f"Image too big error: {table_index} > {file_index}") - if table_index in (14, 25): - data = gzip.compress(data, compresslevel=9) - if len(data) > file_size: - print(f"File too big error: {table_index} > {file_index}") - try: - LocalROM().writeBytes(data) - except Exception: - ROM().writeBytes(data) - - -def writeKasplatHairColorToROM(color, table_index, file_index, format: str): - """Write color to ROM for kasplats.""" - file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] - mask = getRGBFromHash(color) - if format == TextureFormat.RGBA32: - color_lst = mask.copy() - color_lst.append(255) # Alpha - null_color = [0] * 4 - else: - val_r = int((mask[0] >> 3) << 11) - val_g = int((mask[1] >> 3) << 6) - val_b = int((mask[2] >> 3) << 1) - rgba_val = val_r | val_g | val_b | 1 - color_lst = [(rgba_val >> 8) & 0xFF, rgba_val & 0xFF] - null_color = [0, 0] - bytes_array = [] - for y in range(42): - for x in range(32): - bytes_array.extend(color_lst) - for i in range(18): - bytes_array.extend(color_lst) - for i in range(4): - bytes_array.extend(null_color) - for i in range(3): - bytes_array.extend(color_lst) - data = bytearray(bytes_array) - if table_index == 25: - data = gzip.compress(data, compresslevel=9) - ROM().seek(file_start) - ROM().writeBytes(data) - - -def writeWhiteKasplatHairColorToROM(color1, color2, table_index, file_index, format: str): - """Write color to ROM for white kasplats, giving them a black-white block pattern.""" - file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] - mask = getRGBFromHash(color1) - mask2 = getRGBFromHash(color2) - if format == TextureFormat.RGBA32: - color_lst_0 = mask.copy() - color_lst_0.append(255) - color_lst_1 = mask2.copy() - color_lst_1.append(255) - null_color = [0] * 4 - else: - val_r = int((mask[0] >> 3) << 11) - val_g = int((mask[1] >> 3) << 6) - val_b = int((mask[2] >> 3) << 1) - rgba_val = val_r | val_g | val_b | 1 - val_r2 = int((mask2[0] >> 3) << 11) - val_g2 = int((mask2[1] >> 3) << 6) - val_b2 = int((mask2[2] >> 3) << 1) - rgba_val2 = val_r2 | val_g2 | val_b2 | 1 - color_lst_0 = [(rgba_val >> 8) & 0xFF, rgba_val & 0xFF] - color_lst_1 = [(rgba_val2 >> 8) & 0xFF, rgba_val2 & 0xFF] - null_color = [0] * 2 - bytes_array = [] - for y in range(42): - for x in range(32): - if (int(y / 7) + int(x / 8)) % 2 == 0: - bytes_array.extend(color_lst_0) - else: - bytes_array.extend(color_lst_1) - for i in range(18): - bytes_array.extend(color_lst_0) - for i in range(4): - bytes_array.extend(null_color) - for i in range(3): - bytes_array.extend(color_lst_0) - data = bytearray(bytes_array) - if table_index == 25: - data = gzip.compress(data, compresslevel=9) - ROM().seek(file_start) - ROM().writeBytes(data) - - -def writeKlaptrapSkinColorToROM(color_index, table_index, file_index, format: str): - """Write color to ROM for klaptraps.""" - im_f = getImageFile(table_index, file_index, True, 32, 43, format) - im_f = maskImage(im_f, color_index, 0, (color_index != 3)) - pix = im_f.load() - file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] - if format == TextureFormat.RGBA32: - null_color = [0] * 4 - else: - null_color = [0, 0] - bytes_array = [] - for y in range(42): - for x in range(32): - color_lst = calculateKlaptrapPixel(list(pix[x, y]), format) - bytes_array.extend(color_lst) - for i in range(18): - color_lst = calculateKlaptrapPixel(list(pix[i, 42]), format) - bytes_array.extend(color_lst) - for i in range(4): - bytes_array.extend(null_color) - for i in range(3): - color_lst = calculateKlaptrapPixel(list(pix[(22 + i), 42]), format) - bytes_array.extend(color_lst) - data = bytearray(bytes_array) - if table_index == 25: - data = gzip.compress(data, compresslevel=9) - ROM().seek(file_start) - ROM().writeBytes(data) - - -def writeSpecialKlaptrapTextureToROM(color_index, table_index, file_index, format: str, pixels_to_ignore: list): - """Write color to ROM for klaptraps special texture(s).""" - im_f = getImageFile(table_index, file_index, True, 32, 43, format) - pix_original = im_f.load() - pixels_original = [] - for x in range(32): - pixels_original.append([]) - for y in range(43): - pixels_original[x].append(list(pix_original[x, y]).copy()) - im_f_masked = maskImage(im_f, color_index, 0, (color_index != 3)) - pix = im_f_masked.load() - file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] - if format == TextureFormat.RGBA32: - null_color = [0] * 4 - else: - null_color = [0, 0] - bytes_array = [] - for y in range(42): - for x in range(32): - if [x, y] not in pixels_to_ignore: - color_lst = calculateKlaptrapPixel(list(pix[x, y]), format) - else: - color_lst = calculateKlaptrapPixel(list(pixels_original[x][y]), format) - bytes_array.extend(color_lst) - for i in range(18): - if [i, 42] not in pixels_to_ignore: - color_lst = calculateKlaptrapPixel(list(pix[i, 42]), format) - else: - color_lst = calculateKlaptrapPixel(list(pixels_original[i][42]), format) - bytes_array.extend(color_lst) - for i in range(4): - bytes_array.extend(null_color) - for i in range(3): - if [(22 + i), 42] not in pixels_to_ignore: - color_lst = calculateKlaptrapPixel(list(pix[(22 + i), 42]), format) - else: - color_lst = calculateKlaptrapPixel(list(pixels_original[(22 + i)][42]), format) - bytes_array.extend(color_lst) - data = bytearray(bytes_array) - if table_index == 25: - data = gzip.compress(data, compresslevel=9) - ROM().seek(file_start) - ROM().writeBytes(data) - - -def calculateKlaptrapPixel(mask: list, format: str): - """Calculate the new color for the given pixel.""" - if format == TextureFormat.RGBA32: - color_lst = mask.copy() - color_lst.append(255) # Alpha - else: - val_r = int((mask[0] >> 3) << 11) - val_g = int((mask[1] >> 3) << 6) - val_b = int((mask[2] >> 3) << 1) - rgba_val = val_r | val_g | val_b | 1 - color_lst = [(rgba_val >> 8) & 0xFF, rgba_val & 0xFF] - return color_lst - - -def maskBlueprintImage(im_f, base_index): - """Apply RGB mask to blueprint image.""" - w, h = im_f.size - im_f_original = im_f - converter = ImageEnhance.Color(im_f) - im_f = converter.enhance(0) - im_dupe = im_f.crop((0, 0, w, h)) - brightener = ImageEnhance.Brightness(im_dupe) - im_dupe = brightener.enhance(2) - im_f.paste(im_dupe, (0, 0), im_dupe) - pix = im_f.load() - pix2 = im_f_original.load() - mask = getRGBFromHash(color_bases[base_index]) - if max(mask[0], max(mask[1], mask[2])) < 39: - for channel in range(3): - mask[channel] = max(39, mask[channel]) # Too black is bad for these items - w, h = im_f.size - for x in range(w): - for y in range(h): - base = list(pix[x, y]) - base2 = list(pix2[x, y]) - if base[3] > 0: - # Filter out the wooden frame - # brown is orange, is red and (red+green), is very little blue - # but, if the color is light, we can't rely on the blue value alone. - if base2[2] > 20 and (base2[2] > base2[1] or base2[1] - base2[2] < 20): - for channel in range(3): - base[channel] = int(mask[channel] * (base[channel] / 255)) - pix[x, y] = (base[0], base[1], base[2], base[3]) - else: - pix[x, y] = (base2[0], base2[1], base2[2], base2[3]) - return im_f - - -def maskLaserImage(im_f, base_index): - """Apply RGB mask to laser texture.""" - w, h = im_f.size - im_f_original = im_f - converter = ImageEnhance.Color(im_f) - im_f = converter.enhance(0) - im_dupe = im_f.crop((0, 0, w, h)) - brightener = ImageEnhance.Brightness(im_dupe) - im_dupe = brightener.enhance(2) - im_f.paste(im_dupe, (0, 0), im_dupe) - pix = im_f.load() - pix2 = im_f_original.load() - mask = getRGBFromHash(color_bases[base_index]) - w, h = im_f.size - for x in range(w): - for y in range(h): - base = list(pix[x, y]) - base2 = list(pix2[x, y]) - if base[3] > 0: - # Filter out the white center of the laser - if min(base2[0], min(base2[1], base2[2])) <= 210: - for channel in range(3): - base[channel] = int(mask[channel] * (base[channel] / 255)) - pix[x, y] = (base[0], base[1], base[2], base[3]) - else: - pix[x, y] = (base2[0], base2[1], base2[2], base2[3]) - return im_f - - -def maskPotionImage(im_f, primary_color, secondary_color=None): - """Apply RGB mask to DK arcade potion reward preview texture.""" - w, h = im_f.size - pix = im_f.load() - mask = getRGBFromHash(primary_color) - if secondary_color is not None: - mask2 = secondary_color - for channel in range(3): - mask[channel] = max(1, mask[channel]) - w, h = im_f.size - for x in range(w): - for y in range(h): - base = list(pix[x, y]) - # Filter out transparent pixels and the cork - if base[3] > 0 and y > 2 and [x, y] not in [[9, 4], [10, 4]]: - # Filter out the bottle's contents - if base[0] == base[1] and base[1] == base[2]: - if secondary_color is not None: - # Color the bottle itself - for channel in range(3): - base[channel] = int(mask2[channel] * (base[channel] / 255)) - else: - # Color the bottle's contents - average_light = int((base[0] + base[1] + base[2]) / 3) - for channel in range(3): - base[channel] = int(mask[channel] * (average_light / 255)) - pix[x, y] = (base[0], base[1], base[2], base[3]) - return im_f - - -def recolorWrinklyDoors(): - """Recolor the Wrinkly hint door doorframes for colorblind mode.""" - file = [0xF0, 0xF2, 0xEF, 0x67, 0xF1] - for kong in range(5): - wrinkly_door_start = js.pointer_addresses[4]["entries"][file[kong]]["pointing_to"] - wrinkly_door_finish = js.pointer_addresses[4]["entries"][file[kong] + 1]["pointing_to"] - wrinkly_door_size = wrinkly_door_finish - wrinkly_door_start - ROM().seek(wrinkly_door_start) - indicator = int.from_bytes(ROM().readBytes(2), "big") - ROM().seek(wrinkly_door_start) - data = ROM().readBytes(wrinkly_door_size) - if indicator == 0x1F8B: - data = zlib.decompress(data, (15 + 32)) - num_data = [] # data, but represented as nums rather than b strings - for d in data: - num_data.append(d) - # Figure out which colors to use and where to put them (list extensions to mitigate the linter's "artistic freedom" putting 1 value per line) - color1_offsets = [ - 1548, - 1580, - 1612, - 1644, - 1676, - 1708, - 1756, - 1788, - 1804, - 1820, - 1836, - 1852, - 1868, - 1884, - 1900, - 1916, - ] - color1_offsets = color1_offsets + [ - 1932, - 1948, - 1964, - 1980, - 1996, - 2012, - 2028, - 2044, - 2076, - 2108, - 2124, - 2156, - 2188, - 2220, - 2252, - 2284, - ] - color1_offsets = color1_offsets + [ - 2316, - 2348, - 2380, - 2396, - 2412, - 2428, - 2444, - 2476, - 2508, - 2540, - 2572, - 2604, - 2636, - 2652, - 2668, - 2684, - ] - color1_offsets = color1_offsets + [ - 2700, - 2716, - 2732, - 2748, - 2764, - 2780, - 2796, - 2812, - 2828, - 2860, - 2892, - 2924, - 2956, - 2988, - 3020, - 3052, - ] - color2_offsets = [ - 1564, - 1596, - 1628, - 1660, - 1692, - 1724, - 1740, - 1772, - 2332, - 2364, - 2460, - 2492, - 2524, - 2556, - 2588, - 2620, - ] - new_color1 = getRGBFromHash(color_bases[kong]) - new_color2 = getRGBFromHash(color_bases[kong]) - if kong == 0: - for channel in range(3): - new_color2[channel] = max(80, new_color1[channel]) # Too black is bad, because anything times 0 is 0 - - # Recolor the doorframe - for offset in color1_offsets: - for i in range(3): - num_data[offset + i] = new_color1[i] - for offset in color2_offsets: - for i in range(3): - num_data[offset + i] = new_color2[i] - - data = bytearray(num_data) # convert num_data back to binary string - if indicator == 0x1F8B: - data = gzip.compress(data, compresslevel=9) - ROM().seek(wrinkly_door_start) - ROM().writeBytes(data) - - -def recolorSlamSwitches(galleon_switch_value, ROM_COPY: ROM): - """Recolor the Simian Slam switches for colorblind mode.""" - file = [0x94, 0x93, 0x95, 0x96, 0xB8, 0x16C, 0x16B, 0x16D, 0x16E, 0x16A, 0x167, 0x166, 0x168, 0x169, 0x165] - written_galleon_ship = False - for switch in range(15): - slam_switch_start = js.pointer_addresses[4]["entries"][file[switch]]["pointing_to"] - slam_switch_finish = js.pointer_addresses[4]["entries"][file[switch] + 1]["pointing_to"] - slam_switch_size = slam_switch_finish - slam_switch_start - ROM().seek(slam_switch_start) - indicator = int.from_bytes(ROM().readBytes(2), "big") - ROM().seek(slam_switch_start) - data = ROM().readBytes(slam_switch_size) - if indicator == 0x1F8B: - data = zlib.decompress(data, (15 + 32)) - num_data = [] # data, but represented as nums rather than b strings - for d in data: - num_data.append(d) - # Figure out which colors to use and where to put them - color_offsets = [1828, 1844, 1860, 1876, 1892, 1908] - new_color1 = getRGBFromHash(color_bases[4]) # chunky's color - new_color2 = getRGBFromHash(color_bases[2]) # lanky's color - new_color3 = getRGBFromHash(color_bases[1]) # diddy's color - - # Green switches - if switch < 5: - for offset in color_offsets: - for i in range(3): - num_data[offset + i] = new_color1[i] - # Blue switches - elif switch < 10: - for offset in color_offsets: - for i in range(3): - num_data[offset + i] = new_color2[i] - # Red switches - else: - for offset in color_offsets: - for i in range(3): - num_data[offset + i] = new_color3[i] - - data = bytearray(num_data) # convert num_data back to binary string - if indicator == 0x1F8B: - data = gzip.compress(data, compresslevel=9) - ROM().seek(slam_switch_start) - ROM().writeBytes(data) - if not written_galleon_ship: - galleon_switch_color = new_color1.copy() - if galleon_switch_value is not None: - if galleon_switch_value != 1: - galleon_switch_color = new_color3.copy() - if galleon_switch_value == 2: - galleon_switch_color = new_color2.copy() - recolorKRoolShipSwitch(galleon_switch_color, ROM_COPY) - written_galleon_ship = True - - -def recolorBlueprintModelTwo(): - """Recolor the Blueprint Model2 items for colorblind mode.""" - file = [0xDE, 0xE0, 0xE1, 0xDD, 0xDF] - for kong in range(5): - blueprint_model2_start = js.pointer_addresses[4]["entries"][file[kong]]["pointing_to"] - blueprint_model2_finish = js.pointer_addresses[4]["entries"][file[kong] + 1]["pointing_to"] - blueprint_model2_size = blueprint_model2_finish - blueprint_model2_start - ROM().seek(blueprint_model2_start) - indicator = int.from_bytes(ROM().readBytes(2), "big") - ROM().seek(blueprint_model2_start) - data = ROM().readBytes(blueprint_model2_size) - if indicator == 0x1F8B: - data = zlib.decompress(data, (15 + 32)) - num_data = [] # data, but represented as nums rather than b strings - for d in data: - num_data.append(d) - # Figure out which colors to use and where to put them - color1_offsets = [0x52C, 0x54C, 0x57C, 0x58C, 0x5AC, 0x5CC, 0x5FC, 0x61C] - color2_offsets = [0x53C, 0x55C, 0x5EC, 0x60C] - color3_offsets = [0x56C, 0x59C, 0x5BC, 0x5DC] - new_color = getRGBFromHash(color_bases[kong]) - if kong == 0: - for channel in range(3): - new_color[channel] = max(39, new_color[channel]) # Too black is bad, because anything times 0 is 0 - - # Recolor the model2 item - for offset in color1_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color2_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color3_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - - data = bytearray(num_data) # convert num_data back to binary string - if indicator == 0x1F8B: - data = gzip.compress(data, compresslevel=9) - ROM().seek(blueprint_model2_start) - ROM().writeBytes(data) - - -def recolorBells(): - """Recolor the Chunky Minecart bells for colorblind mode (prot/deut).""" - file = 693 - minecart_bell_start = js.pointer_addresses[4]["entries"][file]["pointing_to"] - minecart_bell_finish = js.pointer_addresses[4]["entries"][file + 1]["pointing_to"] - minecart_bell_size = minecart_bell_finish - minecart_bell_start - ROM().seek(minecart_bell_start) - indicator = int.from_bytes(ROM().readBytes(2), "big") - ROM().seek(minecart_bell_start) - data = ROM().readBytes(minecart_bell_size) - if indicator == 0x1F8B: - data = zlib.decompress(data, (15 + 32)) - num_data = [] # data, but represented as nums rather than b strings - for d in data: - num_data.append(d) - # Figure out which colors to use and where to put them - color1_offsets = [0x214, 0x244, 0x264, 0x274, 0x284] - color2_offsets = [0x224, 0x234, 0x254] - new_color1 = getRGBFromHash("#0066FF") - new_color2 = getRGBFromHash("#0000FF") - - # Recolor the bell - for offset in color1_offsets: - for i in range(3): - num_data[offset + i] = new_color1[i] - for offset in color2_offsets: - for i in range(3): - num_data[offset + i] = new_color2[i] - - data = bytearray(num_data) # convert num_data back to binary string - if indicator == 0x1F8B: - data = gzip.compress(data, compresslevel=9) - ROM().seek(minecart_bell_start) - ROM().writeBytes(data) - - -def recolorKlaptraps(): - """Recolor the klaptrap models for colorblind mode.""" - green_files = [0xF31, 0xF32, 0xF33, 0xF35, 0xF37, 0xF39] # 0xF2F collar? 0xF30 feet? - red_files = [0xF44, 0xF45, 0xF46, 0xF47, 0xF48, 0xF49] # , 0xF42 collar? 0xF43 feet? - purple_files = [0xF3C, 0xF3D, 0xF3E, 0xF3F, 0xF40, 0xF41] # 0xF3B feet?, 0xF3A collar? - - # Regular textures - for file in range(6): - writeKlaptrapSkinColorToROM(4, 25, green_files[file], TextureFormat.RGBA5551) - writeKlaptrapSkinColorToROM(1, 25, red_files[file], TextureFormat.RGBA5551) - writeKlaptrapSkinColorToROM(3, 25, purple_files[file], TextureFormat.RGBA5551) - - belly_pixels_to_ignore = [] - for x in range(32): - for y in range(43): - if y < 29 or (y > 31 and y < 39) or y == 40 or y == 42: - belly_pixels_to_ignore.append([x, y]) - elif (y == 39 and x < 16) or (y == 41 and x < 24): - belly_pixels_to_ignore.append([x, y]) - - # Special texture that requires only partial recoloring, in this case file 0xF38 which is the belly, and only the few green pixels - writeSpecialKlaptrapTextureToROM(4, 25, 0xF38, TextureFormat.RGBA5551, belly_pixels_to_ignore) - - -def recolorPotions(colorblind_mode): - """Overwrite potion colors.""" - secondary_color = [color_bases[1], None, color_bases[4], color_bases[1], None, None] - if colorblind_mode == ColorblindMode.trit: - secondary_color[0] = color_bases[4] - secondary_color[2] = None - for color in range(len(secondary_color)): - if secondary_color[color] is not None: - secondary_color[color] = getRGBFromHash(secondary_color[color]) - - # Actor: - file = [[0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2], [0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA]] - for type in range(2): - for potion_color in range(6): - potion_actor_start = js.pointer_addresses[5]["entries"][file[type][potion_color]]["pointing_to"] - potion_actor_finish = js.pointer_addresses[5]["entries"][file[type][potion_color] + 1]["pointing_to"] - potion_actor_size = potion_actor_finish - potion_actor_start - ROM().seek(potion_actor_start) - indicator = int.from_bytes(ROM().readBytes(2), "big") - ROM().seek(potion_actor_start) - data = ROM().readBytes(potion_actor_size) - if indicator == 0x1F8B: - data = zlib.decompress(data, (15 + 32)) - num_data = [] # data, but represented as nums rather than b strings - for d in data: - num_data.append(d) - # Figure out which colors to use and where to put them - color1_offsets = [0x34] - color2_offsets = [0x44, 0x54, 0xA4] - color3_offsets = [0x64, 0x74, 0x84, 0xE4] - color4_offsets = [0x94] - color5_offsets = [0xB4, 0xC4, 0xD4] - # color6_offsets = [0xF4, 0x104, 0x114, 0x124, 0x134, 0x144, 0x154, 0x164] - if potion_color < 5: - new_color = getRGBFromHash(color_bases[potion_color]) - else: - new_color = getRGBFromHash("#FFFFFF") - - # Recolor the actor item - for offset in color1_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - if secondary_color[potion_color] is not None and potion_color == 3: # tiny - num_data[offset + i] = int(num_data[offset + i] * (secondary_color[potion_color][i] / 255)) - elif secondary_color[potion_color] is not None: # donkey gets an even darker shade - num_data[offset + i] = int(num_data[offset + i] * (int(secondary_color[potion_color][i] / 8) / 255)) - elif secondary_color[potion_color] is not None: # other kongs with a secondary color get a darker shade - num_data[offset + i] = int(num_data[offset + i] * (int(secondary_color[potion_color][i] / 4) / 255)) - else: - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color2_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color3_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color4_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color5_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - - data = bytearray(num_data) # convert num_data back to binary string - if indicator == 0x1F8B: - data = gzip.compress(data, compresslevel=9) - if len(data) > potion_actor_size: - print(f"Attempted size bigger {hex(len(data))} than slot {hex(potion_actor_size)}") - continue - ROM().seek(potion_actor_start) - ROM().writeBytes(data) - - # Model2: - file = [91, 498, 89, 499, 501, 502] - for potion_color in range(6): - potion_model2_start = js.pointer_addresses[4]["entries"][file[potion_color]]["pointing_to"] - potion_model2_finish = js.pointer_addresses[4]["entries"][file[potion_color] + 1]["pointing_to"] - potion_model2_size = potion_model2_finish - potion_model2_start - ROM().seek(potion_model2_start) - indicator = int.from_bytes(ROM().readBytes(2), "big") - ROM().seek(potion_model2_start) - data = ROM().readBytes(potion_model2_size) - if indicator == 0x1F8B: - data = zlib.decompress(data, (15 + 32)) - num_data = [] # data, but represented as nums rather than b strings - for d in data: - num_data.append(d) - # Figure out which colors to use and where to put them - color1_offsets = [0x144] - color2_offsets = [0x154, 0x164, 0x1B4] - color3_offsets = [0x174, 0x184, 0x194, 0x1F4] - color4_offsets = [0x1A4] - color5_offsets = [0x1C4, 0x1D4, 0x1E4] - # color6_offsets = [0x204, 0x214, 0x224, 0x234, 0x244, 0x254, 0x264, 0x274] - if potion_color < 5: - new_color = getRGBFromHash(color_bases[potion_color]) - else: - new_color = getRGBFromHash("#FFFFFF") - - # Recolor the model2 item - for offset in color1_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - if secondary_color[potion_color] is not None and potion_color == 3: # tiny - num_data[offset + i] = int(num_data[offset + i] * (secondary_color[potion_color][i] / 255)) - elif secondary_color[potion_color] is not None: # donkey gets an even darker shade - num_data[offset + i] = int(num_data[offset + i] * (int(secondary_color[potion_color][i] / 8) / 255)) - elif secondary_color[potion_color] is not None: # other kongs with a secondary color get a darker shade - num_data[offset + i] = int(num_data[offset + i] * (int(secondary_color[potion_color][i] / 4) / 255)) - else: - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color2_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color3_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color4_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - for offset in color5_offsets: - total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) - for i in range(3): - num_data[offset + i] = int(total_light / 3) - for i in range(3): - num_data[offset + i] = int(num_data[offset + i] * (new_color[i] / 255)) - - data = bytearray(num_data) # convert num_data back to binary string - if indicator == 0x1F8B: - data = gzip.compress(data, compresslevel=9) - ROM().seek(potion_model2_start) - ROM().writeBytes(data) - - return - # DK Arcade sprites - for file in range(8, 14): - index = file - 8 - if index < 5: - color = color_bases[index] - else: - color = "#FFFFFF" - potion_image = getImageFile(6, file, False, 20, 20, TextureFormat.RGBA5551) - potion_image = maskPotionImage(potion_image, color, secondary_color[index]) - writeColorImageToROM(potion_image, 6, file, 20, 20, False, TextureFormat.RGBA5551) - - -def recolorMushrooms(): - """Recolor the various colored mushrooms in the game for colorblind mode.""" - reference_mushroom_image = getImageFile(7, 297, False, 32, 32, TextureFormat.RGBA5551) - reference_mushroom_image_side1 = getImageFile(25, 0xD64, True, 64, 32, TextureFormat.RGBA5551) - reference_mushroom_image_side2 = getImageFile(25, 0xD65, True, 64, 32, TextureFormat.RGBA5551) - files_table_7 = [296, 295, 297, 299, 298] - files_table_25_side_1 = [0xD60, getBonusSkinOffset(ExtraTextures.MushTop0), 0xD64, 0xD62, 0xD66] - files_table_25_side_2 = [0xD61, getBonusSkinOffset(ExtraTextures.MushTop1), 0xD65, 0xD63, 0xD67] - for file in range(5): - # Mushroom on the ceiling inside Fungi Forest Lobby - mushroom_image = getImageFile(7, files_table_7[file], False, 32, 32, TextureFormat.RGBA5551) - mushroom_image = maskMushroomImage(mushroom_image, reference_mushroom_image, color_bases[file]) - writeColorImageToROM(mushroom_image, 7, files_table_7[file], 32, 32, False, TextureFormat.RGBA5551) - # Mushrooms in Lanky's colored mushroom puzzle (and possibly also the bouncy mushrooms) - mushroom_image_side_1 = getImageFile(25, files_table_25_side_1[file], True, 64, 32, TextureFormat.RGBA5551) - mushroom_image_side_1 = maskMushroomImage(mushroom_image_side_1, reference_mushroom_image_side1, color_bases[file]) - writeColorImageToROM(mushroom_image_side_1, 25, files_table_25_side_1[file], 64, 32, False, TextureFormat.RGBA5551) - mushroom_image_side_2 = getImageFile(25, files_table_25_side_2[file], True, 64, 32, TextureFormat.RGBA5551) - mushroom_image_side_2 = maskMushroomImage(mushroom_image_side_2, reference_mushroom_image_side2, color_bases[file], True) - writeColorImageToROM(mushroom_image_side_2, 25, files_table_25_side_2[file], 64, 32, False, TextureFormat.RGBA5551) - - +SINGLE_START = [168, 152, 232, 208, 240] BALLOON_START = [5835, 5827, 5843, 5851, 5819] +LASER_START = [784, 748, 363, 760, 772] +SHOCKWAVE_START = [4897, 4903, 4712, 4950, 4925] +BLUEPRINT_START = [5624, 5608, 5519, 5632, 5616] +COIN_START = [224, 256, 248, 216, 264] +BUNCH_START = [274, 854, 818, 842, 830] def overwrite_object_colors(settings, ROM_COPY: ROM): """Overwrite object colors.""" - global color_bases mode = settings.colorblind_mode sav = settings.rom_data galleon_switch_value = None @@ -2126,105 +369,89 @@ def overwrite_object_colors(settings, ROM_COPY: ROM): ROM_COPY.seek(sav + 0x104 + 3) galleon_switch_value = int.from_bytes(ROM_COPY.readBytes(1), "big") if mode != ColorblindMode.off: - if mode == ColorblindMode.prot: - color_bases = ["#000000", "#0072FF", "#766D5A", "#FFFFFF", "#FDE400"] - elif mode == ColorblindMode.deut: - color_bases = ["#000000", "#318DFF", "#7F6D59", "#FFFFFF", "#E3A900"] - elif mode == ColorblindMode.trit: - color_bases = ["#000000", "#C72020", "#13C4D8", "#FFFFFF", "#FFA4A4"] if mode in (ColorblindMode.prot, ColorblindMode.deut): - recolorBells() + recolorBells(ROM_COPY) # Preload DK single cb image to paste onto balloons - file = 175 - dk_single = getImageFile(7, file, False, 44, 44, TextureFormat.RGBA5551) + dk_single = getImageFile(7, 175, False, 44, 44, TextureFormat.RGBA5551) dk_single = dk_single.resize((21, 21)) blueprint_lanky = [] # Preload blueprint images. Lanky's blueprint image is so much easier to mask, because it is blue, and the frame is brown for file in range(8): - blueprint_lanky.append(getImageFile(25, 5519 + (file), True, 48, 42, TextureFormat.RGBA5551)) - writeWhiteKasplatHairColorToROM("#FFFFFF", "#000000", 25, 4125, TextureFormat.RGBA5551) - recolorWrinklyDoors() - recolorSlamSwitches(galleon_switch_value, ROM_COPY) - recolorRotatingRoomTiles() - recolorBlueprintModelTwo() - recolorKlaptraps() - recolorPotions(mode) - recolorMushrooms() + blueprint_lanky.append(getImageFile(25, 5519 + file, True, 48, 42, TextureFormat.RGBA5551)) + writeWhiteKasplatHairColorToROM("#FFFFFF", "#000000", 25, 4125, TextureFormat.RGBA5551, ROM_COPY) + recolorWrinklyDoors(mode, ROM_COPY) + recolorSlamSwitches(galleon_switch_value, ROM_COPY, mode) + recolorRotatingRoomTiles(mode) + recolorBlueprintModelTwo(mode, ROM_COPY) + recolorKlaptraps(mode, ROM_COPY) + recolorPotions(mode, ROM_COPY) + recolorMushrooms(mode) for kong_index in range(5): # file = 4120 # # Kasplat Hair # hair_im = getFile(25, file, True, 32, 44, TextureFormat.RGBA5551) # hair_im = maskImage(hair_im, kong_index, 0) # writeColorImageToROM(hair_im, 25, [4124, 4122, 4123, 4120, 4121][kong_index], 32, 44, False) - writeKasplatHairColorToROM(color_bases[kong_index], 25, [4124, 4122, 4123, 4120, 4121][kong_index], TextureFormat.RGBA5551) - for file in range(5519, 5527): + writeKasplatHairColorToROM(getKongItemColor(mode, kong_index), 25, [4124, 4122, 4123, 4120, 4121][kong_index], TextureFormat.RGBA5551, ROM_COPY) + for offset in range(8): # Blueprint sprite - blueprint_start = [5624, 5608, 5519, 5632, 5616] - blueprint_im = blueprint_lanky[(file - 5519)] - blueprint_im = maskBlueprintImage(blueprint_im, kong_index) - writeColorImageToROM(blueprint_im, 25, blueprint_start[kong_index] + (file - 5519), 48, 42, False, TextureFormat.RGBA5551) - for file in range(4925, 4931): + blueprint_im = blueprint_lanky[offset] + blueprint_im = maskBlueprintImage(blueprint_im, kong_index, mode) + writeColorImageToROM(blueprint_im, 25, BLUEPRINT_START[kong_index] + offset, 48, 42, False, TextureFormat.RGBA5551) + for offset in range(6): # Shockwave - shockwave_start = [4897, 4903, 4712, 4950, 4925] - shockwave_im = getImageFile(25, shockwave_start[kong_index] + (file - 4925), True, 32, 32, TextureFormat.RGBA32) - shockwave_im = maskImage(shockwave_im, kong_index, 0) - writeColorImageToROM(shockwave_im, 25, shockwave_start[kong_index] + (file - 4925), 32, 32, False, TextureFormat.RGBA32) - for file in range(784, 796): + shockwave_im = getImageFile(25, SHOCKWAVE_START[kong_index] + offset, True, 32, 32, TextureFormat.RGBA32) + shockwave_im = maskImage(shockwave_im, kong_index, 0, False, mode) + writeColorImageToROM(shockwave_im, 25, SHOCKWAVE_START[kong_index] + offset, 32, 32, False, TextureFormat.RGBA32) + for offset in range(12): # Helm Laser (will probably also affect the Pufftoss laser and the Game Over laser) - laser_start = [784, 748, 363, 760, 772] - laser_im = getImageFile(7, laser_start[kong_index] + (file - 784), False, 32, 32, TextureFormat.RGBA32) - laser_im = maskLaserImage(laser_im, kong_index) - writeColorImageToROM(laser_im, 7, laser_start[kong_index] + (file - 784), 32, 32, False, TextureFormat.RGBA32) - if kong_index == 0 or kong_index == 3 or (kong_index == 2 and mode != ColorblindMode.trit): # Lanky (prot, deut only) or DK or Tiny - for file in range(152, 160): + laser_im = getImageFile(7, LASER_START[kong_index] + offset, False, 32, 32, TextureFormat.RGBA32) + laser_im = maskLaserImage(laser_im, kong_index, mode) + writeColorImageToROM(laser_im, 7, LASER_START[kong_index] + offset, 32, 32, False, TextureFormat.RGBA32) + if kong_index in (Kongs.donkey, Kongs.tiny) or (kong_index == Kongs.lanky and mode != ColorblindMode.trit): # Lanky (prot, deut only) or DK or Tiny + for offset in range(8): # Single - single_start = [168, 152, 232, 208, 240] - single_im = getImageFile(7, single_start[kong_index] + (file - 152), False, 44, 44, TextureFormat.RGBA5551) + single_im = getImageFile(7, SINGLE_START[kong_index] + offset, False, 44, 44, TextureFormat.RGBA5551) single_im = maskImageWithOutline(single_im, kong_index, 0, mode, "single") - writeColorImageToROM(single_im, 7, single_start[kong_index] + (file - 152), 44, 44, False, TextureFormat.RGBA5551) - for file in range(216, 224): + writeColorImageToROM(single_im, 7, SINGLE_START[kong_index] + offset, 44, 44, False, TextureFormat.RGBA5551) + for offset in range(8): # Coin - coin_start = [224, 256, 248, 216, 264] - coin_im = getImageFile(7, coin_start[kong_index] + (file - 216), False, 48, 42, TextureFormat.RGBA5551) + coin_im = getImageFile(7, COIN_START[kong_index] + offset, False, 48, 42, TextureFormat.RGBA5551) coin_im = maskImageWithOutline(coin_im, kong_index, 0, mode) - writeColorImageToROM(coin_im, 7, coin_start[kong_index] + (file - 216), 48, 42, False, TextureFormat.RGBA5551) - for file in range(274, 286): + writeColorImageToROM(coin_im, 7, COIN_START[kong_index] + offset, 48, 42, False, TextureFormat.RGBA5551) + for offset in range(12): # Bunch - bunch_start = [274, 854, 818, 842, 830] - bunch_im = getImageFile(7, bunch_start[kong_index] + (file - 274), False, 44, 44, TextureFormat.RGBA5551) + bunch_im = getImageFile(7, BUNCH_START[kong_index] + offset, False, 44, 44, TextureFormat.RGBA5551) bunch_im = maskImageWithOutline(bunch_im, kong_index, 0, mode, "bunch") - writeColorImageToROM(bunch_im, 7, bunch_start[kong_index] + (file - 274), 44, 44, False, TextureFormat.RGBA5551) - for file in range(5819, 5827): + writeColorImageToROM(bunch_im, 7, BUNCH_START[kong_index] + offset, 44, 44, False, TextureFormat.RGBA5551) + for offset in range(8): # Balloon - balloon_im = getImageFile(25, BALLOON_START[kong_index] + (file - 5819), True, 32, 64, TextureFormat.RGBA5551) + balloon_im = getImageFile(25, BALLOON_START[kong_index] + offset, True, 32, 64, TextureFormat.RGBA5551) balloon_im = maskImageWithOutline(balloon_im, kong_index, 33, mode) - balloon_im.paste(dk_single, balloon_single_frames[file - 5819], dk_single) - writeColorImageToROM(balloon_im, 25, BALLOON_START[kong_index] + (file - 5819), 32, 64, False, TextureFormat.RGBA5551) + balloon_im.paste(dk_single, balloon_single_frames[offset], dk_single) + writeColorImageToROM(balloon_im, 25, BALLOON_START[kong_index] + offset, 32, 64, False, TextureFormat.RGBA5551) else: - for file in range(152, 160): + for offset in range(8): # Single - single_start = [168, 152, 232, 208, 240] - single_im = getImageFile(7, single_start[kong_index] + (file - 152), False, 44, 44, TextureFormat.RGBA5551) - single_im = maskImage(single_im, kong_index, 0) - writeColorImageToROM(single_im, 7, single_start[kong_index] + (file - 152), 44, 44, False, TextureFormat.RGBA5551) - for file in range(216, 224): + single_im = getImageFile(7, SINGLE_START[kong_index] + offset, False, 44, 44, TextureFormat.RGBA5551) + single_im = maskImage(single_im, kong_index, 0, False, mode) + writeColorImageToROM(single_im, 7, SINGLE_START[kong_index] + offset, 44, 44, False, TextureFormat.RGBA5551) + for offset in range(8): # Coin - coin_start = [224, 256, 248, 216, 264] - coin_im = getImageFile(7, coin_start[kong_index] + (file - 216), False, 48, 42, TextureFormat.RGBA5551) - coin_im = maskImage(coin_im, kong_index, 0) - writeColorImageToROM(coin_im, 7, coin_start[kong_index] + (file - 216), 48, 42, False, TextureFormat.RGBA5551) - for file in range(274, 286): + coin_im = getImageFile(7, COIN_START[kong_index] + offset, False, 48, 42, TextureFormat.RGBA5551) + coin_im = maskImage(coin_im, kong_index, 0, False, mode) + writeColorImageToROM(coin_im, 7, COIN_START[kong_index] + offset, 48, 42, False, TextureFormat.RGBA5551) + for offset in range(12): # Bunch - bunch_start = [274, 854, 818, 842, 830] - bunch_im = getImageFile(7, bunch_start[kong_index] + (file - 274), False, 44, 44, TextureFormat.RGBA5551) - bunch_im = maskImage(bunch_im, kong_index, 0, True) - writeColorImageToROM(bunch_im, 7, bunch_start[kong_index] + (file - 274), 44, 44, False, TextureFormat.RGBA5551) - for file in range(5819, 5827): + bunch_im = getImageFile(7, BUNCH_START[kong_index] + offset, False, 44, 44, TextureFormat.RGBA5551) + bunch_im = maskImage(bunch_im, kong_index, 0, True, mode) + writeColorImageToROM(bunch_im, 7, BUNCH_START[kong_index] + offset, 44, 44, False, TextureFormat.RGBA5551) + for offset in range(8): # Balloon - balloon_im = getImageFile(25, BALLOON_START[kong_index] + (file - 5819), True, 32, 64, TextureFormat.RGBA5551) - balloon_im = maskImage(balloon_im, kong_index, 33) - balloon_im.paste(dk_single, balloon_single_frames[file - 5819], dk_single) - writeColorImageToROM(balloon_im, 25, BALLOON_START[kong_index] + (file - 5819), 32, 64, False, TextureFormat.RGBA5551) + balloon_im = getImageFile(25, BALLOON_START[kong_index] + offset, True, 32, 64, TextureFormat.RGBA5551) + balloon_im = maskImage(balloon_im, kong_index, 33, False, mode) + balloon_im.paste(dk_single, balloon_single_frames[offset], dk_single) + writeColorImageToROM(balloon_im, 25, BALLOON_START[kong_index] + offset, 32, 64, False, TextureFormat.RGBA5551) else: # Recolor slam switch if colorblind mode is off if galleon_switch_value is not None: @@ -2244,14 +471,6 @@ def overwrite_object_colors(settings, ROM_COPY: ROM): ORANGE_SCALING = 0.7 -kong_index_mapping = { - # Regular model, instrument model - Kongs.donkey: (3, None), - Kongs.diddy: (0, 1), - Kongs.lanky: (5, 6), - Kongs.tiny: (8, 9), - Kongs.chunky: (11, 12), -} model_index_mapping = { # Regular model, instrument model KongModels.krusha: (0xDA, 0xDA), @@ -2263,6 +482,15 @@ def overwrite_object_colors(settings, ROM_COPY: ROM): KongModels.funky: (0x117, 0x117), } +LIME_COLORS = { + Kongs.donkey: (255, 224, 8), + Kongs.diddy: (255, 48, 32), + Kongs.lanky: (40, 168, 255), + Kongs.tiny: (216, 100, 248), + Kongs.chunky: (0, 255, 0), + Kongs.any: (100, 255, 60), +} + def applyKongModelSwaps(settings: Settings) -> None: """Apply Krusha Kong setting.""" @@ -2302,42 +530,17 @@ def applyKongModelSwaps(settings: Settings) -> None: ROM_COPY.writeMultipleBytes(unc_size, 4) changeModelTextures(settings, index) if value in (KongModels.krusha, KongModels.krool_cutscene, KongModels.krool_fight): - fixModelSmallKongCollision(index) + fixModelSmallKongCollision(index, ROM_COPY) if value == KongModels.krusha: placeKrushaHead(settings, index) if index == Kongs.donkey: - fixBaboonBlasts() + fixBaboonBlasts(ROM_COPY) # Orange Switches switch_faces = [0xB25, 0xB1E, 0xC81, 0xC80, 0xB24] base_im = getImageFile(25, 0xC20, True, 32, 32, TextureFormat.RGBA5551) orange_im = getImageFile(7, 0x136, False, 32, 32, TextureFormat.RGBA5551) if settings.colorblind_mode == ColorblindMode.off: - match index: - case Kongs.donkey: - color_r = 255 - color_g = 224 - color_b = 8 - case Kongs.diddy: - color_r = 255 - color_g = 48 - color_b = 32 - case Kongs.lanky: - color_r = 40 - color_g = 168 - color_b = 255 - case Kongs.tiny: - color_r = 216 - color_g = 100 - color_b = 248 - case Kongs.chunky: - color_r = 0 - color_g = 255 - color_b = 0 - case _: - color_r = 100 - color_g = 255 - color_b = 60 - orange_im = maskImageWithColor(orange_im, (color_r, color_g, color_b)) + orange_im = maskImageWithColor(orange_im, LIME_COLORS[index]) else: orange_im = maskImageWithColor(orange_im, (0, 255, 0)) # Brighter green makes this more distinguishable for colorblindness dim_length = int(32 * ORANGE_SCALING) @@ -2347,183 +550,6 @@ def applyKongModelSwaps(settings: Settings) -> None: writeColorImageToROM(base_im, 25, switch_faces[index], 32, 32, False, TextureFormat.RGBA5551) -DK_SCALE = 0.75 -GENERIC_SCALE = 0.49 -krusha_scaling = [ - # [x, y, z, xz, y] - # DK - [ - lambda x: x * DK_SCALE, - lambda x: x * DK_SCALE, - lambda x: x * GENERIC_SCALE, - lambda x: x * DK_SCALE, - lambda x: x * DK_SCALE, - ], - # Diddy - [ - lambda x: (x * 1.043) - 41.146, - lambda x: (x * 9.893) - 8.0, - lambda x: x * GENERIC_SCALE, - lambda x: (x * 1.103) - 14.759, - lambda x: (x * 0.823) + 35.220, - ], - # Lanky - [ - lambda x: (x * 0.841) - 17.231, - lambda x: (x * 6.925) - 2.0, - lambda x: x * GENERIC_SCALE, - lambda x: (x * 0.680) - 18.412, - lambda x: (x * 0.789) + 42.138, - ], - # Tiny - [ - lambda x: (x * 0.632) + 7.590, - lambda x: (x * 6.925) + 0.0, - lambda x: x * GENERIC_SCALE, - lambda x: (x * 1.567) - 21.676, - lambda x: (x * 0.792) + 41.509, - ], - # Chunky - [lambda x: x, lambda x: x, lambda x: x, lambda x: x, lambda x: x], -] - - -def readListAsInt(arr: list, start: int, size: int) -> int: - """Read list and convert to int.""" - val = 0 - for i in range(size): - val = (val * 256) + arr[start + i] - return val - - -def fixModelSmallKongCollision(kong_index: int): - """Modify Krusha Model to be smaller to enable him to fit through smaller gaps.""" - for x in range(2): - file = kong_index_mapping[kong_index][x] - if file is None: - continue - krusha_model_start = js.pointer_addresses[5]["entries"][file]["pointing_to"] - krusha_model_finish = js.pointer_addresses[5]["entries"][file + 1]["pointing_to"] - krusha_model_size = krusha_model_finish - krusha_model_start - ROM_COPY = LocalROM() - ROM_COPY.seek(krusha_model_start) - indicator = int.from_bytes(ROM_COPY.readBytes(2), "big") - ROM_COPY.seek(krusha_model_start) - data = ROM_COPY.readBytes(krusha_model_size) - if indicator == 0x1F8B: - data = zlib.decompress(data, (15 + 32)) - num_data = [] # data, but represented as nums rather than b strings - for d in data: - num_data.append(d) - head = readListAsInt(num_data, 0, 4) - ptr = readListAsInt(num_data, 0xC, 4) - base = (ptr - head) + 0x28 + 8 - count_0 = readListAsInt(num_data, base, 4) - changes = krusha_scaling[kong_index][:3] - changes_0 = [ - krusha_scaling[kong_index][3], - krusha_scaling[kong_index][4], - krusha_scaling[kong_index][3], - ] - for i in range(count_0): - i_start = base + 4 + (i * 0x14) - for coord_index, change in enumerate(changes): - val_i = readListAsInt(num_data, i_start + (4 * coord_index) + 4, 4) - val_f = change(intf_to_float(val_i)) - val_i = int(float_to_hex(val_f), 16) - for di, d in enumerate(int_to_list(val_i, 4)): - num_data[i_start + (4 * coord_index) + 4 + di] = d - section_2_start = base + 4 + (count_0 * 0x14) - count_1 = readListAsInt(num_data, section_2_start, 4) - for i in range(count_1): - i_start = section_2_start + 4 + (i * 0x10) - for coord_index, change in enumerate(changes_0): - val_i = readListAsInt(num_data, i_start + (4 * coord_index), 4) - val_f = change(intf_to_float(val_i)) - val_i = int(float_to_hex(val_f), 16) - for di, d in enumerate(int_to_list(val_i, 4)): - num_data[i_start + (4 * coord_index) + di] = d - data = bytearray(num_data) # convert num_data back to binary string - if indicator == 0x1F8B: - data = gzip.compress(data, compresslevel=9) - LocalROM().seek(krusha_model_start) - LocalROM().writeBytes(data) - - -def changeModelTextures(settings: Settings, kong_index: int): - """Change the textures associated with a model.""" - settings_values = [ - settings.kong_model_dk, - settings.kong_model_diddy, - settings.kong_model_lanky, - settings.kong_model_tiny, - settings.kong_model_chunky, - ] - if kong_index < 0 or kong_index >= len(settings_values): - return - model = settings_values[kong_index] - if model not in model_texture_sections: - return - for x in range(2): - file = kong_index_mapping[kong_index][x] - if file is None: - continue - krusha_model_start = js.pointer_addresses[5]["entries"][file]["pointing_to"] - krusha_model_finish = js.pointer_addresses[5]["entries"][file + 1]["pointing_to"] - krusha_model_size = krusha_model_finish - krusha_model_start - ROM_COPY = LocalROM() - ROM_COPY.seek(krusha_model_start) - indicator = int.from_bytes(ROM_COPY.readBytes(2), "big") - ROM_COPY.seek(krusha_model_start) - data = ROM_COPY.readBytes(krusha_model_size) - if indicator == 0x1F8B: - data = zlib.decompress(data, (15 + 32)) - num_data = [] # data, but represented as nums rather than b strings - for d in data: - num_data.append(d) - # Retexture for colors - for tex_idx in model_texture_sections[model]["skin"]: - for di, d in enumerate(int_to_list(krusha_texture_replacement[kong_index][0], 2)): # Main - num_data[tex_idx + di] = d - for tex_idx in model_texture_sections[model]["kong"]: - for di, d in enumerate(int_to_list(krusha_texture_replacement[kong_index][1], 2)): # Belt - num_data[tex_idx + di] = d - data = bytearray(num_data) # convert num_data back to binary string - if indicator == 0x1F8B: - data = gzip.compress(data, compresslevel=9) - LocalROM().seek(krusha_model_start) - LocalROM().writeBytes(data) - - -def fixBaboonBlasts(): - """Fix various baboon blasts to work for Krusha.""" - # Fungi Baboon Blast - ROM_COPY = LocalROM() - for id in (2, 5): - item_start = getObjectAddress(0xBC, id, "actor") - if item_start is not None: - ROM_COPY.seek(item_start + 0x14) - ROM_COPY.writeMultipleBytes(0xFFFFFFEC, 4) - ROM_COPY.seek(item_start + 0x1B) - ROM_COPY.writeMultipleBytes(0, 1) - # Caves Baboon Blast - item_start = getObjectAddress(0xBA, 4, "actor") - if item_start is not None: - ROM_COPY.seek(item_start + 0x4) - ROM_COPY.writeMultipleBytes(int(float_to_hex(510), 16), 4) - item_start = getObjectAddress(0xBA, 12, "actor") - if item_start is not None: - ROM_COPY.seek(item_start + 0x4) - ROM_COPY.writeMultipleBytes(int(float_to_hex(333), 16), 4) - # Castle Baboon Blast - item_start = getObjectAddress(0xBB, 4, "actor") - if item_start is not None: - ROM_COPY.seek(item_start + 0x0) - ROM_COPY.writeMultipleBytes(int(float_to_hex(2472), 16), 4) - ROM_COPY.seek(item_start + 0x8) - ROM_COPY.writeMultipleBytes(int(float_to_hex(1980), 16), 4) - - def darkenDPad(): """Change the DPad cross texture for the DPad HUD.""" img = getImageFile(14, 187, True, 32, 32, TextureFormat.RGBA5551) @@ -2552,1015 +578,6 @@ def darkenDPad(): ROM().writeBytes(px_data) -def placeKrushaHead(settings: Settings, slot): - """Replace a kong's face with the Krusha face.""" - if settings.colorblind_mode != ColorblindMode.off: - return - - kong_face_textures = [[0x27C, 0x27B], [0x279, 0x27A], [0x277, 0x278], [0x276, 0x275], [0x273, 0x274]] - unc_face_textures = [[579, 586], [580, 587], [581, 588], [582, 589], [577, 578]] - krushaFace64 = getImageFile(TableNames.TexturesGeometry, getBonusSkinOffset(ExtraTextures.KrushaFace1 + slot), True, 64, 64, TextureFormat.RGBA5551) - krushaFace64Left = krushaFace64.crop([0, 0, 32, 64]) - krushaFace64Right = krushaFace64.crop([32, 0, 64, 64]) - # Used in File Select, Pause Menu, Tag Barrels, Switches, Transformation Barrels - writeColorImageToROM(krushaFace64Left, 25, kong_face_textures[slot][0], 32, 64, False, TextureFormat.RGBA5551) - writeColorImageToROM(krushaFace64Right, 25, kong_face_textures[slot][1], 32, 64, False, TextureFormat.RGBA5551) - # Used in Troff and Scoff - writeColorImageToROM(krushaFace64Left, 7, unc_face_textures[slot][0], 32, 64, False, TextureFormat.RGBA5551) - writeColorImageToROM(krushaFace64Right, 7, unc_face_textures[slot][1], 32, 64, False, TextureFormat.RGBA5551) - - krushaFace32 = krushaFace64.resize((32, 32)) - krushaFace32 = krushaFace32.transpose(Image.Transpose.FLIP_TOP_BOTTOM) - krushaFace32RBGA32 = getImageFile(TableNames.TexturesGeometry, getBonusSkinOffset(ExtraTextures.KrushaFace321 + slot), True, 32, 32, TextureFormat.RGBA32) - # Used in the DPad Selection Menu - writeColorImageToROM(krushaFace32, 14, 190 + slot, 32, 32, False, TextureFormat.RGBA5551) - # Used in Shops Previews - writeColorImageToROM(krushaFace32RBGA32, 14, 197 + slot, 32, 32, False, TextureFormat.RGBA32) - - """kong_face_textures = [[0x27C, 0x27B], [0x279, 0x27A], [0x277, 0x278], [0x276, 0x275], [0x273, 0x274]] - unc_face_textures = [[579, 586], [580, 587], [581, 588], [582, 589], [577, 578]] - ROM_COPY = LocalROM() - ROM_COPY.seek(0x1FF6000) - left = [] - right = [] - img32 = [] - img32_rgba32 = [] - y32 = [] - y32_rgba32 = [] - for y in range(64): - x32 = [] - x32_rgba32 = [] - for x in range(64): - data_hi = int.from_bytes(ROM_COPY.readBytes(1), "big") - data_lo = int.from_bytes(ROM_COPY.readBytes(1), "big") - val = (data_hi << 8) | data_lo - val_r = ((val >> 11) & 0x1F) << 3 - val_g = ((val >> 6) & 0x1F) << 3 - val_b = ((val >> 1) & 0x1F) << 3 - val_a = 0 - if val & 1: - val_a = 255 - data_rgba32 = [val_r, val_g, val_b, val_a] - if x < 32: - right.extend([data_hi, data_lo]) - else: - left.extend([data_hi, data_lo]) - if ((x % 2) + (y % 2)) == 0: - x32.extend([data_hi, data_lo]) - x32_rgba32.extend(data_rgba32) - if len(x32) > 0 and len(x32_rgba32): - y32.append(x32) - y32_rgba32.append(x32_rgba32) - y32.reverse() - for y in y32: - img32.extend(y) - y32_rgba32.reverse() - for y in y32_rgba32: - img32_rgba32.extend(y) - for x in range(2): - img_data = [right, left][x] - texture_index = kong_face_textures[slot][x] - unc_index = unc_face_textures[slot][x] - texture_addr = js.pointer_addresses[25]["entries"][texture_index]["pointing_to"] - unc_addr = js.pointer_addresses[7]["entries"][unc_index]["pointing_to"] - data = gzip.compress(bytearray(img_data), compresslevel=9) - ROM_COPY.seek(texture_addr) - ROM_COPY.writeBytes(data) - ROM_COPY.seek(unc_addr) - ROM_COPY.writeBytes(bytearray(img_data)) - rgba32_addr32 = js.pointer_addresses[14]["entries"][197 + slot]["pointing_to"] - rgba16_addr32 = js.pointer_addresses[14]["entries"][190 + slot]["pointing_to"] - data32 = gzip.compress(bytearray(img32), compresslevel=9) - data32_rgba32 = gzip.compress(bytearray(img32_rgba32), compresslevel=9) - ROM_COPY.seek(rgba32_addr32) - ROM_COPY.writeBytes(bytearray(data32_rgba32)) - ROM_COPY.seek(rgba16_addr32) - ROM_COPY.writeBytes(bytearray(data32))""" - - -def getValueFromByteArray(ba: bytearray, offset: int, size: int) -> int: - """Get value from byte array given an offset and size.""" - value = 0 - for x in range(size): - local_value = ba[offset + x] - value <<= 8 - value += local_value - return value - - -def hueShiftImageContainer(table: int, image: int, width: int, height: int, format: TextureFormat, shift: int): - """Load an image, shift the hue and rewrite it back to ROM.""" - loaded_im = getImageFile(table, image, table != 7, width, height, format) - loaded_im = hueShift(loaded_im, shift) - loaded_px = loaded_im.load() - bytes_array = [] - for y in range(height): - for x in range(width): - pix_data = list(loaded_px[x, y]) - if format == TextureFormat.RGBA32: - bytes_array.extend(pix_data) - elif format == TextureFormat.RGBA5551: - red = int((pix_data[0] >> 3) << 11) - green = int((pix_data[1] >> 3) << 6) - blue = int((pix_data[2] >> 3) << 1) - alpha = int(pix_data[3] != 0) - value = red | green | blue | alpha - bytes_array.extend([(value >> 8) & 0xFF, value & 0xFF]) - px_data = bytearray(bytes_array) - if table != 7: - px_data = gzip.compress(px_data, compresslevel=9) - ROM().seek(js.pointer_addresses[table]["entries"][image]["pointing_to"]) - ROM().writeBytes(px_data) - - -def getEnemySwapColor(channel_min: int = 0, channel_max: int = 255, min_channel_variance: int = 0) -> int: - """Get an RGB color compatible with enemy swaps.""" - channels = [] - for _ in range(2): - channels.append(random.randint(channel_min, channel_max)) - min_channel = min(channels[0], channels[1]) - max_channel = max(channels[0], channels[1]) - bounds = [] - if (min_channel - channel_min) >= min_channel_variance: - bounds.append([channel_min, min_channel]) - if (channel_max - max_channel) >= min_channel_variance: - bounds.append([max_channel, channel_max]) - if (len(bounds) == 0) or ((max_channel - min_channel) >= min_channel_variance): - # Default to random number pick - channels.append(random.randint(channel_min, channel_max)) - else: - selected_bound = random.choice(bounds) - channels.append(random.randint(selected_bound[0], selected_bound[1])) - random.shuffle(channels) - value = 0 - for x in range(3): - value <<= 8 - value += channels[x] - return value - - -class EnemyColorSwap: - """Class to store information regarding an enemy color swap.""" - - def __init__(self, search_for: list, forced_color: int = None): - """Initialize with given parameters.""" - self.search_for = search_for.copy() - total_channels = [0] * 3 - for color in self.search_for: - for channel in range(3): - shift = 8 * (2 - channel) - value = (color >> shift) & 0xFF - total_channels[channel] += value - average_channels = [int(x / len(self.search_for)) for x in total_channels] - self.average_color = 0 - for x in average_channels: - self.average_color <<= 8 - self.average_color += x - self.replace_with = forced_color - if forced_color is None: - self.replace_with = getEnemySwapColor(80, min_channel_variance=80) - - def getOutputColor(self, color: int): - """Get output color based on randomization.""" - if color not in self.search_for: - return color - if color == self.search_for[0]: - return self.replace_with - new_color = 0 - total_boost = 0 - for x in range(3): - shift = 8 * (2 - x) - provided_channel = (color >> shift) & 0xFF - primary_channel = (self.search_for[0] >> shift) & 0xFF - boost = 1 # Failsafe for div by 0 - if primary_channel != 0: - boost = provided_channel / primary_channel - total_boost += boost # Used to get an average - for x in range(3): - shift = 8 * (2 - x) - replacement_channel = (self.replace_with >> shift) & 0xFF - replacement_channel = int(replacement_channel * (total_boost / 3)) - if replacement_channel > 255: - replacement_channel = 255 - elif replacement_channel < 0: - replacement_channel = 0 - new_color <<= 8 - new_color += replacement_channel - return new_color - - -def convertColorIntToTuple(color: int) -> tuple: - """Convert color stored as 3-byte int to tuple.""" - return ((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF) - - -def getLuma(color: tuple) -> float: - """Get the luma value of a color.""" - return (0.299 * color[0]) + (0.587 * color[1]) + (0.114 * color[2]) - - -def adjustFungiMushVertexColor(shift: int): - """Adjust the special vertex coloring on Fungi Giant Mushroom.""" - fungi_geo = bytearray(getRawFile(TableNames.MapGeometry, Maps.FungiForest, True)) - DEFAULT_MUSHROOM_COLOR = (255, 90, 82) - NEW_MUSHROOM_COLOR = hueShiftColor(DEFAULT_MUSHROOM_COLOR, shift) - for x in range(0x27DA, 0x2839): - start = 0x25140 + (x * 0x10) + 0xC - channels = [] - is_zero = True - for y in range(3): - val = fungi_geo[start + y] - if val != 0: - is_zero = False - channels.append(val) - if is_zero: - continue - visual_color = [int((x / 255) * DEFAULT_MUSHROOM_COLOR[xi]) for xi, x in enumerate(channels)] - luma = int(getLuma(visual_color)) - # Diversify shading - luma -= 128 - luma = int(luma * 1.2) - luma += 128 - # Brighten - luma += 60 - # Clamp - if luma < 0: - luma = 0 - elif luma > 255: - luma = 255 - # Apply shading - for y in range(3): - fungi_geo[start + y] = luma - fungi_geo[start + 3] = 0xFF - file_data = gzip.compress(fungi_geo, compresslevel=9) - ROM().seek(js.pointer_addresses[TableNames.MapGeometry]["entries"][Maps.FungiForest]["pointing_to"]) - ROM().writeBytes(file_data) - - -def writeMiscCosmeticChanges(settings): - """Write miscellaneous changes to the cosmetic colors.""" - if settings.override_cosmetics: - enemy_setting = RandomModels[js.document.getElementById("random_enemy_colors").value] - else: - enemy_setting = settings.random_enemy_colors - if settings.misc_cosmetics: - # Melon HUD - data = { - 7: [[0x13C, 0x147]], - 14: [[0x5A, 0x5D]], - 25: [ - [getBonusSkinOffset(ExtraTextures.MelonSurface), getBonusSkinOffset(ExtraTextures.MelonSurface)], - [0x144B, 0x1452], - ], - } - shift = getRandomHueShift() - for table in data: - table_data = data[table] - for set in table_data: - for img in range(set[0], set[1] + 1): - if table == 25: - dims = (32, 32) - else: - dims = (48, 42) - melon_im = getImageFile(table, img, table != 7, dims[0], dims[1], TextureFormat.RGBA5551) - melon_im = hueShift(melon_im, shift) - melon_px = melon_im.load() - bytes_array = [] - for y in range(dims[1]): - for x in range(dims[0]): - pix_data = list(melon_px[x, y]) - red = int((pix_data[0] >> 3) << 11) - green = int((pix_data[1] >> 3) << 6) - blue = int((pix_data[2] >> 3) << 1) - alpha = int(pix_data[3] != 0) - value = red | green | blue | alpha - bytes_array.extend([(value >> 8) & 0xFF, value & 0xFF]) - px_data = bytearray(bytes_array) - if table != 7: - px_data = gzip.compress(px_data, compresslevel=9) - ROM().seek(js.pointer_addresses[table]["entries"][img]["pointing_to"]) - ROM().writeBytes(px_data) - - # Shockwave Particles - shockwave_shift = getRandomHueShift() - for img_index in range(0x174F, 0x1757): - hueShiftImageContainer(25, img_index, 16, 16, TextureFormat.RGBA32, shockwave_shift) - if settings.colorblind_mode == ColorblindMode.off: - # Fire-based sprites - fire_shift = getRandomHueShift() - fires = ( - [0x1539, 0x1553, 32], # Fireball. RGBA32 32x32 - [0x14B6, 0x14F5, 32], # Fireball. RGBA32 32x32 - [0x1554, 0x155B, 16], # Small Fireball. RGBA32 16x16 - [0x1654, 0x1683, 32], # Fire Wall. RGBA32 32x32 - [0x1495, 0x14A0, 32], # Small Explosion, RGBA32 32x32 - [0x13B9, 0x13C3, 32], # Small Explosion, RGBA32 32x32 - ) - for sprite_data in fires: - for img_index in range(sprite_data[0], sprite_data[1] + 1): - dim = sprite_data[2] - hueShiftImageContainer(25, img_index, dim, dim, TextureFormat.RGBA32, fire_shift) - for img_index in range(0x29, 0x32 + 1): - hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA32, fire_shift) - for img_index in range(0x250, 0x26F + 1): - hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA32, fire_shift) - for img_index in range(0xA0, 0xA7 + 1): - hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA5551, fire_shift) - # Blue Fire - for img_index in range(129, 138 + 1): - hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA32, fire_shift) - # Number Game Numbers - COLOR_COUNT = 2 # 2 or 16 - colors = [getRandomHueShift() for x in range(16)] - # vanilla_green = [2, 4, 5, 7, 9, 10, 12, 13] - vanilla_blue = [1, 3, 6, 8, 11, 14, 15, 16] - for x in range(16): - number_hue_shift = colors[0] - if COLOR_COUNT == 2: - if (x + 1) in vanilla_blue: - number_hue_shift = colors[1] - else: - number_hue_shift = colors[x] - for sub_img in range(2): - img_index = 0x1FE + (2 * x) + sub_img - hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA5551, number_hue_shift) - if COLOR_COUNT == 2: - hueShiftImageContainer(25, 0xC2D, 32, 32, TextureFormat.RGBA5551, colors[1]) - hueShiftImageContainer(25, 0xC2E, 32, 32, TextureFormat.RGBA5551, colors[0]) - boulder_shift = getRandomHueShift() - hueShiftImageContainer(25, 0x12F4, 1, 1372, TextureFormat.RGBA5551, boulder_shift) - for img_index in range(2): - hueShiftImageContainer(25, 0xDE1 + img_index, 32, 64, TextureFormat.RGBA5551, boulder_shift) - - if enemy_setting != RandomModels.off: - # Barrel Enemy Skins - Random - klobber_shift = getRandomHueShift(0, 300) - kaboom_shift = getRandomHueShift() - for img_index in range(3): - px_count = 1404 if img_index < 2 else 1372 - hueShiftImageContainer(25, 0xF12 + img_index, 1, px_count, TextureFormat.RGBA5551, klobber_shift) - hueShiftImageContainer(25, 0xF22 + img_index, 1, px_count, TextureFormat.RGBA5551, kaboom_shift) - if img_index < 2: - hueShiftImageContainer(25, 0xF2B + img_index, 1, px_count, TextureFormat.RGBA5551, kaboom_shift) - # Klump - klump_jacket_shift = getRandomHueShift() - klump_hatammo_shift = getRandomHueShift() - jacket_images = [ - {"image": 0x104D, "px": 1372}, - {"image": 0x1058, "px": 1372}, - {"image": 0x1059, "px": 176}, - ] - hatammo_images = [ - {"image": 0x104E, "px": 1372}, - {"image": 0x104F, "px": 1372}, - {"image": 0x1050, "px": 1372}, - {"image": 0x1051, "px": 700}, - {"image": 0x1052, "px": 348}, - {"image": 0x1053, "px": 348}, - ] - for img_data in jacket_images: - hueShiftImageContainer(25, img_data["image"], 1, img_data["px"], TextureFormat.RGBA5551, klump_jacket_shift) - for img_data in hatammo_images: - hueShiftImageContainer(25, img_data["image"], 1, img_data["px"], TextureFormat.RGBA5551, klump_hatammo_shift) - # Kosha - kosha_shift = getRandomHueShift() - hueShiftImageContainer(25, 0x1232, 1, 348, TextureFormat.RGBA5551, kosha_shift) - hueShiftImageContainer(25, 0x1235, 1, 348, TextureFormat.RGBA5551, kosha_shift) - if enemy_setting == RandomModels.extreme: - kosha_helmet_int = getEnemySwapColor(80, min_channel_variance=80) - kosha_helmet_list = [ - (kosha_helmet_int >> 16) & 0xFF, - (kosha_helmet_int >> 8) & 0xFF, - kosha_helmet_int & 0xFF, - ] - kosha_club_int = getEnemySwapColor(80, min_channel_variance=80) - kosha_club_list = [(kosha_club_int >> 16) & 0xFF, (kosha_club_int >> 8) & 0xFF, kosha_club_int & 0xFF] - for img in range(0x122E, 0x1230): - kosha_im = getImageFile(25, img, True, 1, 1372, TextureFormat.RGBA5551) - kosha_im = maskImageWithColor(kosha_im, tuple(kosha_helmet_list)) - writeColorImageToROM(kosha_im, 25, img, 1, 1372, False, TextureFormat.RGBA5551) - for img in range(0x1229, 0x122C): - kosha_im = getImageFile(25, img, True, 1, 1372, TextureFormat.RGBA5551) - kosha_im = maskImageWithColor(kosha_im, tuple(kosha_club_list)) - writeColorImageToROM(kosha_im, 25, img, 1, 1372, False, TextureFormat.RGBA5551) - if settings.colorblind_mode == ColorblindMode.off: - # Kremling - kremling_dimensions = [ - [32, 64], # FCE - [64, 24], # FCF - [1, 1372], # fd0 - [32, 32], # fd1 - [24, 8], # fd2 - [24, 8], # fd3 - [24, 8], # fd4 - [24, 24], # fd5 - [32, 32], # fd6 - [32, 64], # fd7 - [32, 64], # fd8 - [36, 16], # fd9 - [20, 28], # fda - [32, 32], # fdb - [32, 32], # fdc - [12, 28], # fdd - [64, 24], # fde - [32, 32], # fdf - ] - while True: - kremling_shift = getRandomHueShift() - # Block red coloring - if kremling_shift > 290: - break - if kremling_shift > -70 and kremling_shift < 228: - break - if kremling_shift < -132: - break - for dim_index, dims in enumerate(kremling_dimensions): - if dims is not None: - hueShiftImageContainer(25, 0xFCE + dim_index, dims[0], dims[1], TextureFormat.RGBA5551, kremling_shift) - # Rabbit - rabbit_dimensions = [ - [1, 1372], # 111A - [1, 1372], # 111B - [1, 700], # 111C - [1, 700], # 111D - [1, 1372], # 111E - [1, 1372], # 111F - [1, 1372], # 1120 - [1, 1404], # 1121 - [1, 348], # 1122 - [32, 64], # 1123 - [1, 688], # 1124 - [64, 32], # 1125 - ] - rabbit_shift = getRandomHueShift() - for dim_index, dims in enumerate(rabbit_dimensions): - if dims is not None: - hueShiftImageContainer(25, 0x111A + dim_index, dims[0], dims[1], TextureFormat.RGBA5551, rabbit_shift) - # Snake - snake_shift = getRandomHueShift() - for x in range(2): - hueShiftImageContainer(25, 0xEF7 + x, 32, 32, TextureFormat.RGBA5551, snake_shift) - # Headphones Sprite - headphones_shift = getRandomHueShift() - for x in range(8): - hueShiftImageContainer(7, 0x3D3 + x, 40, 40, TextureFormat.RGBA5551, headphones_shift) - # Instruments - trombone_sax_shift = getRandomHueShift() - hueShiftImageContainer(25, 0xEA2, 32, 32, TextureFormat.RGBA5551, trombone_sax_shift) # Shine - hueShiftImageContainer(25, 0x15AF, 40, 40, TextureFormat.RGBA5551, trombone_sax_shift) # Trombone Icon - hueShiftImageContainer(25, 0x15AD, 40, 40, TextureFormat.RGBA5551, trombone_sax_shift) # Sax Icon - hueShiftImageContainer(25, 0xBCC, 32, 64, TextureFormat.RGBA5551, trombone_sax_shift) # Sax (Pad) - hueShiftImageContainer(25, 0xBCD, 32, 64, TextureFormat.RGBA5551, trombone_sax_shift) # Sax (Pad) - hueShiftImageContainer(25, 0xBD0, 32, 64, TextureFormat.RGBA5551, trombone_sax_shift) # Trombone (Pad) - hueShiftImageContainer(25, 0xBD1, 32, 64, TextureFormat.RGBA5551, trombone_sax_shift) # Trombone (Pad) - triangle_shift = getRandomHueShift() - hueShiftImageContainer(25, 0xEBF, 32, 32, TextureFormat.RGBA5551, triangle_shift) # Shine - hueShiftImageContainer(25, 0x15AE, 40, 40, TextureFormat.RGBA5551, triangle_shift) # Triangle Icon - hueShiftImageContainer(25, 0xBCE, 32, 64, TextureFormat.RGBA5551, triangle_shift) # Triangle (Pad) - hueShiftImageContainer(25, 0xBCF, 32, 64, TextureFormat.RGBA5551, triangle_shift) # Triangle (Pad) - bongo_shift = getRandomHueShift() - hueShiftImageContainer(25, 0x1317, 1, 1372, TextureFormat.RGBA5551, bongo_shift) # Skin - hueShiftImageContainer(25, 0x1318, 1, 1404, TextureFormat.RGBA5551, bongo_shift) # Side - hueShiftImageContainer(25, 0x1319, 1, 1404, TextureFormat.RGBA5551, bongo_shift) # Side 2 - hueShiftImageContainer(25, 0x15AC, 40, 40, TextureFormat.RGBA5551, bongo_shift) # Bongo Icon - hueShiftImageContainer(25, 0xBC8, 32, 64, TextureFormat.RGBA5551, bongo_shift) # Bongo (Pad) - hueShiftImageContainer(25, 0xBC9, 32, 64, TextureFormat.RGBA5551, bongo_shift) # Bongo (Pad) - if enemy_setting == RandomModels.extreme: - # Beanstalk - beanstalk_unc_size = [ - 0x480, - 0x480, - 0x480, - 0x2B8, - 0xAC0, - 0xAB8, - 0xAB8, - 0xAB8, - 0xAB8, - 0xAB8, - 0xAB8, - 0xAB8, - 0xAB8, - 0xAF8, - 0xAB8, - 0xAB8, - 0xAB8, - 0xAF8, - 0x578, - 0xAB8, - 0x578, - 0x5F8, - 0xAB8, - 0xAB8, - 0xAB8, - 0xAB8, - 0x578, - 0xAB8, - 0xAF8, - 0xAB8, - 0xAB8, - 0x560, - 0xAB8, - 0x2B8, - ] - beanstalk_shift = getRandomHueShift() - for index, size in enumerate(beanstalk_unc_size): - hueShiftImageContainer(25, 0x1126 + index, 1, int(size >> 1), TextureFormat.RGBA5551, beanstalk_shift) - # Fairy Particles Sprites - fairy_particles_shift = getRandomHueShift() - for x in range(0xB): - hueShiftImageContainer(25, 0x138D + x, 32, 32, TextureFormat.RGBA32, fairy_particles_shift) - race_coin_shift = getRandomHueShift() - for x in range(8): - hueShiftImageContainer(7, 0x1F0 + x, 48, 42, TextureFormat.RGBA5551, race_coin_shift) - scoff_shift = getRandomHueShift() - troff_shift = getRandomHueShift() - scoff_data = { - 0xFB8: 0x55C, - 0xFB9: 0x800, - 0xFBA: 0x40, - 0xFBB: 0x800, - 0xFBC: 0x240, - 0xFBD: 0x480, - 0xFBE: 0x80, - 0xFBF: 0x800, - 0xFC0: 0x200, - 0xFC1: 0x240, - 0xFC2: 0x100, - 0xFB2: 0x240, - 0xFB3: 0x800, - 0xFB4: 0x800, - 0xFB5: 0x200, - 0xFB6: 0x200, - 0xFB7: 0x200, - } - troff_data = { - 0xF78: 0x800, - 0xF79: 0x800, - 0xF7A: 0x800, - 0xF7B: 0x800, - 0xF7C: 0x800, - 0xF7D: 0x400, - 0xF7E: 0x600, - 0xF7F: 0x400, - 0xF80: 0x800, - 0xF81: 0x600, - 0xF82: 0x400, - 0xF83: 0x400, - 0xF84: 0x800, - 0xF85: 0x800, - 0xF86: 0x280, - 0xF87: 0x180, - 0xF88: 0x800, - 0xF89: 0x800, - 0xF8A: 0x400, - 0xF8B: 0x300, - 0xF8C: 0x800, - 0xF8D: 0x400, - 0xF8E: 0x500, - 0xF8F: 0x180, - } - for img in scoff_data: - hueShiftImageContainer(25, img, 1, scoff_data[img], TextureFormat.RGBA5551, scoff_shift) - - # Scoff had too many bananas, and passed potassium poisoning onto Troff - # https://i.imgur.com/WFDLSzA.png - # for img in troff_data: - # hueShiftImageContainer(25, img, 1, troff_data[img], TextureFormat.RGBA5551, troff_shift) - # Krobot - spinner_shift = getRandomHueShift() - hueShiftImageContainer(25, 0xFA9, 1, 1372, TextureFormat.RGBA5551, spinner_shift) - krobot_textures = [[[1, 1372], [0xFAF, 0xFAA, 0xFA8, 0xFAB, 0xFAD]], [[32, 32], [0xFAC, 0xFB1, 0xFAE, 0xFB0]]] - krobot_color_int = getEnemySwapColor(80, min_channel_variance=80) - krobot_color_list = [(krobot_color_int >> 16) & 0xFF, (krobot_color_int >> 8) & 0xFF, krobot_color_int & 0xFF] - for tex_set in krobot_textures: - for tex in tex_set[1]: - krobot_im = getImageFile(25, tex, True, tex_set[0][0], tex_set[0][1], TextureFormat.RGBA5551) - krobot_im = maskImageWithColor(krobot_im, tuple(krobot_color_list)) - writeColorImageToROM(krobot_im, 25, tex, tex_set[0][0], tex_set[0][1], False, TextureFormat.RGBA5551) - # Jetman - for xi, x in enumerate(settings.jetman_color): - ROM().seek(settings.rom_data + 0x1E8 + xi) - ROM().writeMultipleBytes(x, 1) - # Blast Barrels - blast_shift = getRandomHueShift() - hueShiftImageContainer(25, 0x127E, 1, 1372, TextureFormat.RGBA5551, blast_shift) - for x in range(4): - hueShiftImageContainer(25, 0x127F + x, 16, 64, TextureFormat.RGBA5551, blast_shift) - hueShiftImageContainer(25, getBonusSkinOffset(ExtraTextures.BlastTop), 1, 1372, TextureFormat.RGBA5551, blast_shift) - # K Rool - red_cs_im = Image.new(mode="RGBA", size=(32, 32), color=convertColorIntToTuple(getEnemySwapColor())) - shorts_im = Image.new(mode="RGBA", size=(32, 32), color=convertColorIntToTuple(getEnemySwapColor())) - glove_im = Image.new(mode="RGBA", size=(32, 32), color=convertColorIntToTuple(getEnemySwapColor())) - krool_data = { - 0x1149: red_cs_im, - 0x1261: shorts_im, - 0xDA8: glove_im, - } - if enemy_setting == RandomModels.extreme: - skin_im = Image.new(mode="RGBA", size=(32, 32), color=convertColorIntToTuple(getEnemySwapColor(80, min_channel_variance=80))) - krool_data[0x114A] = skin_im - krool_data[0x114D] = skin_im - for index in krool_data: - writeColorImageToROM(krool_data[index], 25, index, 32, 32, False, TextureFormat.RGBA5551) - toe_shift = getRandomHueShift() - hueShiftImageContainer(25, 0x126E, 1, 1372, TextureFormat.RGBA5551, toe_shift) - hueShiftImageContainer(25, 0x126F, 1, 1372, TextureFormat.RGBA5551, toe_shift) - if enemy_setting == RandomModels.extreme: - gold_shift = getRandomHueShift() - hueShiftImageContainer(25, 0x1265, 32, 32, TextureFormat.RGBA5551, gold_shift) - hueShiftImageContainer(25, 0x1148, 32, 32, TextureFormat.RGBA5551, gold_shift) - # Ghost - ghost_shift = getRandomHueShift() - for img in range(0x119D, 0x11AF): - px_count = 1372 - if img == 0x119E: - px_count = 176 - elif img == 0x11AC: - px_count = 688 - hueShiftImageContainer(25, img, 1, px_count, TextureFormat.RGBA5551, ghost_shift) - # Funky - funky_shift = getRandomHueShift() - hueShiftImageContainer(25, 0xECF, 1, 1372, TextureFormat.RGBA5551, funky_shift) - hueShiftImageContainer(25, 0xED6, 1, 1372, TextureFormat.RGBA5551, funky_shift) - hueShiftImageContainer(25, 0xEDF, 1, 1372, TextureFormat.RGBA5551, funky_shift) - # Zinger - zinger_shift = getRandomHueShift() - zinger_color = hueShiftColor((0xFF, 0xFF, 0x0A), zinger_shift) - zinger_color_int = (zinger_color[0] << 16) | (zinger_color[1] << 8) | (zinger_color[2]) - hueShiftImageContainer(25, 0xF0A, 1, 1372, TextureFormat.RGBA5551, zinger_shift) - # Mechazinger, use zinger color - for img_index in (0x10A0, 0x10A2, 0x10A4, 0x10A5): - hueShiftImageContainer(25, img_index, 1, 1372, TextureFormat.RGBA5551, zinger_shift) - hueShiftImageContainer(25, 0x10A3, 32, 32, TextureFormat.RGBA32, zinger_shift) - # Rings/DK Star - ring_shift = getRandomHueShift() - for x in range(2): - hueShiftImageContainer(25, 0xE1C + x, 1, 344, TextureFormat.RGBA5551, ring_shift) - hueShiftImageContainer(25, 0xD38 + x, 64, 32, TextureFormat.RGBA5551, ring_shift) - hueShiftImageContainer(7, 0x2EB, 32, 32, TextureFormat.RGBA5551, ring_shift) - # Buoys - for x in range(2): - hueShiftImageContainer(25, 0x133A + x, 1, 1372, TextureFormat.RGBA5551, getRandomHueShift()) - # Trap Bubble - hueShiftImageContainer(25, 0x134C, 32, 32, TextureFormat.RGBA5551, getRandomHueShift()) - # Spider - spider_shift = getRandomHueShift() - spider_dims = { - 0x110A: (32, 64), - 0x110B: (32, 64), - 0x110C: (32, 64), - 0x110D: (64, 16), - 0x110E: (32, 64), - 0x110F: (32, 64), - 0x1110: (32, 64), - 0x1111: (32, 64), - 0x1112: (32, 64), - 0x1113: (16, 32), - 0x1114: (32, 32), - 0x1115: (32, 32), - 0x1116: (32, 32), - 0x1117: (64, 16), - 0x1118: (64, 32), - 0x1119: (64, 32), - } - for img_index in spider_dims: - hueShiftImageContainer( - 25, - img_index, - spider_dims[img_index][0], - spider_dims[img_index][1], - TextureFormat.RGBA5551, - spider_shift, - ) - - if enemy_setting == RandomModels.extreme: - # Army Dillo - dillo_px_count = { - 0x102D: 64 * 32, - 0x103A: 16 * 16, - 0x102A: 24 * 24, - 0x102B: 24 * 24, - 0x102C: 1372, - 0x103D: 688, - 0x103E: 688, - } - dillo_shift = getRandomHueShift() - for img, px_count in dillo_px_count.items(): - hueShiftImageContainer(25, img, 1, px_count, TextureFormat.RGBA5551, dillo_shift) - - # Mushrooms - mush_man_shift = getRandomHueShift() - for img_index in (0x11FC, 0x11FD, 0x11FE, 0x11FF, 0x1200, 0x1209, 0x120A, 0x120B): - hueShiftImageContainer(25, img_index, 1, 1372, TextureFormat.RGBA5551, mush_man_shift) - for img_index in (0x11F8, 0x1205): - hueShiftImageContainer(25, img_index, 1, 692, TextureFormat.RGBA5551, mush_man_shift) - for img_index in (0x67F, 0x680): - hueShiftImageContainer(25, img_index, 32, 64, TextureFormat.RGBA5551, mush_man_shift) - hueShiftImageContainer(25, 0x6F3, 4, 4, TextureFormat.RGBA5551, mush_man_shift) - adjustFungiMushVertexColor(mush_man_shift) - - # Enemy Vertex Swaps - blue_beaver_color = getEnemySwapColor(80, min_channel_variance=80) - enemy_changes = { - Model.BeaverBlue_LowPoly: EnemyColorSwap([0xB2E5FF, 0x65CCFF, 0x00ABE8, 0x004E82, 0x008BD1, 0x001333, 0x1691CE], blue_beaver_color), # Primary - Model.BeaverBlue: EnemyColorSwap([0xB2E5FF, 0x65CCFF, 0x00ABE8, 0x004E82, 0x008BD1, 0x001333, 0x1691CE], blue_beaver_color), # Primary - Model.BeaverGold: EnemyColorSwap([0xFFE5B2, 0xFFCC65, 0xE8AB00, 0x824E00, 0xD18B00, 0x331300, 0xCE9116]), # Primary - Model.Zinger: EnemyColorSwap([0xFFFF0A, 0xFF7F00], zinger_color_int), # Legs - Model.RoboZinger: EnemyColorSwap([0xFFFF00, 0xFF5500], zinger_color_int), # Legs - Model.Candy: EnemyColorSwap( - [ - 0xFF96EB, - 0x572C58, - 0xB86CAA, - 0xEB4C91, - 0x8B2154, - 0xD13B80, - 0xFF77C1, - 0xFF599E, - 0x7F1E4C, - 0x61173A, - 0x902858, - 0xA42E64, - 0x791C49, - 0x67183E, - 0x9E255C, - 0xC12E74, - 0x572C58, - 0xFF96EB, - 0xB86CAA, - ] - ), - Model.Laser: EnemyColorSwap([0xF30000]), - Model.Kasplat: EnemyColorSwap([0x8FD8FF, 0x182A4F, 0x0B162C, 0x7A98D3, 0x3F6CC4, 0x8FD8FF, 0x284581]), - # Model.BananaFairy: EnemyColorSwap([0xFFD400, 0xFFAA00, 0xFCD200, 0xD68F00, 0xD77D0A, 0xe49800, 0xdf7f1f, 0xa26c00, 0xd6b200, 0xdf9f1f]) - } - if enemy_setting == RandomModels.extreme: - enemy_changes[Model.Klump] = EnemyColorSwap([0xE66B78, 0x621738, 0x300F20, 0xD1426F, 0xA32859]) - dogadon_color = getEnemySwapColor(80, 160, min_channel_variance=80) - enemy_changes[Model.Dogadon] = EnemyColorSwap( - [ - 0xFF0000, - 0xFF7F00, - 0x450A1F, - 0xB05800, - 0xFF3200, - 0xFFD400, - 0x4F260D, - 0x600F00, - 0x6A1400, - 0xAA0000, - 0xDF3F1F, - 0xFF251F, - 0x8F4418, - 0x522900, - 0xDF9F1F, - 0x3B0606, - 0x91121E, - 0x700C0D, - 0xFF5900, - 0xFF7217, - 0xFF7425, - 0xFF470B, - 0xA82100, - 0x4A0D18, - 0x580E00, - 0x461309, - 0x4C1503, - 0x780D0E, - 0xFFA74A, - 0x7E120F, - 0x700000, - 0xB64D19, - 0x883A13, - 0xBD351A, - 0xD42900, - 0xFF2A00, - 0x921511, - 0x9C662D, - 0xDF5F1F, - 0x9B1112, - 0x461F0A, - 0x4B0808, - 0x500809, - 0xA42000, - 0x5F0B13, - 0xBF6A3F, - 0x602E10, - 0x971414, - 0x422C15, - 0xFC5800, - 0x5C0D0B, - ], - dogadon_color, - ) - for enemy in enemy_changes: - file_data = bytearray(getRawFile(5, enemy, True)) - vert_start = 0x28 - file_head = getValueFromByteArray(file_data, 0, 4) - disp_list_end = (getValueFromByteArray(file_data, 4, 4) - file_head) + 0x28 - vert_end = (getValueFromByteArray(file_data, disp_list_end, 4) - file_head) + 0x28 - vert_count = int((vert_end - vert_start) / 0x10) - for vert in range(vert_count): - local_start = 0x28 + (0x10 * vert) - test_rgb = getValueFromByteArray(file_data, local_start + 0xC, 3) - new_rgb = enemy_changes[enemy].getOutputColor(test_rgb) - for x in range(3): - shift = 8 * (2 - x) - channel = (new_rgb >> shift) & 0xFF - file_data[local_start + 0xC + x] = channel - file_data = gzip.compress(file_data, compresslevel=9) - ROM().seek(js.pointer_addresses[5]["entries"][enemy]["pointing_to"]) - ROM().writeBytes(file_data) - - -def getNumberImage(number: int) -> PIL.Image.Image: - """Get Number Image from number.""" - if number < 5: - num_0_bounds = [0, 20, 30, 45, 58, 76] - x = number - return getImageFile(14, 15, True, 76, 24, TextureFormat.RGBA5551).crop((num_0_bounds[x], 0, num_0_bounds[x + 1], 24)) - num_1_bounds = [0, 15, 28, 43, 58, 76] - x = number - 5 - return getImageFile(14, 16, True, 76, 24, TextureFormat.RGBA5551).crop((num_1_bounds[x], 0, num_1_bounds[x + 1], 24)) - - -def numberToImage(number: int, dim: Tuple[int, int]) -> PIL.Image.Image: - """Convert multi-digit number to image.""" - digits = 1 - if number < 10: - digits = 1 - elif number < 100: - digits = 2 - else: - digits = 3 - current = number - nums = [] - total_width = 0 - max_height = 0 - sep_dist = 1 - for _ in range(digits): - base = getNumberImage(current % 10) - bbox = base.getbbox() - base = base.crop(bbox) - nums.append(base) - base_w, base_h = base.size - max_height = max(max_height, base_h) - total_width += base_w - current = int(current / 10) - nums.reverse() - total_width += (digits - 1) * sep_dist - base = Image.new(mode="RGBA", size=(total_width, max_height)) - pos = 0 - for num in nums: - base.paste(num, (pos, 0), num) - num_w, num_h = num.size - pos += num_w + sep_dist - output = Image.new(mode="RGBA", size=dim) - xScale = dim[0] / total_width - yScale = dim[1] / max_height - scale = xScale - if yScale < xScale: - scale = yScale - new_w = int(total_width * scale) - new_h = int(max_height * scale) - x_offset = int((dim[0] - new_w) / 2) - y_offset = int((dim[1] - new_h) / 2) - new_dim = (new_w, new_h) - base = base.resize(new_dim) - output.paste(base, (x_offset, y_offset), base) - return output - - -def recolorKRoolShipSwitch(color: tuple, ROM_COPY: ROM): - """Recolors the simian slam switch that is part of K. Rool's ship in galleon.""" - addresses = ( - 0x4C34, - 0x4C44, - 0x4C54, - 0x4C64, - 0x4C74, - 0x4C84, - ) - data = bytearray(getRawFile(TableNames.ModelTwoGeometry, 305, True)) - for addr in addresses: - for x in range(3): - data[addr + x] = color[x] - new_tex = [ - 0xE7, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0xE2, - 0x00, - 0x00, - 0x1C, - 0x0C, - 0x19, - 0x20, - 0x38, - 0xE3, - 0x00, - 0x0A, - 0x01, - 0x00, - 0x10, - 0x00, - 0x00, - 0xE3, - 0x00, - 0x0F, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0xE7, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0xFC, - 0x12, - 0x7E, - 0x03, - 0xFF, - 0xFF, - 0xF9, - 0xF8, - 0xFD, - 0x90, - 0x00, - 0x00, - 0x00, - 0x00, - 0x0B, - 0xAF, - 0xF5, - 0x90, - 0x00, - 0x00, - 0x07, - 0x08, - 0x02, - 0x00, - 0xE6, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0xF3, - 0x00, - 0x00, - 0x00, - 0x07, - 0x7F, - 0xF1, - 0x00, - 0xE7, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0xF5, - 0x88, - 0x10, - 0x00, - 0x00, - 0x08, - 0x02, - 0x00, - 0xF2, - 0x00, - 0x00, - 0x00, - 0x00, - 0x0F, - 0xC0, - 0xFC, - ] - for x in range(8): - data[0x1AD8 + x] = 0 - for xi, x in enumerate(new_tex): - data[0x1AE8 + xi] = x - for x in range(40): - data[0x1B58 + x] = 0 - writeRawFile(TableNames.ModelTwoGeometry, 305, True, data, ROM_COPY) - - def applyHelmDoorCosmetics(settings: Settings) -> None: """Apply Helm Door Cosmetic Changes.""" crown_door_required_item = settings.crown_door_item @@ -3636,343 +653,6 @@ def applyHelmDoorCosmetics(settings: Settings) -> None: ) -def changeBarrelColor(barrel_color: tuple = None, metal_color: tuple = None, brighten_barrel: bool = False): - """Change the colors of the various barrels.""" - wood_img = getImageFile(25, getBonusSkinOffset(ExtraTextures.ShellWood), True, 32, 64, TextureFormat.RGBA5551) - metal_img = getImageFile(25, getBonusSkinOffset(ExtraTextures.ShellMetal), True, 32, 64, TextureFormat.RGBA5551) - qmark_img = getImageFile(25, getBonusSkinOffset(ExtraTextures.ShellQMark), True, 32, 64, TextureFormat.RGBA5551) - if barrel_color is not None: - if brighten_barrel: - enhancer = ImageEnhance.Brightness(wood_img) - wood_img = enhancer.enhance(2) - wood_img = maskImageWithColor(wood_img, barrel_color) - if metal_color is not None: - metal_img = maskImageWithColor(metal_img, metal_color) - wood_img.paste(metal_img, (0, 0), metal_img) - writeColorImageToROM(wood_img, 25, getBonusSkinOffset(ExtraTextures.BonusShell), 32, 64, False, TextureFormat.RGBA5551) # Bonus Barrel - tag_img = Image.new(mode="RGBA", size=(32, 64)) - tag_img.paste(wood_img, (0, 0), wood_img) - tag_img.paste(qmark_img, (0, 0), qmark_img) - writeColorImageToROM(tag_img, 25, 4938, 32, 64, False, TextureFormat.RGBA5551) # Tag Barrel - # Compose Transform Barrels - kongs = [ - {"face_left": 0x27C, "face_right": 0x27B, "barrel_tex_start": 4817, "targ_width": 24}, # DK - {"face_left": 0x279, "face_right": 0x27A, "barrel_tex_start": 4815, "targ_width": 24}, # Diddy - {"face_left": 0x277, "face_right": 0x278, "barrel_tex_start": 4819, "targ_width": 24}, # Lanky - {"face_left": 0x276, "face_right": 0x275, "barrel_tex_start": 4769, "targ_width": 24}, # Tiny - {"face_left": 0x273, "face_right": 0x274, "barrel_tex_start": 4747, "targ_width": 24}, # Chunky - ] - for kong in kongs: - bar_left = Image.new(mode="RGBA", size=(32, 64)) - bar_right = Image.new(mode="RGBA", size=(32, 64)) - face_left = getImageFile(25, kong["face_left"], True, 32, 64, TextureFormat.RGBA5551) - face_right = getImageFile(25, kong["face_right"], True, 32, 64, TextureFormat.RGBA5551) - width = kong["targ_width"] - height = width * 2 - face_left = face_left.resize((width, height)) - face_right = face_right.resize((width, height)) - right_w_offset = 32 - width - top_h_offset = (64 - height) >> 1 - bar_left.paste(wood_img, (0, 0), wood_img) - bar_right.paste(wood_img, (0, 0), wood_img) - bar_left.paste(face_left, (right_w_offset, top_h_offset), face_left) - bar_right.paste(face_right, (0, top_h_offset), face_right) - writeColorImageToROM(bar_left, 25, kong["barrel_tex_start"], 32, 64, False, TextureFormat.RGBA5551) - writeColorImageToROM(bar_right, 25, kong["barrel_tex_start"] + 1, 32, 64, False, TextureFormat.RGBA5551) - # Cannons - barrel_left = Image.new(mode="RGBA", size=(32, 64)) - barrel_right = Image.new(mode="RGBA", size=(32, 64)) - barrel_left.paste(wood_img, (0, 0), wood_img) - barrel_right.paste(wood_img, (0, 0), wood_img) - barrel_left = barrel_left.crop((0, 0, 16, 64)) - barrel_right = barrel_right.crop((16, 0, 32, 64)) - writeColorImageToROM(barrel_left, 25, 0x12B3, 16, 64, False, TextureFormat.RGBA5551) - writeColorImageToROM(barrel_right, 25, 0x12B4, 16, 64, False, TextureFormat.RGBA5551) - if barrel_color is not None: - tex_data = { - getBonusSkinOffset(ExtraTextures.RocketTop): (1, 1372), - 0x12B5: (48, 32), - 0x12B8: (44, 44), - } - for img in tex_data: - dim_x = tex_data[img][0] - dim_y = tex_data[img][1] - img_output = getImageFile(25, img, True, dim_x, dim_y, TextureFormat.RGBA5551) - img_output = maskImageWithColor(img_output, barrel_color) - writeColorImageToROM(img_output, 25, img, dim_x, dim_y, False, TextureFormat.RGBA5551) - - -def applyCelebrationRims(hue_shift: int, enabled_bananas: list[bool] = [False, False, False, False, False]): - """Retexture the warp pad rims to have a more celebratory tone.""" - banana_textures = [] - vanilla_banana_textures = [0xA8, 0x98, 0xE8, 0xD0, 0xF0] - for kong_index, ban in enumerate(enabled_bananas): - if ban: - banana_textures.append(vanilla_banana_textures[kong_index]) - place_bananas = False - if len(banana_textures) > 0: - place_bananas = True - if len(banana_textures) < 4: - banana_textures = (banana_textures * 4)[:4] - if place_bananas: - bananas = [getImageFile(7, x, False, 44, 44, TextureFormat.RGBA5551).resize((14, 14)) for x in banana_textures] - banana_placement = [ - # File, x, y - [0xBB3, 15, 1], # 3 - [0xBB2, 2, 1], # 2 - [0xBB3, 0, 1], # 4 - [0xBB2, 17, 1], # 1 - ] - for img in (0xBB2, 0xBB3): - side_im = getImageFile(25, img, True, 32, 16, TextureFormat.RGBA5551) - hueShift(side_im, hue_shift) - if place_bananas: - for bi, banana in enumerate(bananas): - if banana_placement[bi][0] == img: - b_x = banana_placement[bi][1] - b_y = banana_placement[bi][2] - side_im.paste(banana, (b_x, b_y), banana) - side_by = [] - side_px = side_im.load() - for y in range(16): - for x in range(32): - red_short = (side_px[x, y][0] >> 3) & 31 - green_short = (side_px[x, y][1] >> 3) & 31 - blue_short = (side_px[x, y][2] >> 3) & 31 - alpha_short = 1 if side_px[x, y][3] > 128 else 0 - value = (red_short << 11) | (green_short << 6) | (blue_short << 1) | alpha_short - side_by.extend([(value >> 8) & 0xFF, value & 0xFF]) - px_data = bytearray(side_by) - px_data = gzip.compress(px_data, compresslevel=9) - ROM().seek(js.pointer_addresses[25]["entries"][img]["pointing_to"]) - ROM().writeBytes(px_data) - - -def applyHolidayMode(settings): - """Change grass texture to snow.""" - HOLIDAY = getHoliday(settings) - if HOLIDAY == Holidays.no_holiday: - changeBarrelColor() # Fixes some Krusha stuff - return - if HOLIDAY == Holidays.Christmas: - # Set season to Christmas - ROM().seek(settings.rom_data + 0xDB) - ROM().writeMultipleBytes(2, 1) - # Grab Snow texture, transplant it - ROM().seek(0x1FF8000) - snow_im = Image.new(mode="RGBA", size=((32, 32))) - snow_px = snow_im.load() - snow_by = [] - for y in range(32): - for x in range(32): - rgba_px = int.from_bytes(ROM().readBytes(2), "big") - red = ((rgba_px >> 11) & 31) << 3 - green = ((rgba_px >> 6) & 31) << 3 - blue = ((rgba_px >> 1) & 31) << 3 - alpha = (rgba_px & 1) * 255 - snow_px[x, y] = (red, green, blue, alpha) - for dim in (32, 16, 8, 4): - snow_im = snow_im.resize((dim, dim)) - px = snow_im.load() - for y in range(dim): - for x in range(dim): - rgba_data = list(px[x, y]) - data = 0 - for c in range(3): - data |= (rgba_data[c] >> 3) << (1 + (5 * c)) - if rgba_data[3] != 0: - data |= 1 - snow_by.extend([(data >> 8), (data & 0xFF)]) - byte_data = gzip.compress(bytearray(snow_by), compresslevel=9) - for img in (0x4DD, 0x4E4, 0x6B, 0xF0, 0x8B2, 0x5C2, 0x66E, 0x66F, 0x685, 0x6A1, 0xF8, 0x136): - start = js.pointer_addresses[25]["entries"][img]["pointing_to"] - ROM().seek(start) - ROM().writeBytes(byte_data) - # Alter CI4 Palettes - start = js.pointer_addresses[25]["entries"][2007]["pointing_to"] - mags = [140, 181, 156, 181, 222, 206, 173, 230, 255, 255, 255, 189, 206, 255, 181, 255] - new_ci4_palette = [] - for mag in mags: - comp_mag = mag >> 3 - data = (comp_mag << 11) | (comp_mag << 6) | (comp_mag << 1) | 1 - new_ci4_palette.extend([(data >> 8), (data & 0xFF)]) - byte_data = gzip.compress(bytearray(new_ci4_palette), compresslevel=9) - ROM().seek(start) - ROM().writeBytes(byte_data) - # Alter rims - applyCelebrationRims(50, [True, True, True, True, False]) - # Change DK's Tie and Tiny's Hair - if settings.dk_tie_colors != CharacterColors.custom and settings.kong_model_dk == KongModels.default: - tie_hang = [0xFF] * 0xAB8 - tie_hang_data = gzip.compress(bytearray(tie_hang), compresslevel=9) - ROM().seek(js.pointer_addresses[25]["entries"][0xE8D]["pointing_to"]) - ROM().writeBytes(tie_hang_data) - tie_loop = [0xFF] * (32 * 32 * 2) - tie_loop_data = gzip.compress(bytearray(tie_loop), compresslevel=9) - ROM().seek(js.pointer_addresses[25]["entries"][0x177D]["pointing_to"]) - ROM().writeBytes(tie_loop_data) - if settings.tiny_hair_colors != CharacterColors.custom and settings.kong_model_tiny == KongModels.default: - tiny_hair = [] - for x in range(32 * 32): - tiny_hair.extend([0xF8, 0x01]) - tiny_hair_data = gzip.compress(bytearray(tiny_hair), compresslevel=9) - ROM().seek(js.pointer_addresses[25]["entries"][0xE68]["pointing_to"]) - ROM().writeBytes(tiny_hair_data) - # Tag Barrel, Bonus Barrel & Transform Barrels - changeBarrelColor(None, (0x00, 0xC0, 0x00)) - elif HOLIDAY == Holidays.Halloween: - ROM().seek(settings.rom_data + 0xDB) - ROM().writeMultipleBytes(1, 1) - # Pad Rim - applyCelebrationRims(-12) - # Tag Barrel, Bonus Barrel & Transform Barrels - changeBarrelColor((0x00, 0xC0, 0x00)) - # Turn Ice Tomato Orange - sizes = { - 0x1237: 700, - 0x1238: 1404, - 0x1239: 1372, - 0x123A: 1372, - 0x123B: 692, - 0x123C: 1372, - 0x123D: 1372, - 0x123E: 1372, - 0x123F: 1372, - 0x1240: 1372, - 0x1241: 1404, - } - for img in range(0x1237, 0x1241 + 1): - hueShiftImageContainer(25, img, 1, sizes[img], TextureFormat.RGBA5551, 240) - elif HOLIDAY == Holidays.Anniv25: - changeBarrelColor((0xFF, 0xFF, 0x00), None, True) - sticker_im = getImageFile(25, getBonusSkinOffset(ExtraTextures.Anniv25Sticker), True, 1, 1372, TextureFormat.RGBA5551) - vanilla_sticker_im = getImageFile(25, 0xB7D, True, 1, 1372, TextureFormat.RGBA5551) - sticker_im_snipped = sticker_im.crop((0, 0, 1, 1360)) - writeColorImageToROM(sticker_im_snipped, 25, 0xB7D, 1, 1360, False, TextureFormat.RGBA5551) - vanilla_sticker_portion = vanilla_sticker_im.crop((0, 1360, 1, 1372)) - new_im = Image.new(mode="RGBA", size=(1, 1372)) - new_im.paste(sticker_im_snipped, (0, 0), sticker_im_snipped) - new_im.paste(vanilla_sticker_portion, (0, 1360), vanilla_sticker_portion) - writeColorImageToROM(new_im, 25, 0x1266, 1, 1372, False, TextureFormat.RGBA5551) - applyCelebrationRims(0, [False, True, True, True, True]) - - -def updateMillLeverTexture(settings: Settings) -> None: - """Update the 21132 texture.""" - if settings.mill_levers[0] > 0: - # Get Number bounds - base_num_texture = getImageFile(table_index=25, file_index=0x7CA, compressed=True, width=64, height=32, format=TextureFormat.RGBA5551) - number_textures = [None, None, None] - number_x_bounds = ( - (18, 25), - (5, 16), - (36, 47), - ) - modified_tex = getImageFile(table_index=25, file_index=0x7CA, compressed=True, width=64, height=32, format=TextureFormat.RGBA5551) - for tex in range(3): - number_textures[tex] = base_num_texture.crop((number_x_bounds[tex][0], 7, number_x_bounds[tex][1], 25)) - total_width = 0 - for x in range(5): - if settings.mill_levers[x] > 0: - idx = settings.mill_levers[x] - 1 - total_width += number_x_bounds[idx][1] - number_x_bounds[idx][0] - # Overwrite old panel - overwrite_panel = Image.new(mode="RGBA", size=(58, 26), color=(131, 65, 24)) - modified_tex.paste(overwrite_panel, (3, 3), overwrite_panel) - # Generate new number texture - new_num_texture = Image.new(mode="RGBA", size=(total_width, 18)) - x_pos = 0 - for num in range(5): - if settings.mill_levers[num] > 0: - num_val = settings.mill_levers[num] - 1 - new_num_texture.paste(number_textures[num_val], (x_pos, 0), number_textures[num_val]) - x_pos += number_x_bounds[num_val][1] - number_x_bounds[num_val][0] - scale_x = 58 / total_width - scale_y = 26 / 18 - scale = min(scale_x, scale_y) - x_size = int(total_width * scale) - y_size = int(18 * scale) - new_num_texture = new_num_texture.resize((x_size, y_size)) - x_offset = int((58 - x_size) / 2) - modified_tex.paste(new_num_texture, (3 + x_offset, 3), new_num_texture) - writeColorImageToROM(modified_tex, 25, 0x7CA, 64, 32, False, TextureFormat.RGBA5551) - - -def updateDiddyDoors(settings: Settings): - """Update the textures for the doors.""" - enable_code = False - for code in settings.diddy_rnd_doors: - if sum(code) > 0: # Has a non-zero element - enable_code = True - SEG_WIDTH = 48 - SEG_HEIGHT = 42 - NUMBERS_START = (27, 33) - if enable_code: - # Order: 4231, 3124, 1342 - starts = (0xCE8, 0xCE4, 0xCE0) - for index, code in enumerate(settings.diddy_rnd_doors): - start = starts[index] - total = Image.new(mode="RGBA", size=(SEG_WIDTH * 2, SEG_HEIGHT * 2)) - for img_index in range(4): - img = getImageFile(25, start + img_index, True, SEG_WIDTH, SEG_HEIGHT, TextureFormat.RGBA5551) - x_offset = SEG_WIDTH * (img_index & 1) - y_offset = SEG_HEIGHT * ((img_index & 2) >> 1) - total.paste(img, (x_offset, y_offset), img) - total = total.transpose(Image.FLIP_TOP_BOTTOM) - # Overlay color - cover = Image.new(mode="RGBA", size=(42, 20), color=(115, 98, 65)) - total.paste(cover, NUMBERS_START, cover) - # Paste numbers - number_images = [] - number_offsets = [] - total_length = 0 - for num in code: - num_img = getNumberImage(num + 1) - w, h = num_img.size - number_offsets.append(total_length) - total_length += w - number_images.append(num_img) - total_numbers = Image.new(mode="RGBA", size=(total_length, 24)) - for img_index, img in enumerate(number_images): - total_numbers.paste(img, (number_offsets[img_index], 0), img) - total.paste(total_numbers, (SEG_WIDTH - int(total_length / 2), SEG_HEIGHT - 12), total_numbers) - total = total.transpose(Image.FLIP_TOP_BOTTOM) - for img_index in range(4): - x_offset = SEG_WIDTH * (img_index & 1) - y_offset = SEG_HEIGHT * ((img_index & 2) >> 1) - sub_img = total.crop((x_offset, y_offset, x_offset + SEG_WIDTH, y_offset + SEG_HEIGHT)) - writeColorImageToROM(sub_img, 25, start + img_index, SEG_WIDTH, SEG_HEIGHT, False, TextureFormat.RGBA5551) - - -def updateCryptLeverTexture(settings: Settings) -> None: - """Update the two textures for Donkey Minecart entry.""" - if settings.crypt_levers[0] > 0: - # Get a blank texture - texture_0 = getImageFile(table_index=25, file_index=0x999, compressed=True, width=32, height=64, format=TextureFormat.RGBA5551) - blank = texture_0.crop((8, 5, 23, 22)) - texture_0.paste(blank, (8, 42), blank) - texture_1 = texture_0.copy() - for xi, x in enumerate(settings.crypt_levers): - corrected = x - 1 - y_slot = corrected % 3 - num = getNumberImage(xi + 1) - num = num.transpose(Image.FLIP_TOP_BOTTOM) - w, h = num.size - scale = 2 / 3 - y_offset = int((h * scale) / 2) - x_offset = int((w * scale) / 2) - num = num.resize((int(w * scale), int(h * scale))) - y_pos = (51, 33, 14) - tl_y = y_pos[y_slot] - y_offset - tl_x = 16 - x_offset - if corrected < 3: - texture_0.paste(num, (tl_x, tl_y), num) - else: - texture_1.paste(num, (tl_x, tl_y), num) - writeColorImageToROM(texture_0, 25, 0x99A, 32, 64, False, TextureFormat.RGBA5551) - writeColorImageToROM(texture_1, 25, 0x999, 32, 64, False, TextureFormat.RGBA5551) - - def darkenPauseBubble(settings: Settings): """Change the brightness of the text bubble used for the pause menu for dark mode.""" if not settings.dark_mode_textboxes: @@ -4073,515 +753,6 @@ def showWinCondition(settings: Settings): writeColorImageToROM(base_im, 14, 195, 32, 32, False, TextureFormat.RGBA5551) -boot_phrases = ( - "Removing Lanky Kong", - "Telling 2dos to play DK64", - "Locking K. Lumsy in a cage", - "Stealing the Banana Hoard", - "Finishing the game in a cave", - "Becoming the peak of randomizers", - "Giving kops better eyesight", - "Patching in the glitches", - "Enhancing Cfox Luck", - "Finding Rareware GB in Galleon", - "Resurrecting Chunky Kong", - "Shouting out Grant Kirkhope", - "Crediting L. Godfrey", - "Removing Stop n Swop", - "Assembling the scraps", - "Blowing in the cartridge", - "Backflipping in Chunky Phase", - "Hiding 20 fairies", - "Randomizing collision normals", - "Removing hit detection", - "Compressing K Rools Voice Lines", - "Checking divide by 0 doesnt work", - "Adding every move to Isles", - "Segueing in dk64randomizer.com", - "Removing lag. Or am I?", - "Hiding a dirt patch under grass", - "Giving Wrinkly the spoiler log", - "Questioning sub 2:30 in LUA Rando", - "Chasing Lanky in Fungi Forest", - "Banning Potions from Candys Shop", - "Finding someone who can help you", - "Messing up your seed", - "Crashing Krem Isle", - "Increasing Robot Punch Resistance", - "Caffeinating banana fairies", - "Bothering Beavers", - "Inflating Banana Balloons", - "Counting to 16", - "Removing Walls", - "Taking it to the fridge", - "Brewing potions", - "Reticulating Splines", # SimCity 2000 - "Ironing Donks", - "Replacing mentions of Hero with Hoard", - "Suggesting you also try BK Randomizer", - "Scattering 3500 Bananas", - "Stealing ideas from other randomizers", - "Fixing Krushas Collision", - "Falling on 75m", - "Summoning Salt", - "Combing Chunkys Afro", - "Asking what you gonna do", - "Thinking with portals", - "Reminding you to hydrate", - "Injecting lag", - "Turning Sentient", - "Performing for you", - "Charging 2 coins per save", - "Loading in Beavers", - "Lifting Boulders with Relative Ease", - "Doing Monkey Science Probably", - "Telling Killi to eventually play DK64", - "Crediting Grant Kirkhope", - "Dropping Crayons", - "Saying Hello when others wont", - "Mangling Music", - "Killing Speedrunning", - "Enhancing Cfox Luck Voice Linesmizers", - "Enforcing the law of the Jungle", - "Saving 20 frames", - "Reporting bugs. Unlike some", - "Color-coding Krusha for convenience", -) - -crown_heads = ( - # Object - "Arena", - "Beaver", - "Bish Bash", - "Forest", - "Kamikaze", - "Kritter", - "Pinnacle", - "Plinth", - "Shockwave", - "Bean", - "Dogadon", - "Banana", - "Squawks", - "Lanky", - "Diddy", - "Tiny", - "Chunky", - "DK", - "Krusha", - "Kosha", - "Klaptrap", - "Zinger", - "Gnawty", - "Kasplat", - "Pufftup", - "Shuri", - "Krossbones", - "Caves", - "Castle", - "Helm", - "Japes", - "Jungle", - "Angry", - "Aztec", - "Frantic", - "Factory", - "Gloomy", - "Galleon", - "Crystal", - "Creepy", - "Hideout", - "Cranky", - "Funky", - "Candy", - "Kong", - "Monkey", - "Amazing", - "Incredible", - "Ultimate", - "Wrinkly", - "Heroic", - "Final", - "Fantastic", - "Krazy", - "Komplete", - "Unhinted", - "Unstable", - "Extreme", - "Royal", - "Monster", - "Primate", - "Baboon", - "Walnut", - "Peanut", - "Coconut", - "Feather", - "Grape", - "Pineapple", - "Barrel", - "Monkeyport", - "Kalamity", - "Kaboom", - "Magic", - "Fairy", - "Karnivorous", - "Krispy", - "Kooky", - "Cookin", - "Klutz", - "Kingdom", - "Super Duper", - "Rainbow", - "Bongo", - "Guitar", - "Trombone", - "Saxophone", - "Triangle", - "Dixie", - "Gorilla", - "Chimpy", - "Museum", - "Ballroom", - "Winch", - "Shipyard", - "Hillside", - "Oasis", - "Arcade", - "Mushroom", - "Igloo", - "Stupid", - "Spicy", - "Dizzy", - "Slot Car", - "Minecart", - "Rambi", - "Enguarde", - "Reptile", - "Bramble", - "Toxic", - "Rabbit", - "Beetle", - "Vulture", - "Boulder", -) - -crown_tails = ( - # Synonym for brawl/similar - "Ambush", - "Brawl", - "Fracas", - "Karnage", - "Kremlings", - "Palaver", - "Panic", - "Showdown", - "Slam", - "Melee", - "Tussle", - "Altercation", - "Wrangle", - "Clash", - "Free for All", - "Skirmish", - "Scrap", - "Fight", - "Rumpus", - "Fray", - "Wrestle", - "Brouhaha", - "Commotion", - "Uproar", - "Rough and Tumble", - "Broil", - "Argy Bargy", - "Bother", - "Mayhem", - "Bonanza", - "Battle", - "Kerfuffle", - "Rumble", - "Fisticuffs", - "Ruckus", - "Scrimmage", - "Strife", - "Dog and Duck", - "Joust", - "Scuffle", - "Hootenanny", - "Blitz", - "Tourney", - "Explosion", - "Contest", - "Chaos", - "Combat", - "Knockdown", - "Demolition", - "Capture", - "Storm", - "Earthquake", - "Charge", - "Tremor", - "Trample", - "Gauntlet", - "Challenge", - "Blowout", - "Riot", - "Buffoonery", - "Hijinxs", - "Frenzy", - "Rampage", - "Antics", - "Trouble", - "Revenge", - "Klamber", - "Wreckage", - "Quarrel", - "Feud", - "Thwack", - "Wallop", - "Donnybrook", - "Tangle", - "Crossfire", - "Royale", -) - - -def getCrownNames() -> list: - """Get crown names from head and tail pools.""" - # Get 10 names for heads just in case "Forest" and "Fracas" show up - heads = random.sample(crown_heads, 10) - tails = random.sample(crown_tails, 9) - # Remove "Forest" if both "Forest" and "Fracas" show up - if "Forest" in heads and "Fracas" in tails: - heads.remove("Forest") - # Only get 9 names, Forest Fracas can't be overwritten without having negative impacts - names = [] - for x in range(9): - head = heads[x] - tail = tails[x] - if head[0] == "K" and tail[0] == "C": - split_tail = list(tail) - split_tail[0] = "K" - tail = "".join(split_tail) - names.append(f"{head} {tail}!".upper()) - names.append("Forest Fracas!".upper()) - return names - - -def writeCrownNames(): - """Write Crown Names to ROM.""" - names = getCrownNames() - old_text = grabText(35, True) - for name_index, name in enumerate(names): - old_text[0x1E + name_index] = ({"text": [name]},) - writeText(35, old_text, True) - - -def writeBootMessages() -> None: - """Write boot messages into ROM.""" - ROM_COPY = LocalROM() - placed_messages = random.sample(boot_phrases, 4) - for message_index, message in enumerate(placed_messages): - ROM_COPY.seek(0x1FFD000 + (0x40 * message_index)) - ROM_COPY.writeBytes(message.upper().encode("ascii")) - - -def writeTransition(settings: Settings) -> None: - """Write transition cosmetic to ROM.""" - if js.cosmetics is None: - return - if js.cosmetics.transitions is None: - return - if js.cosmetic_names.transitions is None: - return - file_data = list(zip(js.cosmetics.transitions, js.cosmetic_names.transitions)) - settings.custom_transition = None - if len(file_data) == 0: - return - selected_transition = random.choice(file_data) - settings.custom_transition = selected_transition[1].split("/")[-1] # File Name - im_f = Image.open(BytesIO(bytes(selected_transition[0]))) - writeColorImageToROM(im_f, 14, 95, 64, 64, False, TextureFormat.IA4) - - -def getImageChunk(im_f, width: int, height: int): - """Get an image chunk based on a width and height.""" - width_height_ratio = width / height - im_w, im_h = im_f.size - im_wh_ratio = im_w / im_h - if im_wh_ratio != width_height_ratio: - # Ratio doesn't match, we have to do some rejigging - scale = 1 - if width_height_ratio > im_wh_ratio: - # Scale based on width - scale = width / im_w - else: - # Height needs growing - scale = height / im_h - im_f = im_f.resize((int(im_w * scale), int(im_h * scale))) - im_w, im_h = im_f.size - middle_w = im_w / 2 - middle_h = im_h / 2 - middle_targ_w = width / 2 - middle_targ_h = height / 2 - return im_f.crop( - ( - int(middle_w - middle_targ_w), - int(middle_h - middle_targ_h), - int(middle_w + middle_targ_w), - int(middle_h + middle_targ_h), - ) - ) - # Ratio matches, just scale up - return im_f.resize((width, height)) - - -def writeCustomPortal(settings: Settings) -> None: - """Write custom portal file to ROM.""" - if js.cosmetics is None: - return - if js.cosmetics.tns_portals is None: - return - if js.cosmetic_names.tns_portals is None: - return - file_data = list(zip(js.cosmetics.tns_portals, js.cosmetic_names.tns_portals)) - settings.custom_troff_portal = None - if len(file_data) == 0: - return - selected_portal = random.choice(file_data) - settings.custom_troff_portal = selected_portal[1].split("/")[-1] # File Name - im_f = Image.open(BytesIO(bytes(selected_portal[0]))) - im_f = getImageChunk(im_f, 63, 63) - im_f = im_f.transpose(Image.FLIP_TOP_BOTTOM).convert("RGBA") - portal_data = { - "NW": { - "x_min": 0, - "y_min": 0, - "writes": [0x39E, 0x39F], - }, - "SW": { - "x_min": 0, - "y_min": 31, - "writes": [0x3A0, 0x39D], - }, - "SE": { - "x_min": 31, - "y_min": 31, - "writes": [0x3A2, 0x39B], - }, - "NE": { - "x_min": 31, - "y_min": 0, - "writes": [0x39C, 0x3A1], - }, - } - for sub in portal_data.keys(): - x_min = portal_data[sub]["x_min"] - y_min = portal_data[sub]["y_min"] - local_img = im_f.crop((x_min, y_min, x_min + 32, y_min + 32)) - for idx in portal_data[sub]["writes"]: - writeColorImageToROM(local_img, 7, idx, 32, 32, False, TextureFormat.RGBA5551) - - -class PaintingData: - """Class to store information regarding a painting.""" - - def __init__(self, width: int, height: int, x_split: int, y_split: int, is_bordered: bool, texture_order: list, is_ci: bool = False): - """Initialize with given parameters.""" - self.width = width - self.height = height - self.x_split = x_split - self.y_split = y_split - self.is_bordered = is_bordered - self.texture_order = texture_order.copy() - self.name = None - - -def writeCustomPaintings(settings: Settings) -> None: - """Write custom painting files to ROM.""" - if js.cosmetics is None: - return - if js.cosmetics.tns_portals is None: - return - if js.cosmetic_names.tns_portals is None: - return - PAINTING_INFO = [ - PaintingData(64, 64, 2, 1, False, [0x1EA, 0x1E9]), # DK Isles - PaintingData(128, 128, 2, 4, True, [0x90A, 0x909, 0x903, 0x908, 0x904, 0x907, 0x905, 0x906]), # K Rool - PaintingData(128, 128, 2, 4, True, [0x9B4, 0x9AD, 0x9B3, 0x9AE, 0x9B2, 0x9AF, 0x9B1, 0x9B0]), # Knight - PaintingData(128, 128, 2, 4, True, [0x9A5, 0x9AC, 0x9A6, 0x9AB, 0x9A7, 0x9AA, 0x9A8, 0x9A9]), # Sword - PaintingData(64, 32, 1, 1, False, [0xA53]), # Dolphin - PaintingData(32, 64, 1, 1, False, [0xA46]), # Candy - # PaintingData(64, 64, 1, 1, False, [0x614, 0x615], True), # K Rool Run - # PaintingData(64, 64, 1, 1, False, [0x625, 0x626], True), # K Rool Blunderbuss - # PaintingData(64, 64, 1, 1, False, [0x627, 0x628], True), # K Rool Head - ] - file_data = list(zip(js.cosmetics.paintings, js.cosmetic_names.paintings)) - settings.painting_isles = None - settings.painting_museum_krool = None - settings.painting_museum_knight = None - settings.painting_museum_swords = None - settings.painting_treehouse_dolphin = None - settings.painting_treehouse_candy = None - if len(file_data) == 0: - return - list_pool = file_data.copy() - PAINTING_COUNT = len(PAINTING_INFO) - if len(list_pool) < PAINTING_COUNT: - mult = math.ceil(PAINTING_COUNT / len(list_pool)) - 1 - for _ in range(mult): - list_pool.extend(file_data.copy()) - random.shuffle(list_pool) - for painting in PAINTING_INFO: - painting.name = None - selected_painting = list_pool.pop(0) - painting.name = selected_painting[1].split("/")[-1] # File Name - im_f = Image.open(BytesIO(bytes(selected_painting[0]))) - im_f = getImageChunk(im_f, painting.width, painting.height) - im_f = im_f.transpose(Image.FLIP_TOP_BOTTOM).convert("RGBA") - chunks = [] - chunk_w = int(painting.width / painting.x_split) - chunk_h = int(painting.height / painting.y_split) - for y in range(painting.y_split): - for x in range(painting.x_split): - left = x * chunk_w - top = y * chunk_h - chunk_im = im_f.crop((int(left), int(top), int(left + chunk_w), int(top + chunk_h))) - chunks.append(chunk_im) - border_imgs = [] - for x in range(8): - border_tex = PAINTING_INFO[1].texture_order[x] - border_img = getImageFile(25, border_tex, True, 64, 32, TextureFormat.RGBA5551) - border_imgs.append(border_img) - for chunk_index, chunk in enumerate(chunks): - if painting.is_bordered: - border_img = border_imgs[chunk_index] - if chunk_index in (0, 1): - # Top - border_seg_img = border_img.crop((0, 0, 64, 14)) - chunk.paste(border_seg_img, (0, 0), border_seg_img) - if chunk_index in (0, 2, 4, 6): - # Left - border_seg_img = border_img.crop((0, 0, 14, 32)) - chunk.paste(border_seg_img, (0, 0), border_seg_img) - if chunk_index in (1, 3, 5, 7): - # Right - border_seg_img = border_img.crop((50, 0, 64, 32)) - chunk.paste(border_seg_img, (50, 0), border_seg_img) - if chunk_index in (6, 7): - # Bottom - border_seg_img = border_img.crop((0, 20, 64, 32)) - chunk.paste(border_seg_img, (0, 20), border_seg_img) - img_index = painting.texture_order[chunk_index] - writeColorImageToROM(chunk, 25, img_index, chunk_w, chunk_h, False, TextureFormat.RGBA5551) - settings.painting_isles = PAINTING_INFO[0].name - settings.painting_museum_krool = PAINTING_INFO[1].name - settings.painting_museum_knight = PAINTING_INFO[2].name - settings.painting_museum_swords = PAINTING_INFO[3].name - settings.painting_treehouse_dolphin = PAINTING_INFO[4].name - settings.painting_treehouse_candy = PAINTING_INFO[5].name - - def randomizePlants(ROM_COPY: ROM, settings: Settings): """Randomize the plants in the setup file.""" if not settings.misc_cosmetics: diff --git a/randomizer/Patching/Cosmetics/Colorblind.py b/randomizer/Patching/Cosmetics/Colorblind.py new file mode 100644 index 000000000..944b47dd8 --- /dev/null +++ b/randomizer/Patching/Cosmetics/Colorblind.py @@ -0,0 +1,815 @@ +"""All code associated with colorblind mode.""" + +import js +import gzip +import zlib +from randomizer.Settings import ColorblindMode +from randomizer.Patching.LibImage import ( + getRGBFromHash, + TextureFormat, + maskImage, + getImageFile, + getKongItemColor, + writeColorImageToROM, + ExtraTextures, + getBonusSkinOffset, + rgba32to5551, +) +from randomizer.Patching.Lib import getRawFile, TableNames, writeRawFile +from randomizer.Patching.Patcher import ROM +from randomizer.Enums.Kongs import Kongs +from PIL import ImageEnhance + + +def changeVertexColor(num_data: list[int], offset: int, new_color: list[int]) -> list[int]: + """Change the vertex color based on the luminance of the original.""" + total_light = int(num_data[offset] + num_data[offset + 1] + num_data[offset + 2]) + channel_light = int(total_light / 3) + for i in range(3): + num_data[offset + i] = int(channel_light * (new_color[i] / 255)) + return num_data + + +def writeKasplatHairColorToROM(color: str, table_index: TableNames, file_index: int, format: str, ROM_COPY: ROM): + """Write color to ROM for kasplats.""" + file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] + mask = getRGBFromHash(color) + if format == TextureFormat.RGBA32: + color_lst = mask.copy() + color_lst.append(255) # Alpha + null_color = [0] * 4 + else: + color_lst = rgba32to5551(mask) + null_color = [0, 0] + bytes_array = [] + for y in range(42): + for x in range(32): + bytes_array.extend(color_lst) + for i in range(18): + bytes_array.extend(color_lst) + for i in range(4): + bytes_array.extend(null_color) + for i in range(3): + bytes_array.extend(color_lst) + data = bytearray(bytes_array) + if table_index == TableNames.TexturesGeometry: + data = gzip.compress(data, compresslevel=9) + ROM_COPY.seek(file_start) + ROM_COPY.writeBytes(data) + + +def writeWhiteKasplatHairColorToROM(color1: str, color2: str, table_index: TableNames, file_index: int, format: str, ROM_COPY: ROM): + """Write color to ROM for white kasplats, giving them a black-white block pattern.""" + file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] + mask = getRGBFromHash(color1) + mask2 = getRGBFromHash(color2) + if format == TextureFormat.RGBA32: + color_lst_0 = mask.copy() + color_lst_0.append(255) + color_lst_1 = mask2.copy() + color_lst_1.append(255) + null_color = [0] * 4 + else: + color_lst_0 = rgba32to5551(mask) + color_lst_1 = rgba32to5551(mask2) + null_color = [0] * 2 + bytes_array = [] + for y in range(42): + for x in range(32): + if (int(y / 7) + int(x / 8)) % 2 == 0: + bytes_array.extend(color_lst_0) + else: + bytes_array.extend(color_lst_1) + for i in range(18): + bytes_array.extend(color_lst_0) + for i in range(4): + bytes_array.extend(null_color) + for i in range(3): + bytes_array.extend(color_lst_0) + data = bytearray(bytes_array) + if table_index == 25: + data = gzip.compress(data, compresslevel=9) + ROM_COPY.seek(file_start) + ROM_COPY.writeBytes(data) + + +def writeKlaptrapSkinColorToROM(color_index, table_index, file_index, format: str, mode: ColorblindMode, ROM_COPY: ROM): + """Write color to ROM for klaptraps.""" + im_f = getImageFile(table_index, file_index, True, 32, 43, format) + im_f = maskImage(im_f, color_index, 0, (color_index != 3), mode) + pix = im_f.load() + file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] + if format == TextureFormat.RGBA32: + null_color = [0] * 4 + else: + null_color = [0, 0] + bytes_array = [] + for y in range(42): + for x in range(32): + color_lst = calculateKlaptrapPixel(list(pix[x, y]), format) + bytes_array.extend(color_lst) + for i in range(18): + color_lst = calculateKlaptrapPixel(list(pix[i, 42]), format) + bytes_array.extend(color_lst) + for i in range(4): + bytes_array.extend(null_color) + for i in range(3): + color_lst = calculateKlaptrapPixel(list(pix[(22 + i), 42]), format) + bytes_array.extend(color_lst) + data = bytearray(bytes_array) + if table_index == 25: + data = gzip.compress(data, compresslevel=9) + ROM_COPY.seek(file_start) + ROM_COPY.writeBytes(data) + + +def writeSpecialKlaptrapTextureToROM(color_index, table_index, file_index, format: str, pixels_to_ignore: list, mode: ColorblindMode, ROM_COPY: ROM): + """Write color to ROM for klaptraps special texture(s).""" + im_f = getImageFile(table_index, file_index, True, 32, 43, format) + pix_original = im_f.load() + pixels_original = [] + for x in range(32): + pixels_original.append([]) + for y in range(43): + pixels_original[x].append(list(pix_original[x, y]).copy()) + im_f_masked = maskImage(im_f, color_index, 0, (color_index != 3), mode) + pix = im_f_masked.load() + file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] + if format == TextureFormat.RGBA32: + null_color = [0] * 4 + else: + null_color = [0, 0] + bytes_array = [] + for y in range(42): + for x in range(32): + if [x, y] not in pixels_to_ignore: + color_lst = calculateKlaptrapPixel(list(pix[x, y]), format) + else: + color_lst = calculateKlaptrapPixel(list(pixels_original[x][y]), format) + bytes_array.extend(color_lst) + for i in range(18): + if [i, 42] not in pixels_to_ignore: + color_lst = calculateKlaptrapPixel(list(pix[i, 42]), format) + else: + color_lst = calculateKlaptrapPixel(list(pixels_original[i][42]), format) + bytes_array.extend(color_lst) + for i in range(4): + bytes_array.extend(null_color) + for i in range(3): + if [(22 + i), 42] not in pixels_to_ignore: + color_lst = calculateKlaptrapPixel(list(pix[(22 + i), 42]), format) + else: + color_lst = calculateKlaptrapPixel(list(pixels_original[(22 + i)][42]), format) + bytes_array.extend(color_lst) + data = bytearray(bytes_array) + if table_index == 25: + data = gzip.compress(data, compresslevel=9) + ROM_COPY.seek(file_start) + ROM_COPY.writeBytes(data) + + +def calculateKlaptrapPixel(mask: list, format: str): + """Calculate the new color for the given pixel.""" + if format == TextureFormat.RGBA32: + return mask + [255] + return rgba32to5551(mask) + + +def maskBlueprintImage(im_f, base_index, mode: ColorblindMode): + """Apply RGB mask to blueprint image.""" + w, h = im_f.size + im_f_original = im_f + converter = ImageEnhance.Color(im_f) + im_f = converter.enhance(0) + im_dupe = im_f.crop((0, 0, w, h)) + brightener = ImageEnhance.Brightness(im_dupe) + im_dupe = brightener.enhance(2) + im_f.paste(im_dupe, (0, 0), im_dupe) + pix = im_f.load() + pix2 = im_f_original.load() + mask = getKongItemColor(mode, base_index, True) + if max(mask[0], max(mask[1], mask[2])) < 39: + for channel in range(3): + mask[channel] = max(39, mask[channel]) # Too black is bad for these items + w, h = im_f.size + for x in range(w): + for y in range(h): + base = list(pix[x, y]) + base2 = list(pix2[x, y]) + if base[3] > 0: + # Filter out the wooden frame + # brown is orange, is red and (red+green), is very little blue + # but, if the color is light, we can't rely on the blue value alone. + if base2[2] > 20 and (base2[2] > base2[1] or base2[1] - base2[2] < 20): + for channel in range(3): + base[channel] = int(mask[channel] * (base[channel] / 255)) + pix[x, y] = (base[0], base[1], base[2], base[3]) + else: + pix[x, y] = (base2[0], base2[1], base2[2], base2[3]) + return im_f + + +def maskLaserImage(im_f, base_index, mode: ColorblindMode): + """Apply RGB mask to laser texture.""" + w, h = im_f.size + im_f_original = im_f + converter = ImageEnhance.Color(im_f) + im_f = converter.enhance(0) + im_dupe = im_f.crop((0, 0, w, h)) + brightener = ImageEnhance.Brightness(im_dupe) + im_dupe = brightener.enhance(2) + im_f.paste(im_dupe, (0, 0), im_dupe) + pix = im_f.load() + pix2 = im_f_original.load() + mask = getKongItemColor(mode, base_index, True) + w, h = im_f.size + for x in range(w): + for y in range(h): + base = list(pix[x, y]) + base2 = list(pix2[x, y]) + if base[3] > 0: + # Filter out the white center of the laser + if min(base2[0], min(base2[1], base2[2])) <= 210: + for channel in range(3): + base[channel] = int(mask[channel] * (base[channel] / 255)) + pix[x, y] = (base[0], base[1], base[2], base[3]) + else: + pix[x, y] = (base2[0], base2[1], base2[2], base2[3]) + return im_f + + +def maskPotionImage(im_f, primary_color, secondary_color=None): + """Apply RGB mask to DK arcade potion reward preview texture.""" + w, h = im_f.size + pix = im_f.load() + mask = getRGBFromHash(primary_color) + if secondary_color is not None: + mask2 = secondary_color + for channel in range(3): + mask[channel] = max(1, mask[channel]) + w, h = im_f.size + for x in range(w): + for y in range(h): + base = list(pix[x, y]) + # Filter out transparent pixels and the cork + if base[3] > 0 and y > 2 and [x, y] not in [[9, 4], [10, 4]]: + # Filter out the bottle's contents + if base[0] == base[1] and base[1] == base[2]: + if secondary_color is not None: + # Color the bottle itself + for channel in range(3): + base[channel] = int(mask2[channel] * (base[channel] / 255)) + else: + # Color the bottle's contents + average_light = int((base[0] + base[1] + base[2]) / 3) + for channel in range(3): + base[channel] = int(mask[channel] * (average_light / 255)) + pix[x, y] = (base[0], base[1], base[2], base[3]) + return im_f + + +WRINKLY_DOOR_COLOR_1_OFFSETS = [ + 1548, + 1580, + 1612, + 1644, + 1676, + 1708, + 1756, + 1788, + 1804, + 1820, + 1836, + 1852, + 1868, + 1884, + 1900, + 1916, + 1932, + 1948, + 1964, + 1980, + 1996, + 2012, + 2028, + 2044, + 2076, + 2108, + 2124, + 2156, + 2188, + 2220, + 2252, + 2284, + 2316, + 2348, + 2380, + 2396, + 2412, + 2428, + 2444, + 2476, + 2508, + 2540, + 2572, + 2604, + 2636, + 2652, + 2668, + 2684, + 2700, + 2716, + 2732, + 2748, + 2764, + 2780, + 2796, + 2812, + 2828, + 2860, + 2892, + 2924, + 2956, + 2988, + 3020, + 3052, +] +WRINKLY_DOOR_COLOR_2_OFFSETS = [1564, 1596, 1628, 1660, 1692, 1724, 1740, 1772, 2332, 2364, 2460, 2492, 2524, 2556, 2588, 2620] + + +def recolorWrinklyDoors(mode: ColorblindMode, ROM_COPY: ROM): + """Recolor the Wrinkly hint door doorframes for colorblind mode.""" + file = [0xF0, 0xF2, 0xEF, 0x67, 0xF1] + for kong in range(5): + file_index = file[kong] + data = getRawFile(TableNames.ModelTwoGeometry, file_index, True) + num_data = [] # data, but represented as nums rather than b strings + for d in data: + num_data.append(d) + # Figure out which colors to use and where to put them (list extensions to mitigate the linter's "artistic freedom" putting 1 value per line) + color_str = getKongItemColor(mode, kong) + new_color1 = getRGBFromHash(color_str) + new_color2 = getRGBFromHash(color_str) + if kong == 0: + for channel in range(3): + new_color2[channel] = max(80, new_color1[channel]) # Too black is bad, because anything times 0 is 0 + + # Recolor the doorframe + for offset in WRINKLY_DOOR_COLOR_1_OFFSETS: + for i in range(3): + num_data[offset + i] = new_color1[i] + for offset in WRINKLY_DOOR_COLOR_2_OFFSETS: + for i in range(3): + num_data[offset + i] = new_color2[i] + + data = bytearray(num_data) # convert num_data back to binary string + writeRawFile(TableNames.ModelTwoGeometry, file_index, True, data, ROM_COPY) + + +def recolorKRoolShipSwitch(color: tuple, ROM_COPY: ROM): + """Recolors the simian slam switch that is part of K. Rool's ship in galleon.""" + addresses = ( + 0x4C34, + 0x4C44, + 0x4C54, + 0x4C64, + 0x4C74, + 0x4C84, + ) + data = bytearray(getRawFile(TableNames.ModelTwoGeometry, 305, True)) + for addr in addresses: + for x in range(3): + data[addr + x] = color[x] + new_tex = [ + 0xE7000000, + 0x00000000, + 0xE200001C, + 0x0C192038, + 0xE3000A01, + 0x00100000, + 0xE3000F00, + 0x00000000, + 0xE7000000, + 0x00000000, + 0xFC127E03, + 0xFFFFF9F8, + 0xFD900000, + 0x00000BAF, + 0xF5900000, + 0x07080200, + 0xE6000000, + 0x00000000, + 0xF3000000, + 0x077FF100, + 0xE7000000, + 0x00000000, + 0xF5881000, + 0x00080200, + 0xF2000000, + 0x000FC0FC, + ] + for x in range(8): + data[0x1AD8 + x] = 0 + for xi, x in enumerate(new_tex): + for y in range(4): + offset = (xi * 4) + y + shift = 24 - (8 * y) + data[0x1AE8 + offset] = (x >> shift) & 0xFF + for x in range(40): + data[0x1B58 + x] = 0 + writeRawFile(TableNames.ModelTwoGeometry, 305, True, data, ROM_COPY) + + +def recolorSlamSwitches(galleon_switch_value, ROM_COPY: ROM, mode: ColorblindMode): + """Recolor the Simian Slam switches for colorblind mode.""" + file = [0x94, 0x93, 0x95, 0x96, 0xB8, 0x16C, 0x16B, 0x16D, 0x16E, 0x16A, 0x167, 0x166, 0x168, 0x169, 0x165] + written_galleon_ship = False + for switch_index, file_index in enumerate(file): + data = getRawFile(TableNames.ModelTwoGeometry, file_index, True) + num_data = [] # data, but represented as nums rather than b strings + for d in data: + num_data.append(d) + # Figure out which colors to use and where to put them + color_offsets = [1828, 1844, 1860, 1876, 1892, 1908] + new_color1 = getKongItemColor(mode, Kongs.chunky, True) + new_color2 = getKongItemColor(mode, Kongs.lanky, True) + new_color3 = getKongItemColor(mode, Kongs.diddy, True) + + for offset in color_offsets: + # Green switches + if switch_index < 5: + for i in range(3): + num_data[offset + i] = new_color1[i] + # Blue switches + elif switch_index < 10: + for i in range(3): + num_data[offset + i] = new_color2[i] + # Red switches + else: + for i in range(3): + num_data[offset + i] = new_color3[i] + + data = bytearray(num_data) # convert num_data back to binary string + writeRawFile(TableNames.ModelTwoGeometry, file_index, True, data, ROM_COPY) + if not written_galleon_ship: + galleon_switch_color = new_color1.copy() + if galleon_switch_value is not None: + if galleon_switch_value != 1: + galleon_switch_color = new_color3.copy() + if galleon_switch_value == 2: + galleon_switch_color = new_color2.copy() + recolorKRoolShipSwitch(galleon_switch_color, ROM_COPY) + written_galleon_ship = True + + +def recolorBlueprintModelTwo(mode: ColorblindMode, ROM_COPY: ROM): + """Recolor the Blueprint Model2 items for colorblind mode.""" + file = [0xDE, 0xE0, 0xE1, 0xDD, 0xDF] + for kong, file_index in enumerate(file): + data = getRawFile(TableNames.ModelTwoGeometry, file_index, True) + num_data = [] # data, but represented as nums rather than b strings + for d in data: + num_data.append(d) + # Figure out which colors to use and where to put them + color1_offsets = [0x52C, 0x54C, 0x57C, 0x58C, 0x5AC, 0x5CC, 0x5FC, 0x61C] + color2_offsets = [0x53C, 0x55C, 0x5EC, 0x60C] + color3_offsets = [0x56C, 0x59C, 0x5BC, 0x5DC] + color_offsets = color1_offsets + color2_offsets + color3_offsets + new_color = getKongItemColor(mode, kong, True) + if kong == 0: + for channel in range(3): + new_color[channel] = max(39, new_color[channel]) # Too black is bad, because anything times 0 is 0 + + # Recolor the model2 item + for offset in color_offsets: + num_data = changeVertexColor(num_data, offset, new_color) + + data = bytearray(num_data) # convert num_data back to binary string + writeRawFile(TableNames.ModelTwoGeometry, file_index, True, data, ROM_COPY) + + +def maskImageRotatingRoomTile(im_f, im_mask, paste_coords, image_color_index, tile_side, mode: ColorblindMode): + """Apply RGB mask to image of a Rotating Room Memory Tile.""" + w, h = im_f.size + im_original = im_f + pix_original = im_original.load() + pixels_original = [] + for x in range(w): + pixels_original.append([]) + for y in range(h): + pixels_original[x].append(list(pix_original[x, y]).copy()) + converter = ImageEnhance.Color(im_f) + im_f = converter.enhance(0) + brightener = ImageEnhance.Brightness(im_f) + im_f = brightener.enhance(2) + pix = im_f.load() + pix_mask = im_mask.load() + w2, h2 = im_mask.size + mask_coords = [] + for x in range(w2): + for y in range(h2): + coord = list(pix_mask[x, y]) + if coord[3] > 0: + mask_coords.append([(x + paste_coords[0]), (y + paste_coords[1])]) + if image_color_index < 5: + mask = getKongItemColor(mode, image_color_index, True) + for channel in range(3): + mask[channel] = max(39, mask[channel]) # Too dark looks bad + else: + mask = getKongItemColor(mode, Kongs.lanky, True) + mask2 = [0x00, 0x00, 0x00] + if image_color_index == 0: + mask2 = [0xFF, 0xFF, 0xFF] + for x in range(w): + for y in range(h): + base = list(pix[x, y]) + base_original = list(pixels_original[x][y]) + if [x, y] not in mask_coords: + if image_color_index in [1, 2, 4]: # Diddy, Lanky and Chunky don't get any special features + for channel in range(3): + base[channel] = int(mask[channel] * (base[channel] / 255)) + elif image_color_index in [0, 3]: # Donkey and Tiny get a diamond-shape frame + side = w + if tile_side == 1: + side = 0 + if abs(abs(side - x) - y) < 2 or abs(abs(side - x) - abs(h - y)) < 2: + for channel in range(3): + base[channel] = int(mask2[channel] * (base[channel] / 255)) + else: + for channel in range(3): + base[channel] = int(mask[channel] * (base[channel] / 255)) + else: # Golden Banana gets a block-pattern + if (int(x / 8) + int(y / 8)) % 2 == 0: + for channel in range(3): + base[channel] = int(mask[channel] * (base[channel] / 255)) + else: + for channel in range(3): + base[channel] = int(mask2[channel] * (base[channel] / 255)) + else: + for channel in range(3): + base[channel] = base_original[channel] + pix[x, y] = (base[0], base[1], base[2], base[3]) + return im_f + + +def recolorRotatingRoomTiles(mode): + """Determine how to recolor the tiles rom the memory game in Donkey's Rotating Room in Caves.""" + question_mark_tiles = [900, 901, 892, 893, 896, 897, 890, 891, 898, 899, 894, 895] + face_tiles = [ + 874, + 878, + 875, + 879, + 876, + 886, + 877, + 885, + 880, + 887, + 881, + 888, + 870, + 872, + 871, + 873, + 866, + 882, + 867, + 883, + 868, + 889, + 869, + 884, + ] + question_mark_tile_masks = [508, 509] + face_tile_masks = [636, 635, 633, 634, 631, 632, 630, 629, 627, 628, 5478, 5478] + question_mark_resize = [17, 37] + face_resize = [[32, 64], [32, 64], [32, 64], [32, 64], [32, 64], [71, 66]] + question_mark_offsets = [[16, 14], [0, 14]] + face_offsets = [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [-5, -1], [-38, -1]] + + for tile in range(len(question_mark_tiles)): + tile_image = getImageFile(7, question_mark_tiles[tile], False, 32, 64, TextureFormat.RGBA5551) + mask = getImageFile(7, question_mark_tile_masks[(tile % 2)], False, 32, 64, TextureFormat.RGBA5551) + resize = question_mark_resize + mask = mask.resize((resize[0], resize[1])) + masked_tile = maskImageRotatingRoomTile(tile_image, mask, question_mark_offsets[(tile % 2)], int(tile / 2), (tile % 2), mode) + writeColorImageToROM(masked_tile, 7, question_mark_tiles[tile], 32, 64, False, TextureFormat.RGBA5551) + for tile_index, tile in enumerate(face_tiles): + face_index = int(tile_index / 4) + if face_index < 5: + width = 32 + height = 64 + else: + width = 44 + height = 44 + mask = getImageFile(25, face_tile_masks[int(tile_index / 2)], True, width, height, TextureFormat.RGBA5551) + resize = face_resize[face_index] + mask = mask.resize((resize[0], resize[1])) + tile_image = getImageFile(7, tile, False, 32, 64, TextureFormat.RGBA5551) + masked_tile = maskImageRotatingRoomTile(tile_image, mask, face_offsets[int(tile_index / 2)], face_index, (int(tile_index / 2) % 2), mode) + writeColorImageToROM(masked_tile, 7, tile, 32, 64, False, TextureFormat.RGBA5551) + + +def recolorBells(ROM_COPY: ROM): + """Recolor the Chunky Minecart bells for colorblind mode (prot/deut).""" + data = getRawFile(TableNames.ModelTwoGeometry, 693, True) + num_data = [] # data, but represented as nums rather than b strings + for d in data: + num_data.append(d) + # Figure out which colors to use and where to put them + color1_offsets = [0x214, 0x244, 0x264, 0x274, 0x284] + color2_offsets = [0x224, 0x234, 0x254] + new_color1 = [0x00, 0x66, 0xFF] + new_color2 = [0x00, 0x00, 0xFF] + + # Recolor the bell + for offset in color1_offsets: + for i in range(3): + num_data[offset + i] = new_color1[i] + for offset in color2_offsets: + for i in range(3): + num_data[offset + i] = new_color2[i] + + data = bytearray(num_data) # convert num_data back to binary string + writeRawFile(TableNames.ModelTwoGeometry, 693, True, data, ROM_COPY) + + +def recolorKlaptraps(mode, ROM_COPY: ROM): + """Recolor the klaptrap models for colorblind mode.""" + green_files = [0xF31, 0xF32, 0xF33, 0xF35, 0xF37, 0xF39] # 0xF2F collar? 0xF30 feet? + red_files = [0xF44, 0xF45, 0xF46, 0xF47, 0xF48, 0xF49] # , 0xF42 collar? 0xF43 feet? + purple_files = [0xF3C, 0xF3D, 0xF3E, 0xF3F, 0xF40, 0xF41] # 0xF3B feet?, 0xF3A collar? + + # Regular textures + for file in range(6): + writeKlaptrapSkinColorToROM(4, 25, green_files[file], TextureFormat.RGBA5551, mode, ROM_COPY) + writeKlaptrapSkinColorToROM(1, 25, red_files[file], TextureFormat.RGBA5551, mode, ROM_COPY) + writeKlaptrapSkinColorToROM(3, 25, purple_files[file], TextureFormat.RGBA5551, mode, ROM_COPY) + + belly_pixels_to_ignore = [] + for x in range(32): + for y in range(43): + if y < 29 or (y > 31 and y < 39) or y == 40 or y == 42: + belly_pixels_to_ignore.append([x, y]) + elif (y == 39 and x < 16) or (y == 41 and x < 24): + belly_pixels_to_ignore.append([x, y]) + + # Special texture that requires only partial recoloring, in this case file 0xF38 which is the belly, and only the few green pixels + writeSpecialKlaptrapTextureToROM(4, 25, 0xF38, TextureFormat.RGBA5551, belly_pixels_to_ignore, mode, ROM_COPY) + + +def getPotionColor(colorblind_mode: ColorblindMode, kong: Kongs) -> list[int]: + """Get the potion color associated with a kong.""" + diddy_color = getKongItemColor(colorblind_mode, Kongs.diddy, True) + chunky_color = getKongItemColor(colorblind_mode, Kongs.chunky, True) + secondary_color = [diddy_color, None, chunky_color, diddy_color, None, None] + if colorblind_mode == ColorblindMode.trit: + secondary_color[Kongs.donkey] = chunky_color + secondary_color[Kongs.lanky] = None + if kong < 5: + new_color = getKongItemColor(colorblind_mode, kong, True) + else: + new_color = [0xFF, 0xFF, 0xFF] + if secondary_color[kong] is not None: + if kong == Kongs.tiny: + return secondary_color[kong].copy() + elif kong == Kongs.donkey: + return [int(x / 8) for x in secondary_color[kong]] + else: + return [int(x / 4) for x in secondary_color[kong]] + return new_color.copy() + + +def recolorPotions(colorblind_mode: ColorblindMode, ROM_COPY: ROM): + """Overwrite potion colors.""" + # Actor: + file = [[0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2], [0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA]] + for type in range(2): + for potion_color in range(6): + file_index = file[type][potion_color] + data = getRawFile(TableNames.ActorGeometry, file_index, True) + num_data = [] # data, but represented as nums rather than b strings + for d in data: + num_data.append(d) + # Figure out which colors to use and where to put them + color1_offsets = [0x34] + color2_offsets = [0x44, 0x54, 0xA4] + color3_offsets = [0x64, 0x74, 0x84, 0xE4] + color4_offsets = [0x94] + color5_offsets = [0xB4, 0xC4, 0xD4] + color_not_base_offsets = color2_offsets + color3_offsets + color4_offsets + color5_offsets + # color6_offsets = [0xF4, 0x104, 0x114, 0x124, 0x134, 0x144, 0x154, 0x164] + if potion_color < 5: + new_color = getKongItemColor(colorblind_mode, potion_color, True) + else: + new_color = [0xFF, 0xFF, 0xFF] + + # Recolor the actor item + color_applied = getPotionColor(colorblind_mode, potion_color) + for offset in color1_offsets: + num_data = changeVertexColor(num_data, offset, color_applied) + for offset in color_not_base_offsets: + num_data = changeVertexColor(num_data, offset, new_color) + + data = bytearray(num_data) # convert num_data back to binary string + writeRawFile(TableNames.ActorGeometry, file_index, True, data, ROM_COPY) + + # Model2: + file = [91, 498, 89, 499, 501, 502] + for potion_color in range(6): + file_index = file[potion_color] + data = getRawFile(TableNames.ModelTwoGeometry, file_index, True) + num_data = [] # data, but represented as nums rather than b strings + for d in data: + num_data.append(d) + # Figure out which colors to use and where to put them + color1_offsets = [0x144] + color2_offsets = [0x154, 0x164, 0x1B4] + color3_offsets = [0x174, 0x184, 0x194, 0x1F4] + color4_offsets = [0x1A4] + color5_offsets = [0x1C4, 0x1D4, 0x1E4] + color_not_base_offsets = color2_offsets + color3_offsets + color4_offsets + color5_offsets + # color6_offsets = [0x204, 0x214, 0x224, 0x234, 0x244, 0x254, 0x264, 0x274] + if potion_color < 5: + new_color = getKongItemColor(colorblind_mode, potion_color, True) + else: + new_color = [0xFF, 0xFF, 0xFF] + + # Recolor the model2 item + color_applied = getPotionColor(colorblind_mode, potion_color) + for offset in color1_offsets: + num_data = changeVertexColor(num_data, offset, color_applied) + for offset in color_not_base_offsets: + num_data = changeVertexColor(num_data, offset, new_color) + + data = bytearray(num_data) # convert num_data back to binary string + writeRawFile(TableNames.ModelTwoGeometry, file_index, True, data, ROM_COPY) + + return + # DK Arcade sprites + # for file in range(8, 14): + # index = file - 8 + # if index < 5: + # color = getKongItemColor(colorblind_mode, index) + # else: + # color = "#FFFFFF" + # potion_image = getImageFile(6, file, False, 20, 20, TextureFormat.RGBA5551) + # potion_image = maskPotionImage(potion_image, color, secondary_color[index]) + # writeColorImageToROM(potion_image, 6, file, 20, 20, False, TextureFormat.RGBA5551) + + +def maskMushroomImage(im_f, reference_image, color, side_2=False): + """Apply RGB mask to mushroom image.""" + w, h = im_f.size + pixels_to_mask = [] + pix_ref = reference_image.load() + for x in range(w): + for y in range(h): + base_ref = list(pix_ref[x, y]) + # Filter out the white dots that won't get filtered out correctly with the below conditions + if not (max(abs(base_ref[0] - base_ref[2]), abs(base_ref[1] - base_ref[2])) < 41 and abs(base_ref[0] - base_ref[1]) < 11): + # Filter out that one lone pixel that is technically blue AND gets through the above filter, but should REALLY not be blue + if not (side_2 is True and x == 51 and y == 21): + # Select the exact pixels to mask, which is all the "blue" pixels, filtering out the white spots + if base_ref[2] > base_ref[0] and base_ref[2] > base_ref[1] and int(base_ref[0] + base_ref[1]) < 200: + pixels_to_mask.append([x, y]) + # Select the darker blue pixels as well + elif base_ref[2] > int(base_ref[0] + base_ref[1]): + pixels_to_mask.append([x, y]) + pix = im_f.load() + mask = getRGBFromHash(color) + for channel in range(3): + mask[channel] = max(1, mask[channel]) # Absolute black is bad + for x in range(w): + for y in range(h): + base = list(pix[x, y]) + if base[3] > 0 and [x, y] in pixels_to_mask: + average_light = int((base[0] + base[1] + base[2]) / 3) + for channel in range(3): + base[channel] = int(mask[channel] * (average_light / 255)) + pix[x, y] = (base[0], base[1], base[2], base[3]) + return im_f + + +def recolorMushrooms(mode: ColorblindMode): + """Recolor the various colored mushrooms in the game for colorblind mode.""" + reference_mushroom_image = getImageFile(7, 297, False, 32, 32, TextureFormat.RGBA5551) + reference_mushroom_image_side1 = getImageFile(25, 0xD64, True, 64, 32, TextureFormat.RGBA5551) + reference_mushroom_image_side2 = getImageFile(25, 0xD65, True, 64, 32, TextureFormat.RGBA5551) + files_table_7 = [296, 295, 297, 299, 298] + files_table_25_side_1 = [0xD60, getBonusSkinOffset(ExtraTextures.MushTop0), 0xD64, 0xD62, 0xD66] + files_table_25_side_2 = [0xD61, getBonusSkinOffset(ExtraTextures.MushTop1), 0xD65, 0xD63, 0xD67] + for file in range(5): + # Mushroom on the ceiling inside Fungi Forest Lobby + file_color = getKongItemColor(mode, file) + mushroom_image = getImageFile(7, files_table_7[file], False, 32, 32, TextureFormat.RGBA5551) + mushroom_image = maskMushroomImage(mushroom_image, reference_mushroom_image, file_color) + writeColorImageToROM(mushroom_image, 7, files_table_7[file], 32, 32, False, TextureFormat.RGBA5551) + # Mushrooms in Lanky's colored mushroom puzzle (and possibly also the bouncy mushrooms) + mushroom_image_side_1 = getImageFile(25, files_table_25_side_1[file], True, 64, 32, TextureFormat.RGBA5551) + mushroom_image_side_1 = maskMushroomImage(mushroom_image_side_1, reference_mushroom_image_side1, file_color) + writeColorImageToROM(mushroom_image_side_1, 25, files_table_25_side_1[file], 64, 32, False, TextureFormat.RGBA5551) + mushroom_image_side_2 = getImageFile(25, files_table_25_side_2[file], True, 64, 32, TextureFormat.RGBA5551) + mushroom_image_side_2 = maskMushroomImage(mushroom_image_side_2, reference_mushroom_image_side2, file_color, True) + writeColorImageToROM(mushroom_image_side_2, 25, files_table_25_side_2[file], 64, 32, False, TextureFormat.RGBA5551) diff --git a/randomizer/Patching/Cosmetics/CustomTextures.py b/randomizer/Patching/Cosmetics/CustomTextures.py new file mode 100644 index 000000000..c34f8b0e7 --- /dev/null +++ b/randomizer/Patching/Cosmetics/CustomTextures.py @@ -0,0 +1,206 @@ +"""Code associated with custom textures that can be applied through the cosmetic pack.""" + +import js +import random +import math +from io import BytesIO + +from randomizer.Settings import Settings +from randomizer.Patching.LibImage import writeColorImageToROM, TextureFormat, getImageFile +from PIL import Image + + +def writeTransition(settings: Settings) -> None: + """Write transition cosmetic to ROM.""" + if js.cosmetics is None: + return + if js.cosmetics.transitions is None: + return + if js.cosmetic_names.transitions is None: + return + file_data = list(zip(js.cosmetics.transitions, js.cosmetic_names.transitions)) + settings.custom_transition = None + if len(file_data) == 0: + return + selected_transition = random.choice(file_data) + settings.custom_transition = selected_transition[1].split("/")[-1] # File Name + im_f = Image.open(BytesIO(bytes(selected_transition[0]))) + writeColorImageToROM(im_f, 14, 95, 64, 64, False, TextureFormat.IA4) + + +def getImageChunk(im_f, width: int, height: int): + """Get an image chunk based on a width and height.""" + width_height_ratio = width / height + im_w, im_h = im_f.size + im_wh_ratio = im_w / im_h + if im_wh_ratio != width_height_ratio: + # Ratio doesn't match, we have to do some rejigging + scale = 1 + if width_height_ratio > im_wh_ratio: + # Scale based on width + scale = width / im_w + else: + # Height needs growing + scale = height / im_h + im_f = im_f.resize((int(im_w * scale), int(im_h * scale))) + im_w, im_h = im_f.size + middle_w = im_w / 2 + middle_h = im_h / 2 + middle_targ_w = width / 2 + middle_targ_h = height / 2 + return im_f.crop( + ( + int(middle_w - middle_targ_w), + int(middle_h - middle_targ_h), + int(middle_w + middle_targ_w), + int(middle_h + middle_targ_h), + ) + ) + # Ratio matches, just scale up + return im_f.resize((width, height)) + + +def writeCustomPortal(settings: Settings) -> None: + """Write custom portal file to ROM.""" + if js.cosmetics is None: + return + if js.cosmetics.tns_portals is None: + return + if js.cosmetic_names.tns_portals is None: + return + file_data = list(zip(js.cosmetics.tns_portals, js.cosmetic_names.tns_portals)) + settings.custom_troff_portal = None + if len(file_data) == 0: + return + selected_portal = random.choice(file_data) + settings.custom_troff_portal = selected_portal[1].split("/")[-1] # File Name + im_f = Image.open(BytesIO(bytes(selected_portal[0]))) + im_f = getImageChunk(im_f, 63, 63) + im_f = im_f.transpose(Image.FLIP_TOP_BOTTOM).convert("RGBA") + portal_data = { + "NW": { + "x_min": 0, + "y_min": 0, + "writes": [0x39E, 0x39F], + }, + "SW": { + "x_min": 0, + "y_min": 31, + "writes": [0x3A0, 0x39D], + }, + "SE": { + "x_min": 31, + "y_min": 31, + "writes": [0x3A2, 0x39B], + }, + "NE": { + "x_min": 31, + "y_min": 0, + "writes": [0x39C, 0x3A1], + }, + } + for sub in portal_data.keys(): + x_min = portal_data[sub]["x_min"] + y_min = portal_data[sub]["y_min"] + local_img = im_f.crop((x_min, y_min, x_min + 32, y_min + 32)) + for idx in portal_data[sub]["writes"]: + writeColorImageToROM(local_img, 7, idx, 32, 32, False, TextureFormat.RGBA5551) + + +class PaintingData: + """Class to store information regarding a painting.""" + + def __init__(self, width: int, height: int, x_split: int, y_split: int, is_bordered: bool, texture_order: list, is_ci: bool = False): + """Initialize with given parameters.""" + self.width = width + self.height = height + self.x_split = x_split + self.y_split = y_split + self.is_bordered = is_bordered + self.texture_order = texture_order.copy() + self.name = None + + +def writeCustomPaintings(settings: Settings) -> None: + """Write custom painting files to ROM.""" + if js.cosmetics is None: + return + if js.cosmetics.tns_portals is None: + return + if js.cosmetic_names.tns_portals is None: + return + PAINTING_INFO = [ + PaintingData(64, 64, 2, 1, False, [0x1EA, 0x1E9]), # DK Isles + PaintingData(128, 128, 2, 4, True, [0x90A, 0x909, 0x903, 0x908, 0x904, 0x907, 0x905, 0x906]), # K Rool + PaintingData(128, 128, 2, 4, True, [0x9B4, 0x9AD, 0x9B3, 0x9AE, 0x9B2, 0x9AF, 0x9B1, 0x9B0]), # Knight + PaintingData(128, 128, 2, 4, True, [0x9A5, 0x9AC, 0x9A6, 0x9AB, 0x9A7, 0x9AA, 0x9A8, 0x9A9]), # Sword + PaintingData(64, 32, 1, 1, False, [0xA53]), # Dolphin + PaintingData(32, 64, 1, 1, False, [0xA46]), # Candy + # PaintingData(64, 64, 1, 1, False, [0x614, 0x615], True), # K Rool Run + # PaintingData(64, 64, 1, 1, False, [0x625, 0x626], True), # K Rool Blunderbuss + # PaintingData(64, 64, 1, 1, False, [0x627, 0x628], True), # K Rool Head + ] + file_data = list(zip(js.cosmetics.paintings, js.cosmetic_names.paintings)) + settings.painting_isles = None + settings.painting_museum_krool = None + settings.painting_museum_knight = None + settings.painting_museum_swords = None + settings.painting_treehouse_dolphin = None + settings.painting_treehouse_candy = None + if len(file_data) == 0: + return + list_pool = file_data.copy() + PAINTING_COUNT = len(PAINTING_INFO) + if len(list_pool) < PAINTING_COUNT: + mult = math.ceil(PAINTING_COUNT / len(list_pool)) - 1 + for _ in range(mult): + list_pool.extend(file_data.copy()) + random.shuffle(list_pool) + for painting in PAINTING_INFO: + painting.name = None + selected_painting = list_pool.pop(0) + painting.name = selected_painting[1].split("/")[-1] # File Name + im_f = Image.open(BytesIO(bytes(selected_painting[0]))) + im_f = getImageChunk(im_f, painting.width, painting.height) + im_f = im_f.transpose(Image.FLIP_TOP_BOTTOM).convert("RGBA") + chunks = [] + chunk_w = int(painting.width / painting.x_split) + chunk_h = int(painting.height / painting.y_split) + for y in range(painting.y_split): + for x in range(painting.x_split): + left = x * chunk_w + top = y * chunk_h + chunk_im = im_f.crop((int(left), int(top), int(left + chunk_w), int(top + chunk_h))) + chunks.append(chunk_im) + border_imgs = [] + for x in range(8): + border_tex = PAINTING_INFO[1].texture_order[x] + border_img = getImageFile(25, border_tex, True, 64, 32, TextureFormat.RGBA5551) + border_imgs.append(border_img) + for chunk_index, chunk in enumerate(chunks): + if painting.is_bordered: + border_img = border_imgs[chunk_index] + if chunk_index in (0, 1): + # Top + border_seg_img = border_img.crop((0, 0, 64, 14)) + chunk.paste(border_seg_img, (0, 0), border_seg_img) + if chunk_index in (0, 2, 4, 6): + # Left + border_seg_img = border_img.crop((0, 0, 14, 32)) + chunk.paste(border_seg_img, (0, 0), border_seg_img) + if chunk_index in (1, 3, 5, 7): + # Right + border_seg_img = border_img.crop((50, 0, 64, 32)) + chunk.paste(border_seg_img, (50, 0), border_seg_img) + if chunk_index in (6, 7): + # Bottom + border_seg_img = border_img.crop((0, 20, 64, 32)) + chunk.paste(border_seg_img, (0, 20), border_seg_img) + img_index = painting.texture_order[chunk_index] + writeColorImageToROM(chunk, 25, img_index, chunk_w, chunk_h, False, TextureFormat.RGBA5551) + settings.painting_isles = PAINTING_INFO[0].name + settings.painting_museum_krool = PAINTING_INFO[1].name + settings.painting_museum_knight = PAINTING_INFO[2].name + settings.painting_museum_swords = PAINTING_INFO[3].name + settings.painting_treehouse_dolphin = PAINTING_INFO[4].name + settings.painting_treehouse_candy = PAINTING_INFO[5].name diff --git a/randomizer/Patching/Cosmetics/EnemyColors.py b/randomizer/Patching/Cosmetics/EnemyColors.py new file mode 100644 index 000000000..980333c67 --- /dev/null +++ b/randomizer/Patching/Cosmetics/EnemyColors.py @@ -0,0 +1,723 @@ +"""All code changes associated with enemy color rando.""" + +import js +import gzip +import random +from randomizer.Enums.Maps import Maps +from randomizer.Enums.Models import Model +from randomizer.Enums.Settings import RandomModels, ColorblindMode +from randomizer.Patching.LibImage import ( + getBonusSkinOffset, + ExtraTextures, + getRandomHueShift, + getImageFile, + TextureFormat, + hueShift, + hueShiftImageContainer, + maskImageWithColor, + writeColorImageToROM, + getLuma, + hueShiftColor, +) +from randomizer.Patching.Lib import getRawFile, TableNames, getValueFromByteArray +from randomizer.Patching.Patcher import ROM +from PIL import Image + + +def getEnemySwapColor(channel_min: int = 0, channel_max: int = 255, min_channel_variance: int = 0) -> int: + """Get an RGB color compatible with enemy swaps.""" + channels = [] + for _ in range(2): + channels.append(random.randint(channel_min, channel_max)) + min_channel = min(channels[0], channels[1]) + max_channel = max(channels[0], channels[1]) + bounds = [] + if (min_channel - channel_min) >= min_channel_variance: + bounds.append([channel_min, min_channel]) + if (channel_max - max_channel) >= min_channel_variance: + bounds.append([max_channel, channel_max]) + if (len(bounds) == 0) or ((max_channel - min_channel) >= min_channel_variance): + # Default to random number pick + channels.append(random.randint(channel_min, channel_max)) + else: + selected_bound = random.choice(bounds) + channels.append(random.randint(selected_bound[0], selected_bound[1])) + random.shuffle(channels) + value = 0 + for x in range(3): + value <<= 8 + value += channels[x] + return value + + +class EnemyColorSwap: + """Class to store information regarding an enemy color swap.""" + + def __init__(self, search_for: list, forced_color: int = None): + """Initialize with given parameters.""" + self.search_for = search_for.copy() + total_channels = [0] * 3 + for color in self.search_for: + for channel in range(3): + shift = 8 * (2 - channel) + value = (color >> shift) & 0xFF + total_channels[channel] += value + average_channels = [int(x / len(self.search_for)) for x in total_channels] + self.average_color = 0 + for x in average_channels: + self.average_color <<= 8 + self.average_color += x + self.replace_with = forced_color + if forced_color is None: + self.replace_with = getEnemySwapColor(80, min_channel_variance=80) + + def getOutputColor(self, color: int): + """Get output color based on randomization.""" + if color not in self.search_for: + return color + if color == self.search_for[0]: + return self.replace_with + new_color = 0 + total_boost = 0 + for x in range(3): + shift = 8 * (2 - x) + provided_channel = (color >> shift) & 0xFF + primary_channel = (self.search_for[0] >> shift) & 0xFF + boost = 1 # Failsafe for div by 0 + if primary_channel != 0: + boost = provided_channel / primary_channel + total_boost += boost # Used to get an average + for x in range(3): + shift = 8 * (2 - x) + replacement_channel = (self.replace_with >> shift) & 0xFF + replacement_channel = int(replacement_channel * (total_boost / 3)) + if replacement_channel > 255: + replacement_channel = 255 + elif replacement_channel < 0: + replacement_channel = 0 + new_color <<= 8 + new_color += replacement_channel + return new_color + + +# Enemy texture data +FIRE_TEXTURES = ( + [0x1539, 0x1553, 32], # Fireball. RGBA32 32x32 + [0x14B6, 0x14F5, 32], # Fireball. RGBA32 32x32 + [0x1554, 0x155B, 16], # Small Fireball. RGBA32 16x16 + [0x1654, 0x1683, 32], # Fire Wall. RGBA32 32x32 + [0x1495, 0x14A0, 32], # Small Explosion, RGBA32 32x32 + [0x13B9, 0x13C3, 32], # Small Explosion, RGBA32 32x32 +) +KLUMP_JACKET_TEXTURES = [ + {"image": 0x104D, "px": 1372}, + {"image": 0x1058, "px": 1372}, + {"image": 0x1059, "px": 176}, +] +KLUMP_HAT_AMMO_TEXTURES = [ + {"image": 0x104E, "px": 1372}, + {"image": 0x104F, "px": 1372}, + {"image": 0x1050, "px": 1372}, + {"image": 0x1051, "px": 700}, + {"image": 0x1052, "px": 348}, + {"image": 0x1053, "px": 348}, +] +KREMLING_TEXTURE_DIMENSIONS = [ + [32, 64], # FCE + [64, 24], # FCF + [1, 1372], # fd0 + [32, 32], # fd1 + [24, 8], # fd2 + [24, 8], # fd3 + [24, 8], # fd4 + [24, 24], # fd5 + [32, 32], # fd6 + [32, 64], # fd7 + [32, 64], # fd8 + [36, 16], # fd9 + [20, 28], # fda + [32, 32], # fdb + [32, 32], # fdc + [12, 28], # fdd + [64, 24], # fde + [32, 32], # fdf +] +RABBIT_TEXTURE_DIMENSIONS = [ + [1, 1372], # 111A + [1, 1372], # 111B + [1, 700], # 111C + [1, 700], # 111D + [1, 1372], # 111E + [1, 1372], # 111F + [1, 1372], # 1120 + [1, 1404], # 1121 + [1, 348], # 1122 + [32, 64], # 1123 + [1, 688], # 1124 + [64, 32], # 1125 +] +BEANSTALK_TEXTURE_FILE_SIZES = [ + 0x480, + 0x480, + 0x480, + 0x2B8, + 0xAC0, + 0xAB8, + 0xAB8, + 0xAB8, + 0xAB8, + 0xAB8, + 0xAB8, + 0xAB8, + 0xAB8, + 0xAF8, + 0xAB8, + 0xAB8, + 0xAB8, + 0xAF8, + 0x578, + 0xAB8, + 0x578, + 0x5F8, + 0xAB8, + 0xAB8, + 0xAB8, + 0xAB8, + 0x578, + 0xAB8, + 0xAF8, + 0xAB8, + 0xAB8, + 0x560, + 0xAB8, + 0x2B8, +] +SCOFF_TEXTURE_DATA = { + 0xFB8: 0x55C, + 0xFB9: 0x800, + 0xFBA: 0x40, + 0xFBB: 0x800, + 0xFBC: 0x240, + 0xFBD: 0x480, + 0xFBE: 0x80, + 0xFBF: 0x800, + 0xFC0: 0x200, + 0xFC1: 0x240, + 0xFC2: 0x100, + 0xFB2: 0x240, + 0xFB3: 0x800, + 0xFB4: 0x800, + 0xFB5: 0x200, + 0xFB6: 0x200, + 0xFB7: 0x200, +} +TROFF_TEXTURE_DATA = { + 0xF78: 0x800, + 0xF79: 0x800, + 0xF7A: 0x800, + 0xF7B: 0x800, + 0xF7C: 0x800, + 0xF7D: 0x400, + 0xF7E: 0x600, + 0xF7F: 0x400, + 0xF80: 0x800, + 0xF81: 0x600, + 0xF82: 0x400, + 0xF83: 0x400, + 0xF84: 0x800, + 0xF85: 0x800, + 0xF86: 0x280, + 0xF87: 0x180, + 0xF88: 0x800, + 0xF89: 0x800, + 0xF8A: 0x400, + 0xF8B: 0x300, + 0xF8C: 0x800, + 0xF8D: 0x400, + 0xF8E: 0x500, + 0xF8F: 0x180, +} +SPIDER_TEXTURE_DIMENSIONS = { + 0x110A: (32, 64), + 0x110B: (32, 64), + 0x110C: (32, 64), + 0x110D: (64, 16), + 0x110E: (32, 64), + 0x110F: (32, 64), + 0x1110: (32, 64), + 0x1111: (32, 64), + 0x1112: (32, 64), + 0x1113: (16, 32), + 0x1114: (32, 32), + 0x1115: (32, 32), + 0x1116: (32, 32), + 0x1117: (64, 16), + 0x1118: (64, 32), + 0x1119: (64, 32), +} + + +def convertColorIntToTuple(color: int) -> tuple: + """Convert color stored as 3-byte int to tuple.""" + return ((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF) + + +def adjustFungiMushVertexColor(shift: int): + """Adjust the special vertex coloring on Fungi Giant Mushroom.""" + fungi_geo = bytearray(getRawFile(TableNames.MapGeometry, Maps.FungiForest, True)) + DEFAULT_MUSHROOM_COLOR = (255, 90, 82) + for x in range(0x27DA, 0x2839): + start = 0x25140 + (x * 0x10) + 0xC + channels = [] + is_zero = True + for y in range(3): + val = fungi_geo[start + y] + if val != 0: + is_zero = False + channels.append(val) + if is_zero: + continue + visual_color = [int((x / 255) * DEFAULT_MUSHROOM_COLOR[xi]) for xi, x in enumerate(channels)] + luma = int(getLuma(visual_color)) + # Diversify shading + luma -= 128 + luma = int(luma * 1.2) + luma += 128 + # Brighten + luma += 60 + # Clamp + if luma < 0: + luma = 0 + elif luma > 255: + luma = 255 + # Apply shading + for y in range(3): + fungi_geo[start + y] = luma + fungi_geo[start + 3] = 0xFF + file_data = gzip.compress(fungi_geo, compresslevel=9) + ROM_COPY = ROM() + ROM_COPY.seek(js.pointer_addresses[TableNames.MapGeometry]["entries"][Maps.FungiForest]["pointing_to"]) + ROM_COPY.writeBytes(file_data) + + +def writeMiscCosmeticChanges(settings): + """Write miscellaneous changes to the cosmetic colors.""" + ROM_COPY = ROM() + if settings.override_cosmetics: + enemy_setting = RandomModels[js.document.getElementById("random_enemy_colors").value] + else: + enemy_setting = settings.random_enemy_colors + if settings.misc_cosmetics: + # Melon HUD + data = { + 7: [[0x13C, 0x147]], + 14: [[0x5A, 0x5D]], + 25: [ + [getBonusSkinOffset(ExtraTextures.MelonSurface), getBonusSkinOffset(ExtraTextures.MelonSurface)], + [0x144B, 0x1452], + ], + } + shift = getRandomHueShift() + for table in data: + table_data = data[table] + for set in table_data: + for img in range(set[0], set[1] + 1): + if table == 25: + dims = (32, 32) + else: + dims = (48, 42) + melon_im = getImageFile(table, img, table != 7, dims[0], dims[1], TextureFormat.RGBA5551) + melon_im = hueShift(melon_im, shift) + melon_px = melon_im.load() + bytes_array = [] + for y in range(dims[1]): + for x in range(dims[0]): + pix_data = list(melon_px[x, y]) + red = int((pix_data[0] >> 3) << 11) + green = int((pix_data[1] >> 3) << 6) + blue = int((pix_data[2] >> 3) << 1) + alpha = int(pix_data[3] != 0) + value = red | green | blue | alpha + bytes_array.extend([(value >> 8) & 0xFF, value & 0xFF]) + px_data = bytearray(bytes_array) + if table != 7: + px_data = gzip.compress(px_data, compresslevel=9) + ROM_COPY.seek(js.pointer_addresses[table]["entries"][img]["pointing_to"]) + ROM_COPY.writeBytes(px_data) + + # Shockwave Particles + shockwave_shift = getRandomHueShift() + for img_index in range(0x174F, 0x1757): + hueShiftImageContainer(25, img_index, 16, 16, TextureFormat.RGBA32, shockwave_shift, ROM_COPY) + if settings.colorblind_mode == ColorblindMode.off: + # Fire-based sprites + fire_shift = getRandomHueShift() + for sprite_data in FIRE_TEXTURES: + for img_index in range(sprite_data[0], sprite_data[1] + 1): + dim = sprite_data[2] + hueShiftImageContainer(25, img_index, dim, dim, TextureFormat.RGBA32, fire_shift, ROM_COPY) + for img_index in range(0x29, 0x32 + 1): + hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA32, fire_shift, ROM_COPY) + for img_index in range(0x250, 0x26F + 1): + hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA32, fire_shift, ROM_COPY) + for img_index in range(0xA0, 0xA7 + 1): + hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA5551, fire_shift, ROM_COPY) + # Blue Fire + for img_index in range(129, 138 + 1): + hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA32, fire_shift, ROM_COPY) + # Number Game Numbers + colors = [getRandomHueShift() for _ in range(2)] + vanilla_blue = [1, 3, 6, 8, 11, 14, 15, 16] + for x in range(16): + number_hue_shift = colors[0] + if (x + 1) in vanilla_blue: + number_hue_shift = colors[1] + for sub_img in range(2): + img_index = 0x1FE + (2 * x) + sub_img + hueShiftImageContainer(7, img_index, 32, 32, TextureFormat.RGBA5551, number_hue_shift, ROM_COPY) + hueShiftImageContainer(25, 0xC2D, 32, 32, TextureFormat.RGBA5551, colors[1], ROM_COPY) + hueShiftImageContainer(25, 0xC2E, 32, 32, TextureFormat.RGBA5551, colors[0], ROM_COPY) + boulder_shift = getRandomHueShift() + hueShiftImageContainer(25, 0x12F4, 1, 1372, TextureFormat.RGBA5551, boulder_shift, ROM_COPY) + for img_index in range(2): + hueShiftImageContainer(25, 0xDE1 + img_index, 32, 64, TextureFormat.RGBA5551, boulder_shift, ROM_COPY) + + if enemy_setting != RandomModels.off: + # Barrel Enemy Skins - Random + klobber_shift = getRandomHueShift(0, 300) + kaboom_shift = getRandomHueShift() + for img_index in range(3): + px_count = 1404 if img_index < 2 else 1372 + hueShiftImageContainer(25, 0xF12 + img_index, 1, px_count, TextureFormat.RGBA5551, klobber_shift, ROM_COPY) + hueShiftImageContainer(25, 0xF22 + img_index, 1, px_count, TextureFormat.RGBA5551, kaboom_shift, ROM_COPY) + if img_index < 2: + hueShiftImageContainer(25, 0xF2B + img_index, 1, px_count, TextureFormat.RGBA5551, kaboom_shift, ROM_COPY) + # Klump + klump_jacket_shift = getRandomHueShift() + klump_hatammo_shift = getRandomHueShift() + + for img_data in KLUMP_JACKET_TEXTURES: + hueShiftImageContainer(25, img_data["image"], 1, img_data["px"], TextureFormat.RGBA5551, klump_jacket_shift, ROM_COPY) + for img_data in KLUMP_HAT_AMMO_TEXTURES: + hueShiftImageContainer(25, img_data["image"], 1, img_data["px"], TextureFormat.RGBA5551, klump_hatammo_shift, ROM_COPY) + # Kosha + kosha_shift = getRandomHueShift() + hueShiftImageContainer(25, 0x1232, 1, 348, TextureFormat.RGBA5551, kosha_shift, ROM_COPY) + hueShiftImageContainer(25, 0x1235, 1, 348, TextureFormat.RGBA5551, kosha_shift, ROM_COPY) + if enemy_setting == RandomModels.extreme: + kosha_helmet_int = getEnemySwapColor(80, min_channel_variance=80) + kosha_helmet_list = [ + (kosha_helmet_int >> 16) & 0xFF, + (kosha_helmet_int >> 8) & 0xFF, + kosha_helmet_int & 0xFF, + ] + kosha_club_int = getEnemySwapColor(80, min_channel_variance=80) + kosha_club_list = [(kosha_club_int >> 16) & 0xFF, (kosha_club_int >> 8) & 0xFF, kosha_club_int & 0xFF] + for img in range(0x122E, 0x1230): + kosha_im = getImageFile(25, img, True, 1, 1372, TextureFormat.RGBA5551) + kosha_im = maskImageWithColor(kosha_im, tuple(kosha_helmet_list)) + writeColorImageToROM(kosha_im, 25, img, 1, 1372, False, TextureFormat.RGBA5551) + for img in range(0x1229, 0x122C): + kosha_im = getImageFile(25, img, True, 1, 1372, TextureFormat.RGBA5551) + kosha_im = maskImageWithColor(kosha_im, tuple(kosha_club_list)) + writeColorImageToROM(kosha_im, 25, img, 1, 1372, False, TextureFormat.RGBA5551) + if settings.colorblind_mode == ColorblindMode.off: + # Kremling + + while True: + kremling_shift = getRandomHueShift() + # Block red coloring + if kremling_shift > 290: + break + if kremling_shift > -70 and kremling_shift < 228: + break + if kremling_shift < -132: + break + for dim_index, dims in enumerate(KREMLING_TEXTURE_DIMENSIONS): + if dims is not None: + hueShiftImageContainer(25, 0xFCE + dim_index, dims[0], dims[1], TextureFormat.RGBA5551, kremling_shift, ROM_COPY) + # Rabbit + + rabbit_shift = getRandomHueShift() + for dim_index, dims in enumerate(RABBIT_TEXTURE_DIMENSIONS): + if dims is not None: + hueShiftImageContainer(25, 0x111A + dim_index, dims[0], dims[1], TextureFormat.RGBA5551, rabbit_shift, ROM_COPY) + # Snake + snake_shift = getRandomHueShift() + for x in range(2): + hueShiftImageContainer(25, 0xEF7 + x, 32, 32, TextureFormat.RGBA5551, snake_shift, ROM_COPY) + # Headphones Sprite + headphones_shift = getRandomHueShift() + for x in range(8): + hueShiftImageContainer(7, 0x3D3 + x, 40, 40, TextureFormat.RGBA5551, headphones_shift, ROM_COPY) + # Instruments + trombone_sax_shift = getRandomHueShift() + hueShiftImageContainer(25, 0xEA2, 32, 32, TextureFormat.RGBA5551, trombone_sax_shift) # Shin, ROM_COPYe + hueShiftImageContainer(25, 0x15AF, 40, 40, TextureFormat.RGBA5551, trombone_sax_shift) # Trombone Ico, ROM_COPYn + hueShiftImageContainer(25, 0x15AD, 40, 40, TextureFormat.RGBA5551, trombone_sax_shift) # Sax Ico, ROM_COPYn + hueShiftImageContainer(25, 0xBCC, 32, 64, TextureFormat.RGBA5551, trombone_sax_shift) # Sax (Pad, ROM_COPY) + hueShiftImageContainer(25, 0xBCD, 32, 64, TextureFormat.RGBA5551, trombone_sax_shift) # Sax (Pad, ROM_COPY) + hueShiftImageContainer(25, 0xBD0, 32, 64, TextureFormat.RGBA5551, trombone_sax_shift) # Trombone (Pad, ROM_COPY) + hueShiftImageContainer(25, 0xBD1, 32, 64, TextureFormat.RGBA5551, trombone_sax_shift) # Trombone (Pad, ROM_COPY) + triangle_shift = getRandomHueShift() + hueShiftImageContainer(25, 0xEBF, 32, 32, TextureFormat.RGBA5551, triangle_shift) # Shin, ROM_COPYe + hueShiftImageContainer(25, 0x15AE, 40, 40, TextureFormat.RGBA5551, triangle_shift) # Triangle Ico, ROM_COPYn + hueShiftImageContainer(25, 0xBCE, 32, 64, TextureFormat.RGBA5551, triangle_shift) # Triangle (Pad, ROM_COPY) + hueShiftImageContainer(25, 0xBCF, 32, 64, TextureFormat.RGBA5551, triangle_shift) # Triangle (Pad, ROM_COPY) + bongo_shift = getRandomHueShift() + hueShiftImageContainer(25, 0x1317, 1, 1372, TextureFormat.RGBA5551, bongo_shift) # Ski, ROM_COPYn + hueShiftImageContainer(25, 0x1318, 1, 1404, TextureFormat.RGBA5551, bongo_shift) # Sid, ROM_COPYe + hueShiftImageContainer(25, 0x1319, 1, 1404, TextureFormat.RGBA5551, bongo_shift) # Side , ROM_COPY2 + hueShiftImageContainer(25, 0x15AC, 40, 40, TextureFormat.RGBA5551, bongo_shift) # Bongo Ico, ROM_COPYn + hueShiftImageContainer(25, 0xBC8, 32, 64, TextureFormat.RGBA5551, bongo_shift) # Bongo (Pad, ROM_COPY) + hueShiftImageContainer(25, 0xBC9, 32, 64, TextureFormat.RGBA5551, bongo_shift) # Bongo (Pad, ROM_COPY) + if enemy_setting == RandomModels.extreme: + # Beanstalk + beanstalk_shift = getRandomHueShift() + for index, size in enumerate(BEANSTALK_TEXTURE_FILE_SIZES): + hueShiftImageContainer(25, 0x1126 + index, 1, int(size >> 1), TextureFormat.RGBA5551, beanstalk_shift, ROM_COPY) + # Fairy Particles Sprites + fairy_particles_shift = getRandomHueShift() + for x in range(0xB): + hueShiftImageContainer(25, 0x138D + x, 32, 32, TextureFormat.RGBA32, fairy_particles_shift, ROM_COPY) + race_coin_shift = getRandomHueShift() + for x in range(8): + hueShiftImageContainer(7, 0x1F0 + x, 48, 42, TextureFormat.RGBA5551, race_coin_shift, ROM_COPY) + scoff_shift = getRandomHueShift() + troff_shift = getRandomHueShift() + + for img in SCOFF_TEXTURE_DATA: + hueShiftImageContainer(25, img, 1, SCOFF_TEXTURE_DATA[img], TextureFormat.RGBA5551, scoff_shift, ROM_COPY) + + # Scoff had too many bananas, and passed potassium poisoning onto Troff + # https://i.imgur.com/WFDLSzA.png + # for img in TROFF_TEXTURE_DATA: + # hueShiftImageContainer(25, img, 1, TROFF_TEXTURE_DATA[img], TextureFormat.RGBA5551, troff_shift, ROM_COPY) + # Krobot + spinner_shift = getRandomHueShift() + hueShiftImageContainer(25, 0xFA9, 1, 1372, TextureFormat.RGBA5551, spinner_shift, ROM_COPY) + krobot_textures = [[[1, 1372], [0xFAF, 0xFAA, 0xFA8, 0xFAB, 0xFAD]], [[32, 32], [0xFAC, 0xFB1, 0xFAE, 0xFB0]]] + krobot_color_int = getEnemySwapColor(80, min_channel_variance=80) + krobot_color_list = [(krobot_color_int >> 16) & 0xFF, (krobot_color_int >> 8) & 0xFF, krobot_color_int & 0xFF] + for tex_set in krobot_textures: + for tex in tex_set[1]: + krobot_im = getImageFile(25, tex, True, tex_set[0][0], tex_set[0][1], TextureFormat.RGBA5551) + krobot_im = maskImageWithColor(krobot_im, tuple(krobot_color_list)) + writeColorImageToROM(krobot_im, 25, tex, tex_set[0][0], tex_set[0][1], False, TextureFormat.RGBA5551) + # Jetman + for xi, x in enumerate(settings.jetman_color): + ROM_COPY.seek(settings.rom_data + 0x1E8 + xi) + ROM_COPY.writeMultipleBytes(x, 1) + # Blast Barrels + blast_shift = getRandomHueShift() + hueShiftImageContainer(25, 0x127E, 1, 1372, TextureFormat.RGBA5551, blast_shift, ROM_COPY) + for x in range(4): + hueShiftImageContainer(25, 0x127F + x, 16, 64, TextureFormat.RGBA5551, blast_shift, ROM_COPY) + hueShiftImageContainer(25, getBonusSkinOffset(ExtraTextures.BlastTop), 1, 1372, TextureFormat.RGBA5551, blast_shift, ROM_COPY) + # K Rool + red_cs_im = Image.new(mode="RGBA", size=(32, 32), color=convertColorIntToTuple(getEnemySwapColor())) + shorts_im = Image.new(mode="RGBA", size=(32, 32), color=convertColorIntToTuple(getEnemySwapColor())) + glove_im = Image.new(mode="RGBA", size=(32, 32), color=convertColorIntToTuple(getEnemySwapColor())) + krool_data = { + 0x1149: red_cs_im, + 0x1261: shorts_im, + 0xDA8: glove_im, + } + if enemy_setting == RandomModels.extreme: + skin_im = Image.new(mode="RGBA", size=(32, 32), color=convertColorIntToTuple(getEnemySwapColor(80, min_channel_variance=80))) + krool_data[0x114A] = skin_im + krool_data[0x114D] = skin_im + for index in krool_data: + writeColorImageToROM(krool_data[index], 25, index, 32, 32, False, TextureFormat.RGBA5551) + toe_shift = getRandomHueShift() + hueShiftImageContainer(25, 0x126E, 1, 1372, TextureFormat.RGBA5551, toe_shift, ROM_COPY) + hueShiftImageContainer(25, 0x126F, 1, 1372, TextureFormat.RGBA5551, toe_shift, ROM_COPY) + if enemy_setting == RandomModels.extreme: + gold_shift = getRandomHueShift() + hueShiftImageContainer(25, 0x1265, 32, 32, TextureFormat.RGBA5551, gold_shift, ROM_COPY) + hueShiftImageContainer(25, 0x1148, 32, 32, TextureFormat.RGBA5551, gold_shift, ROM_COPY) + # Ghost + ghost_shift = getRandomHueShift() + for img in range(0x119D, 0x11AF): + px_count = 1372 + if img == 0x119E: + px_count = 176 + elif img == 0x11AC: + px_count = 688 + hueShiftImageContainer(25, img, 1, px_count, TextureFormat.RGBA5551, ghost_shift, ROM_COPY) + # Funky + funky_shift = getRandomHueShift() + hueShiftImageContainer(25, 0xECF, 1, 1372, TextureFormat.RGBA5551, funky_shift, ROM_COPY) + hueShiftImageContainer(25, 0xED6, 1, 1372, TextureFormat.RGBA5551, funky_shift, ROM_COPY) + hueShiftImageContainer(25, 0xEDF, 1, 1372, TextureFormat.RGBA5551, funky_shift, ROM_COPY) + # Zinger + zinger_shift = getRandomHueShift() + zinger_color = hueShiftColor((0xFF, 0xFF, 0x0A), zinger_shift) + zinger_color_int = (zinger_color[0] << 16) | (zinger_color[1] << 8) | (zinger_color[2]) + hueShiftImageContainer(25, 0xF0A, 1, 1372, TextureFormat.RGBA5551, zinger_shift, ROM_COPY) + # Mechazinger, use zinger color + for img_index in (0x10A0, 0x10A2, 0x10A4, 0x10A5): + hueShiftImageContainer(25, img_index, 1, 1372, TextureFormat.RGBA5551, zinger_shift, ROM_COPY) + hueShiftImageContainer(25, 0x10A3, 32, 32, TextureFormat.RGBA32, zinger_shift, ROM_COPY) + # Rings/DK Star + ring_shift = getRandomHueShift() + for x in range(2): + hueShiftImageContainer(25, 0xE1C + x, 1, 344, TextureFormat.RGBA5551, ring_shift, ROM_COPY) + hueShiftImageContainer(25, 0xD38 + x, 64, 32, TextureFormat.RGBA5551, ring_shift, ROM_COPY) + hueShiftImageContainer(7, 0x2EB, 32, 32, TextureFormat.RGBA5551, ring_shift, ROM_COPY) + # Buoys + for x in range(2): + hueShiftImageContainer(25, 0x133A + x, 1, 1372, TextureFormat.RGBA5551, getRandomHueShift(), ROM_COPY) + # Trap Bubble + hueShiftImageContainer(25, 0x134C, 32, 32, TextureFormat.RGBA5551, getRandomHueShift(), ROM_COPY) + # Spider + spider_shift = getRandomHueShift() + + for img_index in SPIDER_TEXTURE_DIMENSIONS: + hueShiftImageContainer, ROM_COPY( + 25, + img_index, + SPIDER_TEXTURE_DIMENSIONS[img_index][0], + SPIDER_TEXTURE_DIMENSIONS[img_index][1], + TextureFormat.RGBA5551, + spider_shift, + ) + + if enemy_setting == RandomModels.extreme: + # Army Dillo + dillo_px_count = { + 0x102D: 64 * 32, + 0x103A: 16 * 16, + 0x102A: 24 * 24, + 0x102B: 24 * 24, + 0x102C: 1372, + 0x103D: 688, + 0x103E: 688, + } + dillo_shift = getRandomHueShift() + for img, px_count in dillo_px_count.items(): + hueShiftImageContainer(25, img, 1, px_count, TextureFormat.RGBA5551, dillo_shift, ROM_COPY) + + # Mushrooms + mush_man_shift = getRandomHueShift() + for img_index in (0x11FC, 0x11FD, 0x11FE, 0x11FF, 0x1200, 0x1209, 0x120A, 0x120B): + hueShiftImageContainer(25, img_index, 1, 1372, TextureFormat.RGBA5551, mush_man_shift, ROM_COPY) + for img_index in (0x11F8, 0x1205): + hueShiftImageContainer(25, img_index, 1, 692, TextureFormat.RGBA5551, mush_man_shift, ROM_COPY) + for img_index in (0x67F, 0x680): + hueShiftImageContainer(25, img_index, 32, 64, TextureFormat.RGBA5551, mush_man_shift, ROM_COPY) + hueShiftImageContainer(25, 0x6F3, 4, 4, TextureFormat.RGBA5551, mush_man_shift, ROM_COPY) + adjustFungiMushVertexColor(mush_man_shift) + + # Enemy Vertex Swaps + blue_beaver_color = getEnemySwapColor(80, min_channel_variance=80) + enemy_changes = { + Model.BeaverBlue_LowPoly: EnemyColorSwap([0xB2E5FF, 0x65CCFF, 0x00ABE8, 0x004E82, 0x008BD1, 0x001333, 0x1691CE], blue_beaver_color), # Primary + Model.BeaverBlue: EnemyColorSwap([0xB2E5FF, 0x65CCFF, 0x00ABE8, 0x004E82, 0x008BD1, 0x001333, 0x1691CE], blue_beaver_color), # Primary + Model.BeaverGold: EnemyColorSwap([0xFFE5B2, 0xFFCC65, 0xE8AB00, 0x824E00, 0xD18B00, 0x331300, 0xCE9116]), # Primary + Model.Zinger: EnemyColorSwap([0xFFFF0A, 0xFF7F00], zinger_color_int), # Legs + Model.RoboZinger: EnemyColorSwap([0xFFFF00, 0xFF5500], zinger_color_int), # Legs + Model.Candy: EnemyColorSwap( + [ + 0xFF96EB, + 0x572C58, + 0xB86CAA, + 0xEB4C91, + 0x8B2154, + 0xD13B80, + 0xFF77C1, + 0xFF599E, + 0x7F1E4C, + 0x61173A, + 0x902858, + 0xA42E64, + 0x791C49, + 0x67183E, + 0x9E255C, + 0xC12E74, + 0x572C58, + 0xFF96EB, + 0xB86CAA, + ] + ), + Model.Laser: EnemyColorSwap([0xF30000]), + Model.Kasplat: EnemyColorSwap([0x8FD8FF, 0x182A4F, 0x0B162C, 0x7A98D3, 0x3F6CC4, 0x8FD8FF, 0x284581]), + # Model.BananaFairy: EnemyColorSwap([0xFFD400, 0xFFAA00, 0xFCD200, 0xD68F00, 0xD77D0A, 0xe49800, 0xdf7f1f, 0xa26c00, 0xd6b200, 0xdf9f1f]) + } + if enemy_setting == RandomModels.extreme: + enemy_changes[Model.Klump] = EnemyColorSwap([0xE66B78, 0x621738, 0x300F20, 0xD1426F, 0xA32859]) + dogadon_color = getEnemySwapColor(80, 160, min_channel_variance=80) + enemy_changes[Model.Dogadon] = EnemyColorSwap( + [ + 0xFF0000, + 0xFF7F00, + 0x450A1F, + 0xB05800, + 0xFF3200, + 0xFFD400, + 0x4F260D, + 0x600F00, + 0x6A1400, + 0xAA0000, + 0xDF3F1F, + 0xFF251F, + 0x8F4418, + 0x522900, + 0xDF9F1F, + 0x3B0606, + 0x91121E, + 0x700C0D, + 0xFF5900, + 0xFF7217, + 0xFF7425, + 0xFF470B, + 0xA82100, + 0x4A0D18, + 0x580E00, + 0x461309, + 0x4C1503, + 0x780D0E, + 0xFFA74A, + 0x7E120F, + 0x700000, + 0xB64D19, + 0x883A13, + 0xBD351A, + 0xD42900, + 0xFF2A00, + 0x921511, + 0x9C662D, + 0xDF5F1F, + 0x9B1112, + 0x461F0A, + 0x4B0808, + 0x500809, + 0xA42000, + 0x5F0B13, + 0xBF6A3F, + 0x602E10, + 0x971414, + 0x422C15, + 0xFC5800, + 0x5C0D0B, + ], + dogadon_color, + ) + for enemy in enemy_changes: + file_data = bytearray(getRawFile(5, enemy, True)) + vert_start = 0x28 + file_head = getValueFromByteArray(file_data, 0, 4) + disp_list_end = (getValueFromByteArray(file_data, 4, 4) - file_head) + 0x28 + vert_end = (getValueFromByteArray(file_data, disp_list_end, 4) - file_head) + 0x28 + vert_count = int((vert_end - vert_start) / 0x10) + for vert in range(vert_count): + local_start = 0x28 + (0x10 * vert) + test_rgb = getValueFromByteArray(file_data, local_start + 0xC, 3) + new_rgb = enemy_changes[enemy].getOutputColor(test_rgb) + for x in range(3): + shift = 8 * (2 - x) + channel = (new_rgb >> shift) & 0xFF + file_data[local_start + 0xC + x] = channel + file_data = gzip.compress(file_data, compresslevel=9) + ROM_COPY.seek(js.pointer_addresses[5]["entries"][enemy]["pointing_to"]) + ROM_COPY.writeBytes(file_data) diff --git a/randomizer/Patching/Cosmetics/Holiday.py b/randomizer/Patching/Cosmetics/Holiday.py new file mode 100644 index 000000000..625d4b79e --- /dev/null +++ b/randomizer/Patching/Cosmetics/Holiday.py @@ -0,0 +1,239 @@ +"""All code associated with temporary holiday-based cosmetic effects.""" + +import gzip +import js +from PIL import Image, ImageEnhance +from randomizer.Patching.Patcher import ROM +from randomizer.Patching.Lib import Holidays, getHoliday +from randomizer.Patching.LibImage import ( + getImageFile, + getBonusSkinOffset, + ExtraTextures, + TextureFormat, + maskImageWithColor, + writeColorImageToROM, + hueShift, + hueShiftImageContainer, +) +from randomizer.Settings import CharacterColors, KongModels + + +def changeBarrelColor(barrel_color: tuple = None, metal_color: tuple = None, brighten_barrel: bool = False): + """Change the colors of the various barrels.""" + wood_img = getImageFile(25, getBonusSkinOffset(ExtraTextures.ShellWood), True, 32, 64, TextureFormat.RGBA5551) + metal_img = getImageFile(25, getBonusSkinOffset(ExtraTextures.ShellMetal), True, 32, 64, TextureFormat.RGBA5551) + qmark_img = getImageFile(25, getBonusSkinOffset(ExtraTextures.ShellQMark), True, 32, 64, TextureFormat.RGBA5551) + if barrel_color is not None: + if brighten_barrel: + enhancer = ImageEnhance.Brightness(wood_img) + wood_img = enhancer.enhance(2) + wood_img = maskImageWithColor(wood_img, barrel_color) + if metal_color is not None: + metal_img = maskImageWithColor(metal_img, metal_color) + wood_img.paste(metal_img, (0, 0), metal_img) + writeColorImageToROM(wood_img, 25, getBonusSkinOffset(ExtraTextures.BonusShell), 32, 64, False, TextureFormat.RGBA5551) # Bonus Barrel + tag_img = Image.new(mode="RGBA", size=(32, 64)) + tag_img.paste(wood_img, (0, 0), wood_img) + tag_img.paste(qmark_img, (0, 0), qmark_img) + writeColorImageToROM(tag_img, 25, 4938, 32, 64, False, TextureFormat.RGBA5551) # Tag Barrel + # Compose Transform Barrels + kongs = [ + {"face_left": 0x27C, "face_right": 0x27B, "barrel_tex_start": 4817, "targ_width": 24}, # DK + {"face_left": 0x279, "face_right": 0x27A, "barrel_tex_start": 4815, "targ_width": 24}, # Diddy + {"face_left": 0x277, "face_right": 0x278, "barrel_tex_start": 4819, "targ_width": 24}, # Lanky + {"face_left": 0x276, "face_right": 0x275, "barrel_tex_start": 4769, "targ_width": 24}, # Tiny + {"face_left": 0x273, "face_right": 0x274, "barrel_tex_start": 4747, "targ_width": 24}, # Chunky + ] + for kong in kongs: + bar_left = Image.new(mode="RGBA", size=(32, 64)) + bar_right = Image.new(mode="RGBA", size=(32, 64)) + face_left = getImageFile(25, kong["face_left"], True, 32, 64, TextureFormat.RGBA5551) + face_right = getImageFile(25, kong["face_right"], True, 32, 64, TextureFormat.RGBA5551) + width = kong["targ_width"] + height = width * 2 + face_left = face_left.resize((width, height)) + face_right = face_right.resize((width, height)) + right_w_offset = 32 - width + top_h_offset = (64 - height) >> 1 + bar_left.paste(wood_img, (0, 0), wood_img) + bar_right.paste(wood_img, (0, 0), wood_img) + bar_left.paste(face_left, (right_w_offset, top_h_offset), face_left) + bar_right.paste(face_right, (0, top_h_offset), face_right) + writeColorImageToROM(bar_left, 25, kong["barrel_tex_start"], 32, 64, False, TextureFormat.RGBA5551) + writeColorImageToROM(bar_right, 25, kong["barrel_tex_start"] + 1, 32, 64, False, TextureFormat.RGBA5551) + # Cannons + barrel_left = Image.new(mode="RGBA", size=(32, 64)) + barrel_right = Image.new(mode="RGBA", size=(32, 64)) + barrel_left.paste(wood_img, (0, 0), wood_img) + barrel_right.paste(wood_img, (0, 0), wood_img) + barrel_left = barrel_left.crop((0, 0, 16, 64)) + barrel_right = barrel_right.crop((16, 0, 32, 64)) + writeColorImageToROM(barrel_left, 25, 0x12B3, 16, 64, False, TextureFormat.RGBA5551) + writeColorImageToROM(barrel_right, 25, 0x12B4, 16, 64, False, TextureFormat.RGBA5551) + if barrel_color is not None: + tex_data = { + getBonusSkinOffset(ExtraTextures.RocketTop): (1, 1372), + 0x12B5: (48, 32), + 0x12B8: (44, 44), + } + for img in tex_data: + dim_x = tex_data[img][0] + dim_y = tex_data[img][1] + img_output = getImageFile(25, img, True, dim_x, dim_y, TextureFormat.RGBA5551) + img_output = maskImageWithColor(img_output, barrel_color) + writeColorImageToROM(img_output, 25, img, dim_x, dim_y, False, TextureFormat.RGBA5551) + + +def applyCelebrationRims(hue_shift: int, enabled_bananas: list[bool] = [False, False, False, False, False]): + """Retexture the warp pad rims to have a more celebratory tone.""" + banana_textures = [] + vanilla_banana_textures = [0xA8, 0x98, 0xE8, 0xD0, 0xF0] + for kong_index, ban in enumerate(enabled_bananas): + if ban: + banana_textures.append(vanilla_banana_textures[kong_index]) + place_bananas = False + if len(banana_textures) > 0: + place_bananas = True + if len(banana_textures) < 4: + banana_textures = (banana_textures * 4)[:4] + if place_bananas: + bananas = [getImageFile(7, x, False, 44, 44, TextureFormat.RGBA5551).resize((14, 14)) for x in banana_textures] + banana_placement = [ + # File, x, y + [0xBB3, 15, 1], # 3 + [0xBB2, 2, 1], # 2 + [0xBB3, 0, 1], # 4 + [0xBB2, 17, 1], # 1 + ] + for img in (0xBB2, 0xBB3): + side_im = getImageFile(25, img, True, 32, 16, TextureFormat.RGBA5551) + hueShift(side_im, hue_shift) + if place_bananas: + for bi, banana in enumerate(bananas): + if banana_placement[bi][0] == img: + b_x = banana_placement[bi][1] + b_y = banana_placement[bi][2] + side_im.paste(banana, (b_x, b_y), banana) + side_by = [] + side_px = side_im.load() + for y in range(16): + for x in range(32): + red_short = (side_px[x, y][0] >> 3) & 31 + green_short = (side_px[x, y][1] >> 3) & 31 + blue_short = (side_px[x, y][2] >> 3) & 31 + alpha_short = 1 if side_px[x, y][3] > 128 else 0 + value = (red_short << 11) | (green_short << 6) | (blue_short << 1) | alpha_short + side_by.extend([(value >> 8) & 0xFF, value & 0xFF]) + px_data = bytearray(side_by) + px_data = gzip.compress(px_data, compresslevel=9) + ROM().seek(js.pointer_addresses[25]["entries"][img]["pointing_to"]) + ROM().writeBytes(px_data) + + +def applyHolidayMode(settings): + """Change grass texture to snow.""" + HOLIDAY = getHoliday(settings) + if HOLIDAY == Holidays.no_holiday: + changeBarrelColor() # Fixes some Krusha stuff + return + if HOLIDAY == Holidays.Christmas: + # Set season to Christmas + ROM().seek(settings.rom_data + 0xDB) + ROM().writeMultipleBytes(2, 1) + # Grab Snow texture, transplant it + ROM().seek(0x1FF8000) + snow_im = Image.new(mode="RGBA", size=((32, 32))) + snow_px = snow_im.load() + snow_by = [] + for y in range(32): + for x in range(32): + rgba_px = int.from_bytes(ROM().readBytes(2), "big") + red = ((rgba_px >> 11) & 31) << 3 + green = ((rgba_px >> 6) & 31) << 3 + blue = ((rgba_px >> 1) & 31) << 3 + alpha = (rgba_px & 1) * 255 + snow_px[x, y] = (red, green, blue, alpha) + for dim in (32, 16, 8, 4): + snow_im = snow_im.resize((dim, dim)) + px = snow_im.load() + for y in range(dim): + for x in range(dim): + rgba_data = list(px[x, y]) + data = 0 + for c in range(3): + data |= (rgba_data[c] >> 3) << (1 + (5 * c)) + if rgba_data[3] != 0: + data |= 1 + snow_by.extend([(data >> 8), (data & 0xFF)]) + byte_data = gzip.compress(bytearray(snow_by), compresslevel=9) + for img in (0x4DD, 0x4E4, 0x6B, 0xF0, 0x8B2, 0x5C2, 0x66E, 0x66F, 0x685, 0x6A1, 0xF8, 0x136): + start = js.pointer_addresses[25]["entries"][img]["pointing_to"] + ROM().seek(start) + ROM().writeBytes(byte_data) + # Alter CI4 Palettes + start = js.pointer_addresses[25]["entries"][2007]["pointing_to"] + mags = [140, 181, 156, 181, 222, 206, 173, 230, 255, 255, 255, 189, 206, 255, 181, 255] + new_ci4_palette = [] + for mag in mags: + comp_mag = mag >> 3 + data = (comp_mag << 11) | (comp_mag << 6) | (comp_mag << 1) | 1 + new_ci4_palette.extend([(data >> 8), (data & 0xFF)]) + byte_data = gzip.compress(bytearray(new_ci4_palette), compresslevel=9) + ROM().seek(start) + ROM().writeBytes(byte_data) + # Alter rims + applyCelebrationRims(50, [True, True, True, True, False]) + # Change DK's Tie and Tiny's Hair + if settings.dk_tie_colors != CharacterColors.custom and settings.kong_model_dk == KongModels.default: + tie_hang = [0xFF] * 0xAB8 + tie_hang_data = gzip.compress(bytearray(tie_hang), compresslevel=9) + ROM().seek(js.pointer_addresses[25]["entries"][0xE8D]["pointing_to"]) + ROM().writeBytes(tie_hang_data) + tie_loop = [0xFF] * (32 * 32 * 2) + tie_loop_data = gzip.compress(bytearray(tie_loop), compresslevel=9) + ROM().seek(js.pointer_addresses[25]["entries"][0x177D]["pointing_to"]) + ROM().writeBytes(tie_loop_data) + if settings.tiny_hair_colors != CharacterColors.custom and settings.kong_model_tiny == KongModels.default: + tiny_hair = [] + for x in range(32 * 32): + tiny_hair.extend([0xF8, 0x01]) + tiny_hair_data = gzip.compress(bytearray(tiny_hair), compresslevel=9) + ROM().seek(js.pointer_addresses[25]["entries"][0xE68]["pointing_to"]) + ROM().writeBytes(tiny_hair_data) + # Tag Barrel, Bonus Barrel & Transform Barrels + changeBarrelColor(None, (0x00, 0xC0, 0x00)) + elif HOLIDAY == Holidays.Halloween: + ROM().seek(settings.rom_data + 0xDB) + ROM().writeMultipleBytes(1, 1) + # Pad Rim + applyCelebrationRims(-12) + # Tag Barrel, Bonus Barrel & Transform Barrels + changeBarrelColor((0x00, 0xC0, 0x00)) + # Turn Ice Tomato Orange + sizes = { + 0x1237: 700, + 0x1238: 1404, + 0x1239: 1372, + 0x123A: 1372, + 0x123B: 692, + 0x123C: 1372, + 0x123D: 1372, + 0x123E: 1372, + 0x123F: 1372, + 0x1240: 1372, + 0x1241: 1404, + } + for img in range(0x1237, 0x1241 + 1): + hueShiftImageContainer(25, img, 1, sizes[img], TextureFormat.RGBA5551, 240) + elif HOLIDAY == Holidays.Anniv25: + changeBarrelColor((0xFF, 0xFF, 0x00), None, True) + sticker_im = getImageFile(25, getBonusSkinOffset(ExtraTextures.Anniv25Sticker), True, 1, 1372, TextureFormat.RGBA5551) + vanilla_sticker_im = getImageFile(25, 0xB7D, True, 1, 1372, TextureFormat.RGBA5551) + sticker_im_snipped = sticker_im.crop((0, 0, 1, 1360)) + writeColorImageToROM(sticker_im_snipped, 25, 0xB7D, 1, 1360, False, TextureFormat.RGBA5551) + vanilla_sticker_portion = vanilla_sticker_im.crop((0, 1360, 1, 1372)) + new_im = Image.new(mode="RGBA", size=(1, 1372)) + new_im.paste(sticker_im_snipped, (0, 0), sticker_im_snipped) + new_im.paste(vanilla_sticker_portion, (0, 1360), vanilla_sticker_portion) + writeColorImageToROM(new_im, 25, 0x1266, 1, 1372, False, TextureFormat.RGBA5551) + applyCelebrationRims(0, [False, True, True, True, True]) diff --git a/randomizer/Patching/Cosmetics/KongColor.py b/randomizer/Patching/Cosmetics/KongColor.py new file mode 100644 index 000000000..2382ea6a2 --- /dev/null +++ b/randomizer/Patching/Cosmetics/KongColor.py @@ -0,0 +1,259 @@ +"""All code related to changing the color of kongs.""" + +import js +import zlib +import gzip +import random +from randomizer.Enums.Kongs import Kongs +from randomizer.Enums.Settings import CharacterColors, KongModels +from randomizer.Settings import Settings +from randomizer.Patching.Lib import PaletteFillType, int_to_list, getRawFile, writeRawFile, TableNames +from randomizer.Patching.LibImage import getKongItemColor +from randomizer.Patching.generate_kong_color_images import convertColors +from randomizer.Patching.Cosmetics.Krusha import kong_index_mapping +from randomizer.Patching.Cosmetics.ModelSwaps import model_texture_sections +from randomizer.Patching.Patcher import LocalROM + +DEFAULT_COLOR = "#000000" + + +class KongPalette: + """Class to store information regarding a kong palette.""" + + def __init__(self, name: str, image: int, fill_type: PaletteFillType, alt_name: str = None): + """Initialize with given parameters.""" + self.name = name + self.image = image + self.fill_type = fill_type + self.alt_name = alt_name + if alt_name is None: + self.alt_name = name + + +class KongPaletteSetting: + """Class to store information regarding the kong palette setting.""" + + def __init__(self, kong: str, kong_index: int, palettes: list[KongPalette]): + """Initialize with given parameters.""" + self.kong = kong + self.kong_index = kong_index + self.palettes = palettes.copy() + self.setting_kong = kong + + +krusha_texture_replacement = { + # Textures Krusha can use when he replaces various kongs (Main color, belt color) + Kongs.donkey: (3724, 0x177D), + Kongs.diddy: (4971, 4966), + Kongs.lanky: (3689, 0xE9A), + Kongs.tiny: (6014, 0xE68), + Kongs.chunky: (3687, 3778), +} + +KONG_ZONES = { + "DK": ["Fur", "Tie"], + "Diddy": ["Clothes"], + "Lanky": ["Clothes", "Fur"], + "Tiny": ["Clothes", "Hair"], + "Chunky": ["Main", "Other"], + "Rambi": ["Skin"], + "Enguarde": ["Skin"], +} + + +def writeKongColors(settings: Settings): + """Write kong colors based on the settings.""" + color_palettes = [] + color_obj = {} + colors_dict = {} + kong_settings = [ + KongPaletteSetting( + "dk", + 0, + [ + KongPalette("fur", 3724, PaletteFillType.block), + KongPalette("tie", 0x177D, PaletteFillType.block), + KongPalette("tie", 0xE8D, PaletteFillType.patch), + ], + ), + KongPaletteSetting( + "diddy", + 1, + [ + KongPalette("clothes", 3686, PaletteFillType.block), + ], + ), + KongPaletteSetting( + "lanky", + 2, + [ + KongPalette("clothes", 3689, PaletteFillType.block), + KongPalette("clothes", 3734, PaletteFillType.patch), + KongPalette("fur", 0xE9A, PaletteFillType.block), + KongPalette("fur", 0xE94, PaletteFillType.block), + ], + ), + KongPaletteSetting( + "tiny", + 3, + [ + KongPalette("clothes", 6014, PaletteFillType.block), + KongPalette("hair", 0xE68, PaletteFillType.block), + ], + ), + KongPaletteSetting( + "chunky", + 4, + [ + KongPalette("main", 3769, PaletteFillType.checkered, "other"), + KongPalette("main", 3687, PaletteFillType.block), + ], + ), + KongPaletteSetting( + "rambi", + 5, + [ + KongPalette("skin", 3826, PaletteFillType.block), + ], + ), + KongPaletteSetting( + "enguarde", + 6, + [ + KongPalette("skin", 3847, PaletteFillType.block), + ], + ), + ] + + if js.document.getElementById("override_cosmetics").checked or True: + if js.document.getElementById("random_colors").checked: + for kong in KONG_ZONES: + for zone in KONG_ZONES[kong]: + settings.__setattr__(f"{kong.lower()}_{zone.lower()}_colors", CharacterColors.randomized) + else: + for kong in KONG_ZONES: + for zone in KONG_ZONES[kong]: + settings.__setattr__( + f"{kong.lower()}_{zone.lower()}_colors", + CharacterColors[js.document.getElementById(f"{kong.lower()}_{zone.lower()}_colors").value], + ) + settings.__setattr__( + f"{kong.lower()}_{zone.lower()}_custom_color", + js.document.getElementById(f"{kong.lower()}_{zone.lower()}_custom_color").value, + ) + else: + if settings.random_colors: + for kong in KONG_ZONES: + for zone in KONG_ZONES[kong]: + settings.__setattr__(f"{kong.lower()}_{zone.lower()}_colors", CharacterColors.randomized) + + colors_dict = {} + for kong in KONG_ZONES: + for zone in KONG_ZONES[kong]: + colors_dict[f"{kong.lower()}_{zone.lower()}_colors"] = settings.__getattribute__(f"{kong.lower()}_{zone.lower()}_colors") + colors_dict[f"{kong.lower()}_{zone.lower()}_custom_color"] = settings.__getattribute__(f"{kong.lower()}_{zone.lower()}_custom_color") + for kong in kong_settings: + if kong.kong_index == 4: + if settings.kong_model_chunky == KongModels.disco_chunky: + kong.palettes = [ + KongPalette("main", 3777, PaletteFillType.sparkle), + KongPalette("other", 3778, PaletteFillType.sparkle), + ] + settings_values = [ + settings.kong_model_dk, + settings.kong_model_diddy, + settings.kong_model_lanky, + settings.kong_model_tiny, + settings.kong_model_chunky, + ] + if kong.kong_index >= 0 and kong.kong_index < len(settings_values): + if settings_values[kong.kong_index] in model_texture_sections: + base_setting = kong.palettes[0].name + kong.palettes = [ + KongPalette(base_setting, krusha_texture_replacement[kong.kong_index][0], PaletteFillType.block), # krusha_skin + KongPalette(base_setting, krusha_texture_replacement[kong.kong_index][1], PaletteFillType.kong), # krusha_indicator + ] + base_obj = {"kong": kong.kong, "zones": []} + zone_to_colors = {} + for palette in kong.palettes: + arr = [DEFAULT_COLOR] + if palette.fill_type == PaletteFillType.checkered: + arr = ["#FFFF00", "#00FF00"] + elif palette.fill_type == PaletteFillType.kong: + arr = [getKongItemColor(settings.colorblind_mode, kong.kong_index)] + zone_data = { + "zone": palette.name, + "image": palette.image, + "fill_type": palette.fill_type, + "colors": arr, + } + for index in range(len(arr)): + base_setting = f"{kong.kong}_{palette.name}_colors" + custom_setting = f"{kong.kong}_{palette.name}_custom_color" + if index == 1: # IS THE CHECKERED PATTERN + base_setting = f"{kong.kong}_{palette.alt_name}_colors" + custom_setting = f"{kong.kong}_{palette.alt_name}_custom_color" + if (settings.override_cosmetics and colors_dict[base_setting] != CharacterColors.vanilla) or (palette.fill_type == PaletteFillType.kong): + color = None + # if this palette color is randomized, and isn't krusha's kong indicator: + if colors_dict[base_setting] == CharacterColors.randomized and palette.fill_type != PaletteFillType.kong: + if base_setting in zone_to_colors: + color = zone_to_colors[base_setting] + else: + color = f"#{format(random.randint(0, 0xFFFFFF), '06x')}" + zone_to_colors[base_setting] = color + # if this palette color is not randomized (but might be a custom color) and isn't krusha's kong indicator: + elif palette.fill_type != PaletteFillType.kong: + color = colors_dict[custom_setting] + if not color: + color = DEFAULT_COLOR + # if this is krusha's kong indicator: + else: + color = getKongItemColor(settings.colorblind_mode, kong.kong_index) + if color is not None: + zone_data["colors"][index] = color + base_obj["zones"].append(zone_data) + color_palettes.append(base_obj) + color_obj[f"{kong.kong} {palette.name}"] = color + settings.colors = color_obj + if len(color_palettes) > 0: + # this is just to prune the duplicates that appear. someone should probably fix the root of the dupe issue tbh + new_color_palettes = [] + for pal in color_palettes: + if pal not in new_color_palettes: + new_color_palettes.append(pal) + convertColors(new_color_palettes) + + +def changeModelTextures(settings: Settings, kong_index: int): + """Change the textures associated with a model.""" + settings_values = [ + settings.kong_model_dk, + settings.kong_model_diddy, + settings.kong_model_lanky, + settings.kong_model_tiny, + settings.kong_model_chunky, + ] + if kong_index < 0 or kong_index >= len(settings_values): + return + model = settings_values[kong_index] + if model not in model_texture_sections: + return + ROM_COPY = LocalROM() + for x in range(2): + file = kong_index_mapping[kong_index][x] + if file is None: + continue + data = getRawFile(TableNames.ActorGeometry, file, True) + num_data = [] # data, but represented as nums rather than b strings + for d in data: + num_data.append(d) + # Retexture for colors + for tex_idx in model_texture_sections[model]["skin"]: + for di, d in enumerate(int_to_list(krusha_texture_replacement[kong_index][0], 2)): # Main + num_data[tex_idx + di] = d + for tex_idx in model_texture_sections[model]["kong"]: + for di, d in enumerate(int_to_list(krusha_texture_replacement[kong_index][1], 2)): # Belt + num_data[tex_idx + di] = d + data = bytearray(num_data) # convert num_data back to binary string + writeRawFile(TableNames.ActorGeometry, file, True, data, ROM_COPY) diff --git a/randomizer/Patching/Cosmetics/Krusha.py b/randomizer/Patching/Cosmetics/Krusha.py new file mode 100644 index 000000000..ef560f3f6 --- /dev/null +++ b/randomizer/Patching/Cosmetics/Krusha.py @@ -0,0 +1,173 @@ +"""All code associated with Krusha.""" + +import js +import zlib +import gzip +from typing import TYPE_CHECKING +from randomizer.Settings import Settings +from randomizer.Enums.Settings import ColorblindMode +from randomizer.Patching.Lib import TableNames, getObjectAddress, float_to_hex, intf_to_float, int_to_list, getRawFile, writeRawFile +from randomizer.Patching.Patcher import LocalROM +from randomizer.Patching.LibImage import ( + writeColorImageToROM, + getImageFile, + getBonusSkinOffset, + ExtraTextures, + TextureFormat, +) +from randomizer.Enums.Kongs import Kongs +from PIL import Image + +DK_SCALE = 0.75 +GENERIC_SCALE = 0.49 +krusha_scaling = [ + # [x, y, z, xz, y] + # DK + [ + lambda x: x * DK_SCALE, + lambda x: x * DK_SCALE, + lambda x: x * GENERIC_SCALE, + lambda x: x * DK_SCALE, + lambda x: x * DK_SCALE, + ], + # Diddy + [ + lambda x: (x * 1.043) - 41.146, + lambda x: (x * 9.893) - 8.0, + lambda x: x * GENERIC_SCALE, + lambda x: (x * 1.103) - 14.759, + lambda x: (x * 0.823) + 35.220, + ], + # Lanky + [ + lambda x: (x * 0.841) - 17.231, + lambda x: (x * 6.925) - 2.0, + lambda x: x * GENERIC_SCALE, + lambda x: (x * 0.680) - 18.412, + lambda x: (x * 0.789) + 42.138, + ], + # Tiny + [ + lambda x: (x * 0.632) + 7.590, + lambda x: (x * 6.925) + 0.0, + lambda x: x * GENERIC_SCALE, + lambda x: (x * 1.567) - 21.676, + lambda x: (x * 0.792) + 41.509, + ], + # Chunky + [lambda x: x, lambda x: x, lambda x: x, lambda x: x, lambda x: x], +] + + +def readListAsInt(arr: list, start: int, size: int) -> int: + """Read list and convert to int.""" + val = 0 + for i in range(size): + val = (val * 256) + arr[start + i] + return val + + +kong_index_mapping = { + # Regular model, instrument model + Kongs.donkey: (3, None), + Kongs.diddy: (0, 1), + Kongs.lanky: (5, 6), + Kongs.tiny: (8, 9), + Kongs.chunky: (11, 12), +} + + +def fixModelSmallKongCollision(kong_index: int, ROM_COPY: LocalROM): + """Modify Krusha Model to be smaller to enable him to fit through smaller gaps.""" + for x in range(2): + file = kong_index_mapping[kong_index][x] + if file is None: + continue + data = getRawFile(TableNames.ActorGeometry, file, True) + num_data = [] # data, but represented as nums rather than b strings + for d in data: + num_data.append(d) + head = readListAsInt(num_data, 0, 4) + ptr = readListAsInt(num_data, 0xC, 4) + base = (ptr - head) + 0x28 + 8 + count_0 = readListAsInt(num_data, base, 4) + changes = krusha_scaling[kong_index][:3] + changes_0 = [ + krusha_scaling[kong_index][3], + krusha_scaling[kong_index][4], + krusha_scaling[kong_index][3], + ] + for i in range(count_0): + i_start = base + 4 + (i * 0x14) + for coord_index, change in enumerate(changes): + val_i = readListAsInt(num_data, i_start + (4 * coord_index) + 4, 4) + val_f = change(intf_to_float(val_i)) + val_i = int(float_to_hex(val_f), 16) + for di, d in enumerate(int_to_list(val_i, 4)): + num_data[i_start + (4 * coord_index) + 4 + di] = d + section_2_start = base + 4 + (count_0 * 0x14) + count_1 = readListAsInt(num_data, section_2_start, 4) + for i in range(count_1): + i_start = section_2_start + 4 + (i * 0x10) + for coord_index, change in enumerate(changes_0): + val_i = readListAsInt(num_data, i_start + (4 * coord_index), 4) + val_f = change(intf_to_float(val_i)) + val_i = int(float_to_hex(val_f), 16) + for di, d in enumerate(int_to_list(val_i, 4)): + num_data[i_start + (4 * coord_index) + di] = d + data = bytearray(num_data) # convert num_data back to binary string + writeRawFile(TableNames.ActorGeometry, file, True, data, ROM_COPY) + + +def fixBaboonBlasts(ROM_COPY: LocalROM): + """Fix various baboon blasts to work for Krusha.""" + # Fungi Baboon Blast + for id in (2, 5): + item_start = getObjectAddress(0xBC, id, "actor") + if item_start is not None: + ROM_COPY.seek(item_start + 0x14) + ROM_COPY.writeMultipleBytes(0xFFFFFFEC, 4) + ROM_COPY.seek(item_start + 0x1B) + ROM_COPY.writeMultipleBytes(0, 1) + # Caves Baboon Blast + item_start = getObjectAddress(0xBA, 4, "actor") + if item_start is not None: + ROM_COPY.seek(item_start + 0x4) + ROM_COPY.writeMultipleBytes(int(float_to_hex(510), 16), 4) + item_start = getObjectAddress(0xBA, 12, "actor") + if item_start is not None: + ROM_COPY.seek(item_start + 0x4) + ROM_COPY.writeMultipleBytes(int(float_to_hex(333), 16), 4) + # Castle Baboon Blast + item_start = getObjectAddress(0xBB, 4, "actor") + if item_start is not None: + ROM_COPY.seek(item_start + 0x0) + ROM_COPY.writeMultipleBytes(int(float_to_hex(2472), 16), 4) + ROM_COPY.seek(item_start + 0x8) + ROM_COPY.writeMultipleBytes(int(float_to_hex(1980), 16), 4) + + +def placeKrushaHead(settings: Settings, slot): + """Replace a kong's face with the Krusha face.""" + if settings.colorblind_mode != ColorblindMode.off: + return + + kong_face_textures = [[0x27C, 0x27B], [0x279, 0x27A], [0x277, 0x278], [0x276, 0x275], [0x273, 0x274]] + unc_face_textures = [[579, 586], [580, 587], [581, 588], [582, 589], [577, 578]] + krushaFace64 = getImageFile(TableNames.TexturesGeometry, getBonusSkinOffset(ExtraTextures.KrushaFace1 + slot), True, 64, 64, TextureFormat.RGBA5551) + krushaFace64Left = krushaFace64.crop([0, 0, 32, 64]) + krushaFace64Right = krushaFace64.crop([32, 0, 64, 64]) + # Used in File Select, Pause Menu, Tag Barrels, Switches, Transformation Barrels + writeColorImageToROM(krushaFace64Left, 25, kong_face_textures[slot][0], 32, 64, False, TextureFormat.RGBA5551) + writeColorImageToROM(krushaFace64Right, 25, kong_face_textures[slot][1], 32, 64, False, TextureFormat.RGBA5551) + # Used in Troff and Scoff + writeColorImageToROM(krushaFace64Left, 7, unc_face_textures[slot][0], 32, 64, False, TextureFormat.RGBA5551) + writeColorImageToROM(krushaFace64Right, 7, unc_face_textures[slot][1], 32, 64, False, TextureFormat.RGBA5551) + + krushaFace32 = krushaFace64.resize((32, 32)) + krushaFace32 = krushaFace32.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + krushaFace32RBGA32 = getImageFile(TableNames.TexturesGeometry, getBonusSkinOffset(ExtraTextures.KrushaFace321 + slot), True, 32, 32, TextureFormat.RGBA32) + # Used in the DPad Selection Menu + writeColorImageToROM(krushaFace32, 14, 190 + slot, 32, 32, False, TextureFormat.RGBA5551) + # Used in Shops Previews + writeColorImageToROM(krushaFace32RBGA32, 14, 197 + slot, 32, 32, False, TextureFormat.RGBA32) diff --git a/randomizer/Patching/Cosmetics/ModelSwaps.py b/randomizer/Patching/Cosmetics/ModelSwaps.py new file mode 100644 index 000000000..6623483df --- /dev/null +++ b/randomizer/Patching/Cosmetics/ModelSwaps.py @@ -0,0 +1,501 @@ +"""All code associated with model swaps.""" + +import random +import js +from randomizer.Enums.Models import Model, Sprite +from randomizer.Enums.Maps import Maps +from randomizer.Enums.Settings import KongModels, RandomModels +from randomizer.Settings import Settings +from randomizer.Patching.Patcher import ROM +from randomizer.Patching.Lib import applyCharacterSpawnerChanges, SpawnerChange + +turtle_models = [ + Model.Diddy, # Diddy + Model.DK, # DK + Model.Lanky, # Lanky + Model.Tiny, # Tiny + Model.Chunky, # Regular Chunky + Model.ChunkyDisco, # Disco Chunky + Model.Cranky, # Cranky + Model.Funky, # Funky + Model.Candy, # Candy + Model.Seal, # Seal + Model.Enguarde, # Enguarde + Model.BeaverBlue_LowPoly, # Beaver + Model.Squawks_28, # Squawks + Model.KlaptrapGreen, # Klaptrap Green + Model.KlaptrapPurple, # Klaptrap Purple + Model.KlaptrapRed, # Klaptrap Red + Model.KlaptrapTeeth, # Klaptrap Teeth + Model.SirDomino, # Sir Domino + Model.MrDice_41, # Mr Dice + Model.Beetle, # Beetle + Model.NintendoLogo, # N64 Logo + Model.MechanicalFish, # Mech Fish + Model.ToyCar, # Toy Car + Model.BananaFairy, # Fairy + Model.Shuri, # Starfish + Model.Gimpfish, # Gimpfish + Model.Spider, # Spider + Model.Rabbit, # Rabbit + Model.KRoolCutscene, # K Rool + Model.SkeletonHead, # Skeleton Head + Model.Vulture_76, # Vulture + Model.Vulture_77, # Racing Vulture + Model.Tomato, # Tomato + Model.Fly, # Fly + Model.SpotlightFish, # Spotlight Fish + Model.Puftup, # Pufftup + Model.CuckooBird, # Cuckoo Bird + Model.IceTomato, # Ice Tomato + Model.Boombox, # Boombox + Model.KRoolFight, # K Rool (Boxing) + Model.Microphone, # Microbuffer + Model.DeskKRool, # K Rool's Desk + Model.Bell, # Bell + Model.BonusBarrel, # Bonus Barrel + Model.HunkyChunkyBarrel, # HC Barrel + Model.MiniMonkeyBarrel, # MM Barrel + Model.TNTBarrel, # TNT Barrel + Model.Rocketbarrel, # RB Barrel + Model.StrongKongBarrel, # SK Barrel + Model.OrangstandSprintBarrel, # OSS Barrel + Model.BBBSlot_143, # BBB Slot + Model.PlayerCar, # Tiny Car + Model.Boulder, # Boulder + Model.Boat_158, # Boat + Model.Potion, # Potion + Model.ArmyDilloMissle, # AD Missile + Model.TagBarrel, # Tag Barrel + Model.QuestionMark, # Question Mark + Model.Krusha, # Krusha + Model.BananaPeel, # Banana Peel + Model.Butterfly, # Butterfly + Model.FunkyGun, # Funky's Gun +] + +panic_models = [ + Model.Diddy, # Diddy + Model.DK, # DK + Model.Lanky, # Lanky + Model.Tiny, # Tiny + Model.Chunky, # Regular Chunky + Model.ChunkyDisco, # Disco Chunky + Model.Cranky, # Cranky + Model.Funky, # Funky + Model.Candy, # Candy + Model.Seal, # Seal + Model.Enguarde, # Enguarde + Model.BeaverBlue_LowPoly, # Beaver + Model.Squawks_28, # Squawks + Model.KlaptrapGreen, # Klaptrap Green + Model.KlaptrapPurple, # Klaptrap Purple + Model.KlaptrapRed, # Klaptrap Red + Model.MadJack, # Mad Jack + Model.Troff, # Troff + Model.SirDomino, # Sir Domino + Model.MrDice_41, # Mr Dice + Model.RoboKremling, # Robo Kremling + Model.Scoff, # Scoff + Model.Beetle, # Beetle + Model.NintendoLogo, # N64 Logo + Model.MechanicalFish, # Mech Fish + Model.ToyCar, # Toy Car + Model.Klump, # Klump + Model.Dogadon, # Dogadon + Model.BananaFairy, # Fairy + Model.Guard, # Guard + Model.Shuri, # Starfish + Model.Gimpfish, # Gimpfish + Model.KLumsy, # K Lumsy + Model.Spider, # Spider + Model.Rabbit, # Rabbit + # Model.Beanstalk, # Beanstalk + Model.KRoolCutscene, # K Rool + Model.SkeletonHead, # Skeleton Head + Model.Vulture_76, # Vulture + Model.Vulture_77, # Racing Vulture + Model.Ghost, # Ghost + Model.Fly, # Fly + Model.FlySwatter_83, # Fly Swatter + Model.Owl, # Owl + Model.Book, # Book + Model.SpotlightFish, # Spotlight Fish + Model.Puftup, # Pufftup + Model.Mermaid, # Mermaid + Model.Mushroom, # Mushroom Man + Model.Worm, # Worm + Model.EscapeShip, # Escape Ship + Model.KRoolFight, # K Rool (Boxing) + Model.Microphone, # Microbuffer + Model.BonusBarrel, # Bonus Barrel + Model.HunkyChunkyBarrel, # HC Barrel + Model.MiniMonkeyBarrel, # MM Barrel + Model.TNTBarrel, # TNT Barrel + Model.Rocketbarrel, # RB Barrel + Model.StrongKongBarrel, # SK Barrel + Model.OrangstandSprintBarrel, # OSS Barrel + Model.PlayerCar, # Tiny Car + Model.Boulder, # Boulder + Model.VaseCircle, # Vase + Model.VaseColon, # Vase + Model.VaseTriangle, # Vase + Model.VasePlus, # Vase + Model.ArmyDilloMissle, # AD Missile + Model.TagBarrel, # Tag Barrel + Model.QuestionMark, # Question Mark + Model.Krusha, # Krusha + Model.Light, # Light + Model.BananaPeel, # Banana Peel + Model.FunkyGun, # Funky's Gun +] + +bother_models = [ + Model.BeaverBlue_LowPoly, # Beaver + Model.Klobber, # Klobber + Model.Kaboom, # Kaboom + Model.KlaptrapGreen, # Green Klap + Model.KlaptrapPurple, # Purple Klap + Model.KlaptrapRed, # Red Klap + Model.KlaptrapTeeth, # Klap Teeth + Model.Krash, # Krash + Model.Troff, # Troff + Model.NintendoLogo, # N64 Logo + Model.MechanicalFish, # Mech Fish + Model.Krossbones, # Krossbones + Model.Rabbit, # Rabbit + Model.SkeletonHead, # Minecart Skeleton Head + Model.Tomato, # Tomato + Model.IceTomato, # Ice Tomato + Model.GoldenBanana_104, # Golden Banana + Model.Microphone, # Microbuffer + Model.Bell, # Bell + Model.Missile, # Missile (Car Race) + Model.Buoy, # Red Buoy + Model.BuoyGreen, # Green Buoy + Model.RarewareLogo, # Rareware Logo +] + +piano_models = [ + Model.Krash, + Model.RoboKremling, + Model.KoshKremling, + Model.KoshKremlingRed, + Model.Kasplat, + Model.Guard, + Model.Krossbones, + Model.Mermaid, + Model.Mushroom, + Model.GoldenBanana_104, + Model.FlySwatter_83, + Model.Ruler, +] +piano_extreme_model = [ + Model.SkeletonHead, + Model.Owl, + Model.Kosha, + # Model.Beanstalk, +] + +spotlight_fish_models = [ + # Model.Turtle, # Lighting Bug + Model.Seal, + Model.BeaverBlue, + Model.BeaverGold, + Model.Zinger, + Model.Squawks_28, + Model.Klobber, + Model.Kaboom, + Model.KlaptrapGreen, + Model.KlaptrapPurple, + Model.KlaptrapRed, + Model.Krash, + # Model.SirDomino, # Lighting issue + # Model.MrDice_41, # Lighting issue + # Model.Ruler, # Lighting issue + # Model.RoboKremling, # Lighting issue + Model.NintendoLogo, + Model.MechanicalFish, + Model.ToyCar, + Model.Kasplat, + Model.BananaFairy, + Model.Guard, + Model.Gimpfish, + # Model.Shuri, # Lighting issue + Model.Spider, + Model.Rabbit, + Model.KRoolCutscene, + Model.KRoolFight, + # Model.SkeletonHead, # Lighting bug + # Model.Vulture_76, # Lighting bug + # Model.Vulture_77, # Lighting bug + # Model.Bat, # Lighting bug + # Model.Tomato, # Lighting bug + # Model.IceTomato, # Lighting bug + # Model.FlySwatter_83, # Lighting bug + Model.SpotlightFish, + Model.Microphone, + # Model.Rocketbarrel, # Model too big, obstructs view + # Model.StrongKongBarrel, # Model too big, obstructs view + # Model.OrangstandSprintBarrel, # Model too big, obstructs view + # Model.MiniMonkeyBarrel, # Model too big, obstructs view + # Model.HunkyChunkyBarrel, # Model too big, obstructs view +] +candy_cutscene_models = [ + Model.Cranky, + # Model.Funky, # Disappears with collision + Model.Candy, + Model.Snide, + Model.Seal, + Model.BeaverBlue, + Model.BeaverGold, + Model.Klobber, + Model.Kaboom, + Model.Krash, + Model.Troff, + Model.Scoff, + Model.RoboKremling, + Model.Beetle, + Model.MrDice_41, + Model.MrDice_56, + Model.BananaFairy, + Model.Rabbit, + Model.KRoolCutscene, + Model.KRoolFight, + Model.Vulture_76, + Model.Vulture_77, + Model.Tomato, + Model.IceTomato, + Model.FlySwatter_83, + Model.Microphone, + Model.StrongKongBarrel, + Model.Rocketbarrel, + Model.OrangstandSprintBarrel, + Model.MiniMonkeyBarrel, + Model.HunkyChunkyBarrel, + Model.RambiCrate, + Model.EnguardeCrate, + Model.Boulder, + Model.SteelKeg, + Model.GoldenBanana_104, +] + +funky_cutscene_models = [ + Model.Cranky, + Model.Candy, + Model.Funky, + Model.Troff, + Model.Scoff, + Model.Ruler, + Model.RoboKremling, + Model.KRoolCutscene, + Model.KRoolFight, + Model.Microphone, +] + +# Not holding gun +funky_cutscene_models_extreme = [ + Model.BeaverBlue, + Model.BeaverGold, + Model.Klobber, + Model.Kaboom, + Model.SirDomino, + Model.MechanicalFish, + Model.BananaFairy, + Model.SkeletonHand, + Model.IceTomato, + Model.Tomato, +] + +boot_cutscene_models = [ + Model.Turtle, + Model.Enguarde, + Model.BeaverBlue, + Model.BeaverGold, + Model.Zinger, + Model.Squawks_28, + Model.KlaptrapGreen, + Model.KlaptrapPurple, + Model.KlaptrapRed, + Model.BananaFairy, + Model.Spider, + Model.Bat, + Model.KRoolGlove, +] + +melon_random_sprites = [ + Sprite.BouncingMelon, + Sprite.BouncingOrange, + Sprite.Coconut, + Sprite.Peanut, + Sprite.Grape, + Sprite.Feather, + Sprite.Pineapple, + Sprite.CrystalCoconut0, + Sprite.DKCoin, + Sprite.DiddyCoin, + Sprite.LankyCoin, + Sprite.TinyCoin, + Sprite.ChunkyCoin, + Sprite.Fairy, + Sprite.RaceCoin, +] + +model_mapping = { + KongModels.default: 0, + KongModels.disco_chunky: 6, + KongModels.krusha: 7, + KongModels.krool_cutscene: 9, + KongModels.krool_fight: 8, + KongModels.cranky: 10, + KongModels.candy: 11, + KongModels.funky: 12, +} + +model_texture_sections = { + KongModels.krusha: { + "skin": [0x4738, 0x2E96, 0x3A5E], + "kong": [0x3126, 0x354E, 0x37FE, 0x41E6], + }, + KongModels.krool_fight: { + "skin": [ + 0x61D6, + 0x63FE, + 0x6786, + 0x7DD6, + 0x7E8E, + 0x7F3E, + 0x7FEE, + 0x5626, + 0x56E6, + 0x5A86, + 0x5BAE, + 0x5D46, + 0x5E2E, + 0x5FAE, + 0x69BE, + 0x735E, + 0x7C5E, + 0x7E4E, + 0x7EF6, + 0x7FA6, + 0x8056, + ], + "kong": [0x607E, 0x7446, 0x7D46, 0x80FE], + }, + # KongModels.krool_cutscene: { + # "skin": [0x4A6E, 0x4CBE, 0x52AE, 0x55BE, 0x567E, 0x57E6, 0x5946, 0x5AA6, 0x5E06, 0x5EC6, 0x6020, 0x618E, 0x62F6, 0x6946, 0x6A6E, 0x6C5E, 0x6D86, 0x6F76, 0x702E, 0x70DE, 0x718E, 0x72FE, 0x4FBE, 0x51FE, 0x5C26, 0x6476, 0x6826, 0x6B26, 0x6E3E, 0x6FE6, 0x7096, 0x7146, 0x71F6, 0x733E, 0x743E], + # "kong": [], + # } +} + +KLAPTRAPS = [Model.KlaptrapGreen, Model.KlaptrapPurple, Model.KlaptrapRed] + + +def getRandomKlaptrapModel() -> Model: + """Get random klaptrap model.""" + return random.choice(KLAPTRAPS) + + +def applyCosmeticModelSwaps(settings: Settings, ROM_COPY: ROM): + """Apply model swaps to the settings dict.""" + sav = settings.rom_data + + bother_model_index = Model.KlaptrapGreen + panic_fairy_model_index = Model.BananaFairy + panic_klap_model_index = Model.KlaptrapGreen + turtle_model_index = Model.Turtle + sseek_klap_model_index = Model.KlaptrapGreen + fungi_tomato_model_index = Model.Tomato + caves_tomato_model_index = Model.IceTomato + racer_beetle = Model.Beetle + racer_rabbit = Model.Rabbit + piano_burper = Model.KoshKremlingRed + spotlight_fish_model_index = Model.SpotlightFish + candy_model_index = Model.Candy + funky_model_index = Model.Funky + boot_model_index = Model.Boot + melon_sprite = Sprite.BouncingMelon + swap_bitfield = 0 + + model_inverse_mapping = {} + for model in model_mapping: + val = model_mapping[model] + model_inverse_mapping[val] = model + + ROM_COPY.seek(settings.rom_data + 0x1B8) + settings.kong_model_dk = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] + settings.kong_model_diddy = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] + settings.kong_model_lanky = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] + settings.kong_model_tiny = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] + settings.kong_model_chunky = model_inverse_mapping[int.from_bytes(ROM_COPY.readBytes(1), "big")] + + if settings.override_cosmetics: + model_setting = RandomModels[js.document.getElementById("random_models").value] + else: + model_setting = settings.random_models + if model_setting == RandomModels.random: + bother_model_index = getRandomKlaptrapModel() + elif model_setting == RandomModels.extreme: + bother_model_index = random.choice(bother_models) + racer_beetle = random.choice([Model.Beetle, Model.Rabbit]) + racer_rabbit = random.choice([Model.Beetle, Model.Rabbit]) + if racer_rabbit == Model.Beetle: + spawner_changes = [] + # Fungi + rabbit_race_fungi_change = SpawnerChange(Maps.FungiForest, 2) + rabbit_race_fungi_change.new_scale = 50 + rabbit_race_fungi_change.new_speed_0 = 70 + rabbit_race_fungi_change.new_speed_1 = 136 + spawner_changes.append(rabbit_race_fungi_change) + # Caves + rabbit_caves_change = SpawnerChange(Maps.CavesChunkyIgloo, 1) + rabbit_caves_change.new_scale = 40 + spawner_changes.append(rabbit_caves_change) + applyCharacterSpawnerChanges(spawner_changes) + if model_setting != RandomModels.off: + panic_fairy_model_index = random.choice(panic_models) + turtle_model_index = random.choice(turtle_models) + panic_klap_model_index = getRandomKlaptrapModel() + sseek_klap_model_index = getRandomKlaptrapModel() + fungi_tomato_model_index = random.choice([Model.Tomato, Model.IceTomato]) + caves_tomato_model_index = random.choice([Model.Tomato, Model.IceTomato]) + referenced_piano_models = piano_models.copy() + referenced_funky_models = funky_cutscene_models.copy() + if model_setting == RandomModels.extreme: + referenced_piano_models.extend(piano_extreme_model) + spotlight_fish_model_index = random.choice(spotlight_fish_models) + referenced_funky_models.extend(funky_cutscene_models_extreme) + boot_model_index = random.choice(boot_cutscene_models) + piano_burper = random.choice(referenced_piano_models) + candy_model_index = random.choice(candy_cutscene_models) + funky_model_index = random.choice(funky_cutscene_models) + settings.bother_klaptrap_model = bother_model_index + settings.beetle_model = racer_beetle + settings.rabbit_model = racer_rabbit + settings.panic_fairy_model = panic_fairy_model_index + settings.turtle_model = turtle_model_index + settings.panic_klaptrap_model = panic_klap_model_index + settings.seek_klaptrap_model = sseek_klap_model_index + settings.fungi_tomato_model = fungi_tomato_model_index + settings.caves_tomato_model = caves_tomato_model_index + settings.piano_burp_model = piano_burper + settings.spotlight_fish_model = spotlight_fish_model_index + settings.candy_cutscene_model = candy_model_index + settings.funky_cutscene_model = funky_model_index + settings.boot_cutscene_model = boot_model_index + settings.wrinkly_rgb = [255, 255, 255] + # Compute swap bitfield + swap_bitfield |= 0x10 if settings.rabbit_model == Model.Beetle else 0 + swap_bitfield |= 0x20 if settings.beetle_model == Model.Rabbit else 0 + swap_bitfield |= 0x40 if settings.fungi_tomato_model == Model.IceTomato else 0 + swap_bitfield |= 0x80 if settings.caves_tomato_model == Model.Tomato else 0 + if settings.misc_cosmetics and settings.override_cosmetics: + melon_sprite = random.choice(melon_random_sprites) + settings.wrinkly_rgb = [random.randint(0, 255) for _ in range(3)] + settings.minigame_melon_sprite = melon_sprite + # Write Models + ROM_COPY.seek(sav + 0x1B5) + ROM_COPY.writeMultipleBytes(settings.panic_fairy_model + 1, 1) # Still needed for end seq fairy swap + ROM_COPY.seek(sav + 0x1E2) + ROM_COPY.write(swap_bitfield) diff --git a/randomizer/Patching/Cosmetics/Puzzles.py b/randomizer/Patching/Cosmetics/Puzzles.py new file mode 100644 index 000000000..77828c5fa --- /dev/null +++ b/randomizer/Patching/Cosmetics/Puzzles.py @@ -0,0 +1,121 @@ +"""All code associated with updating textures for puzzles.""" + +from randomizer.Settings import Settings +from randomizer.Patching.LibImage import writeColorImageToROM, TextureFormat, getImageFile, getNumberImage +from PIL import Image + + +def updateMillLeverTexture(settings: Settings) -> None: + """Update the 21132 texture.""" + if settings.mill_levers[0] > 0: + # Get Number bounds + base_num_texture = getImageFile(table_index=25, file_index=0x7CA, compressed=True, width=64, height=32, format=TextureFormat.RGBA5551) + number_textures = [None, None, None] + number_x_bounds = ( + (18, 25), + (5, 16), + (36, 47), + ) + modified_tex = getImageFile(table_index=25, file_index=0x7CA, compressed=True, width=64, height=32, format=TextureFormat.RGBA5551) + for tex in range(3): + number_textures[tex] = base_num_texture.crop((number_x_bounds[tex][0], 7, number_x_bounds[tex][1], 25)) + total_width = 0 + for x in range(5): + if settings.mill_levers[x] > 0: + idx = settings.mill_levers[x] - 1 + total_width += number_x_bounds[idx][1] - number_x_bounds[idx][0] + # Overwrite old panel + overwrite_panel = Image.new(mode="RGBA", size=(58, 26), color=(131, 65, 24)) + modified_tex.paste(overwrite_panel, (3, 3), overwrite_panel) + # Generate new number texture + new_num_texture = Image.new(mode="RGBA", size=(total_width, 18)) + x_pos = 0 + for num in range(5): + if settings.mill_levers[num] > 0: + num_val = settings.mill_levers[num] - 1 + new_num_texture.paste(number_textures[num_val], (x_pos, 0), number_textures[num_val]) + x_pos += number_x_bounds[num_val][1] - number_x_bounds[num_val][0] + scale_x = 58 / total_width + scale_y = 26 / 18 + scale = min(scale_x, scale_y) + x_size = int(total_width * scale) + y_size = int(18 * scale) + new_num_texture = new_num_texture.resize((x_size, y_size)) + x_offset = int((58 - x_size) / 2) + modified_tex.paste(new_num_texture, (3 + x_offset, 3), new_num_texture) + writeColorImageToROM(modified_tex, 25, 0x7CA, 64, 32, False, TextureFormat.RGBA5551) + + +def updateDiddyDoors(settings: Settings): + """Update the textures for the doors.""" + enable_code = False + for code in settings.diddy_rnd_doors: + if sum(code) > 0: # Has a non-zero element + enable_code = True + SEG_WIDTH = 48 + SEG_HEIGHT = 42 + NUMBERS_START = (27, 33) + if enable_code: + # Order: 4231, 3124, 1342 + starts = (0xCE8, 0xCE4, 0xCE0) + for index, code in enumerate(settings.diddy_rnd_doors): + start = starts[index] + total = Image.new(mode="RGBA", size=(SEG_WIDTH * 2, SEG_HEIGHT * 2)) + for img_index in range(4): + img = getImageFile(25, start + img_index, True, SEG_WIDTH, SEG_HEIGHT, TextureFormat.RGBA5551) + x_offset = SEG_WIDTH * (img_index & 1) + y_offset = SEG_HEIGHT * ((img_index & 2) >> 1) + total.paste(img, (x_offset, y_offset), img) + total = total.transpose(Image.FLIP_TOP_BOTTOM) + # Overlay color + cover = Image.new(mode="RGBA", size=(42, 20), color=(115, 98, 65)) + total.paste(cover, NUMBERS_START, cover) + # Paste numbers + number_images = [] + number_offsets = [] + total_length = 0 + for num in code: + num_img = getNumberImage(num + 1) + w, h = num_img.size + number_offsets.append(total_length) + total_length += w + number_images.append(num_img) + total_numbers = Image.new(mode="RGBA", size=(total_length, 24)) + for img_index, img in enumerate(number_images): + total_numbers.paste(img, (number_offsets[img_index], 0), img) + total.paste(total_numbers, (SEG_WIDTH - int(total_length / 2), SEG_HEIGHT - 12), total_numbers) + total = total.transpose(Image.FLIP_TOP_BOTTOM) + for img_index in range(4): + x_offset = SEG_WIDTH * (img_index & 1) + y_offset = SEG_HEIGHT * ((img_index & 2) >> 1) + sub_img = total.crop((x_offset, y_offset, x_offset + SEG_WIDTH, y_offset + SEG_HEIGHT)) + writeColorImageToROM(sub_img, 25, start + img_index, SEG_WIDTH, SEG_HEIGHT, False, TextureFormat.RGBA5551) + + +def updateCryptLeverTexture(settings: Settings) -> None: + """Update the two textures for Donkey Minecart entry.""" + if settings.crypt_levers[0] > 0: + # Get a blank texture + texture_0 = getImageFile(table_index=25, file_index=0x999, compressed=True, width=32, height=64, format=TextureFormat.RGBA5551) + blank = texture_0.crop((8, 5, 23, 22)) + texture_0.paste(blank, (8, 42), blank) + texture_1 = texture_0.copy() + for xi, x in enumerate(settings.crypt_levers): + corrected = x - 1 + y_slot = corrected % 3 + num = getNumberImage(xi + 1) + num = num.transpose(Image.FLIP_TOP_BOTTOM) + w, h = num.size + scale = 2 / 3 + y_offset = int((h * scale) / 2) + x_offset = int((w * scale) / 2) + num = num.resize((int(w * scale), int(h * scale))) + y_pos = (51, 33, 14) + tl_y = y_pos[y_slot] - y_offset + tl_x = 16 - x_offset + if corrected < 3: + texture_0.paste(num, (tl_x, tl_y), num) + else: + texture_1.paste(num, (tl_x, tl_y), num) + writeColorImageToROM(texture_0, 25, 0x99A, 32, 64, False, TextureFormat.RGBA5551) + writeColorImageToROM(texture_1, 25, 0x999, 32, 64, False, TextureFormat.RGBA5551) diff --git a/randomizer/Patching/Cosmetics/TextRando.py b/randomizer/Patching/Cosmetics/TextRando.py new file mode 100644 index 000000000..a054b02d1 --- /dev/null +++ b/randomizer/Patching/Cosmetics/TextRando.py @@ -0,0 +1,317 @@ +"""All code associated with cosmetic tweaks to text.""" + +import random +from randomizer.Patching.Patcher import LocalROM +from randomizer.Patching.Lib import writeText, grabText + +boot_phrases = ( + "Removing Lanky Kong", + "Telling 2dos to play DK64", + "Locking K. Lumsy in a cage", + "Stealing the Banana Hoard", + "Finishing the game in a cave", + "Becoming the peak of randomizers", + "Giving kops better eyesight", + "Patching in the glitches", + "Enhancing Cfox Luck", + "Finding Rareware GB in Galleon", + "Resurrecting Chunky Kong", + "Shouting out Grant Kirkhope", + "Crediting L. Godfrey", + "Removing Stop n Swop", + "Assembling the scraps", + "Blowing in the cartridge", + "Backflipping in Chunky Phase", + "Hiding 20 fairies", + "Randomizing collision normals", + "Removing hit detection", + "Compressing K Rools Voice Lines", + "Checking divide by 0 doesnt work", + "Adding every move to Isles", + "Segueing in dk64randomizer.com", + "Removing lag. Or am I?", + "Hiding a dirt patch under grass", + "Giving Wrinkly the spoiler log", + "Questioning sub 2:30 in LUA Rando", + "Chasing Lanky in Fungi Forest", + "Banning Potions from Candys Shop", + "Finding someone who can help you", + "Messing up your seed", + "Crashing Krem Isle", + "Increasing Robot Punch Resistance", + "Caffeinating banana fairies", + "Bothering Beavers", + "Inflating Banana Balloons", + "Counting to 16", + "Removing Walls", + "Taking it to the fridge", + "Brewing potions", + "Reticulating Splines", # SimCity 2000 + "Ironing Donks", + "Replacing mentions of Hero with Hoard", + "Suggesting you also try BK Randomizer", + "Scattering 3500 Bananas", + "Stealing ideas from other randomizers", + "Fixing Krushas Collision", + "Falling on 75m", + "Summoning Salt", + "Combing Chunkys Afro", + "Asking what you gonna do", + "Thinking with portals", + "Reminding you to hydrate", + "Injecting lag", + "Turning Sentient", + "Performing for you", + "Charging 2 coins per save", + "Loading in Beavers", + "Lifting Boulders with Relative Ease", + "Doing Monkey Science Probably", + "Telling Killi to eventually play DK64", + "Crediting Grant Kirkhope", + "Dropping Crayons", + "Saying Hello when others wont", + "Mangling Music", + "Killing Speedrunning", + "Enhancing Cfox Luck Voice Linesmizers", + "Enforcing the law of the Jungle", + "Saving 20 frames", + "Reporting bugs. Unlike some", + "Color-coding Krusha for convenience", +) + +crown_heads = ( + # Object + "Arena", + "Beaver", + "Bish Bash", + "Forest", + "Kamikaze", + "Kritter", + "Pinnacle", + "Plinth", + "Shockwave", + "Bean", + "Dogadon", + "Banana", + "Squawks", + "Lanky", + "Diddy", + "Tiny", + "Chunky", + "DK", + "Krusha", + "Kosha", + "Klaptrap", + "Zinger", + "Gnawty", + "Kasplat", + "Pufftup", + "Shuri", + "Krossbones", + "Caves", + "Castle", + "Helm", + "Japes", + "Jungle", + "Angry", + "Aztec", + "Frantic", + "Factory", + "Gloomy", + "Galleon", + "Crystal", + "Creepy", + "Hideout", + "Cranky", + "Funky", + "Candy", + "Kong", + "Monkey", + "Amazing", + "Incredible", + "Ultimate", + "Wrinkly", + "Heroic", + "Final", + "Fantastic", + "Krazy", + "Komplete", + "Unhinted", + "Unstable", + "Extreme", + "Royal", + "Monster", + "Primate", + "Baboon", + "Walnut", + "Peanut", + "Coconut", + "Feather", + "Grape", + "Pineapple", + "Barrel", + "Monkeyport", + "Kalamity", + "Kaboom", + "Magic", + "Fairy", + "Karnivorous", + "Krispy", + "Kooky", + "Cookin", + "Klutz", + "Kingdom", + "Super Duper", + "Rainbow", + "Bongo", + "Guitar", + "Trombone", + "Saxophone", + "Triangle", + "Dixie", + "Gorilla", + "Chimpy", + "Museum", + "Ballroom", + "Winch", + "Shipyard", + "Hillside", + "Oasis", + "Arcade", + "Mushroom", + "Igloo", + "Stupid", + "Spicy", + "Dizzy", + "Slot Car", + "Minecart", + "Rambi", + "Enguarde", + "Reptile", + "Bramble", + "Toxic", + "Rabbit", + "Beetle", + "Vulture", + "Boulder", +) + +crown_tails = ( + # Synonym for brawl/similar + "Ambush", + "Brawl", + "Fracas", + "Karnage", + "Kremlings", + "Palaver", + "Panic", + "Showdown", + "Slam", + "Melee", + "Tussle", + "Altercation", + "Wrangle", + "Clash", + "Free for All", + "Skirmish", + "Scrap", + "Fight", + "Rumpus", + "Fray", + "Wrestle", + "Brouhaha", + "Commotion", + "Uproar", + "Rough and Tumble", + "Broil", + "Argy Bargy", + "Bother", + "Mayhem", + "Bonanza", + "Battle", + "Kerfuffle", + "Rumble", + "Fisticuffs", + "Ruckus", + "Scrimmage", + "Strife", + "Dog and Duck", + "Joust", + "Scuffle", + "Hootenanny", + "Blitz", + "Tourney", + "Explosion", + "Contest", + "Chaos", + "Combat", + "Knockdown", + "Demolition", + "Capture", + "Storm", + "Earthquake", + "Charge", + "Tremor", + "Trample", + "Gauntlet", + "Challenge", + "Blowout", + "Riot", + "Buffoonery", + "Hijinxs", + "Frenzy", + "Rampage", + "Antics", + "Trouble", + "Revenge", + "Klamber", + "Wreckage", + "Quarrel", + "Feud", + "Thwack", + "Wallop", + "Donnybrook", + "Tangle", + "Crossfire", + "Royale", +) + + +def getCrownNames() -> list: + """Get crown names from head and tail pools.""" + # Get 10 names for heads just in case "Forest" and "Fracas" show up + heads = random.sample(crown_heads, 10) + tails = random.sample(crown_tails, 9) + # Remove "Forest" if both "Forest" and "Fracas" show up + if "Forest" in heads and "Fracas" in tails: + heads.remove("Forest") + # Only get 9 names, Forest Fracas can't be overwritten without having negative impacts + names = [] + for x in range(9): + head = heads[x] + tail = tails[x] + if head[0] == "K" and tail[0] == "C": + split_tail = list(tail) + split_tail[0] = "K" + tail = "".join(split_tail) + names.append(f"{head} {tail}!".upper()) + names.append("Forest Fracas!".upper()) + return names + + +def writeCrownNames(): + """Write Crown Names to ROM.""" + names = getCrownNames() + old_text = grabText(35, True) + for name_index, name in enumerate(names): + old_text[0x1E + name_index] = ({"text": [name]},) + writeText(35, old_text, True) + + +def writeBootMessages() -> None: + """Write boot messages into ROM.""" + ROM_COPY = LocalROM() + placed_messages = random.sample(boot_phrases, 4) + for message_index, message in enumerate(placed_messages): + ROM_COPY.seek(0x1FFD000 + (0x40 * message_index)) + ROM_COPY.writeBytes(message.upper().encode("ascii")) diff --git a/randomizer/Patching/Cosmetics/__init__.py b/randomizer/Patching/Cosmetics/__init__.py new file mode 100644 index 000000000..4f0ec25fe --- /dev/null +++ b/randomizer/Patching/Cosmetics/__init__.py @@ -0,0 +1 @@ +"""Functions to write cosmetic data to ROM.""" diff --git a/randomizer/Patching/Lib.py b/randomizer/Patching/Lib.py index 5a784edfd..a72353a40 100644 --- a/randomizer/Patching/Lib.py +++ b/randomizer/Patching/Lib.py @@ -1130,6 +1130,16 @@ def getProgHintBarrierItem(item: ProgressiveHintItem) -> BarrierItems: return barrier_bijection[item] +def getValueFromByteArray(ba: bytearray, offset: int, size: int) -> int: + """Get value from byte array given an offset and size.""" + value = 0 + for x in range(size): + local_value = ba[offset + x] + value <<= 8 + value += local_value + return value + + class Holidays(IntEnum): """Holiday Enum.""" diff --git a/randomizer/Patching/LibImage.py b/randomizer/Patching/LibImage.py index 50a49d78b..26d178bfd 100644 --- a/randomizer/Patching/LibImage.py +++ b/randomizer/Patching/LibImage.py @@ -6,8 +6,11 @@ import gzip import math from enum import IntEnum, auto -from PIL import Image +from PIL import Image, ImageEnhance from randomizer.Patching.Patcher import ROM, LocalROM +from randomizer.Settings import ColorblindMode +from randomizer.Enums.Kongs import Kongs +from typing import Tuple class TextureFormat(IntEnum): @@ -257,3 +260,300 @@ def imageToCI(ROM_COPY: ROM, im_f, ci_index: int, tex_index: int, pal_index: int ROM_COPY.write(tex_bin_file) ROM_COPY.seek(pal_start) ROM_COPY.write(pal_bin_file) + + +def writeColorImageToROM( + im_f, + table_index: int, + file_index: int, + width: int, + height: int, + transparent_border: bool, + format: TextureFormat, +) -> None: + """Write texture to ROM.""" + file_start = js.pointer_addresses[table_index]["entries"][file_index]["pointing_to"] + file_end = js.pointer_addresses[table_index]["entries"][file_index + 1]["pointing_to"] + file_size = file_end - file_start + try: + LocalROM().seek(file_start) + except Exception: + ROM().seek(file_start) + pix = im_f.load() + width, height = im_f.size + bytes_array = [] + border = 1 + right_border = 3 + for y in range(height): + for x in range(width): + if transparent_border: + if ((x < border) or (y < border) or (x >= (width - border)) or (y >= (height - border))) or (x == (width - right_border)): + pix_data = [0, 0, 0, 0] + else: + pix_data = list(pix[x, y]) + else: + pix_data = list(pix[x, y]) + if format == TextureFormat.RGBA32: + bytes_array.extend(pix_data) + elif format == TextureFormat.RGBA5551: + red = int((pix_data[0] >> 3) << 11) + green = int((pix_data[1] >> 3) << 6) + blue = int((pix_data[2] >> 3) << 1) + alpha = int(pix_data[3] != 0) + value = red | green | blue | alpha + bytes_array.extend([(value >> 8) & 0xFF, value & 0xFF]) + elif format == TextureFormat.IA4: + intensity = pix_data[0] >> 5 + alpha = 0 if pix_data[3] == 0 else 1 + data = ((intensity << 1) | alpha) & 0xF + bytes_array.append(data) + bytes_per_px = 2 + if format == TextureFormat.IA4: + temp_ba = bytes_array.copy() + bytes_array = [] + value_storage = 0 + bytes_per_px = 0.5 + for idx, val in enumerate(temp_ba): + polarity = idx % 2 + if polarity == 0: + value_storage = val << 4 + else: + value_storage |= val + bytes_array.append(value_storage) + data = bytearray(bytes_array) + if format == TextureFormat.RGBA32: + bytes_per_px = 4 + if len(data) > (bytes_per_px * width * height): + print(f"Image too big error: {table_index} > {file_index}") + if table_index in (14, 25): + data = gzip.compress(data, compresslevel=9) + if len(data) > file_size: + print(f"File too big error: {table_index} > {file_index}") + try: + LocalROM().writeBytes(data) + except Exception: + ROM().writeBytes(data) + + +def getNumberImage(number: int): + """Get Number Image from number.""" + if number < 5: + num_0_bounds = [0, 20, 30, 45, 58, 76] + x = number + return getImageFile(14, 15, True, 76, 24, TextureFormat.RGBA5551).crop((num_0_bounds[x], 0, num_0_bounds[x + 1], 24)) + num_1_bounds = [0, 15, 28, 43, 58, 76] + x = number - 5 + return getImageFile(14, 16, True, 76, 24, TextureFormat.RGBA5551).crop((num_1_bounds[x], 0, num_1_bounds[x + 1], 24)) + + +def numberToImage(number: int, dim: Tuple[int, int]): + """Convert multi-digit number to image.""" + digits = 1 + if number < 10: + digits = 1 + elif number < 100: + digits = 2 + else: + digits = 3 + current = number + nums = [] + total_width = 0 + max_height = 0 + sep_dist = 1 + for _ in range(digits): + base = getNumberImage(current % 10) + bbox = base.getbbox() + base = base.crop(bbox) + nums.append(base) + base_w, base_h = base.size + max_height = max(max_height, base_h) + total_width += base_w + current = int(current / 10) + nums.reverse() + total_width += (digits - 1) * sep_dist + base = Image.new(mode="RGBA", size=(total_width, max_height)) + pos = 0 + for num in nums: + base.paste(num, (pos, 0), num) + num_w, num_h = num.size + pos += num_w + sep_dist + output = Image.new(mode="RGBA", size=dim) + xScale = dim[0] / total_width + yScale = dim[1] / max_height + scale = xScale + if yScale < xScale: + scale = yScale + new_w = int(total_width * scale) + new_h = int(max_height * scale) + x_offset = int((dim[0] - new_w) / 2) + y_offset = int((dim[1] - new_h) / 2) + new_dim = (new_w, new_h) + base = base.resize(new_dim) + output.paste(base, (x_offset, y_offset), base) + return output + + +def getRGBFromHash(hash: str): + """Convert hash RGB code to rgb array.""" + red = int(hash[1:3], 16) + green = int(hash[3:5], 16) + blue = int(hash[5:7], 16) + return [red, green, blue] + + +def maskImageWithColor(im_f: Image, mask: tuple): + """Apply rgb mask to image using a rgb color tuple.""" + w, h = im_f.size + converter = ImageEnhance.Color(im_f) + im_f = converter.enhance(0) + im_dupe = im_f.copy() + brightener = ImageEnhance.Brightness(im_dupe) + im_dupe = brightener.enhance(2) + im_f.paste(im_dupe, (0, 0), im_dupe) + pix = im_f.load() + w, h = im_f.size + for x in range(w): + for y in range(h): + base = list(pix[x, y]) + if base[3] > 0: + for channel in range(3): + base[channel] = int(mask[channel] * (base[channel] / 255)) + pix[x, y] = (base[0], base[1], base[2], base[3]) + return im_f + + +def getColorBase(mode: ColorblindMode) -> list[str]: + """Get the color base array.""" + if mode == ColorblindMode.prot: + return ["#000000", "#0072FF", "#766D5A", "#FFFFFF", "#FDE400"] + elif mode == ColorblindMode.deut: + return ["#000000", "#318DFF", "#7F6D59", "#FFFFFF", "#E3A900"] + elif mode == ColorblindMode.trit: + return ["#000000", "#C72020", "#13C4D8", "#FFFFFF", "#FFA4A4"] + return ["#FFD700", "#FF0000", "#1699FF", "#B045FF", "#41FF25"] + + +def getKongItemColor(mode: ColorblindMode, kong: Kongs, output_as_list: bool = False) -> str: + """Get the color assigned to a kong.""" + hash_str = getColorBase(mode)[kong] + if output_as_list: + return getRGBFromHash(hash_str) + return hash_str + + +def maskImage(im_f, base_index, min_y, keep_dark=False, mode=ColorblindMode.off): + """Apply RGB mask to image.""" + w, h = im_f.size + converter = ImageEnhance.Color(im_f) + im_f = converter.enhance(0) + im_dupe = im_f.crop((0, min_y, w, h)) + if keep_dark is False: + brightener = ImageEnhance.Brightness(im_dupe) + im_dupe = brightener.enhance(2) + im_f.paste(im_dupe, (0, min_y), im_dupe) + pix = im_f.load() + mask = getKongItemColor(mode, base_index, True) + w, h = im_f.size + for x in range(w): + for y in range(min_y, h): + base = list(pix[x, y]) + if base[3] > 0: + for channel in range(3): + base[channel] = int(mask[channel] * (base[channel] / 255)) + pix[x, y] = (base[0], base[1], base[2], base[3]) + return im_f + + +def hueShiftImageContainer(table: int, image: int, width: int, height: int, format: TextureFormat, shift: int, ROM_COPY: ROM = None): + """Load an image, shift the hue and rewrite it back to ROM.""" + loaded_im = getImageFile(table, image, table != 7, width, height, format) + loaded_im = hueShift(loaded_im, shift) + loaded_px = loaded_im.load() + bytes_array = [] + for y in range(height): + for x in range(width): + pix_data = list(loaded_px[x, y]) + if format == TextureFormat.RGBA32: + bytes_array.extend(pix_data) + elif format == TextureFormat.RGBA5551: + red = int((pix_data[0] >> 3) << 11) + green = int((pix_data[1] >> 3) << 6) + blue = int((pix_data[2] >> 3) << 1) + alpha = int(pix_data[3] != 0) + value = red | green | blue | alpha + bytes_array.extend([(value >> 8) & 0xFF, value & 0xFF]) + px_data = bytearray(bytes_array) + if table != 7: + px_data = gzip.compress(px_data, compresslevel=9) + if ROM_COPY is None: + ROM_COPY = ROM() + ROM_COPY.seek(js.pointer_addresses[table]["entries"][image]["pointing_to"]) + ROM_COPY.writeBytes(px_data) + + +def getLuma(color: tuple) -> float: + """Get the luma value of a color.""" + return (0.299 * color[0]) + (0.587 * color[1]) + (0.114 * color[2]) + + +def hueShiftColor(color: tuple, amount: int, head_ratio: int = None) -> tuple: + """Apply a hue shift to a color.""" + # RGB -> HSV Conversion + red_ratio = color[0] / 255 + green_ratio = color[1] / 255 + blue_ratio = color[2] / 255 + color_max = max(red_ratio, green_ratio, blue_ratio) + color_min = min(red_ratio, green_ratio, blue_ratio) + color_delta = color_max - color_min + hue = 0 + if color_delta != 0: + if color_max == red_ratio: + hue = 60 * (((green_ratio - blue_ratio) / color_delta) % 6) + elif color_max == green_ratio: + hue = 60 * (((blue_ratio - red_ratio) / color_delta) + 2) + else: + hue = 60 * (((red_ratio - green_ratio) / color_delta) + 4) + sat = 0 if color_max == 0 else color_delta / color_max + val = color_max + # Adjust Hue + if head_ratio is not None and sat != 0: + amount = head_ratio / (sat * 100) + hue = (hue + amount) % 360 + # HSV -> RGB Conversion + c = val * sat + x = c * (1 - abs(((hue / 60) % 2) - 1)) + m = val - c + if hue < 60: + red_ratio = c + green_ratio = x + blue_ratio = 0 + elif hue < 120: + red_ratio = x + green_ratio = c + blue_ratio = 0 + elif hue < 180: + red_ratio = 0 + green_ratio = c + blue_ratio = x + elif hue < 240: + red_ratio = 0 + green_ratio = x + blue_ratio = c + elif hue < 300: + red_ratio = x + green_ratio = 0 + blue_ratio = c + else: + red_ratio = c + green_ratio = 0 + blue_ratio = x + return (int((red_ratio + m) * 255), int((green_ratio + m) * 255), int((blue_ratio + m) * 255)) + + +def rgba32to5551(rgba_32: list[int]) -> list[int]: + """Convert list as RGBA32 bytes with no alpha to list of RGBA5551 bytes.""" + val_r = int((rgba_32[0] >> 3) << 11) + val_g = int((rgba_32[1] >> 3) << 6) + val_b = int((rgba_32[2] >> 3) << 1) + rgba_val = val_r | val_g | val_b | 1 + return [(rgba_val >> 8) & 0xFF, rgba_val & 0xFF] diff --git a/randomizer/SettingStrings.py b/randomizer/SettingStrings.py index f6e9411d0..cb99a109d 100644 --- a/randomizer/SettingStrings.py +++ b/randomizer/SettingStrings.py @@ -221,6 +221,7 @@ def encrypt_settings_string_enum(dict_data: dict): "true_widescreen", "camera_is_not_inverted", "sound_type", + "smoother_camera", "songs_excluded", "excluded_songs_selected", "music_filtering", diff --git a/randomizer/Settings.py b/randomizer/Settings.py index 0e4e80c38..296b0d1e2 100644 --- a/randomizer/Settings.py +++ b/randomizer/Settings.py @@ -615,6 +615,7 @@ def generate_misc(self): self.camera_is_not_inverted = False self.sound_type = SoundType.stereo self.custom_music_proportion = 100 + self.smoother_camera = False self.fill_with_custom_music = False self.show_song_name = False diff --git a/randomizer/ShuffleDoors.py b/randomizer/ShuffleDoors.py index b1ae14910..b0aa3ff9e 100644 --- a/randomizer/ShuffleDoors.py +++ b/randomizer/ShuffleDoors.py @@ -241,7 +241,6 @@ def ShuffleDoors(spoiler, vanilla_doors_placed: bool): shuffled_door_data[level].append((selected_door_index, DoorType.dk_portal)) # Track all touched doors in a variable and put it in the spoiler because changes to the static list do not save - print(shuffled_door_data) spoiler.shuffled_door_data = shuffled_door_data # Give human text to spoiler log if shuffle_wrinkly: diff --git a/static/patches/shrink-dk64.bps b/static/patches/shrink-dk64.bps index f37bdc977..45a580f97 100644 Binary files a/static/patches/shrink-dk64.bps and b/static/patches/shrink-dk64.bps differ diff --git a/static/patches/symbols.json b/static/patches/symbols.json index fefeadd28..b8f0ca056 100644 --- a/static/patches/symbols.json +++ b/static/patches/symbols.json @@ -744,245 +744,245 @@ "minecartjumpfix": 2153688132, "minecartjumpfix_0": 2153688172, "setkrushaammocolor": 2153688252, - "orangeguncode": 2153688484, - "istbarrelflag": 2153689800, - "isfairyflag": 2153689816, - "gethinttextindex": 2153689856, - "isgoodtextbox": 2153691052, - "getmovehint": 2153691164, - "resetdisplayedmusic": 2153691276, - "detectsongchange": 2153691288, - "initsongdisplay": 2153691628, - "displaysongnamehandler": 2153691940, - "curseremoved": 2153692532, - "haspermalossgrace": 2153692544, - "determinekongunlock": 2153692572, - "unlockkongpermaloss": 2153692716, - "givekongmoves": 2153692828, - "isdeathstate": 2153692896, - "kong_has_died": 2153692936, - "determinestartkong_permalossmode": 2153693488, - "transitionkong": 2153693656, - "fixgracecheese": 2153693964, - "changekongontransition_permaloss": 2153694136, - "forcebosskong": 2153694184, - "preventbosscheese": 2153694412, - "doeskongpossessmove": 2153694584, - "issharedmove": 2153695224, - "getcounteritem": 2153695436, - "getmovecountinshop": 2153696008, - "wipecounterimagecache": 2153696388, - "loadinternaltexture": 2153696436, - "loadfonttexture_counter": 2153696600, - "updatecounterdisplay": 2153696764, - "getactormodeltwodist": 2153696944, - "getclosestshop": 2153697080, - "getshopscale": 2153697732, - "newcountercode": 2153698072, - "handlefootprogress": 2153699472, - "changestat": 2153699924, - "setstat": 2153700008, - "getstat": 2153700024, - "getbonusblockstart": 2153700036, - "getbitoffset": 2153700112, - "getbitsize": 2153700188, - "readextradata": 2153700260, - "saveextradata": 2153700544, - "resetextradata": 2153700848, - "setkongigt": 2153700856, - "updatepercentagekongstat": 2153700908, - "genericstatupdate": 2153701120, - "updatetagstat": 2153701232, - "updatefairystat": 2153701348, - "updatekopstat": 2153701388, - "updateenemykillstat": 2153701396, - "createendseqcreditsfile": 2153701500, - "displaynumberonobject": 2153702288, - "shiftbrokenjapesportal": 2153702500, - "displaynumberontns": 2153702616, - "writewti": 2153703164, - "handle_wti": 2153703204, - "warptoisles": 2153703416, - "beatgame": 2153703524, - "finalizebeatgame": 2153703600, - "hasbeatendkrapwincon": 2153703700, - "checkseedvictory": 2153704000, - "checkvictory_flaghook": 2153704288, - "issnapenemyinrange": 2153704316, - "getpkmnsnapdata": 2153704800, - "pokemonsnapmode": 2153704956, - "arcadeexit": 2153705692, - "determinearcadelevel": 2153705724, - "handlearcadevictory": 2153705840, - "spawnoverlaytext": 2153706180, - "overlay_mod_bonus": 2153706492, - "overlay_mod_boss": 2153706788, - "overlay_changes": 2153707036, - "parsecutscenedata": 2153707248, - "loadjetpacsprites_handler": 2153707636, - "initjetpac": 2153707704, - "patchcrankycode": 2153707816, - "give_all_blueprints": 2153707964, - "overlay_mod_menu": 2153708172, - "overlay_mod_race": 2153708616, - "updatepausescreenwheel": 2153708796, - "newpausespritecode": 2153708876, - "totalssprite": 2153709764, - "checkssprite": 2153709772, - "handlespritecode": 2153709780, - "initcarousel_onpause": 2153709848, - "initcarousel_onboot": 2153710216, - "file_sprites": 2153710320, - "file_item_caps": 2153710388, - "file_items": 2153710440, - "initprogressivetimer": 2153710532, - "renderprogressivesprite": 2153710548, - "playprogressiveding": 2153710608, - "handleprogressiveindicator": 2153710628, - "resetprogressive": 2153710660, - "inithints": 2153710700, - "wipehintcache": 2153710876, - "drawhinttext": 2153710944, - "drawsplitstring": 2153711484, - "gethintrequirement": 2153712288, - "displaycbcount": 2153712332, - "gethintitemregion": 2153712528, - "showhint": 2153712552, - "displaybubble": 2153712664, - "gettiedshopmoveflag": 2153712824, - "getitemspecificity": 2153712888, - "inithintflags": 2153713252, - "getitemname": 2153713424, - "drawhintscreen": 2153713564, - "drawitemlocationscreen": 2153714520, - "item_names": 2153715868, - "item_name_plural": 2153715932, - "hints_initialized": 2153715948, - "display_billboard_fix": 2153715949, - "hint_region_names": 2153716988, - "unknown_hints": 2153717232, - "printleveligt": 2153718376, - "inititemcheckdenominators": 2153718760, - "checkitemdb": 2153718996, - "handlecshifting": 2153719556, - "pausescreen3and4header": 2153719688, - "drawtextpointers": 2153720268, - "pausescreen3and4itemname": 2153720420, - "pausescreen3and4counter": 2153720576, - "changepausescreen": 2153720828, - "changeselectedlevel": 2153720972, - "updatefilevariables": 2153721056, - "handleoutofcounters": 2153721092, - "initpausemenu": 2153721264, - "sethintregion": 2153722184, - "storehintregion": 2153723176, - "gethintregiontext": 2153723272, - "displayhintregion": 2153723404, - "getworldoffset": 2153724008, - "setblockerhead": 2153724056, - "displayblockeritemonhud": 2153724204, - "getcountofblockerrequireditem": 2153724296, - "displaycountonblockerteeth": 2153724360, - "cc_enable_drunky": 2153724476, - "cc_disable_drunky": 2153724540, - "cc_allower_generic": 2153724584, - "cc_enabler_icetrap": 2153724732, - "cc_allower_icetrap": 2153724764, - "cc_enabler_warptorap": 2153724816, - "handlegamemodewrapper": 2153724900, - "cc_disabler_warptorap": 2153724972, - "skipdktv": 2153725100, - "displaygetoutreticle": 2153725184, - "cc_enable_getout": 2153725508, - "fakegetout": 2153725692, - "cc_allower_rockfall": 2153726184, - "cc_enabler_rockfall": 2153726200, - "dummyguardcode": 2153726452, - "cc_allower_spawnkop": 2153726684, - "cc_enabler_spawnkop": 2153726716, - "cc_allower_balloon": 2153726880, - "cc_allower_backflip": 2153726932, - "cc_enabler_balloon": 2153726988, - "cc_enabler_slip": 2153727124, - "cc_allower_tag": 2153727196, - "cc_enabler_tag": 2153727316, - "cc_enabler_doabackflip": 2153727512, - "cc_enabler_ice": 2153727596, - "cc_disabler_ice": 2153727620, - "cc_allower_animals": 2153727668, - "cc_enabler_animals": 2153727728, - "cc_disabler_animals": 2153727976, - "cc_allower_mini": 2153728048, - "cc_setscale": 2153728092, - "cc_enabler_mini": 2153728140, - "cc_disabler_mini": 2153728220, - "cc_allower_boulder": 2153728276, - "cc_enabler_boulder": 2153728328, - "cc_effect_handler": 2153728400, - "replace_zones": 2153729304, - "blastwarphandler": 2153729700, - "swap_ending_cutscene_model": 2153729868, - "completeboss": 2153730084, - "fixkroolkong": 2153730768, - "handlekroolsaveprogress": 2153730868, - "swaprequirements": 2153731556, - "level_order_rando_funcs": 2153731632, - "writehudamount": 2153731720, - "movetransplant": 2153732024, - "progressivechange": 2153732092, - "getmoveprogressiveflagtype": 2153732492, - "writeprogressivetext": 2153732600, - "getnextmovepurchase": 2153732860, - "getpurchaseclassification": 2153733612, - "addhelmhurrypurchasetime": 2153734160, - "purchasemove": 2153734224, - "checkfirstmovepurchase": 2153735080, - "purchasefirstmovehandler": 2153735252, - "setlocation": 2153735360, - "getlocation": 2153736368, - "setlocationstatus": 2153736824, - "getlocationstatus": 2153736908, - "displaymovetext": 2153736996, - "getnextmovetext": 2153737308, - "displaybfimovetext": 2153739936, - "showpostmovetext": 2153740064, - "simianslamnames": 2153741304, - "specialmovesnames": 2153741312, - "gunnames": 2153741352, - "gunupgnames": 2153741360, - "ammobeltnames": 2153741364, - "instrumentnames": 2153741368, - "instrumentupgnames": 2153741376, - "alter_price": 2153741384, - "pricetransplant": 2153741420, - "destroybonus": 2153741768, - "completebonus": 2153741800, - "applyfaststart": 2153741988, - "opencrowndoor": 2153742164, - "opencoindoor": 2153742196, - "helminit": 2153742228, - "helmbarrelcode": 2153743096, - "checkdooritem": 2153743436, - "crowndoorcheck": 2153743824, - "coindoorcheck": 2153743836, - "initkongrando": 2153743976, - "initfile_checktraining": 2153744216, - "initfile_hasgun": 2153744248, - "initfile_hasinstrument": 2153744404, - "initfile_getbeltlevel": 2153744556, - "initfile_getinsupgradelevel": 2153744704, - "initfile_getslamlevel": 2153744864, - "initfile_getkongpotionbitfield": 2153745024, - "unlockmoves": 2153745440, - "apply_key": 2153747000, - "pre_turn_keys": 2153747332, - "writekeyflags": 2153747864, - "auto_turn_keys": 2153747880, - "qualityoflife_shorteners": 2153748024, - "fastwarp": 2153748116, - "fastwarp_playmusic": 2153748148, - "fastwarpshockwavefix": 2153748176, - "clearvulturecutscene": 2153748280, + "orangeguncode": 2153688380, + "istbarrelflag": 2153689664, + "isfairyflag": 2153689680, + "gethinttextindex": 2153689720, + "isgoodtextbox": 2153690916, + "getmovehint": 2153691028, + "resetdisplayedmusic": 2153691140, + "detectsongchange": 2153691152, + "initsongdisplay": 2153691492, + "displaysongnamehandler": 2153691804, + "curseremoved": 2153692396, + "haspermalossgrace": 2153692408, + "determinekongunlock": 2153692436, + "unlockkongpermaloss": 2153692580, + "givekongmoves": 2153692692, + "isdeathstate": 2153692760, + "kong_has_died": 2153692800, + "determinestartkong_permalossmode": 2153693352, + "transitionkong": 2153693520, + "fixgracecheese": 2153693828, + "changekongontransition_permaloss": 2153694000, + "forcebosskong": 2153694048, + "preventbosscheese": 2153694276, + "doeskongpossessmove": 2153694448, + "issharedmove": 2153695088, + "getcounteritem": 2153695300, + "getmovecountinshop": 2153695872, + "wipecounterimagecache": 2153696252, + "loadinternaltexture": 2153696300, + "loadfonttexture_counter": 2153696464, + "updatecounterdisplay": 2153696628, + "getactormodeltwodist": 2153696808, + "getclosestshop": 2153696944, + "getshopscale": 2153697596, + "newcountercode": 2153697936, + "handlefootprogress": 2153699336, + "changestat": 2153699788, + "setstat": 2153699872, + "getstat": 2153699888, + "getbonusblockstart": 2153699900, + "getbitoffset": 2153699976, + "getbitsize": 2153700052, + "readextradata": 2153700124, + "saveextradata": 2153700408, + "resetextradata": 2153700712, + "setkongigt": 2153700720, + "updatepercentagekongstat": 2153700772, + "genericstatupdate": 2153700984, + "updatetagstat": 2153701096, + "updatefairystat": 2153701212, + "updatekopstat": 2153701252, + "updateenemykillstat": 2153701260, + "createendseqcreditsfile": 2153701364, + "displaynumberonobject": 2153702152, + "shiftbrokenjapesportal": 2153702364, + "displaynumberontns": 2153702480, + "writewti": 2153703028, + "handle_wti": 2153703068, + "warptoisles": 2153703280, + "beatgame": 2153703388, + "finalizebeatgame": 2153703464, + "hasbeatendkrapwincon": 2153703564, + "checkseedvictory": 2153703864, + "checkvictory_flaghook": 2153704152, + "issnapenemyinrange": 2153704180, + "getpkmnsnapdata": 2153704664, + "pokemonsnapmode": 2153704820, + "arcadeexit": 2153705556, + "determinearcadelevel": 2153705588, + "handlearcadevictory": 2153705704, + "spawnoverlaytext": 2153706044, + "overlay_mod_bonus": 2153706356, + "overlay_mod_boss": 2153706652, + "overlay_changes": 2153706900, + "parsecutscenedata": 2153707112, + "loadjetpacsprites_handler": 2153707500, + "initjetpac": 2153707568, + "patchcrankycode": 2153707680, + "give_all_blueprints": 2153707828, + "overlay_mod_menu": 2153708036, + "overlay_mod_race": 2153708480, + "updatepausescreenwheel": 2153708660, + "newpausespritecode": 2153708740, + "totalssprite": 2153709628, + "checkssprite": 2153709636, + "handlespritecode": 2153709644, + "initcarousel_onpause": 2153709712, + "initcarousel_onboot": 2153710080, + "file_sprites": 2153710184, + "file_item_caps": 2153710252, + "file_items": 2153710304, + "initprogressivetimer": 2153710396, + "renderprogressivesprite": 2153710412, + "playprogressiveding": 2153710472, + "handleprogressiveindicator": 2153710492, + "resetprogressive": 2153710524, + "inithints": 2153710564, + "wipehintcache": 2153710740, + "drawhinttext": 2153710808, + "drawsplitstring": 2153711348, + "gethintrequirement": 2153712152, + "displaycbcount": 2153712196, + "gethintitemregion": 2153712392, + "showhint": 2153712416, + "displaybubble": 2153712528, + "gettiedshopmoveflag": 2153712688, + "getitemspecificity": 2153712752, + "inithintflags": 2153713116, + "getitemname": 2153713288, + "drawhintscreen": 2153713428, + "drawitemlocationscreen": 2153714384, + "item_names": 2153715732, + "item_name_plural": 2153715796, + "hints_initialized": 2153715812, + "display_billboard_fix": 2153715813, + "hint_region_names": 2153716852, + "unknown_hints": 2153717096, + "printleveligt": 2153718240, + "inititemcheckdenominators": 2153718624, + "checkitemdb": 2153718860, + "handlecshifting": 2153719420, + "pausescreen3and4header": 2153719552, + "drawtextpointers": 2153720132, + "pausescreen3and4itemname": 2153720284, + "pausescreen3and4counter": 2153720440, + "changepausescreen": 2153720692, + "changeselectedlevel": 2153720836, + "updatefilevariables": 2153720920, + "handleoutofcounters": 2153720956, + "initpausemenu": 2153721128, + "sethintregion": 2153722048, + "storehintregion": 2153723040, + "gethintregiontext": 2153723136, + "displayhintregion": 2153723268, + "getworldoffset": 2153723872, + "setblockerhead": 2153723920, + "displayblockeritemonhud": 2153724068, + "getcountofblockerrequireditem": 2153724160, + "displaycountonblockerteeth": 2153724224, + "cc_enable_drunky": 2153724340, + "cc_disable_drunky": 2153724404, + "cc_allower_generic": 2153724448, + "cc_enabler_icetrap": 2153724596, + "cc_allower_icetrap": 2153724628, + "cc_enabler_warptorap": 2153724680, + "handlegamemodewrapper": 2153724764, + "cc_disabler_warptorap": 2153724836, + "skipdktv": 2153724964, + "displaygetoutreticle": 2153725048, + "cc_enable_getout": 2153725372, + "fakegetout": 2153725556, + "cc_allower_rockfall": 2153726048, + "cc_enabler_rockfall": 2153726064, + "dummyguardcode": 2153726316, + "cc_allower_spawnkop": 2153726548, + "cc_enabler_spawnkop": 2153726580, + "cc_allower_balloon": 2153726744, + "cc_allower_backflip": 2153726796, + "cc_enabler_balloon": 2153726852, + "cc_enabler_slip": 2153726988, + "cc_allower_tag": 2153727060, + "cc_enabler_tag": 2153727180, + "cc_enabler_doabackflip": 2153727376, + "cc_enabler_ice": 2153727460, + "cc_disabler_ice": 2153727484, + "cc_allower_animals": 2153727532, + "cc_enabler_animals": 2153727592, + "cc_disabler_animals": 2153727840, + "cc_allower_mini": 2153727912, + "cc_setscale": 2153727956, + "cc_enabler_mini": 2153728004, + "cc_disabler_mini": 2153728084, + "cc_allower_boulder": 2153728140, + "cc_enabler_boulder": 2153728192, + "cc_effect_handler": 2153728264, + "replace_zones": 2153729168, + "blastwarphandler": 2153729564, + "swap_ending_cutscene_model": 2153729732, + "completeboss": 2153729948, + "fixkroolkong": 2153730632, + "handlekroolsaveprogress": 2153730732, + "swaprequirements": 2153731420, + "level_order_rando_funcs": 2153731496, + "writehudamount": 2153731584, + "movetransplant": 2153731888, + "progressivechange": 2153731956, + "getmoveprogressiveflagtype": 2153732356, + "writeprogressivetext": 2153732464, + "getnextmovepurchase": 2153732724, + "getpurchaseclassification": 2153733476, + "addhelmhurrypurchasetime": 2153734024, + "purchasemove": 2153734088, + "checkfirstmovepurchase": 2153734944, + "purchasefirstmovehandler": 2153735116, + "setlocation": 2153735224, + "getlocation": 2153736232, + "setlocationstatus": 2153736688, + "getlocationstatus": 2153736772, + "displaymovetext": 2153736860, + "getnextmovetext": 2153737172, + "displaybfimovetext": 2153739800, + "showpostmovetext": 2153739928, + "simianslamnames": 2153741168, + "specialmovesnames": 2153741176, + "gunnames": 2153741216, + "gunupgnames": 2153741224, + "ammobeltnames": 2153741228, + "instrumentnames": 2153741232, + "instrumentupgnames": 2153741240, + "alter_price": 2153741248, + "pricetransplant": 2153741284, + "destroybonus": 2153741632, + "completebonus": 2153741664, + "applyfaststart": 2153741852, + "opencrowndoor": 2153742028, + "opencoindoor": 2153742060, + "helminit": 2153742092, + "helmbarrelcode": 2153742960, + "checkdooritem": 2153743300, + "crowndoorcheck": 2153743688, + "coindoorcheck": 2153743700, + "initkongrando": 2153743840, + "initfile_checktraining": 2153744080, + "initfile_hasgun": 2153744112, + "initfile_hasinstrument": 2153744268, + "initfile_getbeltlevel": 2153744420, + "initfile_getinsupgradelevel": 2153744568, + "initfile_getslamlevel": 2153744728, + "initfile_getkongpotionbitfield": 2153744888, + "unlockmoves": 2153745304, + "apply_key": 2153746864, + "pre_turn_keys": 2153747196, + "writekeyflags": 2153747728, + "auto_turn_keys": 2153747744, + "qualityoflife_shorteners": 2153747888, + "fastwarp": 2153747980, + "fastwarp_playmusic": 2153748012, + "fastwarpshockwavefix": 2153748040, + "clearvulturecutscene": 2153748144, "codeend": 2153754112, "copyfunc": 2153756496, "regularframeloop": 2153759408, diff --git a/templates/cosmetics.html.jinja2 b/templates/cosmetics.html.jinja2 index bf3b57f47..fd1d76488 100644 --- a/templates/cosmetics.html.jinja2 +++ b/templates/cosmetics.html.jinja2 @@ -130,6 +130,7 @@ {{ toggle_input("dark_mode_textboxes", "Dark Mode UI", "Text bubbles will be darkened, with the font brightened. The DPad Graphic will be darkened.") }} {{ toggle_input("pause_hint_coloring", "Color Hints in Pause Menu", "Various important segments in hints on the pause menu will be colored to assist with parsing.", True) }} {{ toggle_input("disco_chunky", "Disco Chunky", "Gives Chunky his disco outfit. Will only work if his model is set to regular chunky.") }} + {{ toggle_input("smoother_camera", "Smoother Camera", "Controlling the camera with the C Buttons behaves closer to how Banjo-Tooie operates.") }}