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 <Berserker66@users.noreply.github.com>
This commit is contained in:
Alchav 2023-06-29 11:47:21 -04:00 committed by GitHub
parent 18e0d25051
commit 776b5fab7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 25 deletions

View File

@ -762,7 +762,9 @@ bonk_addresses = [0x4CF6C, 0x4CFBA, 0x4CFE0, 0x4CFFB, 0x4D018, 0x4D01B, 0x4D028,
0x4D504, 0x4D507, 0x4D55E, 0x4D56A] 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 return 0x6B # set all non-native sprites to Power Star as per 13 to 2 vote at
# https://discord.com/channels/731205301247803413/827141303330406408/852102450822905886 # 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: if location.item.trap:
itemid = 0x5A # Nothing, which disguises itemid = 0x5A # Nothing, which disguises
else: 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 # Keys in their native dungeon should use the orignal item code for keys
elif location.parent_region.dungeon: elif location.parent_region.dungeon:
if location.parent_region.dungeon.is_dungeon_item(location.item): 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']) replacement_price_data = get_price_data(item['replacement_price'], item['replacement_price_type'])
slot = 0 if shop.type == ShopType.TakeAny else index 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 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: else:
item_code = ItemFactory(item['item'], player).code item_code = ItemFactory(item['item'], player).code
if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro_bow[player]: if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro_bow[player]:

View File

