From ac91a15959450274b64f11a28c0ca31763c2bf0c Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:47:21 -0400 Subject: [PATCH] LTTP/SM/SMZ3: Show correct item icon for cross-game items (#1112) Co-authored-by: lordlou <87331798+lordlou@users.noreply.github.com> Co-authored-by: Fabian Dill --- worlds/alttp/Rom.py | 8 ++++-- worlds/sm/__init__.py | 50 ++++++++++++++++++++-------------- worlds/smz3/TotalSMZ3/Patch.py | 25 ++++++++++++++++- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 64f678a68a4b..ed222b5f5d14 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -762,7 +762,9 @@ def write_to_rom(self, rom: LocalRom): 0x4D504, 0x4D507, 0x4D55E, 0x4D56A] -def get_nonnative_item_sprite(item: str) -> int: +def get_nonnative_item_sprite(code: int) -> int: + if 84173 >= code >= 84007: # LttP item in SMZ3 + return code - 84000 return 0x6B # set all non-native sprites to Power Star as per 13 to 2 vote at # https://discord.com/channels/731205301247803413/827141303330406408/852102450822905886 @@ -785,7 +787,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): if location.item.trap: itemid = 0x5A # Nothing, which disguises else: - itemid = get_nonnative_item_sprite(location.item.name) + itemid = get_nonnative_item_sprite(location.item.code) # Keys in their native dungeon should use the orignal item code for keys elif location.parent_region.dungeon: if location.parent_region.dungeon.is_dungeon_item(location.item): @@ -1739,7 +1741,7 @@ def write_custom_shops(rom, world, player): replacement_price_data = get_price_data(item['replacement_price'], item['replacement_price_type']) slot = 0 if shop.type == ShopType.TakeAny else index if item['player'] and world.game[item['player']] != "A Link to the Past": # item not native to ALTTP - item_code = get_nonnative_item_sprite(item['item']) + item_code = get_nonnative_item_sprite(world.worlds[item['player']].item_name_to_id[item['item']]) else: item_code = ItemFactory(item['item'], player).code if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro_bow[player]: diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 60e557aac020..28ed288de76e 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -174,12 +174,12 @@ def create_items(self): isAdvancement = False itemClass = ItemManager.Items[item.Type].Class - smitem = SMItem(item.Name, - ItemClassification.progression if isAdvancement else ItemClassification.filler, + smitem = SMItem(item.Name, + ItemClassification.progression if isAdvancement else ItemClassification.filler, item.Type, - None if itemClass == 'Boss' else self.item_name_to_id[item.Name], + None if itemClass == 'Boss' else self.item_name_to_id[item.Name], player=self.player) - + if itemClass == 'Boss': self.locked_items[item.Name] = smitem elif item.Category == 'Nothing': @@ -192,10 +192,10 @@ def create_items(self): for (location, item) in self.locked_items.items(): self.multiworld.get_location(location, self.player).place_locked_item(item) self.multiworld.get_location(location, self.player).address = None - + def evalSMBool(self, smbool, maxDiff): return smbool.bool == True and smbool.difficulty <= maxDiff - + def add_entrance_rule(self, entrance, player, func): add_rule(entrance, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff)) @@ -221,12 +221,12 @@ def set_entrance_rule(entrance, player, func): add_accessFrom_rule(location, self.player, value.AccessFrom) if value.PostAvailable is not None: add_postAvailable_rule(location, self.player, value.PostAvailable) - + for accessPoint in Logic.accessPoints: if not accessPoint.Escape: for key, value1 in accessPoint.intraTransitions.items(): set_entrance_rule(self.multiworld.get_entrance(accessPoint.Name + "->" + key, self.player), self.player, value1) - + def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None): ret = Region(name, player, world) if locations: @@ -238,7 +238,7 @@ def create_region(self, world: MultiWorld, player: int, name: str, locations=Non for exit in exits: ret.exits.append(Entrance(player, exit, ret)) return ret - + def create_regions(self): # create locations for name in locationsDict: @@ -248,9 +248,9 @@ def create_regions(self): regions = [] for accessPoint in Logic.accessPoints: if not accessPoint.Escape: - regions.append(self.create_region( self.multiworld, - self.player, - accessPoint.Name, + regions.append(self.create_region( self.multiworld, + self.player, + accessPoint.Name, None, [accessPoint.Name + "->" + key for key in accessPoint.intraTransitions.keys()])) @@ -261,9 +261,9 @@ def create_regions(self): # this is required in AP because a location cant have multiple parent regions locationRegions = [] for locationName, value in locationsDict.items(): - locationRegions.append(self.create_region( self.multiworld, - self.player, - locationName, + locationRegions.append(self.create_region( self.multiworld, + self.player, + locationName, [locationName])) for key in value.AccessFrom.keys(): currentRegion = self.multiworld.get_region(key, self.player) @@ -320,7 +320,7 @@ def get_filler_item_name(self) -> str: return "Super Missile" else: return "Nothing" - + def pre_fill(self): if len(self.NothingPool) > 0: nonChozoLoc = [] @@ -371,8 +371,8 @@ def post_fill(self): itemLoc.item.type if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem']), copy.copy(locationsDict[itemLoc.name] if itemLoc.game == self.game else - locationsDict[first_local_collected_loc.name]), - itemLoc.item.player, + locationsDict[first_local_collected_loc.name]), + itemLoc.item.player, True) for itemLoc in SMWorld.spheres if itemLoc.item.player == self.player ] @@ -387,7 +387,7 @@ def post_fill(self): escapeOk = self.variaRando.randoExec.graphBuilder.escapeGraph(self.variaRando.container, self.variaRando.randoExec.areaGraph, self.variaRando.randoExec.randoSettings.maxDiff, escapeTrigger) if (not escapeOk): logger.warning(f"Escape Rando forced to 'Off' for player {self.multiworld.get_player_name(self.player)} because could not find a solution for escape") - + # if we couldn't find an area layout then the escape graph is not created either # and getDoorConnections will crash if random escape is activated. self.variaRando.doors = GraphUtils.getDoorConnections(self.variaRando.randoExec.areaGraph, @@ -406,7 +406,7 @@ def stage_post_fill(cls, world): for item in progitempool: new_state.collect(item, True) - + bossesLoc = ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain'] for player in world.get_game_players("Super Metroid"): for bossLoc in bossesLoc: @@ -548,9 +548,17 @@ def APPostPatchRom(self, romPatcher): vanillaItemTypesCount = 21 for itemLoc in self.multiworld.get_locations(): if itemLoc.player == self.player and "Boss" not in locationsDict[itemLoc.name].Class: - # item to place in this SM world: write full item data to tables + SMZ3NameToSMType = { + "ETank": "ETank", "Missile": "Missile", "Super": "Super", "PowerBomb": "PowerBomb", "Bombs": "Bomb", + "Charge": "Charge", "Ice": "Ice", "HiJump": "HiJump", "SpeedBooster": "SpeedBooster", + "Wave": "Wave", "Spazer": "Spazer", "SpringBall": "SpringBall", "Varia": "Varia", "Plasma": "Plasma", + "Grapple": "Grapple", "Morph": "Morph", "ReserveTank": "Reserve", "Gravity": "Gravity", + "XRay": "XRayScope", "SpaceJump": "SpaceJump", "ScrewAttack": "ScrewAttack" + } if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items: itemId = ItemManager.Items[itemLoc.item.type].Id + elif itemLoc.item.game == "SMZ3" and itemLoc.item.name in SMZ3NameToSMType.keys(): + itemId = ItemManager.Items[SMZ3NameToSMType[itemLoc.item.name]].Id else: itemId = ItemManager.Items["ArchipelagoItem"].Id + idx multiWorldItems.append({"sym": symbols["message_item_names"], diff --git a/worlds/smz3/TotalSMZ3/Patch.py b/worlds/smz3/TotalSMZ3/Patch.py index 7924386af18f..a79c99a8da2d 100644 --- a/worlds/smz3/TotalSMZ3/Patch.py +++ b/worlds/smz3/TotalSMZ3/Patch.py @@ -4,7 +4,7 @@ import random import typing from BaseClasses import Location -from .Item import Item, ItemType +from .Item import Item, ItemType, lookup_id_to_name from .Location import LocationType from .Region import IReward, RewardType, SMRegion, Z3Region from .Regions.Zelda.EasternPalace import EasternPalace @@ -351,6 +351,29 @@ def GetZ3ItemId(self, location: Location): not (item.IsDungeonItem() and location.Region.IsRegionItem(item) and item.World == self.myWorld) else itemDungeon return value.value + elif (location.APLocation.item.game == "A Link to the Past"): + if location.APLocation.item.code + 84000 in lookup_id_to_name: + ALTTPBottleContentCodeToSMZ3ItemCode = { + ItemType.RedContent.value: ItemType.BottleWithRedPotion.value, + ItemType.GreenContent.value: ItemType.BottleWithGreenPotion.value, + ItemType.BlueContent.value: ItemType.BottleWithBluePotion.value, + ItemType.BeeContent.value: ItemType.BottleWithBee.value, + } + return ALTTPBottleContentCodeToSMZ3ItemCode.get(location.APLocation.item.code, location.APLocation.item.code) + else: + return ItemType.Something.value + elif (location.APLocation.item.game == "Super Metroid"): + SMNameToSMZ3Code = { + "Energy Tank": ItemType.ETank, "Missile": ItemType.Missile, "Super Missile": ItemType.Super, + "Power Bomb": ItemType.PowerBomb, "Bomb": ItemType.Bombs, "Charge Beam": ItemType.Charge, + "Ice Beam": ItemType.Ice, "Hi-Jump Boots": ItemType.HiJump, "Speed Booster": ItemType.SpeedBooster, + "Wave Beam": ItemType.Wave, "Spazer": ItemType.Spazer, "Spring Ball": ItemType.SpringBall, + "Varia Suit": ItemType.Varia, "Plasma Beam": ItemType.Plasma, "Grappling Beam": ItemType.Grapple, + "Morph Ball": ItemType.Morph, "Reserve Tank": ItemType.ReserveTank, "Gravity Suit": ItemType.Gravity, + "X-Ray Scope": ItemType.XRay, "Space Jump": ItemType.SpaceJump, "Screw Attack": ItemType.ScrewAttack, + "Nothing": ItemType.Something, "No Energy": ItemType.Something, "Generic": ItemType.Something + } + return SMNameToSMZ3Code.get(location.APLocation.item.name, ItemType.Something).value else: return ItemType.Something.value