@ -174,12 +174,12 @@ class SMWorld(World):
isAdvancement = False isAdvancement = False
itemClass = ItemManager.Items[item.Type].Class itemClass = ItemManager.Items[item.Type].Class
smitem = SMItem(item.Name, smitem = SMItem(item.Name,
ItemClassification.progression if isAdvancement else ItemClassification.filler, ItemClassification.progression if isAdvancement else ItemClassification.filler,
item.Type, 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) player=self.player)
if itemClass == 'Boss': if itemClass == 'Boss':
self.locked_items[item.Name] = smitem self.locked_items[item.Name] = smitem
elif item.Category == 'Nothing': elif item.Category == 'Nothing':
@ -192,10 +192,10 @@ class SMWorld(World):
for (location, item) in self.locked_items.items(): 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).place_locked_item(item)
self.multiworld.get_location(location, self.player).address = None self.multiworld.get_location(location, self.player).address = None
def evalSMBool(self, smbool, maxDiff): def evalSMBool(self, smbool, maxDiff):
return smbool.bool == True and smbool.difficulty <= maxDiff return smbool.bool == True and smbool.difficulty <= maxDiff
def add_entrance_rule(self, entrance, player, func): def add_entrance_rule(self, entrance, player, func):
add_rule(entrance, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff)) add_rule(entrance, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
@ -221,12 +221,12 @@ class SMWorld(World):
add_accessFrom_rule(location, self.player, value.AccessFrom) add_accessFrom_rule(location, self.player, value.AccessFrom)
if value.PostAvailable is not None: if value.PostAvailable is not None:
add_postAvailable_rule(location, self.player, value.PostAvailable) add_postAvailable_rule(location, self.player, value.PostAvailable)
for accessPoint in Logic.accessPoints: for accessPoint in Logic.accessPoints:
if not accessPoint.Escape: if not accessPoint.Escape:
for key, value1 in accessPoint.intraTransitions.items(): for key, value1 in accessPoint.intraTransitions.items():
set_entrance_rule(self.multiworld.get_entrance(accessPoint.Name + "->" + key, self.player), self.player, value1) 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): def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
ret = Region(name, player, world) ret = Region(name, player, world)
if locations: if locations:
@ -238,7 +238,7 @@ class SMWorld(World):
for exit in exits: for exit in exits:
ret.exits.append(Entrance(player, exit, ret)) ret.exits.append(Entrance(player, exit, ret))
return ret return ret
def create_regions(self): def create_regions(self):
# create locations # create locations
for name in locationsDict: for name in locationsDict:
@ -248,9 +248,9 @@ class SMWorld(World):
regions = [] regions = []
for accessPoint in Logic.accessPoints: for accessPoint in Logic.accessPoints:
if not accessPoint.Escape: if not accessPoint.Escape:
regions.append(self.create_region( self.multiworld, regions.append(self.create_region( self.multiworld,
self.player, self.player,
accessPoint.Name, accessPoint.Name,
None, None,
[accessPoint.Name + "->" + key for key in accessPoint.intraTransitions.keys()])) [accessPoint.Name + "->" + key for key in accessPoint.intraTransitions.keys()]))
@ -261,9 +261,9 @@ class SMWorld(World):
# this is required in AP because a location cant have multiple parent regions # this is required in AP because a location cant have multiple parent regions
locationRegions = [] locationRegions = []
for locationName, value in locationsDict.items(): for locationName, value in locationsDict.items():
locationRegions.append(self.create_region( self.multiworld, locationRegions.append(self.create_region( self.multiworld,
self.player, self.player,
locationName, locationName,
[locationName])) [locationName]))
for key in value.AccessFrom.keys(): for key in value.AccessFrom.keys():
currentRegion = self.multiworld.get_region(key, self.player) currentRegion = self.multiworld.get_region(key, self.player)
@ -320,7 +320,7 @@ class SMWorld(World):
return "Super Missile" return "Super Missile"
else: else:
return "Nothing" return "Nothing"
def pre_fill(self): def pre_fill(self):
if len(self.NothingPool) > 0: if len(self.NothingPool) > 0:
nonChozoLoc = [] nonChozoLoc = []
@ -371,8 +371,8 @@ class SMWorld(World):
itemLoc.item.type if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else itemLoc.item.type if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
'ArchipelagoItem']), 'ArchipelagoItem']),
copy.copy(locationsDict[itemLoc.name] if itemLoc.game == self.game else copy.copy(locationsDict[itemLoc.name] if itemLoc.game == self.game else
locationsDict[first_local_collected_loc.name]), locationsDict[first_local_collected_loc.name]),
itemLoc.item.player, itemLoc.item.player,
True) True)
for itemLoc in SMWorld.spheres if itemLoc.item.player == self.player for itemLoc in SMWorld.spheres if itemLoc.item.player == self.player
] ]
@ -387,7 +387,7 @@ class SMWorld(World):
escapeOk = self.variaRando.randoExec.graphBuilder.escapeGraph(self.variaRando.container, self.variaRando.randoExec.areaGraph, self.variaRando.randoExec.randoSettings.maxDiff, escapeTrigger) escapeOk = self.variaRando.randoExec.graphBuilder.escapeGraph(self.variaRando.container, self.variaRando.randoExec.areaGraph, self.variaRando.randoExec.randoSettings.maxDiff, escapeTrigger)
if (not escapeOk): 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") 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 # 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. # and getDoorConnections will crash if random escape is activated.
self.variaRando.doors = GraphUtils.getDoorConnections(self.variaRando.randoExec.areaGraph, self.variaRando.doors = GraphUtils.getDoorConnections(self.variaRando.randoExec.areaGraph,
@ -406,7 +406,7 @@ class SMWorld(World):
for item in progitempool: for item in progitempool:
new_state.collect(item, True) new_state.collect(item, True)
bossesLoc = ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain'] bossesLoc = ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain']
for player in world.get_game_players("Super Metroid"): for player in world.get_game_players("Super Metroid"):
for bossLoc in bossesLoc: for bossLoc in bossesLoc:
@ -548,9 +548,17 @@ class SMWorld(World):
vanillaItemTypesCount = 21 vanillaItemTypesCount = 21
for itemLoc in self.multiworld.get_locations(): for itemLoc in self.multiworld.get_locations():
if itemLoc.player == self.player and "Boss" not in locationsDict[itemLoc.name].Class: 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: if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items:
itemId = ItemManager.Items[itemLoc.item.type].Id 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: else:
itemId = ItemManager.Items["ArchipelagoItem"].Id + idx itemId = ItemManager.Items["ArchipelagoItem"].Id + idx
multiWorldItems.append({"sym": symbols["message_item_names"], multiWorldItems.append({"sym": symbols["message_item_names"],

View File

@ -4,7 +4,7 @@ from typing import Any, Callable, List, Sequence
import random import random
import typing import typing
from BaseClasses import Location from BaseClasses import Location
from .Item import Item, ItemType from .Item import Item, ItemType, lookup_id_to_name
from .Location import LocationType from .Location import LocationType
from .Region import IReward, RewardType, SMRegion, Z3Region from .Region import IReward, RewardType, SMRegion, Z3Region
from .Regions.Zelda.EasternPalace import EasternPalace from .Regions.Zelda.EasternPalace import EasternPalace
@ -351,6 +351,29 @@ class Patch:
not (item.IsDungeonItem() and location.Region.IsRegionItem(item) and item.World == self.myWorld) else itemDungeon not (item.IsDungeonItem() and location.Region.IsRegionItem(item) and item.World == self.myWorld) else itemDungeon
return value.value 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: else:
return ItemType.Something.value return ItemType.Something.value