Smz3 updated to version 11.3 (#886)

This commit is contained in:
lordlou 2022-08-15 10:48:13 -04:00 committed by GitHub
parent c02c6ee58c
commit 898fa203ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 907 additions and 482 deletions

View File

@ -1,5 +1,5 @@
import typing import typing
from Options import Choice, Option from Options import Choice, Option, Toggle, DefaultOnToggle, Range
class SMLogic(Choice): class SMLogic(Choice):
"""This option selects what kind of logic to use for item placement inside """This option selects what kind of logic to use for item placement inside
@ -45,6 +45,22 @@ class MorphLocation(Choice):
option_Original = 2 option_Original = 2
default = 0 default = 0
class Goal(Choice):
"""This option decides what goal is required to finish the randomizer.
Defeat Ganon and Mother Brain - Find the required crystals and boss tokens kill both bosses.
Fast Ganon and Defeat Mother Brain - The hole to ganon is open without having to defeat Agahnim in
Ganon's Tower and Ganon can be defeat as soon you have the required
crystals to make Ganon vulnerable. For keysanity, this mode also removes
the Crateria Boss Key requirement from Tourian to allow faster access.
All Dungeons and Defeat Mother Brain - Similar to "Defeat Ganon and Mother Brain", but also requires all dungeons
to be beaten including Castle Tower and Agahnim."""
display_name = "Goal"
option_DefeatBoth = 0
option_FastGanonDefeatMotherBrain = 1
option_AllDungeonsDefeatMotherBrain = 2
default = 0
class KeyShuffle(Choice): class KeyShuffle(Choice):
"""This option decides how dungeon items such as keys are shuffled. """This option decides how dungeon items such as keys are shuffled.
None - A Link to the Past dungeon items can only be placed inside the None - A Link to the Past dungeon items can only be placed inside the
@ -55,9 +71,75 @@ class KeyShuffle(Choice):
option_Keysanity = 1 option_Keysanity = 1
default = 0 default = 0
class OpenTower(Range):
"""The amount of crystals required to be able to enter Ganon's Tower.
If this is set to Random, the amount can be found in-game on a sign next to Ganon's Tower."""
display_name = "Open Tower"
range_start = 0
range_end = 7
default = 7
class GanonVulnerable(Range):
"""The amount of crystals required to be able to harm Ganon. The amount can be found
in-game on a sign near the top of the Pyramid."""
display_name = "Ganon Vulnerable"
range_start = 0
range_end = 7
default = 7
class OpenTourian(Range):
"""The amount of boss tokens required to enter Tourian. The amount can be found in-game
on a sign above the door leading to the Tourian entrance."""
display_name = "Open Tourian"
range_start = 0
range_end = 4
default = 4
class SpinJumpsAnimation(Toggle):
"""Enable separate space/screw jump animations"""
display_name = "Spin Jumps Animation"
class HeartBeepSpeed(Choice):
"""Sets the speed of the heart beep sound in A Link to the Past."""
display_name = "Heart Beep Speed"
option_Off = 0
option_Quarter = 1
option_Half = 2
option_Normal = 3
option_Double = 4
alias_false = 0
default = 3
class HeartColor(Choice):
"""Changes the color of the hearts in the HUD for A Link to the Past."""
display_name = "Heart Color"
option_Red = 0
option_Green = 1
option_Blue = 2
option_Yellow = 3
default = 0
class QuickSwap(Toggle):
"""When enabled, lets you switch items in ALTTP with L/R"""
display_name = "Quick Swap"
class EnergyBeep(DefaultOnToggle):
"""Toggles the low health energy beep in Super Metroid."""
display_name = "Energy Beep"
smz3_options: typing.Dict[str, type(Option)] = { smz3_options: typing.Dict[str, type(Option)] = {
"sm_logic": SMLogic, "sm_logic": SMLogic,
"sword_location": SwordLocation, "sword_location": SwordLocation,
"morph_location": MorphLocation, "morph_location": MorphLocation,
"key_shuffle": KeyShuffle "goal": Goal,
"key_shuffle": KeyShuffle,
"open_tower": OpenTower,
"ganon_vulnerable": GanonVulnerable,
"open_tourian": OpenTourian,
"spin_jumps_animation": SpinJumpsAnimation,
"heart_beep_speed": HeartBeepSpeed,
"heart_color": HeartColor,
"quick_swap": QuickSwap,
"energy_beep": EnergyBeep
} }

View File

@ -26,16 +26,42 @@ class MorphLocation(Enum):
class Goal(Enum): class Goal(Enum):
DefeatBoth = 0 DefeatBoth = 0
FastGanonDefeatMotherBrain = 1
AllDungeonsDefeatMotherBrain = 2
class KeyShuffle(Enum): class KeyShuffle(Enum):
Null = 0 Null = 0
Keysanity = 1 Keysanity = 1
class GanonInvincible(Enum): class OpenTower(Enum):
Never = 0 Random = -1
BeforeCrystals = 1 NoCrystals = 0
BeforeAllDungeons = 2 OneCrystal = 1
Always = 3 TwoCrystals = 2
ThreeCrystals = 3
FourCrystals = 4
FiveCrystals = 5
SixCrystals = 6
SevenCrystals = 7
class GanonVulnerable(Enum):
Random = -1
NoCrystals = 0
OneCrystal = 1
TwoCrystals = 2
ThreeCrystals = 3
FourCrystals = 4
FiveCrystals = 5
SixCrystals = 6
SevenCrystals = 7
class OpenTourian(Enum):
Random = -1
NoBosses = 0
OneBoss = 1
TwoBosses = 2
ThreeBosses = 3
FourBosses = 4
class Config: class Config:
GameMode: GameMode = GameMode.Multiworld GameMode: GameMode = GameMode.Multiworld
@ -45,63 +71,20 @@ class Config:
MorphLocation: MorphLocation = MorphLocation.Randomized MorphLocation: MorphLocation = MorphLocation.Randomized
Goal: Goal = Goal.DefeatBoth Goal: Goal = Goal.DefeatBoth
KeyShuffle: KeyShuffle = KeyShuffle.Null KeyShuffle: KeyShuffle = KeyShuffle.Null
Keysanity: bool = KeyShuffle != KeyShuffle.Null
Race: bool = False Race: bool = False
GanonInvincible: GanonInvincible = GanonInvincible.BeforeCrystals
def __init__(self, options: Dict[str, str]): OpenTower: OpenTower = OpenTower.SevenCrystals
self.GameMode = self.ParseOption(options, GameMode.Multiworld) GanonVulnerable: GanonVulnerable = GanonVulnerable.SevenCrystals
self.Z3Logic = self.ParseOption(options, Z3Logic.Normal) OpenTourian: OpenTourian = OpenTourian.FourBosses
self.SMLogic = self.ParseOption(options, SMLogic.Normal)
self.SwordLocation = self.ParseOption(options, SwordLocation.Randomized)
self.MorphLocation = self.ParseOption(options, MorphLocation.Randomized)
self.Goal = self.ParseOption(options, Goal.DefeatBoth)
self.GanonInvincible = self.ParseOption(options, GanonInvincible.BeforeCrystals)
self.KeyShuffle = self.ParseOption(options, KeyShuffle.Null)
self.Keysanity = self.KeyShuffle != KeyShuffle.Null
self.Race = self.ParseOptionWith(options, "Race", False)
def ParseOption(self, options:Dict[str, str], defaultValue:Enum): @property
enumKey = defaultValue.__class__.__name__.lower() def SingleWorld(self) -> bool:
if (enumKey in options): return self.GameMode == GameMode.Normal
return defaultValue.__class__[options[enumKey]]
return defaultValue @property
def Multiworld(self) -> bool:
return self.GameMode == GameMode.Multiworld
def ParseOptionWith(self, options:Dict[str, str], option:str, defaultValue:bool): @property
if (option.lower() in options): def Keysanity(self) -> bool:
return options[option.lower()] return self.KeyShuffle != KeyShuffle.Null
return defaultValue
""" public static RandomizerOption GetRandomizerOption<T>(string description, string defaultOption = "") where T : Enum {
var enumType = typeof(T);
var values = Enum.GetValues(enumType).Cast<Enum>();
return new RandomizerOption {
Key = enumType.Name.ToLower(),
Description = description,
Type = RandomizerOptionType.Dropdown,
Default = string.IsNullOrEmpty(defaultOption) ? GetDefaultValue<T>().ToLString() : defaultOption,
Values = values.ToDictionary(k => k.ToLString(), v => v.GetDescription())
};
}
public static RandomizerOption GetRandomizerOption(string name, string description, bool defaultOption = false) {
return new RandomizerOption {
Key = name.ToLower(),
Description = description,
Type = RandomizerOptionType.Checkbox,
Default = defaultOption.ToString().ToLower(),
Values = new Dictionary<string, string>()
};
}
public static TEnum GetDefaultValue<TEnum>() where TEnum : Enum {
Type t = typeof(TEnum);
var attributes = (DefaultValueAttribute[])t.GetCustomAttributes(typeof(DefaultValueAttribute), false);
if ((attributes?.Length ?? 0) > 0) {
return (TEnum)attributes.First().Value;
}
else {
return default;
}
} """

View File

@ -130,6 +130,11 @@ class ItemType(Enum):
CardLowerNorfairL1 = 0xDE CardLowerNorfairL1 = 0xDE
CardLowerNorfairBoss = 0xDF CardLowerNorfairBoss = 0xDF
SmMapBrinstar = 0xCA
SmMapWreckedShip = 0xCB
SmMapMaridia = 0xCC
SmMapLowerNorfair = 0xCD
Missile = 0xC2 Missile = 0xC2
Super = 0xC3 Super = 0xC3
PowerBomb = 0xC4 PowerBomb = 0xC4
@ -174,6 +179,7 @@ class Item:
map = re.compile("^Map") map = re.compile("^Map")
compass = re.compile("^Compass") compass = re.compile("^Compass")
keycard = re.compile("^Card") keycard = re.compile("^Card")
smMap = re.compile("^SmMap")
def IsDungeonItem(self): return self.dungeon.match(self.Type.name) def IsDungeonItem(self): return self.dungeon.match(self.Type.name)
def IsBigKey(self): return self.bigKey.match(self.Type.name) def IsBigKey(self): return self.bigKey.match(self.Type.name)
@ -181,6 +187,7 @@ class Item:
def IsMap(self): return self.map.match(self.Type.name) def IsMap(self): return self.map.match(self.Type.name)
def IsCompass(self): return self.compass.match(self.Type.name) def IsCompass(self): return self.compass.match(self.Type.name)
def IsKeycard(self): return self.keycard.match(self.Type.name) def IsKeycard(self): return self.keycard.match(self.Type.name)
def IsSmMap(self): return self.smMap.match(self.Type.name)
def Is(self, type: ItemType, world): def Is(self, type: ItemType, world):
return self.Type == type and self.World == world return self.Type == type and self.World == world
@ -313,7 +320,7 @@ class Item:
Item.AddRange(itemPool, 4, Item(ItemType.BombUpgrade5)) Item.AddRange(itemPool, 4, Item(ItemType.BombUpgrade5))
Item.AddRange(itemPool, 2, Item(ItemType.OneRupee)) Item.AddRange(itemPool, 2, Item(ItemType.OneRupee))
Item.AddRange(itemPool, 4, Item(ItemType.FiveRupees)) Item.AddRange(itemPool, 4, Item(ItemType.FiveRupees))
Item.AddRange(itemPool, 25 if world.Config.Keysanity else 28, Item(ItemType.TwentyRupees)) Item.AddRange(itemPool, 21 if world.Config.Keysanity else 28, Item(ItemType.TwentyRupees))
Item.AddRange(itemPool, 7, Item(ItemType.FiftyRupees)) Item.AddRange(itemPool, 7, Item(ItemType.FiftyRupees))
Item.AddRange(itemPool, 5, Item(ItemType.ThreeHundredRupees)) Item.AddRange(itemPool, 5, Item(ItemType.ThreeHundredRupees))
@ -421,6 +428,21 @@ class Item:
return itemPool return itemPool
@staticmethod
def CreateSmMaps(world):
itemPool = [
Item(ItemType.SmMapBrinstar, world),
Item(ItemType.SmMapWreckedShip, world),
Item(ItemType.SmMapMaridia, world),
Item(ItemType.SmMapLowerNorfair, world)
]
for item in itemPool:
item.Progression = True
item.World = world
return itemPool
@staticmethod @staticmethod
def Get(items, itemType:ItemType): def Get(items, itemType:ItemType):
item = next((i for i in items if i.Type == itemType), None) item = next((i for i in items if i.Type == itemType), None)
@ -725,7 +747,7 @@ class Progression:
def CanAccessMiseryMirePortal(self, config: Config): def CanAccessMiseryMirePortal(self, config: Config):
if (config.SMLogic == SMLogic.Normal): if (config.SMLogic == SMLogic.Normal):
return (self.CardNorfairL2 or (self.SpeedBooster and self.Wave)) and self.Varia and self.Super and (self.Gravity and self.SpaceJump) and self.CanUsePowerBombs() return (self.CardNorfairL2 or (self.SpeedBooster and self.Wave)) and self.Varia and self.Super and self.Gravity and self.SpaceJump and self.CanUsePowerBombs()
else: else:
return (self.CardNorfairL2 or self.SpeedBooster) and self.Varia and self.Super and \ return (self.CardNorfairL2 or self.SpeedBooster) and self.Varia and self.Super and \
(self.CanFly() or self.HiJump or self.SpeedBooster or self.CanSpringBallJump() or self.Ice) \ (self.CanFly() or self.HiJump or self.SpeedBooster or self.CanSpringBallJump() or self.Ice) \
@ -769,11 +791,11 @@ class Progression:
if (world.Config.SMLogic == SMLogic.Normal): if (world.Config.SMLogic == SMLogic.Normal):
return self.MoonPearl and self.Flippers and \ return self.MoonPearl and self.Flippers and \
self.Gravity and self.Morph and \ self.Gravity and self.Morph and \
(world.CanAquire(self, worlds.smz3.TotalSMZ3.Region.RewardType.Agahnim) or self.Hammer and self.CanLiftLight() or self.CanLiftHeavy()) (world.CanAcquire(self, worlds.smz3.TotalSMZ3.Region.RewardType.Agahnim) or self.Hammer and self.CanLiftLight() or self.CanLiftHeavy())
else: else:
return self.MoonPearl and self.Flippers and \ return self.MoonPearl and self.Flippers and \
(self.CanSpringBallJump() or self.HiJump or self.Gravity) and self.Morph and \ (self.CanSpringBallJump() or self.HiJump or self.Gravity) and self.Morph and \
(world.CanAquire(self, worlds.smz3.TotalSMZ3.Region.RewardType.Agahnim) or self.Hammer and self.CanLiftLight() or self.CanLiftHeavy()) (world.CanAcquire(self, worlds.smz3.TotalSMZ3.Region.RewardType.Agahnim) or self.Hammer and self.CanLiftLight() or self.CanLiftHeavy())
# Start of AP integration # Start of AP integration
items_start_id = 84000 items_start_id = 84000

View File

@ -6,7 +6,7 @@ import typing
from BaseClasses import Location from BaseClasses import Location
from worlds.smz3.TotalSMZ3.Item import Item, ItemType from worlds.smz3.TotalSMZ3.Item import Item, ItemType
from worlds.smz3.TotalSMZ3.Location import LocationType from worlds.smz3.TotalSMZ3.Location import LocationType
from worlds.smz3.TotalSMZ3.Region import IMedallionAccess, IReward, RewardType, SMRegion, Z3Region from worlds.smz3.TotalSMZ3.Region import IReward, RewardType, SMRegion, Z3Region
from worlds.smz3.TotalSMZ3.Regions.Zelda.EasternPalace import EasternPalace from worlds.smz3.TotalSMZ3.Regions.Zelda.EasternPalace import EasternPalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.DesertPalace import DesertPalace from worlds.smz3.TotalSMZ3.Regions.Zelda.DesertPalace import DesertPalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.TowerOfHera import TowerOfHera from worlds.smz3.TotalSMZ3.Regions.Zelda.TowerOfHera import TowerOfHera
@ -18,10 +18,14 @@ from worlds.smz3.TotalSMZ3.Regions.Zelda.IcePalace import IcePalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.MiseryMire import MiseryMire from worlds.smz3.TotalSMZ3.Regions.Zelda.MiseryMire import MiseryMire
from worlds.smz3.TotalSMZ3.Regions.Zelda.TurtleRock import TurtleRock from worlds.smz3.TotalSMZ3.Regions.Zelda.TurtleRock import TurtleRock
from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Brinstar.Kraid import Kraid
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.WreckedShip import WreckedShip
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Maridia.Inner import Inner
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.NorfairLower.East import East
from worlds.smz3.TotalSMZ3.Text.StringTable import StringTable from worlds.smz3.TotalSMZ3.Text.StringTable import StringTable
from worlds.smz3.TotalSMZ3.World import World from worlds.smz3.TotalSMZ3.World import World
from worlds.smz3.TotalSMZ3.Config import Config, GameMode, GanonInvincible from worlds.smz3.TotalSMZ3.Config import Config, OpenTourian, Goal
from worlds.smz3.TotalSMZ3.Text.Texts import Texts from worlds.smz3.TotalSMZ3.Text.Texts import Texts
from worlds.smz3.TotalSMZ3.Text.Dialog import Dialog from worlds.smz3.TotalSMZ3.Text.Dialog import Dialog
@ -30,6 +34,11 @@ class KeycardPlaque:
Level2 = 0xe1 Level2 = 0xe1
Boss = 0xe2 Boss = 0xe2
Null = 0x00 Null = 0x00
Zero = 0xe3
One = 0xe4
Two = 0xe5
Three = 0xe6
Four = 0xe7
class KeycardDoors: class KeycardDoors:
Left = 0xd414 Left = 0xd414
@ -73,8 +82,8 @@ class DropPrize(Enum):
Fairy = 0xE3 Fairy = 0xE3
class Patch: class Patch:
Major = 0 Major = 11
Minor = 1 Minor = 3
allWorlds: List[World] allWorlds: List[World]
myWorld: World myWorld: World
seedGuid: str seedGuid: str
@ -105,13 +114,16 @@ class Patch:
self.WriteDiggingGameRng() self.WriteDiggingGameRng()
self.WritePrizeShuffle() self.WritePrizeShuffle(self.myWorld.WorldState.DropPrizes)
self.WriteRemoveEquipmentFromUncle( self.myWorld.GetLocation("Link's Uncle").APLocation.item.item if self.WriteRemoveEquipmentFromUncle( self.myWorld.GetLocation("Link's Uncle").APLocation.item.item if
self.myWorld.GetLocation("Link's Uncle").APLocation.item.game == "SMZ3" else self.myWorld.GetLocation("Link's Uncle").APLocation.item.game == "SMZ3" else
Item(ItemType.Something)) Item(ItemType.Something))
self.WriteGanonInvicible(config.GanonInvincible) self.WriteGanonInvicible(config.Goal)
self.WritePreOpenPyramid(config.Goal)
self.WriteCrystalsNeeded(self.myWorld.TowerCrystals, self.myWorld.GanonCrystals)
self.WriteBossesNeeded(self.myWorld.TourianBossTokens)
self.WriteRngBlock() self.WriteRngBlock()
self.WriteSaveAndQuitFromBossRoom() self.WriteSaveAndQuitFromBossRoom()
@ -135,26 +147,27 @@ class Patch:
return {patch[0]:patch[1] for patch in self.patches} return {patch[0]:patch[1] for patch in self.patches}
def WriteMedallions(self): def WriteMedallions(self):
from worlds.smz3.TotalSMZ3.WorldState import Medallion
turtleRock = next(region for region in self.myWorld.Regions if isinstance(region, TurtleRock)) turtleRock = next(region for region in self.myWorld.Regions if isinstance(region, TurtleRock))
miseryMire = next(region for region in self.myWorld.Regions if isinstance(region, MiseryMire)) miseryMire = next(region for region in self.myWorld.Regions if isinstance(region, MiseryMire))
turtleRockAddresses = [0x308023, 0xD020, 0xD0FF, 0xD1DE ] turtleRockAddresses = [0x308023, 0xD020, 0xD0FF, 0xD1DE ]
miseryMireAddresses = [ 0x308022, 0xCFF2, 0xD0D1, 0xD1B0 ] miseryMireAddresses = [ 0x308022, 0xCFF2, 0xD0D1, 0xD1B0 ]
if turtleRock.Medallion == ItemType.Bombos: if turtleRock.Medallion == Medallion.Bombos:
turtleRockValues = [0x00, 0x51, 0x10, 0x00] turtleRockValues = [0x00, 0x51, 0x10, 0x00]
elif turtleRock.Medallion == ItemType.Ether: elif turtleRock.Medallion == Medallion.Ether:
turtleRockValues = [0x01, 0x51, 0x18, 0x00] turtleRockValues = [0x01, 0x51, 0x18, 0x00]
elif turtleRock.Medallion == ItemType.Quake: elif turtleRock.Medallion == Medallion.Quake:
turtleRockValues = [0x02, 0x14, 0xEF, 0xC4] turtleRockValues = [0x02, 0x14, 0xEF, 0xC4]
else: else:
raise exception(f"Tried using {turtleRock.Medallion} in place of Turtle Rock medallion") raise exception(f"Tried using {turtleRock.Medallion} in place of Turtle Rock medallion")
if miseryMire.Medallion == ItemType.Bombos: if miseryMire.Medallion == Medallion.Bombos:
miseryMireValues = [0x00, 0x51, 0x00, 0x00] miseryMireValues = [0x00, 0x51, 0x00, 0x00]
elif miseryMire.Medallion == ItemType.Ether: elif miseryMire.Medallion == Medallion.Ether:
miseryMireValues = [0x01, 0x13, 0x9F, 0xF1] miseryMireValues = [0x01, 0x13, 0x9F, 0xF1]
elif miseryMire.Medallion == ItemType.Quake: elif miseryMire.Medallion == Medallion.Quake:
miseryMireValues = [0x02, 0x51, 0x08, 0x00] miseryMireValues = [0x02, 0x51, 0x08, 0x00]
else: else:
raise exception(f"Tried using {miseryMire.Medallion} in place of Misery Mire medallion") raise exception(f"Tried using {miseryMire.Medallion} in place of Misery Mire medallion")
@ -174,12 +187,19 @@ class Patch:
self.rnd.shuffle(pendantsBlueRed) self.rnd.shuffle(pendantsBlueRed)
pendantRewards = pendantsGreen + pendantsBlueRed pendantRewards = pendantsGreen + pendantsBlueRed
bossTokens = [ 1, 2, 3, 4 ]
regions = [region for region in self.myWorld.Regions if isinstance(region, IReward)] regions = [region for region in self.myWorld.Regions if isinstance(region, IReward)]
crystalRegions = [region for region in regions if region.Reward == RewardType.CrystalBlue] + [region for region in regions if region.Reward == RewardType.CrystalRed] crystalRegions = [region for region in regions if region.Reward == RewardType.CrystalBlue] + [region for region in regions if region.Reward == RewardType.CrystalRed]
pendantRegions = [region for region in regions if region.Reward == RewardType.PendantGreen] + [region for region in regions if region.Reward == RewardType.PendantNonGreen] pendantRegions = [region for region in regions if region.Reward == RewardType.PendantGreen] + [region for region in regions if region.Reward == RewardType.PendantNonGreen]
bossRegions = [region for region in regions if region.Reward == RewardType.BossTokenKraid] + \
[region for region in regions if region.Reward == RewardType.BossTokenPhantoon] + \
[region for region in regions if region.Reward == RewardType.BossTokenDraygon] + \
[region for region in regions if region.Reward == RewardType.BossTokenRidley]
self.patches += self.RewardPatches(crystalRegions, crystalRewards, self.CrystalValues) self.patches += self.RewardPatches(crystalRegions, crystalRewards, self.CrystalValues)
self.patches += self.RewardPatches(pendantRegions, pendantRewards, self.PendantValues) self.patches += self.RewardPatches(pendantRegions, pendantRewards, self.PendantValues)
self.patches += self.RewardPatches(bossRegions, bossTokens, self.BossTokenValues)
def RewardPatches(self, regions: List[IReward], rewards: List[int], rewardValues: Callable): def RewardPatches(self, regions: List[IReward], rewards: List[int], rewardValues: Callable):
addresses = [self.RewardAddresses(region) for region in regions] addresses = [self.RewardAddresses(region) for region in regions]
@ -189,17 +209,22 @@ class Patch:
def RewardAddresses(self, region: IReward): def RewardAddresses(self, region: IReward):
regionType = { regionType = {
EasternPalace : [ 0x2A09D, 0xABEF8, 0xABEF9, 0x308052, 0x30807C, 0x1C6FE ], EasternPalace : [ 0x2A09D, 0xABEF8, 0xABEF9, 0x308052, 0x30807C, 0x1C6FE, 0x30D100],
DesertPalace : [ 0x2A09E, 0xABF1C, 0xABF1D, 0x308053, 0x308078, 0x1C6FF ], DesertPalace : [ 0x2A09E, 0xABF1C, 0xABF1D, 0x308053, 0x308078, 0x1C6FF, 0x30D101 ],
TowerOfHera : [ 0x2A0A5, 0xABF0A, 0xABF0B, 0x30805A, 0x30807A, 0x1C706 ], TowerOfHera : [ 0x2A0A5, 0xABF0A, 0xABF0B, 0x30805A, 0x30807A, 0x1C706, 0x30D102 ],
PalaceOfDarkness : [ 0x2A0A1, 0xABF00, 0xABF01, 0x308056, 0x30807D, 0x1C702 ], PalaceOfDarkness : [ 0x2A0A1, 0xABF00, 0xABF01, 0x308056, 0x30807D, 0x1C702, 0x30D103 ],
SwampPalace : [ 0x2A0A0, 0xABF6C, 0xABF6D, 0x308055, 0x308071, 0x1C701 ], SwampPalace : [ 0x2A0A0, 0xABF6C, 0xABF6D, 0x308055, 0x308071, 0x1C701, 0x30D104 ],
SkullWoods : [ 0x2A0A3, 0xABF12, 0xABF13, 0x308058, 0x30807B, 0x1C704 ], SkullWoods : [ 0x2A0A3, 0xABF12, 0xABF13, 0x308058, 0x30807B, 0x1C704, 0x30D105 ],
ThievesTown : [ 0x2A0A6, 0xABF36, 0xABF37, 0x30805B, 0x308077, 0x1C707 ], ThievesTown : [ 0x2A0A6, 0xABF36, 0xABF37, 0x30805B, 0x308077, 0x1C707, 0x30D106 ],
IcePalace : [ 0x2A0A4, 0xABF5A, 0xABF5B, 0x308059, 0x308073, 0x1C705 ], IcePalace : [ 0x2A0A4, 0xABF5A, 0xABF5B, 0x308059, 0x308073, 0x1C705, 0x30D107 ],
MiseryMire : [ 0x2A0A2, 0xABF48, 0xABF49, 0x308057, 0x308075, 0x1C703 ], MiseryMire : [ 0x2A0A2, 0xABF48, 0xABF49, 0x308057, 0x308075, 0x1C703, 0x30D108 ],
TurtleRock : [ 0x2A0A7, 0xABF24, 0xABF25, 0x30805C, 0x308079, 0x1C708 ] TurtleRock : [ 0x2A0A7, 0xABF24, 0xABF25, 0x30805C, 0x308079, 0x1C708, 0x30D109 ],
Kraid : [ 0xF26002, 0xF26004, 0xF26005, 0xF26000, 0xF26006, 0xF26007, 0x82FD36 ],
WreckedShip : [ 0xF2600A, 0xF2600C, 0xF2600D, 0xF26008, 0xF2600E, 0xF2600F, 0x82FE26 ],
Inner : [ 0xF26012, 0xF26014, 0xF26015, 0xF26010, 0xF26016, 0xF26017, 0x82FE76 ],
East : [ 0xF2601A, 0xF2601C, 0xF2601D, 0xF26018, 0xF2601E, 0xF2601F, 0x82FDD6 ]
} }
result = regionType.get(type(region), None) result = regionType.get(type(region), None)
if result is None: if result is None:
raise exception(f"Region {region} should not be a dungeon reward region") raise exception(f"Region {region} should not be a dungeon reward region")
@ -208,13 +233,13 @@ class Patch:
def CrystalValues(self, crystal: int): def CrystalValues(self, crystal: int):
crystalMap = { crystalMap = {
1 : [ 0x02, 0x34, 0x64, 0x40, 0x7F, 0x06 ], 1 : [ 0x02, 0x34, 0x64, 0x40, 0x7F, 0x06, 0x10 ],
2 : [ 0x10, 0x34, 0x64, 0x40, 0x79, 0x06 ], 2 : [ 0x10, 0x34, 0x64, 0x40, 0x79, 0x06, 0x10 ],
3 : [ 0x40, 0x34, 0x64, 0x40, 0x6C, 0x06 ], 3 : [ 0x40, 0x34, 0x64, 0x40, 0x6C, 0x06, 0x10 ],
4 : [ 0x20, 0x34, 0x64, 0x40, 0x6D, 0x06 ], 4 : [ 0x20, 0x34, 0x64, 0x40, 0x6D, 0x06, 0x10 ],
5 : [ 0x04, 0x32, 0x64, 0x40, 0x6E, 0x06 ], 5 : [ 0x04, 0x32, 0x64, 0x40, 0x6E, 0x06, 0x11 ],
6 : [ 0x01, 0x32, 0x64, 0x40, 0x6F, 0x06 ], 6 : [ 0x01, 0x32, 0x64, 0x40, 0x6F, 0x06, 0x11 ],
7 : [ 0x08, 0x34, 0x64, 0x40, 0x7C, 0x06 ], 7 : [ 0x08, 0x34, 0x64, 0x40, 0x7C, 0x06, 0x10 ],
} }
result = crystalMap.get(crystal, None) result = crystalMap.get(crystal, None)
if result is None: if result is None:
@ -224,15 +249,28 @@ class Patch:
def PendantValues(self, pendant: int): def PendantValues(self, pendant: int):
pendantMap = { pendantMap = {
1 : [ 0x04, 0x38, 0x62, 0x00, 0x69, 0x01 ], 1 : [ 0x04, 0x38, 0x62, 0x00, 0x69, 0x01, 0x12 ],
2 : [ 0x01, 0x32, 0x60, 0x00, 0x69, 0x03 ], 2 : [ 0x01, 0x32, 0x60, 0x00, 0x69, 0x03, 0x14 ],
3 : [ 0x02, 0x34, 0x60, 0x00, 0x69, 0x02 ], 3 : [ 0x02, 0x34, 0x60, 0x00, 0x69, 0x02, 0x13 ]
} }
result = pendantMap.get(pendant, None) result = pendantMap.get(pendant, None)
if result is None: if result is None:
raise exception(f"Tried using {pendant} as a pendant number") raise exception(f"Tried using {pendant} as a pendant number")
else: else:
return result return result
def BossTokenValues(self, token: int):
tokenMap = {
1 : [ 0x01, 0x38, 0x40, 0x80, 0x69, 0x80, 0x15 ],
2 : [ 0x02, 0x34, 0x42, 0x80, 0x69, 0x81, 0x16 ],
3 : [ 0x04, 0x34, 0x44, 0x80, 0x69, 0x82, 0x17 ],
4 : [ 0x08, 0x32, 0x46, 0x80, 0x69, 0x83, 0x18 ]
}
result = tokenMap.get(token, None)
if result is None:
raise exception(f"Tried using {token} as a boss token number")
else:
return result
def WriteSMLocations(self, locations: List[Location]): def WriteSMLocations(self, locations: List[Location]):
def GetSMItemPLM(location:Location): def GetSMItemPLM(location:Location):
@ -259,7 +297,7 @@ class Patch:
ItemType.SpaceJump : 0xEF1B, ItemType.SpaceJump : 0xEF1B,
ItemType.ScrewAttack : 0xEF1F ItemType.ScrewAttack : 0xEF1F
} }
plmId = 0xEFE0 if self.myWorld.Config.GameMode == GameMode.Multiworld else \ plmId = 0xEFE0 if self.myWorld.Config.Multiworld else \
itemMap.get(location.APLocation.item.item.Type, 0xEFE0) itemMap.get(location.APLocation.item.item.Type, 0xEFE0)
if (plmId == 0xEFE0): if (plmId == 0xEFE0):
plmId += 4 if location.Type == LocationType.Chozo else 8 if location.Type == LocationType.Hidden else 0 plmId += 4 if location.Type == LocationType.Chozo else 8 if location.Type == LocationType.Hidden else 0
@ -268,7 +306,7 @@ class Patch:
return plmId return plmId
for location in locations: for location in locations:
if (self.myWorld.Config.GameMode == GameMode.Multiworld): if (self.myWorld.Config.Multiworld):
self.patches.append((Snes(location.Address), getWordArray(GetSMItemPLM(location)))) self.patches.append((Snes(location.Address), getWordArray(GetSMItemPLM(location))))
self.patches.append(self.ItemTablePatch(location, self.GetZ3ItemId(location))) self.patches.append(self.ItemTablePatch(location, self.GetZ3ItemId(location)))
else: else:
@ -283,18 +321,14 @@ class Patch:
self.patches.append((Snes(0x9E3BB), [0xE4] if location.APLocation.item.game == "SMZ3" and location.APLocation.item.item.Type == ItemType.KeyTH else [0xEB])) self.patches.append((Snes(0x9E3BB), [0xE4] if location.APLocation.item.game == "SMZ3" and location.APLocation.item.item.Type == ItemType.KeyTH else [0xEB]))
elif (location.Type in [LocationType.Pedestal, LocationType.Ether, LocationType.Bombos]): elif (location.Type in [LocationType.Pedestal, LocationType.Ether, LocationType.Bombos]):
text = Texts.ItemTextbox(location.APLocation.item.item if location.APLocation.item.game == "SMZ3" else Item(ItemType.Something)) text = Texts.ItemTextbox(location.APLocation.item.item if location.APLocation.item.game == "SMZ3" else Item(ItemType.Something))
dialog = Dialog.Simple(text)
if (location.Type == LocationType.Pedestal): if (location.Type == LocationType.Pedestal):
self.stringTable.SetPedestalText(text) self.stringTable.SetPedestalText(text)
self.patches.append((Snes(0x308300), dialog))
elif (location.Type == LocationType.Ether): elif (location.Type == LocationType.Ether):
self.stringTable.SetEtherText(text) self.stringTable.SetEtherText(text)
self.patches.append((Snes(0x308F00), dialog))
elif (location.Type == LocationType.Bombos): elif (location.Type == LocationType.Bombos):
self.stringTable.SetBombosText(text) self.stringTable.SetBombosText(text)
self.patches.append((Snes(0x309000), dialog))
if (self.myWorld.Config.GameMode == GameMode.Multiworld): if (self.myWorld.Config.Multiworld):
self.patches.append((Snes(location.Address), [(location.Id - 256)])) self.patches.append((Snes(location.Address), [(location.Id - 256)]))
self.patches.append(self.ItemTablePatch(location, self.GetZ3ItemId(location))) self.patches.append(self.ItemTablePatch(location, self.GetZ3ItemId(location)))
else: else:
@ -305,11 +339,11 @@ class Patch:
item = location.APLocation.item.item item = location.APLocation.item.item
itemDungeon = None itemDungeon = None
if item.IsKey(): if item.IsKey():
itemDungeon = ItemType.Key if (not item.World.Config.Keysanity or item.Type != ItemType.KeyHC) else ItemType.KeyHC itemDungeon = ItemType.Key
elif item.IsBigKey(): elif item.IsBigKey():
itemDungeon = ItemType.BigKey itemDungeon = ItemType.BigKey
elif item.IsMap(): elif item.IsMap():
itemDungeon = ItemType.Map if (not item.World.Config.Keysanity or item.Type != ItemType.MapHC) else ItemType.MapHC itemDungeon = ItemType.Map
elif item.IsCompass(): elif item.IsCompass():
itemDungeon = ItemType.Compass itemDungeon = ItemType.Compass
@ -327,15 +361,11 @@ class Patch:
def WriteDungeonMusic(self, keysanity: bool): def WriteDungeonMusic(self, keysanity: bool):
if (not keysanity): if (not keysanity):
regions = [region for region in self.myWorld.Regions if isinstance(region, IReward)] regions = [region for region in self.myWorld.Regions if isinstance(region, Z3Region) and isinstance(region, IReward) and
music = [] region.Reward != None and region.Reward != RewardType.Agahnim]
pendantRegions = [region for region in regions if region.Reward in [RewardType.PendantGreen, RewardType.PendantNonGreen]] pendantRegions = [region for region in regions if region.Reward in [RewardType.PendantGreen, RewardType.PendantNonGreen]]
crystalRegions = [region for region in regions if region.Reward in [RewardType.CrystalBlue, RewardType.CrystalRed]] crystalRegions = [region for region in regions if region.Reward in [RewardType.CrystalBlue, RewardType.CrystalRed]]
regions = pendantRegions + crystalRegions music = [0x11 if (region.Reward == RewardType.PendantGreen or region.Reward == RewardType.PendantNonGreen) else 0x16 for region in regions]
music = [
0x11, 0x11, 0x11, 0x16, 0x16,
0x16, 0x16, 0x16, 0x16, 0x16,
]
self.patches += self.MusicPatches(regions, music) self.patches += self.MusicPatches(regions, music)
#IEnumerable<byte> RandomDungeonMusic() { #IEnumerable<byte> RandomDungeonMusic() {
@ -366,51 +396,13 @@ class Patch:
else: else:
return result return result
def WritePrizeShuffle(self): def WritePrizeShuffle(self, dropPrizes):
prizePackItems = 56 self.patches.append((Snes(0x6FA78), [e.value for e in dropPrizes.Packs]))
treePullItems = 3 self.patches.append((Snes(0x1DFBD4), [e.value for e in dropPrizes.TreePulls]))
self.patches.append((Snes(0x6A9C8), [dropPrizes.CrabContinous.value]))
bytes = [] self.patches.append((Snes(0x6A9C4), [dropPrizes.CrabFinal.value]))
drop = 0 self.patches.append((Snes(0x6F993), [dropPrizes.Stun.value]))
final = 0 self.patches.append((Snes(0x1D82CC), [dropPrizes.Fish.value]))
pool = [
DropPrize.Heart, DropPrize.Heart, DropPrize.Heart, DropPrize.Heart, DropPrize.Green, DropPrize.Heart, DropPrize.Heart, DropPrize.Green, #// pack 1
DropPrize.Blue, DropPrize.Green, DropPrize.Blue, DropPrize.Red, DropPrize.Blue, DropPrize.Green, DropPrize.Blue, DropPrize.Blue, #// pack 2
DropPrize.FullMagic, DropPrize.Magic, DropPrize.Magic, DropPrize.Blue, DropPrize.FullMagic, DropPrize.Magic, DropPrize.Heart, DropPrize.Magic, #// pack 3
DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb4, DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb8, DropPrize.Bomb1, #// pack 4
DropPrize.Arrow5, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Arrow10, DropPrize.Arrow5, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Arrow10,#// pack 5
DropPrize.Magic, DropPrize.Green, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Magic, DropPrize.Bomb1, DropPrize.Green, DropPrize.Heart, #// pack 6
DropPrize.Heart, DropPrize.Fairy, DropPrize.FullMagic, DropPrize.Red, DropPrize.Bomb8, DropPrize.Heart, DropPrize.Red, DropPrize.Arrow10, #// pack 7
DropPrize.Green, DropPrize.Blue, DropPrize.Red,#// from pull trees
DropPrize.Green, DropPrize.Red,#// from prize crab
DropPrize.Green, #// stunned prize
DropPrize.Red,#// saved fish prize
]
prizes = pool
self.rnd.shuffle(prizes)
#/* prize pack drop order */
(bytes, prizes) = SplitOff(prizes, prizePackItems)
self.patches.append((Snes(0x6FA78), [byte.value for byte in bytes]))
#/* tree pull prizes */
(bytes, prizes) = SplitOff(prizes, treePullItems)
self.patches.append((Snes(0x1DFBD4), [byte.value for byte in bytes]))
#/* crab prizes */
(drop, final, prizes) = (prizes[0], prizes[1], prizes[2:])
self.patches.append((Snes(0x6A9C8), [ drop.value ]))
self.patches.append((Snes(0x6A9C4), [ final.value ]))
#/* stun prize */
(drop, prizes) = (prizes[0], prizes[1:])
self.patches.append((Snes(0x6F993), [ drop.value ]))
#/* fish prize */
drop = prizes[0]
self.patches.append((Snes(0x1D82CC), [ drop.value ]))
self.patches += self.EnemyPrizePackDistribution() self.patches += self.EnemyPrizePackDistribution()
@ -524,46 +516,29 @@ class Patch:
redCrystalDungeons = [region for region in regions if region.Reward == RewardType.CrystalRed] redCrystalDungeons = [region for region in regions if region.Reward == RewardType.CrystalRed]
sahasrahla = Texts.SahasrahlaReveal(greenPendantDungeon) sahasrahla = Texts.SahasrahlaReveal(greenPendantDungeon)
self.patches.append((Snes(0x308A00), Dialog.Simple(sahasrahla)))
self.stringTable.SetSahasrahlaRevealText(sahasrahla) self.stringTable.SetSahasrahlaRevealText(sahasrahla)
bombShop = Texts.BombShopReveal(redCrystalDungeons) bombShop = Texts.BombShopReveal(redCrystalDungeons)
self.patches.append((Snes(0x308E00), Dialog.Simple(bombShop)))
self.stringTable.SetBombShopRevealText(bombShop) self.stringTable.SetBombShopRevealText(bombShop)
blind = Texts.Blind(self.rnd) blind = Texts.Blind(self.rnd)
self.patches.append((Snes(0x308800), Dialog.Simple(blind)))
self.stringTable.SetBlindText(blind) self.stringTable.SetBlindText(blind)
tavernMan = Texts.TavernMan(self.rnd) tavernMan = Texts.TavernMan(self.rnd)
self.patches.append((Snes(0x308C00), Dialog.Simple(tavernMan)))
self.stringTable.SetTavernManText(tavernMan) self.stringTable.SetTavernManText(tavernMan)
ganon = Texts.GanonFirstPhase(self.rnd) ganon = Texts.GanonFirstPhase(self.rnd)
self.patches.append((Snes(0x308600), Dialog.Simple(ganon)))
self.stringTable.SetGanonFirstPhaseText(ganon) self.stringTable.SetGanonFirstPhaseText(ganon)
#// Todo: Verify these two are correct if ganon invincible patch is ever added
#// ganon_fall_in_alt in v30
ganonFirstPhaseInvincible = "You think you\nare ready to\nface me?\n\nI will not die\n\nunless you\ncomplete your\ngoals. Dingus!"
self.patches.append((Snes(0x309100), Dialog.Simple(ganonFirstPhaseInvincible)))
#// ganon_phase_3_alt in v30
ganonThirdPhaseInvincible = "Got wax in\nyour ears?\nI cannot die!"
self.patches.append((Snes(0x309200), Dialog.Simple(ganonThirdPhaseInvincible)))
#// ---
silversLocation = [loc for world in self.allWorlds for loc in world.Locations if loc.ItemIs(ItemType.SilverArrows, self.myWorld)] silversLocation = [loc for world in self.allWorlds for loc in world.Locations if loc.ItemIs(ItemType.SilverArrows, self.myWorld)]
if len(silversLocation) == 0: if len(silversLocation) == 0:
silvers = Texts.GanonThirdPhaseMulti(None, self.myWorld, self.silversWorldID, self.playerIDToNames[self.silversWorldID]) silvers = Texts.GanonThirdPhaseMulti(None, self.myWorld, self.silversWorldID, self.playerIDToNames[self.silversWorldID])
else: else:
silvers = Texts.GanonThirdPhaseMulti(silversLocation[0].Region, self.myWorld) if config.GameMode == GameMode.Multiworld else \ silvers = Texts.GanonThirdPhaseMulti(silversLocation[0].Region, self.myWorld) if config.Multiworld else \
Texts.GanonThirdPhaseSingle(silversLocation[0].Region) Texts.GanonThirdPhaseSingle(silversLocation[0].Region)
self.patches.append((Snes(0x308700), Dialog.Simple(silvers)))
self.stringTable.SetGanonThirdPhaseText(silvers) self.stringTable.SetGanonThirdPhaseText(silvers)
triforceRoom = Texts.TriforceRoom(self.rnd) triforceRoom = Texts.TriforceRoom(self.rnd)
self.patches.append((Snes(0x308400), Dialog.Simple(triforceRoom)))
self.stringTable.SetTriforceRoomText(triforceRoom) self.stringTable.SetTriforceRoomText(triforceRoom)
def WriteStringTable(self): def WriteStringTable(self):
@ -579,26 +554,32 @@ class Patch:
return bytearray(name, 'utf8') return bytearray(name, 'utf8')
def WriteSeedData(self): def WriteSeedData(self):
configField = \ configField1 = \
((1 if self.myWorld.Config.Race else 0) << 15) | \ ((1 if self.myWorld.Config.Race else 0) << 15) | \
((1 if self.myWorld.Config.Keysanity else 0) << 13) | \ ((1 if self.myWorld.Config.Keysanity else 0) << 13) | \
((1 if self.myWorld.Config.GameMode == Config.GameMode.Multiworld else 0) << 12) | \ ((1 if self.myWorld.Config.Multiworld else 0) << 12) | \
(self.myWorld.Config.Z3Logic.value << 10) | \ (self.myWorld.Config.Z3Logic.value << 10) | \
(self.myWorld.Config.SMLogic.value << 8) | \ (self.myWorld.Config.SMLogic.value << 8) | \
(Patch.Major << 4) | \ (Patch.Major << 4) | \
(Patch.Minor << 0) (Patch.Minor << 0)
configField2 = \
((1 if self.myWorld.Config.SwordLocation else 0) << 14) | \
((1 if self.myWorld.Config.MorphLocation else 0) << 12) | \
((1 if self.myWorld.Config.Goal else 0) << 8)
self.patches.append((Snes(0x80FF50), getWordArray(self.myWorld.Id))) self.patches.append((Snes(0x80FF50), getWordArray(self.myWorld.Id)))
self.patches.append((Snes(0x80FF52), getWordArray(configField))) self.patches.append((Snes(0x80FF52), getWordArray(configField1)))
self.patches.append((Snes(0x80FF54), getDoubleWordArray(self.seed))) self.patches.append((Snes(0x80FF54), getDoubleWordArray(self.seed)))
self.patches.append((Snes(0x80FF58), getWordArray(configField2)))
#/* Reserve the rest of the space for future use */ #/* Reserve the rest of the space for future use */
self.patches.append((Snes(0x80FF58), [0x00] * 8)) self.patches.append((Snes(0x80FF5A), [0x00] * 6))
self.patches.append((Snes(0x80FF60), bytearray(self.seedGuid, 'utf8'))) self.patches.append((Snes(0x80FF60), bytearray(self.seedGuid, 'utf8')))
self.patches.append((Snes(0x80FF80), bytearray(self.myWorld.Guid, 'utf8'))) self.patches.append((Snes(0x80FF80), bytearray(self.myWorld.Guid, 'utf8')))
def WriteCommonFlags(self): def WriteCommonFlags(self):
#/* Common Combo Configuration flags at [asm]/config.asm */ #/* Common Combo Configuration flags at [asm]/config.asm */
if (self.myWorld.Config.GameMode == GameMode.Multiworld): if (self.myWorld.Config.Multiworld):
self.patches.append((Snes(0xF47000), getWordArray(0x0001))) self.patches.append((Snes(0xF47000), getWordArray(0x0001)))
if (self.myWorld.Config.Keysanity): if (self.myWorld.Config.Keysanity):
self.patches.append((Snes(0xF47006), getWordArray(0x0001))) self.patches.append((Snes(0xF47006), getWordArray(0x0001)))
@ -619,97 +600,104 @@ class Patch:
if (self.myWorld.Config.Keysanity): if (self.myWorld.Config.Keysanity):
self.patches.append((Snes(0x40003B), [ 1 ])) #// MapMode #$00 = Always On (default) - #$01 = Require Map Item self.patches.append((Snes(0x40003B), [ 1 ])) #// MapMode #$00 = Always On (default) - #$01 = Require Map Item
self.patches.append((Snes(0x400045), [ 0x0f ])) #// display ----dcba a: Small Keys, b: Big Key, c: Map, d: Compass self.patches.append((Snes(0x400045), [ 0x0f ])) #// display ----dcba a: Small Keys, b: Big Key, c: Map, d: Compass
self.patches.append((Snes(0x40016A), [ 0x01 ])) #// enable local item dialog boxes for dungeon and keycard items self.patches.append((Snes(0x40016A), [ 0x01 ])) #// FreeItemText: db #$01 ; #00 = Off (default) - #$01 = On
def WriteSMKeyCardDoors(self): def WriteSMKeyCardDoors(self):
if (not self.myWorld.Config.Keysanity): plaquePlm = 0xd410
return
plaquePLm = 0xd410
doorList = [
#// RoomId Door Facing yyxx Keycard Event Type Plaque type yyxx, Address (if 0 a dynamic PLM is created)
#// Crateria
[ 0x91F8, KeycardDoors.Right, 0x2601, KeycardEvents.CrateriaLevel1, KeycardPlaque.Level1, 0x2400, 0x0000 ], #// Crateria - Landing Site - Door to gauntlet
[ 0x91F8, KeycardDoors.Left, 0x168E, KeycardEvents.CrateriaLevel1, KeycardPlaque.Level1, 0x148F, 0x801E ], #// Crateria - Landing Site - Door to landing site PB
[ 0x948C, KeycardDoors.Left, 0x062E, KeycardEvents.CrateriaLevel2, KeycardPlaque.Level2, 0x042F, 0x8222 ], #// Crateria - Before Moat - Door to moat (overwrite PB door)
[ 0x99BD, KeycardDoors.Left, 0x660E, KeycardEvents.CrateriaBoss, KeycardPlaque.Boss, 0x640F, 0x8470 ], #// Crateria - Before G4 - Door to G4
[ 0x9879, KeycardDoors.Left, 0x062E, KeycardEvents.CrateriaBoss, KeycardPlaque.Boss, 0x042F, 0x8420 ], #// Crateria - Before BT - Door to Bomb Torizo
#// Brinstar
[ 0x9F11, KeycardDoors.Left, 0x060E, KeycardEvents.BrinstarLevel1, KeycardPlaque.Level1, 0x040F, 0x8784 ], #// Brinstar - Blue Brinstar - Door to ceiling e-tank room
[ 0x9AD9, KeycardDoors.Right, 0xA601, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0xA400, 0x0000 ], #// Brinstar - Green Brinstar - Door to etecoon area
[ 0x9D9C, KeycardDoors.Down, 0x0336, KeycardEvents.BrinstarBoss, KeycardPlaque.Boss, 0x0234, 0x863A ], #// Brinstar - Pink Brinstar - Door to spore spawn
[ 0xA130, KeycardDoors.Left, 0x161E, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0x141F, 0x881C ], #// Brinstar - Pink Brinstar - Door to wave gate e-tank
[ 0xA0A4, KeycardDoors.Left, 0x062E, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0x042F, 0x0000 ], #// Brinstar - Pink Brinstar - Door to spore spawn super
[ 0xA56B, KeycardDoors.Left, 0x161E, KeycardEvents.BrinstarBoss, KeycardPlaque.Boss, 0x141F, 0x8A1A ], #// Brinstar - Before Kraid - Door to Kraid
#// Upper Norfair
[ 0xA7DE, KeycardDoors.Right, 0x3601, KeycardEvents.NorfairLevel1, KeycardPlaque.Level1, 0x3400, 0x8B00 ], #// Norfair - Business Centre - Door towards Ice
[ 0xA923, KeycardDoors.Right, 0x0601, KeycardEvents.NorfairLevel1, KeycardPlaque.Level1, 0x0400, 0x0000 ], #// Norfair - Pre-Crocomire - Door towards Ice
[ 0xA788, KeycardDoors.Left, 0x162E, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x142F, 0x8AEA ], #// Norfair - Lava Missile Room - Door towards Bubble Mountain
[ 0xAF72, KeycardDoors.Left, 0x061E, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x041F, 0x0000 ], #// Norfair - After frog speedway - Door to Bubble Mountain
[ 0xAEDF, KeycardDoors.Down, 0x0206, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x0204, 0x0000 ], #// Norfair - Below bubble mountain - Door to Bubble Mountain
[ 0xAD5E, KeycardDoors.Right, 0x0601, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x0400, 0x0000 ], #// Norfair - LN Escape - Door to Bubble Mountain
[ 0xA923, KeycardDoors.Up, 0x2DC6, KeycardEvents.NorfairBoss, KeycardPlaque.Boss, 0x2EC4, 0x8B96 ], #// Norfair - Pre-Crocomire - Door to Crocomire
#// Lower Norfair
[ 0xB4AD, KeycardDoors.Left, 0x160E, KeycardEvents.LowerNorfairLevel1, KeycardPlaque.Level1, 0x140F, 0x0000 ], #// Lower Norfair - WRITG - Door to Amphitheatre
[ 0xAD5E, KeycardDoors.Left, 0x065E, KeycardEvents.LowerNorfairLevel1, KeycardPlaque.Level1, 0x045F, 0x0000 ], #// Lower Norfair - Exit - Door to "Reverse LN Entry"
[ 0xB37A, KeycardDoors.Right, 0x0601, KeycardEvents.LowerNorfairBoss, KeycardPlaque.Boss, 0x0400, 0x8EA6 ], #// Lower Norfair - Pre-Ridley - Door to Ridley
#// Maridia
[ 0xD0B9, KeycardDoors.Left, 0x065E, KeycardEvents.MaridiaLevel1, KeycardPlaque.Level1, 0x045F, 0x0000 ], #// Maridia - Mt. Everest - Door to Pink Maridia
[ 0xD5A7, KeycardDoors.Right, 0x1601, KeycardEvents.MaridiaLevel1, KeycardPlaque.Level1, 0x1400, 0x0000 ], #// Maridia - Aqueduct - Door towards Beach
[ 0xD617, KeycardDoors.Left, 0x063E, KeycardEvents.MaridiaLevel2, KeycardPlaque.Level2, 0x043F, 0x0000 ], #// Maridia - Pre-Botwoon - Door to Botwoon
[ 0xD913, KeycardDoors.Right, 0x2601, KeycardEvents.MaridiaLevel2, KeycardPlaque.Level2, 0x2400, 0x0000 ], #// Maridia - Pre-Colloseum - Door to post-botwoon
[ 0xD78F, KeycardDoors.Right, 0x2601, KeycardEvents.MaridiaBoss, KeycardPlaque.Boss, 0x2400, 0xC73B ], #// Maridia - Precious Room - Door to Draygon
[ 0xDA2B, KeycardDoors.BossLeft, 0x164E, 0x00f0, KeycardPlaque.Null, 0x144F, 0x0000 ], #// Maridia - Change Cac Alley Door to Boss Door (prevents key breaking)
#// Wrecked Ship
[ 0x93FE, KeycardDoors.Left, 0x167E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x147F, 0x0000 ], #// Wrecked Ship - Outside Wrecked Ship West - Door to Reserve Tank Check
[ 0x968F, KeycardDoors.Left, 0x060E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x040F, 0x0000 ], #// Wrecked Ship - Outside Wrecked Ship West - Door to Bowling Alley
[ 0xCE40, KeycardDoors.Left, 0x060E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x040F, 0x0000 ], #// Wrecked Ship - Gravity Suit - Door to Bowling Alley
[ 0xCC6F, KeycardDoors.Left, 0x064E, KeycardEvents.WreckedShipBoss, KeycardPlaque.Boss, 0x044F, 0xC29D ], #// Wrecked Ship - Pre-Phantoon - Door to Phantoon
]
doorId = 0x0000
plmTablePos = 0xf800 plmTablePos = 0xf800
for door in doorList:
doorArgs = doorId | door[3] if door[4] != KeycardPlaque.Null else door[3]
if (door[6] == 0):
#// Write dynamic door
doorData = []
for x in door[0:3]:
doorData += getWordArray(x)
doorData += getWordArray(doorArgs)
self.patches.append((Snes(0x8f0000 + plmTablePos), doorData))
plmTablePos += 0x08
else:
#// Overwrite existing door
doorData = []
for x in door[1:3]:
doorData += getWordArray(x)
doorData += getWordArray(doorArgs)
self.patches.append((Snes(0x8f0000 + door[6]), doorData))
if((door[3] == KeycardEvents.BrinstarBoss and door[0] != 0x9D9C) or door[3] == KeycardEvents.LowerNorfairBoss or door[3] == KeycardEvents.MaridiaBoss or door[3] == KeycardEvents.WreckedShipBoss):
#// Overwrite the extra parts of the Gadora with a PLM that just deletes itself
self.patches.append((Snes(0x8f0000 + door[6] + 0x06), [ 0x2F, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xB6, 0x00, 0x00, 0x00, 0x00 ]))
#// Plaque data if ( self.myWorld.Config.Keysanity):
if (door[4] != KeycardPlaque.Null): doorList = [
plaqueData = getWordArray(door[0]) + getWordArray(plaquePLm) + getWordArray(door[5]) + getWordArray(door[4]) #// RoomId Door Facing yyxx Keycard Event Type Plaque type yyxx, Address (if 0 a dynamic PLM is created)
self.patches.append((Snes(0x8f0000 + plmTablePos), plaqueData)) #// Crateria
plmTablePos += 0x08 [ 0x91F8, KeycardDoors.Right, 0x2601, KeycardEvents.CrateriaLevel1, KeycardPlaque.Level1, 0x2400, 0x0000 ], #// Crateria - Landing Site - Door to gauntlet
doorId += 1 [ 0x91F8, KeycardDoors.Left, 0x168E, KeycardEvents.CrateriaLevel1, KeycardPlaque.Level1, 0x148F, 0x801E ], #// Crateria - Landing Site - Door to landing site PB
[ 0x948C, KeycardDoors.Left, 0x062E, KeycardEvents.CrateriaLevel2, KeycardPlaque.Level2, 0x042F, 0x8222 ], #// Crateria - Before Moat - Door to moat (overwrite PB door)
[ 0x99BD, KeycardDoors.Left, 0x660E, KeycardEvents.CrateriaBoss, KeycardPlaque.Boss, 0x640F, 0x8470 ], #// Crateria - Before G4 - Door to G4
[ 0x9879, KeycardDoors.Left, 0x062E, KeycardEvents.CrateriaBoss, KeycardPlaque.Boss, 0x042F, 0x8420 ], #// Crateria - Before BT - Door to Bomb Torizo
#// Brinstar
[ 0x9F11, KeycardDoors.Left, 0x060E, KeycardEvents.BrinstarLevel1, KeycardPlaque.Level1, 0x040F, 0x8784 ], #// Brinstar - Blue Brinstar - Door to ceiling e-tank room
[ 0x9AD9, KeycardDoors.Right, 0xA601, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0xA400, 0x0000 ], #// Brinstar - Green Brinstar - Door to etecoon area
[ 0x9D9C, KeycardDoors.Down, 0x0336, KeycardEvents.BrinstarBoss, KeycardPlaque.Boss, 0x0234, 0x863A ], #// Brinstar - Pink Brinstar - Door to spore spawn
[ 0xA130, KeycardDoors.Left, 0x161E, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0x141F, 0x881C ], #// Brinstar - Pink Brinstar - Door to wave gate e-tank
[ 0xA0A4, KeycardDoors.Left, 0x062E, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0x042F, 0x0000 ], #// Brinstar - Pink Brinstar - Door to spore spawn super
[ 0xA56B, KeycardDoors.Left, 0x161E, KeycardEvents.BrinstarBoss, KeycardPlaque.Boss, 0x141F, 0x8A1A ], #// Brinstar - Before Kraid - Door to Kraid
#// Upper Norfair
[ 0xA7DE, KeycardDoors.Right, 0x3601, KeycardEvents.NorfairLevel1, KeycardPlaque.Level1, 0x3400, 0x8B00 ], #// Norfair - Business Centre - Door towards Ice
[ 0xA923, KeycardDoors.Right, 0x0601, KeycardEvents.NorfairLevel1, KeycardPlaque.Level1, 0x0400, 0x0000 ], #// Norfair - Pre-Crocomire - Door towards Ice
[ 0xA788, KeycardDoors.Left, 0x162E, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x142F, 0x8AEA ], #// Norfair - Lava Missile Room - Door towards Bubble Mountain
[ 0xAF72, KeycardDoors.Left, 0x061E, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x041F, 0x0000 ], #// Norfair - After frog speedway - Door to Bubble Mountain
[ 0xAEDF, KeycardDoors.Down, 0x0206, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x0204, 0x0000 ], #// Norfair - Below bubble mountain - Door to Bubble Mountain
[ 0xAD5E, KeycardDoors.Right, 0x0601, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x0400, 0x0000 ], #// Norfair - LN Escape - Door to Bubble Mountain
[ 0xA923, KeycardDoors.Up, 0x2DC6, KeycardEvents.NorfairBoss, KeycardPlaque.Boss, 0x2EC4, 0x8B96 ], #// Norfair - Pre-Crocomire - Door to Crocomire
#// Lower Norfair
[ 0xB4AD, KeycardDoors.Left, 0x160E, KeycardEvents.LowerNorfairLevel1, KeycardPlaque.Level1, 0x140F, 0x0000 ], #// Lower Norfair - WRITG - Door to Amphitheatre
[ 0xAD5E, KeycardDoors.Left, 0x065E, KeycardEvents.LowerNorfairLevel1, KeycardPlaque.Level1, 0x045F, 0x0000 ], #// Lower Norfair - Exit - Door to "Reverse LN Entry"
[ 0xB37A, KeycardDoors.Right, 0x0601, KeycardEvents.LowerNorfairBoss, KeycardPlaque.Boss, 0x0400, 0x8EA6 ], #// Lower Norfair - Pre-Ridley - Door to Ridley
#// Maridia
[ 0xD0B9, KeycardDoors.Left, 0x065E, KeycardEvents.MaridiaLevel1, KeycardPlaque.Level1, 0x045F, 0x0000 ], #// Maridia - Mt. Everest - Door to Pink Maridia
[ 0xD5A7, KeycardDoors.Right, 0x1601, KeycardEvents.MaridiaLevel1, KeycardPlaque.Level1, 0x1400, 0x0000 ], #// Maridia - Aqueduct - Door towards Beach
[ 0xD617, KeycardDoors.Left, 0x063E, KeycardEvents.MaridiaLevel2, KeycardPlaque.Level2, 0x043F, 0x0000 ], #// Maridia - Pre-Botwoon - Door to Botwoon
[ 0xD913, KeycardDoors.Right, 0x2601, KeycardEvents.MaridiaLevel2, KeycardPlaque.Level2, 0x2400, 0x0000 ], #// Maridia - Pre-Colloseum - Door to post-botwoon
[ 0xD78F, KeycardDoors.Right, 0x2601, KeycardEvents.MaridiaBoss, KeycardPlaque.Boss, 0x2400, 0xC73B ], #// Maridia - Precious Room - Door to Draygon
[ 0xDA2B, KeycardDoors.BossLeft, 0x164E, 0x00f0, KeycardPlaque.Null, 0x144F, 0x0000 ], #// Maridia - Change Cac Alley Door to Boss Door (prevents key breaking)
#// Wrecked Ship
[ 0x93FE, KeycardDoors.Left, 0x167E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x147F, 0x0000 ], #// Wrecked Ship - Outside Wrecked Ship West - Door to Reserve Tank Check
[ 0x968F, KeycardDoors.Left, 0x060E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x040F, 0x0000 ], #// Wrecked Ship - Outside Wrecked Ship West - Door to Bowling Alley
[ 0xCE40, KeycardDoors.Left, 0x060E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x040F, 0x0000 ], #// Wrecked Ship - Gravity Suit - Door to Bowling Alley
[ 0xCC6F, KeycardDoors.Left, 0x064E, KeycardEvents.WreckedShipBoss, KeycardPlaque.Boss, 0x044F, 0xC29D ], #// Wrecked Ship - Pre-Phantoon - Door to Phantoon
]
doorId = 0x0000
for door in doorList:
#/* When "Fast Ganon" is set, don't place the G4 Boss key door to enable faster games */
if (door[0] == 0x99BD and self.myWorld.Config.Goal == Goal.FastGanonDefeatMotherBrain):
continue
doorArgs = doorId | door[3] if door[4] != KeycardPlaque.Null else door[3]
if (door[6] == 0):
#// Write dynamic door
doorData = []
for x in door[0:3]:
doorData += getWordArray(x)
doorData += getWordArray(doorArgs)
self.patches.append((Snes(0x8f0000 + plmTablePos), doorData))
plmTablePos += 0x08
else:
#// Overwrite existing door
doorData = []
for x in door[1:3]:
doorData += getWordArray(x)
doorData += getWordArray(doorArgs)
self.patches.append((Snes(0x8f0000 + door[6]), doorData))
if((door[3] == KeycardEvents.BrinstarBoss and door[0] != 0x9D9C) or door[3] == KeycardEvents.LowerNorfairBoss or door[3] == KeycardEvents.MaridiaBoss or door[3] == KeycardEvents.WreckedShipBoss):
#// Overwrite the extra parts of the Gadora with a PLM that just deletes itself
self.patches.append((Snes(0x8f0000 + door[6] + 0x06), [ 0x2F, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xB6, 0x00, 0x00, 0x00, 0x00 ]))
#// Plaque data
if (door[4] != KeycardPlaque.Null):
plaqueData = getWordArray(door[0]) + getWordArray(plaquePlm) + getWordArray(door[5]) + getWordArray(door[4])
self.patches.append((Snes(0x8f0000 + plmTablePos), plaqueData))
plmTablePos += 0x08
doorId += 1
#/* Write plaque showing SM bosses that needs to be killed */
if (self.myWorld.Config.OpenTourian != OpenTourian.FourBosses):
plaqueData = getWordArray(0xA5ED) + getWordArray(plaquePlm) + getWordArray(0x044F) + getWordArray(KeycardPlaque.Zero + self.myWorld.TourianBossTokens)
self.patches.append((Snes(0x8f0000 + plmTablePos), plaqueData))
plmTablePos += 0x08
self.patches.append((Snes(0x8f0000 + plmTablePos), [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ])) self.patches.append((Snes(0x8f0000 + plmTablePos), [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]))
@ -745,20 +733,32 @@ class Patch:
(Snes(0xDD313), [ 0x00, 0x00, 0xE4, 0xFF, 0x08, 0x0E ]), (Snes(0xDD313), [ 0x00, 0x00, 0xE4, 0xFF, 0x08, 0x0E ]),
] ]
def WriteGanonInvicible(self, invincible: GanonInvincible): def WritePreOpenPyramid(self, goal: Goal):
if (goal == Goal.FastGanonDefeatMotherBrain):
self.patches.append((Snes(0x30808B), [0x01]))
def WriteGanonInvicible(self, goal: Goal):
#/* Defaults to $00 (never) at [asm]/z3/randomizer/tables.asm */ #/* Defaults to $00 (never) at [asm]/z3/randomizer/tables.asm */
invincibleMap = { valueMap = {
GanonInvincible.Never : 0x00, Goal.DefeatBoth : 0x03,
GanonInvincible.Always : 0x01, Goal.FastGanonDefeatMotherBrain : 0x04,
GanonInvincible.BeforeAllDungeons : 0x02, Goal.AllDungeonsDefeatMotherBrain : 0x02
GanonInvincible.BeforeCrystals : 0x03, }
} value = valueMap.get(goal, None)
value = invincibleMap.get(invincible, None)
if (value is None): if (value is None):
raise exception(f"Unknown Ganon invincible value {invincible}") raise exception(f"Unknown Ganon invincible value {goal}")
else: else:
self.patches.append((Snes(0x30803E), [value])) self.patches.append((Snes(0x30803E), [value]))
def WriteBossesNeeded(self, tourianBossTokens):
self.patches.append((Snes(0xF47200), getWordArray(tourianBossTokens)))
def WriteCrystalsNeeded(self, towerCrystals, ganonCrystals):
self.patches.append((Snes(0x30805E), [towerCrystals]))
self.patches.append((Snes(0x30805F), [ganonCrystals]))
self.stringTable.SetTowerRequirementText(f"You need {towerCrystals} crystals to enter Ganon's Tower.")
self.stringTable.SetGanonRequirementText(f"You need {ganonCrystals} crystals to defeat Ganon.")
def WriteRngBlock(self): def WriteRngBlock(self):
#/* Repoint RNG Block */ #/* Repoint RNG Block */

View File

@ -5,12 +5,19 @@ from worlds.smz3.TotalSMZ3.Item import Item, ItemType
class RewardType(Enum): class RewardType(Enum):
Null = 0 Null = 0
Agahnim = 1 Agahnim = 1 << 0
PendantGreen = 2 PendantGreen = 1 << 1
PendantNonGreen = 3 PendantNonGreen = 1 << 2
CrystalBlue = 4 CrystalBlue = 1 << 3
CrystalRed = 5 CrystalRed = 1 << 4
GoldenFourBoss = 6 BossTokenKraid = 1 << 5
BossTokenPhantoon = 1 << 6
BossTokenDraygon = 1 << 7
BossTokenRidley = 1 << 8
AnyPendant = PendantGreen | PendantNonGreen
AnyCrystal = CrystalBlue | CrystalRed
AnyBossToken = BossTokenKraid | BossTokenPhantoon | BossTokenDraygon | BossTokenRidley
class IReward: class IReward:
Reward: RewardType Reward: RewardType
@ -18,7 +25,7 @@ class IReward:
pass pass
class IMedallionAccess: class IMedallionAccess:
Medallion: object Medallion = None
class Region: class Region:
import worlds.smz3.TotalSMZ3.Location as Location import worlds.smz3.TotalSMZ3.Location as Location

View File

@ -7,7 +7,7 @@ class Kraid(SMRegion, IReward):
Name = "Brinstar Kraid" Name = "Brinstar Kraid"
Area = "Brinstar" Area = "Brinstar"
Reward = RewardType.GoldenFourBoss Reward = RewardType.Null
def __init__(self, world, config: Config): def __init__(self, world, config: Config):
super().__init__(world, config) super().__init__(world, config)

View File

@ -40,5 +40,5 @@ class Pink(SMRegion):
else: else:
return items.CanOpenRedDoors() and (items.CanDestroyBombWalls() or items.SpeedBooster) or \ return items.CanOpenRedDoors() and (items.CanDestroyBombWalls() or items.SpeedBooster) or \
items.CanUsePowerBombs() or \ items.CanUsePowerBombs() or \
items.CanAccessNorfairUpperPortal() and items.Morph and (items.CanOpenRedDoors() or items.Wave) and \ items.CanAccessNorfairUpperPortal() and items.Morph and (items.Missile or items.Super or items.Wave ) and \
(items.Ice or items.HiJump or items.CanSpringBallJump() or items.CanFly()) (items.Ice or items.HiJump or items.CanSpringBallJump() or items.CanFly())

View File

@ -17,9 +17,9 @@ class East(SMRegion):
self.world.CanEnter("Wrecked Ship", items)) if self.Logic == SMLogic.Normal else \ self.world.CanEnter("Wrecked Ship", items)) if self.Logic == SMLogic.Normal else \
lambda items: items.Morph), lambda items: items.Morph),
Location(self, 2, 0x8F81EE, LocationType.Hidden, "Missile (outside Wrecked Ship top)", Location(self, 2, 0x8F81EE, LocationType.Hidden, "Missile (outside Wrecked Ship top)",
lambda items: self.world.CanEnter("Wrecked Ship", items) and (not self.Config.Keysanity or items.CardWreckedShipBoss) and items.CanPassBombPassages()), lambda items: self.world.CanEnter("Wrecked Ship", items) and items.CardWreckedShipBoss and items.CanPassBombPassages()),
Location(self, 3, 0x8F81F4, LocationType.Visible, "Missile (outside Wrecked Ship middle)", Location(self, 3, 0x8F81F4, LocationType.Visible, "Missile (outside Wrecked Ship middle)",
lambda items: self.world.CanEnter("Wrecked Ship", items) and (not self.Config.Keysanity or items.CardWreckedShipBoss) and items.CanPassBombPassages()), lambda items: self.world.CanEnter("Wrecked Ship", items) and items.CardWreckedShipBoss and items.CanPassBombPassages()),
Location(self, 4, 0x8F8248, LocationType.Visible, "Missile (Crateria moat)", Location(self, 4, 0x8F8248, LocationType.Visible, "Missile (Crateria moat)",
lambda items: True) lambda items: True)
] ]

View File

@ -9,20 +9,17 @@ class Inner(SMRegion, IReward):
def __init__(self, world, config: Config): def __init__(self, world, config: Config):
super().__init__(world, config) super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss self.Reward = RewardType.Null
self.Locations = [ self.Locations = [
Location(self, 140, 0x8FC4AF, LocationType.Visible, "Super Missile (yellow Maridia)", Location(self, 140, 0x8FC4AF, LocationType.Visible, "Super Missile (yellow Maridia)",
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \ lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and self.CanReachAqueduct(items) and
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and (items.Gravity or items.Ice or items.HiJump and items.SpringBall)),
(items.Gravity or items.Ice or items.HiJump and items.CanSpringBallJump())),
Location(self, 141, 0x8FC4B5, LocationType.Visible, "Missile (yellow Maridia super missile)", Location(self, 141, 0x8FC4B5, LocationType.Visible, "Missile (yellow Maridia super missile)",
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and
(items.Gravity or items.Ice or items.HiJump and items.CanSpringBallJump())), (items.Gravity or items.Ice or items.HiJump and items.SpringBall)),
Location(self, 142, 0x8FC533, LocationType.Visible, "Missile (yellow Maridia false wall)", Location(self, 142, 0x8FC533, LocationType.Visible, "Missile (yellow Maridia false wall)",
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and
(items.Gravity or items.Ice or items.HiJump and items.CanSpringBallJump())), (items.Gravity or items.Ice or items.HiJump and items.SpringBall)),
Location(self, 143, 0x8FC559, LocationType.Chozo, "Plasma Beam", Location(self, 143, 0x8FC559, LocationType.Chozo, "Plasma Beam",
lambda items: self.CanDefeatDraygon(items) and (items.ScrewAttack or items.Plasma) and (items.HiJump or items.CanFly()) if self.Logic == SMLogic.Normal else \ lambda items: self.CanDefeatDraygon(items) and (items.ScrewAttack or items.Plasma) and (items.HiJump or items.CanFly()) if self.Logic == SMLogic.Normal else \
lambda items: self.CanDefeatDraygon(items) and lambda items: self.CanDefeatDraygon(items) and
@ -31,17 +28,17 @@ class Inner(SMRegion, IReward):
Location(self, 144, 0x8FC5DD, LocationType.Visible, "Missile (left Maridia sand pit room)", Location(self, 144, 0x8FC5DD, LocationType.Visible, "Missile (left Maridia sand pit room)",
lambda items: self.CanReachAqueduct(items) and items.Super and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \ lambda items: self.CanReachAqueduct(items) and items.Super and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Super and lambda items: self.CanReachAqueduct(items) and items.Super and
(items.HiJump and (items.SpaceJump or items.CanSpringBallJump()) or items.Gravity)), (items.Gravity or items.HiJump and (items.SpaceJump or items.CanSpringBallJump()))),
Location(self, 145, 0x8FC5E3, LocationType.Chozo, "Reserve Tank, Maridia", Location(self, 145, 0x8FC5E3, LocationType.Chozo, "Reserve Tank, Maridia",
lambda items: self.CanReachAqueduct(items) and items.Super and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \ lambda items: self.CanReachAqueduct(items) and items.Super and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Super and lambda items: self.CanReachAqueduct(items) and items.Super and
(items.HiJump and (items.SpaceJump or items.CanSpringBallJump()) or items.Gravity)), (items.Gravity or items.HiJump and (items.SpaceJump or items.CanSpringBallJump()))),
Location(self, 146, 0x8FC5EB, LocationType.Visible, "Missile (right Maridia sand pit room)", Location(self, 146, 0x8FC5EB, LocationType.Visible, "Missile (right Maridia sand pit room)",
lambda items: self.CanReachAqueduct(items) and items.Super) if self.Logic == SMLogic.Normal else \ lambda items: self.CanReachAqueduct(items) and items.Super) if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Super and (items.HiJump or items.Gravity), lambda items: self.CanReachAqueduct(items) and items.Super and (items.HiJump or items.Gravity),
Location(self, 147, 0x8FC5F1, LocationType.Visible, "Power Bomb (right Maridia sand pit room)", Location(self, 147, 0x8FC5F1, LocationType.Visible, "Power Bomb (right Maridia sand pit room)",
lambda items: self.CanReachAqueduct(items) and items.Super) if self.Logic == SMLogic.Normal else \ lambda items: self.CanReachAqueduct(items) and items.Super) if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Super and (items.HiJump and items.CanSpringBallJump() or items.Gravity), lambda items: self.CanReachAqueduct(items) and items.Super and (items.Gravity or items.HiJump and items.CanSpringBallJump()),
Location(self, 148, 0x8FC603, LocationType.Visible, "Missile (pink Maridia)", Location(self, 148, 0x8FC603, LocationType.Visible, "Missile (pink Maridia)",
lambda items: self.CanReachAqueduct(items) and items.SpeedBooster if self.Logic == SMLogic.Normal else \ lambda items: self.CanReachAqueduct(items) and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Gravity), lambda items: self.CanReachAqueduct(items) and items.Gravity),

View File

@ -9,7 +9,7 @@ class East(SMRegion, IReward):
def __init__(self, world, config: Config): def __init__(self, world, config: Config):
super().__init__(world, config) super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss self.Reward = RewardType.Null
self.Locations = [ self.Locations = [
Location(self, 74, 0x8F8FCA, LocationType.Visible, "Missile (lower Norfair above fire flea room)", Location(self, 74, 0x8F8FCA, LocationType.Visible, "Missile (lower Norfair above fire flea room)",
lambda items: self.CanExit(items)), lambda items: self.CanExit(items)),
@ -30,11 +30,11 @@ class East(SMRegion, IReward):
def CanExit(self, items:Progression): def CanExit(self, items:Progression):
if self.Logic == SMLogic.Normal: if self.Logic == SMLogic.Normal:
# /*Bubble Mountain*/ # /*Bubble Mountain*/
return items.CardNorfairL2 or ( return items.Morph and (items.CardNorfairL2 or (
# /* Volcano Room and Blue Gate */ # /* Volcano Room and Blue Gate */
items.Gravity) and items.Wave and ( items.Gravity) and items.Wave and (
# /*Spikey Acid Snakes and Croc Escape*/ # /*Spikey Acid Snakes and Croc Escape*/
items.Grapple or items.SpaceJump) items.Grapple or items.SpaceJump))
else: else:
# /*Vanilla LN Escape*/ # /*Vanilla LN Escape*/
return (items.Morph and (items.CardNorfairL2 or # /*Bubble Mountain*/ return (items.Morph and (items.CardNorfairL2 or # /*Bubble Mountain*/

View File

@ -17,13 +17,13 @@ class West(SMRegion):
items.CanAccessNorfairLowerPortal() and (items.CanFly() or items.CanSpringBallJump() or items.SpeedBooster) and items.Super)), items.CanAccessNorfairLowerPortal() and (items.CanFly() or items.CanSpringBallJump() or items.SpeedBooster) and items.Super)),
Location(self, 71, 0x8F8E74, LocationType.Hidden, "Super Missile (Gold Torizo)", Location(self, 71, 0x8F8E74, LocationType.Hidden, "Super Missile (Gold Torizo)",
lambda items: items.CanDestroyBombWalls() and (items.Super or items.Charge) and lambda items: items.CanDestroyBombWalls() and (items.Super or items.Charge) and
(items.CanAccessNorfairLowerPortal() or items.SpaceJump and items.CanUsePowerBombs()) if self.Logic == SMLogic.Normal else \ (items.CanAccessNorfairLowerPortal() or items.CanUsePowerBombs() and items.SpaceJump) if self.Logic == SMLogic.Normal else \
lambda items: items.CanDestroyBombWalls() and items.Varia and (items.Super or items.Charge)), lambda items: items.CanDestroyBombWalls() and items.Varia and (items.Super or items.Charge)),
Location(self, 79, 0x8F9110, LocationType.Chozo, "Screw Attack", Location(self, 79, 0x8F9110, LocationType.Chozo, "Screw Attack",
lambda items: items.CanDestroyBombWalls() and (items.SpaceJump and items.CanUsePowerBombs() or items.CanAccessNorfairLowerPortal()) if self.Logic == SMLogic.Normal else \ lambda items: items.CanDestroyBombWalls() and (items.CanAccessNorfairLowerPortal() or items.CanUsePowerBombs() and items.SpaceJump) if self.Logic == SMLogic.Normal else \
lambda items: items.CanDestroyBombWalls() and (items.Varia or items.CanAccessNorfairLowerPortal())), lambda items: items.CanDestroyBombWalls() and (items.CanAccessNorfairLowerPortal() or items.Varia)),
Location(self, 73, 0x8F8F30, LocationType.Visible, "Missile (Mickey Mouse room)", Location(self, 73, 0x8F8F30, LocationType.Visible, "Missile (Mickey Mouse room)",
lambda items: items.CanFly() and items.Morph and items.Super and ( lambda items: items.Morph and items.Super and items.CanFly() and items.CanUsePowerBombs() and (
# /*Exit to Upper Norfair*/ # /*Exit to Upper Norfair*/
(items.CardLowerNorfairL1 or (items.CardLowerNorfairL1 or
# /*Vanilla or Reverse Lava Dive*/ # /*Vanilla or Reverse Lava Dive*/
@ -33,17 +33,20 @@ class West(SMRegion):
# /* Volcano Room and Blue Gate */ # /* Volcano Room and Blue Gate */
items.Gravity and items.Wave and items.Gravity and items.Wave and
# /*Spikey Acid Snakes and Croc Escape*/ # /*Spikey Acid Snakes and Croc Escape*/
(items.Grapple or items.SpaceJump) or (items.Grapple or items.SpaceJump) or
# /*Exit via GT fight and Portal*/ # /*Exit via GT fight and Portal*/
(items.CanUsePowerBombs() and items.SpaceJump and (items.Super or items.Charge))) if self.Logic == SMLogic.Normal else \ items.CanUsePowerBombs() and items.SpaceJump and (items.Super or items.Charge)) if self.Logic == SMLogic.Normal else \
lambda items: lambda items:
items.Morph and items.Varia and items.Super and ((items.CanFly() or items.CanSpringBallJump() or items.Varia and items.Morph and items.Super and
items.SpeedBooster and (items.HiJump and items.CanUsePowerBombs() or items.Charge and items.Ice)) and #/* Climb worst room (from LN East CanEnter) */
# /*Exit to Upper Norfair*/ (items.CanFly() or items.HiJump or items.CanSpringBallJump() or items.Ice and items.Charge) and
(items.CardNorfairL2 or (items.SpeedBooster or items.CanFly() or items.Grapple or items.HiJump and (items.CanPassBombPassages() or items.ScrewAttack and items.SpaceJump) and (
(items.CanSpringBallJump() or items.Ice))) or #/* Exit to Upper Norfair */
# /*Return to Portal*/ items.CardNorfairL2 or items.SpeedBooster or items.CanFly() or items.Grapple or
items.CanUsePowerBombs())) items.HiJump and (items.CanSpringBallJump() or items.Ice) or
#/* Portal (with GGG) */
items.CanUsePowerBombs()
))
] ]
# // Todo: account for Croc Speedway once Norfair Upper East also do so, otherwise it would be inconsistent to do so here # // Todo: account for Croc Speedway once Norfair Upper East also do so, otherwise it would be inconsistent to do so here

View File

@ -45,11 +45,10 @@ class Crocomire(SMRegion):
# /* Cathedral -> through the floor or Vulcano */ # /* Cathedral -> through the floor or Vulcano */
items.CanOpenRedDoors() and (items.CardNorfairL2 if self.Config.Keysanity else items.Super) and items.CanOpenRedDoors() and (items.CardNorfairL2 if self.Config.Keysanity else items.Super) and
(items.CanFly() or items.HiJump or items.SpeedBooster) and (items.CanFly() or items.HiJump or items.SpeedBooster) and
(items.CanPassBombPassages() or items.Gravity and items.Morph) and items.Wave (items.CanPassBombPassages() or items.Gravity and items.Morph) and items.Wave) or (
or
# /* Reverse Lava Dive */ # /* Reverse Lava Dive */
items.CanAccessNorfairLowerPortal() and items.ScrewAttack and items.SpaceJump and items.Super and items.Varia) and items.CanAccessNorfairLowerPortal() and items.ScrewAttack and items.SpaceJump and items.Super and (
items.Gravity and items.Wave and (items.CardNorfairL2 or items.Morph)) items.Gravity) and items.Wave and (items.CardNorfairL2 or items.Morph)
else: else:
return ((items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or items.CanAccessNorfairUpperPortal()) and ( return ((items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or items.CanAccessNorfairUpperPortal()) and (
# /* Ice Beam -> Croc Speedway */ # /* Ice Beam -> Croc Speedway */
@ -65,5 +64,5 @@ class Crocomire(SMRegion):
(items.Missile or items.Super or items.Wave) # /* Blue Gate */ (items.Missile or items.Super or items.Wave) # /* Blue Gate */
) or ( ) or (
# /* Reverse Lava Dive */ # /* Reverse Lava Dive */
items.CanAccessNorfairLowerPortal()) and items.ScrewAttack and items.SpaceJump and items.Varia and items.Super and ( items.Varia and items.CanAccessNorfairLowerPortal()) and items.ScrewAttack and items.SpaceJump and items.Super and (
items.HasEnergyReserves(2)) and (items.CardNorfairL2 or items.Morph) items.HasEnergyReserves(2)) and (items.CardNorfairL2 or items.Morph)

View File

@ -9,12 +9,13 @@ class WreckedShip(SMRegion, IReward):
def __init__(self, world, config: Config): def __init__(self, world, config: Config):
super().__init__(world, config) super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss self.Weight = 4
self.Reward = RewardType.Null
self.Locations = [ self.Locations = [
Location(self, 128, 0x8FC265, LocationType.Visible, "Missile (Wrecked Ship middle)", Location(self, 128, 0x8FC265, LocationType.Visible, "Missile (Wrecked Ship middle)",
lambda items: items.CanPassBombPassages()), lambda items: items.CanPassBombPassages()),
Location(self, 129, 0x8FC2E9, LocationType.Chozo, "Reserve Tank, Wrecked Ship", Location(self, 129, 0x8FC2E9, LocationType.Chozo, "Reserve Tank, Wrecked Ship",
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and items.SpeedBooster and items.CanUsePowerBombs() and lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and items.CanUsePowerBombs() and items.SpeedBooster and
(items.Grapple or items.SpaceJump or items.Varia and items.HasEnergyReserves(2) or items.HasEnergyReserves(3)) if self.Logic == SMLogic.Normal else \ (items.Grapple or items.SpaceJump or items.Varia and items.HasEnergyReserves(2) or items.HasEnergyReserves(3)) if self.Logic == SMLogic.Normal else \
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and items.CanUsePowerBombs() and items.SpeedBooster and lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and items.CanUsePowerBombs() and items.SpeedBooster and
(items.Varia or items.HasEnergyReserves(2))), (items.Varia or items.HasEnergyReserves(2))),
@ -27,7 +28,7 @@ class WreckedShip(SMRegion, IReward):
Location(self, 132, 0x8FC337, LocationType.Visible, "Energy Tank, Wrecked Ship", Location(self, 132, 0x8FC337, LocationType.Visible, "Energy Tank, Wrecked Ship",
lambda items: self.CanUnlockShip(items) and lambda items: self.CanUnlockShip(items) and
(items.HiJump or items.SpaceJump or items.SpeedBooster or items.Gravity) if self.Logic == SMLogic.Normal else \ (items.HiJump or items.SpaceJump or items.SpeedBooster or items.Gravity) if self.Logic == SMLogic.Normal else \
lambda items: self.CanUnlockShip(items) and (items.Bombs or items.PowerBomb or items.CanSpringBallJump() or lambda items: self.CanUnlockShip(items) and (items.Morph and (items.Bombs or items.PowerBomb) or items.CanSpringBallJump() or
items.HiJump or items.SpaceJump or items.SpeedBooster or items.Gravity)), items.HiJump or items.SpaceJump or items.SpeedBooster or items.Gravity)),
Location(self, 133, 0x8FC357, LocationType.Visible, "Super Missile (Wrecked Ship left)", Location(self, 133, 0x8FC357, LocationType.Visible, "Super Missile (Wrecked Ship left)",
lambda items: self.CanUnlockShip(items)), lambda items: self.CanUnlockShip(items)),

View File

@ -14,15 +14,15 @@ class NorthEast(Z3Region):
lambda items: items.MoonPearl and items.CanLiftLight()), lambda items: items.MoonPearl and items.CanLiftLight()),
Location(self, 256+79, 0x308147, LocationType.Regular, "Pyramid"), Location(self, 256+79, 0x308147, LocationType.Regular, "Pyramid"),
Location(self, 256+80, 0x1E980, LocationType.Regular, "Pyramid Fairy - Left", Location(self, 256+80, 0x1E980, LocationType.Regular, "Pyramid Fairy - Left",
lambda items: self.world.CanAquireAll(items, RewardType.CrystalRed) and items.MoonPearl and self.world.CanEnter("Dark World South", items) and lambda items: self.world.CanAcquireAll(items, RewardType.CrystalRed) and items.MoonPearl and self.world.CanEnter("Dark World South", items) and
(items.Hammer or items.Mirror and self.world.CanAquire(items, RewardType.Agahnim))), (items.Hammer or items.Mirror and self.world.CanAcquire(items, RewardType.Agahnim))),
Location(self, 256+81, 0x1E983, LocationType.Regular, "Pyramid Fairy - Right", Location(self, 256+81, 0x1E983, LocationType.Regular, "Pyramid Fairy - Right",
lambda items: self.world.CanAquireAll(items, RewardType.CrystalRed) and items.MoonPearl and self.world.CanEnter("Dark World South", items) and lambda items: self.world.CanAcquireAll(items, RewardType.CrystalRed) and items.MoonPearl and self.world.CanEnter("Dark World South", items) and
(items.Hammer or items.Mirror and self.world.CanAquire(items, RewardType.Agahnim))) (items.Hammer or items.Mirror and self.world.CanAcquire(items, RewardType.Agahnim)))
] ]
def CanEnter(self, items: Progression): def CanEnter(self, items: Progression):
return self.world.CanAquire(items, RewardType.Agahnim) or items.MoonPearl and ( return self.world.CanAcquire(items, RewardType.Agahnim) or items.MoonPearl and (
items.Hammer and items.CanLiftLight() or items.Hammer and items.CanLiftLight() or
items.CanLiftHeavy() and items.Flippers or items.CanLiftHeavy() and items.Flippers or
items.CanAccessDarkWorldPortal(self.Config) and items.Flippers) items.CanAccessDarkWorldPortal(self.Config) and items.Flippers)

View File

@ -25,7 +25,7 @@ class NorthWest(Z3Region):
def CanEnter(self, items: Progression): def CanEnter(self, items: Progression):
return items.MoonPearl and (( return items.MoonPearl and ((
self.world.CanAquire(items, RewardType.Agahnim) or self.world.CanAcquire(items, RewardType.Agahnim) or
items.CanAccessDarkWorldPortal(self.Config) and items.Flippers items.CanAccessDarkWorldPortal(self.Config) and items.Flippers
) and items.Hookshot and (items.Flippers or items.CanLiftLight() or items.Hammer) or ) and items.Hookshot and (items.Flippers or items.CanLiftLight() or items.Hammer) or
items.Hammer and items.CanLiftLight() or items.Hammer and items.CanLiftLight() or

View File

@ -21,7 +21,7 @@ class South(Z3Region):
def CanEnter(self, items: Progression): def CanEnter(self, items: Progression):
return items.MoonPearl and (( return items.MoonPearl and ((
self.world.CanAquire(items, RewardType.Agahnim) or self.world.CanAcquire(items, RewardType.Agahnim) or
items.CanAccessDarkWorldPortal(self.Config) and items.Flippers items.CanAccessDarkWorldPortal(self.Config) and items.Flippers
) and (items.Hammer or items.Hookshot and (items.Flippers or items.CanLiftLight())) or ) and (items.Hammer or items.Hookshot and (items.Flippers or items.CanLiftLight())) or
items.Hammer and items.CanLiftLight() or items.Hammer and items.CanLiftLight() or

View File

@ -34,33 +34,33 @@ class GanonsTower(Z3Region):
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"), self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right") self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right")
]) or self.GetLocation("Ganon's Tower - Firesnake Room").ItemIs(ItemType.KeyGT, self.world) else 3)), ]) or self.GetLocation("Ganon's Tower - Firesnake Room").ItemIs(ItemType.KeyGT, self.world) else 3)),
Location(self, 256+196, 0x1EAC4, LocationType.Regular, "Ganon's Tower - Randomizer Room - Top Left", Location(self, 256+230, 0x1EAC4, LocationType.Regular, "Ganon's Tower - Randomizer Room - Top Left",
lambda items: self.LeftSide(items, [ lambda items: self.LeftSide(items, [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"), self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"), self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right") self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right")
])), ])),
Location(self, 256+197, 0x1EAC7, LocationType.Regular, "Ganon's Tower - Randomizer Room - Top Right", Location(self, 256+231, 0x1EAC7, LocationType.Regular, "Ganon's Tower - Randomizer Room - Top Right",
lambda items: self.LeftSide(items, [ lambda items: self.LeftSide(items, [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"), self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"), self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right") self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right")
])), ])),
Location(self, 256+198, 0x1EACA, LocationType.Regular, "Ganon's Tower - Randomizer Room - Bottom Left", Location(self, 256+232, 0x1EACA, LocationType.Regular, "Ganon's Tower - Randomizer Room - Bottom Left",
lambda items: self.LeftSide(items, [ lambda items: self.LeftSide(items, [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"), self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"),
self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"), self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right") self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right")
])), ])),
Location(self, 256+199, 0x1EACD, LocationType.Regular, "Ganon's Tower - Randomizer Room - Bottom Right", Location(self, 256+233, 0x1EACD, LocationType.Regular, "Ganon's Tower - Randomizer Room - Bottom Right",
lambda items: self.LeftSide(items, [ lambda items: self.LeftSide(items, [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"), self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"),
self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"), self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left") self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left")
])), ])),
Location(self, 256+200, 0x1EAD9, LocationType.Regular, "Ganon's Tower - Hope Room - Left"), Location(self, 256+234, 0x1EAD9, LocationType.Regular, "Ganon's Tower - Hope Room - Left"),
Location(self, 256+201, 0x1EADC, LocationType.Regular, "Ganon's Tower - Hope Room - Right"), Location(self, 256+235, 0x1EADC, LocationType.Regular, "Ganon's Tower - Hope Room - Right"),
Location(self, 256+202, 0x1EAE2, LocationType.Regular, "Ganon's Tower - Tile Room", Location(self, 256+236, 0x1EAE2, LocationType.Regular, "Ganon's Tower - Tile Room",
lambda items: items.Somaria), lambda items: items.Somaria),
Location(self, 256+203, 0x1EAE5, LocationType.Regular, "Ganon's Tower - Compass Room - Top Left", Location(self, 256+203, 0x1EAE5, LocationType.Regular, "Ganon's Tower - Compass Room - Top Left",
lambda items: self.RightSide(items, [ lambda items: self.RightSide(items, [
@ -118,8 +118,9 @@ class GanonsTower(Z3Region):
return items.Somaria and items.Firerod and items.KeyGT >= (3 if any(l.ItemIs(ItemType.BigKeyGT, self.world) for l in locations) else 4) return items.Somaria and items.Firerod and items.KeyGT >= (3 if any(l.ItemIs(ItemType.BigKeyGT, self.world) for l in locations) else 4)
def BigKeyRoom(self, items: Progression): def BigKeyRoom(self, items: Progression):
return items.KeyGT >= 3 and self.CanBeatArmos(items) \ return items.KeyGT >= 3 and \
and (items.Hammer and items.Hookshot or items.Firerod and items.Somaria) (items.Hammer and items.Hookshot or items.Firerod and items.Somaria) \
and self.CanBeatArmos(items)
def TowerAscend(self, items: Progression): def TowerAscend(self, items: Progression):
return items.BigKeyGT and items.KeyGT >= 3 and items.Bow and items.CanLightTorches() return items.BigKeyGT and items.KeyGT >= 3 and items.Bow and items.CanLightTorches()
@ -134,13 +135,14 @@ class GanonsTower(Z3Region):
def CanEnter(self, items: Progression): def CanEnter(self, items: Progression):
return items.MoonPearl and self.world.CanEnter("Dark World Death Mountain East", items) and \ return items.MoonPearl and self.world.CanEnter("Dark World Death Mountain East", items) and \
self.world.CanAquireAll(items, RewardType.CrystalBlue, RewardType.CrystalRed, RewardType.GoldenFourBoss) self.world.CanAcquireAtLeast(self.world.TowerCrystals, items, RewardType.AnyCrystal) and \
self.world.CanAcquireAtLeast(self.world.TourianBossTokens * (self.world.TowerCrystals / 7), items, RewardType.AnyBossToken)
def CanFill(self, item: Item): def CanFill(self, item: Item):
if (self.Config.GameMode == GameMode.Multiworld): if (self.Config.Multiworld):
if (item.World != self.world or item.Progression): if (item.World != self.world or item.Progression):
return False return False
if (self.Config.KeyShuffle == KeyShuffle.Keysanity and not ((item.Type == ItemType.BigKeyGT or item.Type == ItemType.KeyGT) and item.World == self.world) and (item.IsKey() or item.IsBigKey() or item.IsKeycard())): if (self.Config.Keysanity and not ((item.Type == ItemType.BigKeyGT or item.Type == ItemType.KeyGT) and item.World == self.world) and (item.IsKey() or item.IsBigKey() or item.IsKeycard())):
return False return False
return super().CanFill(item) return super().CanFill(item)

View File

@ -10,6 +10,7 @@ class IcePalace(Z3Region, IReward):
def __init__(self, world, config: Config): def __init__(self, world, config: Config):
super().__init__(world, config) super().__init__(world, config)
self.Weight = 4
self.RegionItems = [ ItemType.KeyIP, ItemType.BigKeyIP, ItemType.MapIP, ItemType.CompassIP] self.RegionItems = [ ItemType.KeyIP, ItemType.BigKeyIP, ItemType.MapIP, ItemType.CompassIP]
self.Reward = RewardType.Null self.Reward = RewardType.Null
self.Locations = [ self.Locations = [
@ -43,7 +44,7 @@ class IcePalace(Z3Region, IReward):
] ]
def CanNotWasteKeysBeforeAccessible(self, items: Progression, locations: List[Location]): def CanNotWasteKeysBeforeAccessible(self, items: Progression, locations: List[Location]):
return not items.BigKeyIP or any(l.ItemIs(ItemType.BigKeyIP, self.world) for l in locations) return self.world.ForwardSearch or not items.BigKeyIP or any(l.ItemIs(ItemType.BigKeyIP, self.world) for l in locations)
def CanEnter(self, items: Progression): def CanEnter(self, items: Progression):
return items.MoonPearl and items.Flippers and items.CanLiftHeavy() and items.CanMeltFreezors() return items.MoonPearl and items.Flippers and items.CanLiftHeavy() and items.CanMeltFreezors()

View File

@ -24,5 +24,5 @@ class NorthEast(Z3Region):
Location(self, 256+42, 0x1EA85, LocationType.Regular, "Sahasrahla's Hut - Middle").Weighted(sphereOne), Location(self, 256+42, 0x1EA85, LocationType.Regular, "Sahasrahla's Hut - Middle").Weighted(sphereOne),
Location(self, 256+43, 0x1EA88, LocationType.Regular, "Sahasrahla's Hut - Right").Weighted(sphereOne), Location(self, 256+43, 0x1EA88, LocationType.Regular, "Sahasrahla's Hut - Right").Weighted(sphereOne),
Location(self, 256+44, 0x5F1FC, LocationType.Regular, "Sahasrahla", Location(self, 256+44, 0x5F1FC, LocationType.Regular, "Sahasrahla",
lambda items: self.world.CanAquire(items, RewardType.PendantGreen)) lambda items: self.world.CanAcquire(items, RewardType.PendantGreen))
] ]

View File

@ -11,11 +11,11 @@ class NorthWest(Z3Region):
sphereOne = -14 sphereOne = -14
self.Locations = [ self.Locations = [
Location(self, 256+14, 0x589B0, LocationType.Pedestal, "Master Sword Pedestal", Location(self, 256+14, 0x589B0, LocationType.Pedestal, "Master Sword Pedestal",
lambda items: self.world.CanAquireAll(items, RewardType.PendantGreen, RewardType.PendantNonGreen)), lambda items: self.world.CanAcquireAll(items, RewardType.AnyPendant)),
Location(self, 256+15, 0x308013, LocationType.Regular, "Mushroom").Weighted(sphereOne), Location(self, 256+15, 0x308013, LocationType.Regular, "Mushroom").Weighted(sphereOne),
Location(self, 256+16, 0x308000, LocationType.Regular, "Lost Woods Hideout").Weighted(sphereOne), Location(self, 256+16, 0x308000, LocationType.Regular, "Lost Woods Hideout").Weighted(sphereOne),
Location(self, 256+17, 0x308001, LocationType.Regular, "Lumberjack Tree", Location(self, 256+17, 0x308001, LocationType.Regular, "Lumberjack Tree",
lambda items: self.world.CanAquire(items, RewardType.Agahnim) and items.Boots), lambda items: self.world.CanAcquire(items, RewardType.Agahnim) and items.Boots),
Location(self, 256+18, 0x1EB3F, LocationType.Regular, "Pegasus Rocks", Location(self, 256+18, 0x1EB3F, LocationType.Regular, "Pegasus Rocks",
lambda items: items.Boots), lambda items: items.Boots),
Location(self, 256+19, 0x308004, LocationType.Regular, "Graveyard Ledge", Location(self, 256+19, 0x308004, LocationType.Regular, "Graveyard Ledge",

View File

@ -10,9 +10,10 @@ class MiseryMire(Z3Region, IReward, IMedallionAccess):
def __init__(self, world, config: Config): def __init__(self, world, config: Config):
super().__init__(world, config) super().__init__(world, config)
self.Weight = 2
self.RegionItems = [ ItemType.KeyMM, ItemType.BigKeyMM, ItemType.MapMM, ItemType.CompassMM] self.RegionItems = [ ItemType.KeyMM, ItemType.BigKeyMM, ItemType.MapMM, ItemType.CompassMM]
self.Reward = RewardType.Null self.Reward = RewardType.Null
self.Medallion = ItemType.Nothing self.Medallion = None
self.Locations = [ self.Locations = [
Location(self, 256+169, 0x1EA5E, LocationType.Regular, "Misery Mire - Main Lobby", Location(self, 256+169, 0x1EA5E, LocationType.Regular, "Misery Mire - Main Lobby",
lambda items: items.BigKeyMM or items.KeyMM >= 1), lambda items: items.BigKeyMM or items.KeyMM >= 1),
@ -34,8 +35,9 @@ class MiseryMire(Z3Region, IReward, IMedallionAccess):
# // Need "CanKillManyEnemies" if implementing swordless # // Need "CanKillManyEnemies" if implementing swordless
def CanEnter(self, items: Progression): def CanEnter(self, items: Progression):
return (items.Bombos if self.Medallion == ItemType.Bombos else ( from worlds.smz3.TotalSMZ3.WorldState import Medallion
items.Ether if self.Medallion == ItemType.Ether else items.Quake)) and items.Sword and \ return (items.Bombos if self.Medallion == Medallion.Bombos else (
items.Ether if self.Medallion == Medallion.Ether else items.Quake)) and items.Sword and \
items.MoonPearl and (items.Boots or items.Hookshot) and \ items.MoonPearl and (items.Boots or items.Hookshot) and \
self.world.CanEnter("Dark World Mire", items) self.world.CanEnter("Dark World Mire", items)

View File

@ -10,6 +10,7 @@ class SwampPalace(Z3Region, IReward):
def __init__(self, world, config: Config): def __init__(self, world, config: Config):
super().__init__(world, config) super().__init__(world, config)
self.Weight = 3
self.RegionItems = [ ItemType.KeySP, ItemType.BigKeySP, ItemType.MapSP, ItemType.CompassSP] self.RegionItems = [ ItemType.KeySP, ItemType.BigKeySP, ItemType.MapSP, ItemType.CompassSP]
self.Reward = RewardType.Null self.Reward = RewardType.Null
self.Locations = [ self.Locations = [

View File

@ -10,9 +10,10 @@ class TurtleRock(Z3Region, IReward, IMedallionAccess):
def __init__(self, world, config: Config): def __init__(self, world, config: Config):
super().__init__(world, config) super().__init__(world, config)
self.Weight = 6
self.RegionItems = [ ItemType.KeyTR, ItemType.BigKeyTR, ItemType.MapTR, ItemType.CompassTR] self.RegionItems = [ ItemType.KeyTR, ItemType.BigKeyTR, ItemType.MapTR, ItemType.CompassTR]
self.Reward = RewardType.Null self.Reward = RewardType.Null
self.Medallion = ItemType.Nothing self.Medallion = None
self.Locations = [ self.Locations = [
Location(self, 256+177, 0x1EA22, LocationType.Regular, "Turtle Rock - Compass Chest"), Location(self, 256+177, 0x1EA22, LocationType.Regular, "Turtle Rock - Compass Chest"),
Location(self, 256+178, 0x1EA1C, LocationType.Regular, "Turtle Rock - Roller Room - Left", Location(self, 256+178, 0x1EA1C, LocationType.Regular, "Turtle Rock - Roller Room - Left",
@ -46,8 +47,9 @@ class TurtleRock(Z3Region, IReward, IMedallionAccess):
return items.Firerod and items.Icerod return items.Firerod and items.Icerod
def CanEnter(self, items: Progression): def CanEnter(self, items: Progression):
return (items.Bombos if self.Medallion == ItemType.Bombos else ( from worlds.smz3.TotalSMZ3.WorldState import Medallion
items.Ether if self.Medallion == ItemType.Ether else items.Quake)) and items.Sword and \ return (items.Bombos if self.Medallion == Medallion.Bombos else (
items.Ether if self.Medallion == Medallion.Ether else items.Quake)) and items.Sword and \
items.MoonPearl and items.CanLiftHeavy() and items.Hammer and items.Somaria and \ items.MoonPearl and items.CanLiftHeavy() and items.Hammer and items.Somaria and \
self.world.CanEnter("Light World Death Mountain East", items) self.world.CanEnter("Light World Death Mountain East", items)

View File

@ -4,9 +4,7 @@ class Dialog:
command = re.compile(r"^\{[^}]*\}") command = re.compile(r"^\{[^}]*\}")
invalid = re.compile(r"(?<!^)\{[^}]*\}(?!$)", re.MULTILINE) invalid = re.compile(r"(?<!^)\{[^}]*\}(?!$)", re.MULTILINE)
digit = re.compile(r"\d") character = re.compile(r"(?P<digit>[0-9])|(?P<upper>[A-Z])|(?P<lower>[a-z])")
uppercaseLetter = re.compile(r"[A-Z]")
lowercaseLetter = re.compile(r"[a-z]")
@staticmethod @staticmethod
def Simple(text: str): def Simple(text: str):
@ -29,19 +27,16 @@ class Dialog:
lineIndex += 1 lineIndex += 1
if (lineIndex % 3 == 0 and lineIndex < len(lines)): if (lineIndex < len(lines)):
bytes.append(0x7E) if (lineIndex % 3 == 0):
if (lineIndex >= 3 and lineIndex < len(lines)): bytes.append(0x7E) # pause for input
bytes.append(0x73) if (lineIndex >= 3):
bytes.append(0x73) # scroll
bytes.append(0x7F) return bytes[:maxBytes - 1].append(0x7F)
if (len(bytes) > maxBytes):
return bytes[:maxBytes - 1].append(0x7F)
return bytes
@staticmethod @staticmethod
def Compiled(text: str, pause = True): def Compiled(text: str):
maxBytes = 2046 maxBytes = 2046
wrap = 19 wrap = 19
@ -49,6 +44,7 @@ class Dialog:
raise Exception("Dialog commands must be placed on separate lines", text) raise Exception("Dialog commands must be placed on separate lines", text)
padOut = False padOut = False
pause = True
bytes = [ 0xFB ] bytes = [ 0xFB ]
lines = Dialog.Wordwrap(text, wrap) lines = Dialog.Wordwrap(text, wrap)
@ -61,33 +57,11 @@ class Dialog:
return [ 0xFB, 0xFE, 0x6E, 0x00, 0xFE, 0x6B, 0x04 ] return [ 0xFB, 0xFE, 0x6E, 0x00, 0xFE, 0x6B, 0x04 ]
if (match.string == "{INTRO}"): if (match.string == "{INTRO}"):
padOut = True padOut = True
if (match.string == "{NOPAUSE}"):
pause = False
continue
bytesMap = { result = Dialog.CommandBytesFor(match.string)
"{SPEED0}" : [ 0xFC, 0x00 ],
"{SPEED2}" : [ 0xFC, 0x02 ],
"{SPEED6}" : [ 0xFC, 0x06 ],
"{PAUSE1}" : [ 0xFE, 0x78, 0x01 ],
"{PAUSE3}" : [ 0xFE, 0x78, 0x03 ],
"{PAUSE5}" : [ 0xFE, 0x78, 0x05 ],
"{PAUSE7}" : [ 0xFE, 0x78, 0x07 ],
"{PAUSE9}" : [ 0xFE, 0x78, 0x09 ],
"{INPUT}" : [ 0xFA ],
"{CHOICE}" : [ 0xFE, 0x68 ],
"{ITEMSELECT}" : [ 0xFE, 0x69 ],
"{CHOICE2}" : [ 0xFE, 0x71 ],
"{CHOICE3}" : [ 0xFE, 0x72 ],
"{C:GREEN}" : [ 0xFE, 0x77, 0x07 ],
"{C:YELLOW}" : [ 0xFE, 0x77, 0x02 ],
"{HARP}" : [ 0xFE, 0x79, 0x2D ],
"{MENU}" : [ 0xFE, 0x6D, 0x00 ],
"{BOTTOM}" : [ 0xFE, 0x6D, 0x01 ],
"{NOBORDER}" : [ 0xFE, 0x6B, 0x02 ],
"{CHANGEPIC}" : [ 0xFE, 0x67, 0xFE, 0x67 ],
"{CHANGEMUSIC}" : [ 0xFE, 0x67 ],
"{INTRO}" : [ 0xFE, 0x6E, 0x00, 0xFE, 0x77, 0x07, 0xFC, 0x03, 0xFE, 0x6B, 0x02, 0xFE, 0x67 ],
"{IBOX}" : [ 0xFE, 0x6B, 0x02, 0xFE, 0x77, 0x07, 0xFC, 0x03, 0xF7 ],
}
result = bytesMap.get(match.string, None)
if (result is None): if (result is None):
raise Exception(f"Dialog text contained unknown command {match.string}", text) raise Exception(f"Dialog text contained unknown command {match.string}", text)
else: else:
@ -98,12 +72,10 @@ class Dialog:
continue continue
if (lineIndex == 1): if (lineIndex > 0):
bytes.append(0xF8); #// row 2 bytes.append(0xF8 if lineIndex == 1 else #// row 2
elif (lineIndex >= 3 and lineIndex < lineCount): 0xF9 if lineIndex == 2 else #// row 3
bytes.append(0xF6); #// scroll 0xF6) #// scroll
elif (lineIndex >= 2):
bytes.append(0xF9); #// row 3
#// The first box needs to fill the full width with spaces as the palette is loaded weird. #// The first box needs to fill the full width with spaces as the palette is loaded weird.
letters = line + (" " * wrap) if padOut and lineIndex < 3 else line letters = line + (" " * wrap) if padOut and lineIndex < 3 else line
@ -113,10 +85,39 @@ class Dialog:
lineIndex += 1 lineIndex += 1
if (pause and lineIndex % 3 == 0 and lineIndex < lineCount): if (pause and lineIndex % 3 == 0 and lineIndex < lineCount):
bytes.append(0xFA) #// wait for input bytes.append(0xFA) #// pause for input
return bytes[:maxBytes] return bytes[:maxBytes]
@staticmethod
def CommandBytesFor(text: str):
bytesMap = {
"{SPEED0}" : [ 0xFC, 0x00 ],
"{SPEED2}" : [ 0xFC, 0x02 ],
"{SPEED6}" : [ 0xFC, 0x06 ],
"{PAUSE1}" : [ 0xFE, 0x78, 0x01 ],
"{PAUSE3}" : [ 0xFE, 0x78, 0x03 ],
"{PAUSE5}" : [ 0xFE, 0x78, 0x05 ],
"{PAUSE7}" : [ 0xFE, 0x78, 0x07 ],
"{PAUSE9}" : [ 0xFE, 0x78, 0x09 ],
"{INPUT}" : [ 0xFA ],
"{CHOICE}" : [ 0xFE, 0x68 ],
"{ITEMSELECT}" : [ 0xFE, 0x69 ],
"{CHOICE2}" : [ 0xFE, 0x71 ],
"{CHOICE3}" : [ 0xFE, 0x72 ],
"{C:GREEN}" : [ 0xFE, 0x77, 0x07 ],
"{C:YELLOW}" : [ 0xFE, 0x77, 0x02 ],
"{HARP}" : [ 0xFE, 0x79, 0x2D ],
"{MENU}" : [ 0xFE, 0x6D, 0x00 ],
"{BOTTOM}" : [ 0xFE, 0x6D, 0x01 ],
"{NOBORDER}" : [ 0xFE, 0x6B, 0x02 ],
"{CHANGEPIC}" : [ 0xFE, 0x67, 0xFE, 0x67 ],
"{CHANGEMUSIC}" : [ 0xFE, 0x67 ],
"{INTRO}" : [ 0xFE, 0x6E, 0x00, 0xFE, 0x77, 0x07, 0xFC, 0x03, 0xFE, 0x6B, 0x02, 0xFE, 0x67 ],
"{IBOX}" : [ 0xFE, 0x6B, 0x02, 0xFE, 0x77, 0x07, 0xFC, 0x03, 0xF7 ],
}
return bytesMap.get(text, None)
@staticmethod @staticmethod
def Wordwrap(text: str, width: int): def Wordwrap(text: str, width: int):
result = [] result = []
@ -146,9 +147,13 @@ class Dialog:
@staticmethod @staticmethod
def LetterToBytes(c: str): def LetterToBytes(c: str):
if Dialog.digit.match(c): return [(ord(c) - ord('0') + 0xA0) ] match = Dialog.character.match(c)
elif Dialog.uppercaseLetter.match(c): return [ (ord(c) - ord('A') + 0xAA) ] if match is None:
elif Dialog.lowercaseLetter.match(c): return [ (ord(c) - ord('a') + 0x30) ] value = Dialog.letters.get(c, None)
return value if value else [ 0xFF ]
elif match.group("digit") != None: return [(ord(c) - ord('0') + 0xA0) ]
elif match.group("upper") != None: return [ (ord(c) - ord('A') + 0xAA) ]
elif match.group("lower") != None: return [ (ord(c) - ord('a') + 0x30) ]
else: else:
value = Dialog.letters.get(c, None) value = Dialog.letters.get(c, None)
return value if value else [ 0xFF ] return value if value else [ 0xFF ]

View File

@ -377,9 +377,76 @@ Items:
THE GREEN THE GREEN
BOOMERANG IS BOOMERANG IS
THE FASTEST! THE FASTEST!
Keycard: |-
A key from CardCrateriaL1: |-
the future? An Alien Key!
It says On top
of the world!
CardCrateriaL2: |-
An Alien Key!
It says Lower
the drawbridge
CardCrateriaBoss: |-
An Alien Key!
It says The First
and The Last
CardBrinstarL1: |-
An Alien Key!
It says But wait
there's more!
CardBrinstarL2: |-
An Alien Key!
It says
Green Monkeys
CardBrinstarBoss: |-
An Alien Key!
It says
Metroid DLC
CardNorfairL1: |-
An Alien Key!
It says ice?
In this heat?
CardNorfairL2: |-
An Alien Key!
It says
THE BUBBLES!
CardNorfairBoss: |-
An Alien Key!
It says
Place your bets
CardMaridiaL1: |-
An Alien Key!
It says A
Day at the Beach
CardMaridiaL2: |-
An Alien Key!
It says
That's a Moray
CardMaridiaBoss: |-
An Alien Key!
It says Shrimp
for dinner?
CardWreckedShipL1: |-
An Alien Key!
It says
Gutter Ball
CardWreckedShipBoss: |-
An Alien Key!
It says The
Ghost of Arrghus
CardLowerNorfairL1: |-
An Alien Key!
It says Worst
Key in the Game
CardLowerNorfairBoss: |-
An Alien Key!
It says
This guy again?
SmMap: |-
You can now
find your way
to the stars!
Something: |- Something: |-
A small victory! A small victory!

View File

@ -4,8 +4,8 @@
# The order of the dialog entries is significant # The order of the dialog entries is significant
- set_cursor: [0xFB, 0xFC, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xE4, 0xFE, 0x68] - set_cursor: [0xFB, 0xFC, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xE4, 0xFE, 0x68]
- set_cursor2: [0xFB, 0xFC, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xE4, 0xFE, 0x68] - set_cursor2: [0xFB, 0xFC, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xE4, 0xFE, 0x68]
- game_over_menu: { NoPause: "{SPEED0}\nSave and Continue\nSave and Quit\nContinue" } - game_over_menu: "{NOPAUSE}\n{SPEED0}\nSave and Continue\nSave and Quit\nContinue"
- var_test: { NoPause: "0= ᚋ, 1= ᚌ\n2= ᚍ, 3= ᚎ" } - var_test: "{NOPAUSE}\n0= ᚋ, 1= ᚌ\n2= ᚍ, 3= ᚎ"
- follower_no_enter: "Can't you take me some place nice." - follower_no_enter: "Can't you take me some place nice."
- choice_1_3: [0xFB, 0xFC, 0x00, 0xF7, 0xE4, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, 0x71] - choice_1_3: [0xFB, 0xFC, 0x00, 0xF7, 0xE4, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, 0x71]
- choice_2_3: [0xFB, 0xFC, 0x00, 0xF7, 0xFF, 0xF8, 0xE4, 0xF9, 0xFF, 0xFE, 0x71] - choice_2_3: [0xFB, 0xFC, 0x00, 0xF7, 0xFF, 0xF8, 0xE4, 0xF9, 0xFF, 0xFE, 0x71]
@ -290,10 +290,10 @@
# $110 # $110
- magic_bat_wake: "You bum! I was sleeping! Where's my magic bolts?" - magic_bat_wake: "You bum! I was sleeping! Where's my magic bolts?"
- magic_bat_give_half_magic: "How you like me now?" - magic_bat_give_half_magic: "How you like me now?"
- intro_main: { NoPause: "{INTRO}\n Episode III\n{PAUSE3}\n A Link to\n the Past\n{PAUSE3}\n Randomizer\n{PAUSE3}\nAfter mostly disregarding what happened in the first two games.\n{PAUSE3}\nLink awakens to his uncle leaving the house.\n{PAUSE3}\nHe just runs out the door,\n{PAUSE3}\ninto the rainy night.\n{PAUSE3}\n{CHANGEPIC}\nGanon has moved around all the items in Hyrule.\n{PAUSE7}\nYou will have to find all the items necessary to beat Ganon.\n{PAUSE7}\nThis is your chance to be a hero.\n{PAUSE3}\n{CHANGEPIC}\nYou must get the 7 crystals to beat Ganon.\n{PAUSE9}\n{CHANGEPIC}" } - intro_main: "{NOPAUSE}\n{INTRO}\n Episode III\n{PAUSE3}\n A Link to\n the Past\n{PAUSE3}\n Randomizer\n{PAUSE3}\nAfter mostly disregarding what happened in the first two games.\n{PAUSE3}\nLink awakens to his uncle leaving the house.\n{PAUSE3}\nHe just runs out the door,\n{PAUSE3}\ninto the rainy night.\n{PAUSE3}\n{CHANGEPIC}\nGanon has moved around all the items in Hyrule.\n{PAUSE7}\nYou will have to find all the items necessary to beat Ganon.\n{PAUSE7}\nThis is your chance to be a hero.\n{PAUSE3}\n{CHANGEPIC}\nYou must get the 7 crystals to beat Ganon.\n{PAUSE9}\n{CHANGEPIC}"
- intro_throne_room: { NoPause: "{IBOX}\nLook at this Stalfos on the throne." } - intro_throne_room: "{NOPAUSE}\n{IBOX}\nLook at this Stalfos on the throne."
- intro_zelda_cell: { NoPause: "{IBOX}\nIt is your time to shine!" } - intro_zelda_cell: "{NOPAUSE}\n{IBOX}\nIt is your time to shine!"
- intro_agahnim: { NoPause: "{IBOX}\nAlso, you need to defeat this guy!" } - intro_agahnim: "{NOPAUSE}\n{IBOX}\nAlso, you need to defeat this guy!"
- pickup_purple_chest: "A curious box. Let's take it with us!" - pickup_purple_chest: "A curious box. Let's take it with us!"
- bomb_shop: "30 bombs for 100 rupees. Good deals all day!" - bomb_shop: "30 bombs for 100 rupees. Good deals all day!"
- bomb_shop_big_bomb: "30 bombs for 100 rupees, 100 rupees 1 BIG bomb. Good deals all day!" - bomb_shop_big_bomb: "30 bombs for 100 rupees, 100 rupees 1 BIG bomb. Good deals all day!"
@ -341,7 +341,6 @@
# $140 # $140
- agahnim_defeated: "Arrrgggghhh. Well you're coming with me!" - agahnim_defeated: "Arrrgggghhh. Well you're coming with me!"
- agahnim_final_meeting: "You have done well to come this far. Now, die!" - agahnim_final_meeting: "You have done well to come this far. Now, die!"
# $142
- zora_meeting: "What do you want?\n ≥ Flippers\n _Nothin'\n{CHOICE}" - zora_meeting: "What do you want?\n ≥ Flippers\n _Nothin'\n{CHOICE}"
- zora_tells_cost: "Fine! But they aren't cheap. You got 500 rupees?\n ≥ Duh\n _Oh carp\n{CHOICE}" - zora_tells_cost: "Fine! But they aren't cheap. You got 500 rupees?\n ≥ Duh\n _Oh carp\n{CHOICE}"
- zora_get_flippers: "Here's some Flippers for you! Swim little fish, swim." - zora_get_flippers: "Here's some Flippers for you! Swim little fish, swim."
@ -396,14 +395,12 @@
- lost_woods_thief: "Have you seen Andy?\n\nHe was out looking for our prized Ether medallion.\nI wonder when he will be back?" - lost_woods_thief: "Have you seen Andy?\n\nHe was out looking for our prized Ether medallion.\nI wonder when he will be back?"
- blinds_hut_dude: "I'm just some dude. This is Blind's hut." - blinds_hut_dude: "I'm just some dude. This is Blind's hut."
- end_triforce: "{SPEED2}\n{MENU}\n{NOBORDER}\n G G" - end_triforce: "{SPEED2}\n{MENU}\n{NOBORDER}\n G G"
# $174
- toppi_fallen: "Ouch!\n\nYou Jerk!" - toppi_fallen: "Ouch!\n\nYou Jerk!"
- kakariko_tavern_fisherman: "Don't argue\nwith a frozen\nDeadrock.\nHe'll never\nchange his\nposition!" - kakariko_tavern_fisherman: "Don't argue\nwith a frozen\nDeadrock.\nHe'll never\nchange his\nposition!"
- thief_money: "It's a secret to everyone." - thief_money: "It's a secret to everyone."
- thief_desert_rupee_cave: "So you, like, busted down my door, and are being a jerk by talking to me? Normally I would be angry and make you pay for it, but I bet you're just going to break all my pots and steal my 50 rupees." - thief_desert_rupee_cave: "So you, like, busted down my door, and are being a jerk by talking to me? Normally I would be angry and make you pay for it, but I bet you're just going to break all my pots and steal my 50 rupees."
- thief_ice_rupee_cave: "I'm a rupee pot farmer. One day I will take over the world with my skillz. Have you met my brother in the desert? He's way richer than I am." - thief_ice_rupee_cave: "I'm a rupee pot farmer. One day I will take over the world with my skillz. Have you met my brother in the desert? He's way richer than I am."
- telepathic_tile_south_east_darkworld_cave: "~~ Dev cave ~~\n No farming\n required" - telepathic_tile_south_east_darkworld_cave: "~~ Dev cave ~~\n No farming\n required"
# $17A
- cukeman: "Did you hear that Veetorp beat ajneb174 in a 1 on 1 race at AGDQ?" - cukeman: "Did you hear that Veetorp beat ajneb174 in a 1 on 1 race at AGDQ?"
- cukeman_2: "You found Shabadoo, huh?\nNiiiiice." - cukeman_2: "You found Shabadoo, huh?\nNiiiiice."
- potion_shop_no_cash: "Yo! I'm not running a charity here." - potion_shop_no_cash: "Yo! I'm not running a charity here."
@ -415,19 +412,25 @@
- game_chest_lost_woods: "Pay 100 rupees, open 1 chest. Are you lucky?\nSo, Play game?\n ≥ Play\n Never!\n{CHOICE}" - game_chest_lost_woods: "Pay 100 rupees, open 1 chest. Are you lucky?\nSo, Play game?\n ≥ Play\n Never!\n{CHOICE}"
- kakariko_flophouse_man_no_flippers: "I sure do have a lot of beds.\n\nZora is a cheapskate and will try to sell you his trash for 500 rupees…" - kakariko_flophouse_man_no_flippers: "I sure do have a lot of beds.\n\nZora is a cheapskate and will try to sell you his trash for 500 rupees…"
- kakariko_flophouse_man: "I sure do have a lot of beds.\n\nDid you know if you played the flute in the center of town things could happen?" - kakariko_flophouse_man: "I sure do have a lot of beds.\n\nDid you know if you played the flute in the center of town things could happen?"
- menu_start_2: { NoPause: "{MENU}\n{SPEED0}\n≥£'s House\n_Sanctuary\n{CHOICE3}" } - menu_start_2: "{NOPAUSE}\n{MENU}\n{SPEED0}\n≥£'s House\n_Sanctuary\n{CHOICE3}"
- menu_start_3: { NoPause: "{MENU}\n{SPEED0}\n≥£'s House\n_Sanctuary\n_Mountain Cave\n{CHOICE2}" } - menu_start_3: "{NOPAUSE}\n{MENU}\n{SPEED0}\n≥£'s House\n_Sanctuary\n_Mountain Cave\n{CHOICE2}"
- menu_pause: { NoPause: "{SPEED0}\n≥Continue Game\n_Save and Quit\n{CHOICE3}" } - menu_pause: "{NOPAUSE}\n{SPEED0}\n≥Continue Game\n_Save and Quit\n{CHOICE3}"
- game_digging_choice: "Have 80 Rupees? Want to play digging game?\n ≥Yes\n _No\n{CHOICE}" - game_digging_choice: "Have 80 Rupees? Want to play digging game?\n ≥Yes\n _No\n{CHOICE}"
- game_digging_start: "Okay, use the shovel with Y!" - game_digging_start: "Okay, use the shovel with Y!"
- game_digging_no_cash: "Shovel rental is 80 rupees.\nI have all day" - game_digging_no_cash: "Shovel rental is 80 rupees.\nI have all day"
- game_digging_end_time: "Time's up!\nTime for you to go." - game_digging_end_time: "Time's up!\nTime for you to go."
- game_digging_come_back_later: "Come back later, I have to bury things." - game_digging_come_back_later: "Come back later, I have to bury things."
- game_digging_no_follower: "Something is following you. I don't like." - game_digging_no_follower: "Something is following you. I don't like."
- menu_start_4: { NoPause: "{MENU}\n{SPEED0}\n≥£'s House\n_Mountain Cave\n{CHOICE3}" } - menu_start_4: "{NOPAUSE}\n{MENU}\n{SPEED0}\n≥£'s House\n_Mountain Cave\n{CHOICE3}"
- ganon_fall_in_alt: "You think you\nare ready to\nface me?\n\nI will not die\n\nunless you\ncomplete your\ngoals. Dingus!" - ganon_fall_in_alt: "You think you\nare ready to\nface me?\n\nI will not die\n\nunless you\ncomplete your\ngoals. Dingus!"
- ganon_phase_3_alt: "Got wax in your ears? I cannot die!" - ganon_phase_3_alt: "Got wax in your ears? I cannot die!"
# $190 # $190
- sign_east_death_mountain_bridge: "How did you get up here?" - sign_east_death_mountain_bridge: "How did you get up here?"
- fish_money: "It's a secret to everyone." - fish_money: "It's a secret to everyone."
- sign_ganons_tower: "You need all 7 crystals to enter."
- sign_ganon: "You need all 7 crystals to beat Ganon."
- ganon_phase_3_no_bow: "You have no bow. Dingus!"
- ganon_phase_3_no_silvers_alt: "You can't best me without silver arrows!"
- ganon_phase_3_no_silvers: "You can't best me without silver arrows!"
- ganon_phase_3_silvers: "Oh no! Silver! My one true weakness!"
- end_pad_data: "" - end_pad_data: ""

View File

@ -3,7 +3,7 @@ from typing import Any, List
import copy import copy
from worlds.smz3.TotalSMZ3.Text.Dialog import Dialog from worlds.smz3.TotalSMZ3.Text.Dialog import Dialog
from worlds.smz3.TotalSMZ3.Text.Texts import text_folder from worlds.smz3.TotalSMZ3.Text.Texts import text_folder
from yaml import load, Loader from Utils import unsafe_parse_yaml
class StringTable: class StringTable:
@ -11,7 +11,7 @@ class StringTable:
def ParseEntries(resource: str): def ParseEntries(resource: str):
with open(resource, 'rb') as f: with open(resource, 'rb') as f:
yaml = str(f.read(), "utf-8") yaml = str(f.read(), "utf-8")
content = load(yaml, Loader) content = unsafe_parse_yaml(yaml)
result = [] result = []
for entryValue in content: for entryValue in content:
@ -20,8 +20,6 @@ class StringTable:
result.append((key, value)) result.append((key, value))
elif isinstance(value, str): elif isinstance(value, str):
result.append((key, Dialog.Compiled(value))) result.append((key, Dialog.Compiled(value)))
elif isinstance(value, dict):
result.append((key, Dialog.Compiled(value["NoPause"], False)))
else: raise Exception(f"Did not expect an object of type {type(value)}") else: raise Exception(f"Did not expect an object of type {type(value)}")
return result return result
@ -47,9 +45,11 @@ class StringTable:
def SetGanonThirdPhaseText(self, text: str): def SetGanonThirdPhaseText(self, text: str):
self.SetText("ganon_phase_3", text) self.SetText("ganon_phase_3", text)
self.SetText("ganon_phase_3_no_silvers", text)
self.SetText("ganon_phase_3_no_silvers_alt", text)
def SetTriforceRoomText(self, text: str): def SetTriforceRoomText(self, text: str):
self.SetText("end_triforce", "{NOBORDER}\n" + text) self.SetText("end_triforce", f"{{NOBORDER}}\n{text}")
def SetPedestalText(self, text: str): def SetPedestalText(self, text: str):
self.SetText("mastersword_pedestal_translated", text) self.SetText("mastersword_pedestal_translated", text)
@ -60,6 +60,12 @@ class StringTable:
def SetBombosText(self, text: str): def SetBombosText(self, text: str):
self.SetText("tablet_bombos_book", text) self.SetText("tablet_bombos_book", text)
def SetTowerRequirementText(self, text: str):
self.SetText("sign_ganons_tower", text)
def SetGanonRequirementText(self, text: str):
self.SetText("sign_ganon", text)
def SetText(self, name: str, text: str): def SetText(self, name: str, text: str):
count = 0 count = 0
for key, value in self.entries: for key, value in self.entries:

View File

@ -2,7 +2,7 @@
from worlds.smz3.TotalSMZ3.Region import Region from worlds.smz3.TotalSMZ3.Region import Region
from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower
from worlds.smz3.TotalSMZ3.Item import Item, ItemType from worlds.smz3.TotalSMZ3.Item import Item, ItemType
from yaml import load, Loader from Utils import unsafe_parse_yaml
import random import random
import os import os
@ -13,7 +13,7 @@ class Texts:
def ParseYamlScripts(resource: str): def ParseYamlScripts(resource: str):
with open(resource, 'rb') as f: with open(resource, 'rb') as f:
yaml = str(f.read(), "utf-8") yaml = str(f.read(), "utf-8")
return load(yaml, Loader) return unsafe_parse_yaml(yaml)
@staticmethod @staticmethod
def ParseTextScript(resource: str): def ParseTextScript(resource: str):
@ -75,7 +75,7 @@ class Texts:
} }
if item.IsMap(): name = "Map" if item.IsMap(): name = "Map"
elif item.IsCompass(): name = "Compass" elif item.IsCompass(): name = "Compass"
elif item.IsKeycard(): name = "Keycard" elif item.IsSmMap(): name = "SmMap"
else: name = nameMap[item.Type] else: name = nameMap[item.Type]
items = Texts.scripts["Items"] items = Texts.scripts["Items"]

View File

@ -54,10 +54,26 @@ class World:
Player: str Player: str
Guid: str Guid: str
Id: int Id: int
WorldState = None
@property
def TowerCrystals(self):
return 7 if self.WorldState is None else self.WorldState.TowerCrystals
@property
def GanonCrystals(self):
return 7 if self.WorldState is None else self.WorldState.GanonCrystals
@property
def TourianBossTokens(self):
return 4 if self.WorldState is None else self.WorldState.TourianBossTokens
def Items(self): def Items(self):
return [l.Item for l in self.Locations if l.Item != None] return [l.Item for l in self.Locations if l.Item != None]
ForwardSearch: bool = False
rewardLookup: Dict[int, List[Region.IReward]]
locationLookup: Dict[str, Location.Location] locationLookup: Dict[str, Location.Location]
regionLookup: Dict[str, Region.Region] regionLookup: Dict[str, Region.Region]
@ -95,22 +111,22 @@ class World:
DarkWorldNorthEast(self, self.Config), DarkWorldNorthEast(self, self.Config),
DarkWorldSouth(self, self.Config), DarkWorldSouth(self, self.Config),
DarkWorldMire(self, self.Config), DarkWorldMire(self, self.Config),
Central(self, self.Config),
CrateriaWest(self, self.Config), CrateriaWest(self, self.Config),
Central(self, self.Config),
CrateriaEast(self, self.Config), CrateriaEast(self, self.Config),
Blue(self, self.Config), Blue(self, self.Config),
Green(self, self.Config), Green(self, self.Config),
Kraid(self, self.Config),
Pink(self, self.Config), Pink(self, self.Config),
Red(self, self.Config), Red(self, self.Config),
Kraid(self, self.Config),
WreckedShip(self, self.Config),
Outer(self, self.Config), Outer(self, self.Config),
Inner(self, self.Config), Inner(self, self.Config),
NorfairUpperWest(self, self.Config), NorfairUpperWest(self, self.Config),
NorfairUpperEast(self, self.Config), NorfairUpperEast(self, self.Config),
Crocomire(self, self.Config), Crocomire(self, self.Config),
NorfairLowerWest(self, self.Config), NorfairLowerWest(self, self.Config),
NorfairLowerEast(self, self.Config), NorfairLowerEast(self, self.Config)
WreckedShip(self, self.Config)
] ]
self.Locations = [] self.Locations = []
@ -130,37 +146,32 @@ class World:
raise Exception(f"World.CanEnter: Invalid region name {regionName}", f'{regionName=}'.partition('=')[0]) raise Exception(f"World.CanEnter: Invalid region name {regionName}", f'{regionName=}'.partition('=')[0])
return region.CanEnter(items) return region.CanEnter(items)
def CanAquire(self, items: Item.Progression, reward: Region.RewardType): def CanAcquire(self, items: Item.Progression, reward: Region.RewardType):
return next(iter([region for region in self.Regions if isinstance(region, Region.IReward) and region.Reward == reward])).CanComplete(items) return next(iter([region for region in self.Regions if isinstance(region, Region.IReward) and region.Reward == reward])).CanComplete(items)
def CanAquireAll(self, items: Item.Progression, *rewards: Region.RewardType): def CanAcquireAll(self, items: Item.Progression, rewardsMask: Region.RewardType):
for region in self.Regions: return all(region.CanComplete(items) for region in self.rewardLookup[rewardsMask.value])
if issubclass(type(region), Region.IReward):
if (region.Reward in rewards):
if not region.CanComplete(items):
return False
return True
# return all(region.CanComplete(items) for region in self.Regions if (isinstance(region, Region.IReward) and region.Reward in rewards)) def CanAcquireAtLeast(self, amount, items: Item.Progression, rewardsMask: Region.RewardType):
return len([region for region in self.rewardLookup[rewardsMask.value] if region.CanComplete(items)]) >= amount
def Setup(self, rnd: random): def Setup(self, state):
self.SetMedallions(rnd) self.WorldState = state
self.SetRewards(rnd) self.SetMedallions(state.Medallions)
self.SetRewards(state.Rewards)
self.SetRewardLookup()
def SetMedallions(self, rnd: random): def SetRewards(self, rewards: List):
medallionMap = {0: Item.ItemType.Bombos, 1: Item.ItemType.Ether, 2: Item.ItemType.Quake} regions = [region for region in self.Regions if isinstance(region, Region.IReward) and region.Reward == Region.RewardType.Null]
regionList = [region for region in self.Regions if isinstance(region, Region.IMedallionAccess)] for (region, reward) in zip(regions, rewards):
for region in regionList: region.Reward = reward
region.Medallion = medallionMap[rnd.randint(0, 2)]
def SetRewards(self, rnd: random): def SetMedallions(self, medallions: List):
rewards = [ self.GetRegion("Misery Mire").Medallion = medallions[0]
Region.RewardType.PendantGreen, Region.RewardType.PendantNonGreen, Region.RewardType.PendantNonGreen, Region.RewardType.CrystalRed, Region.RewardType.CrystalRed, self.GetRegion("Turtle Rock").Medallion = medallions[1]
Region.RewardType.CrystalBlue, Region.RewardType.CrystalBlue, Region.RewardType.CrystalBlue, Region.RewardType.CrystalBlue, Region.RewardType.CrystalBlue
]
rnd.shuffle(rewards)
regionList = [region for region in self.Regions if isinstance(region, Region.IReward) and region.Reward == Region.RewardType.Null]
for region in regionList:
region.Reward = rewards[0]
rewards.remove(region.Reward)
def SetRewardLookup(self):
#/* Generate a lookup of all possible regions for any given reward combination for faster lookup later */
self.rewardLookup: Dict[int, Region.IReward] = {}
for i in range(0, 512):
self.rewardLookup[i] = [region for region in self.Regions if isinstance(region, Region.IReward) and (region.Reward.value & i) != 0]

View File

@ -0,0 +1,170 @@
from enum import Enum
from typing import List
from copy import copy
from worlds.smz3.TotalSMZ3.Patch import DropPrize
from worlds.smz3.TotalSMZ3.Region import RewardType
from worlds.smz3.TotalSMZ3.Config import OpenTower, GanonVulnerable, OpenTourian
class Medallion(Enum):
Bombos = 0
Ether = 1
Quake = 2
class DropPrizeRecord:
Packs: List[DropPrize]
TreePulls: List[DropPrize]
CrabContinous: DropPrize
CrabFinal: DropPrize
Stun: DropPrize
Fish: DropPrize
def __init__(self, Packs, TreePulls, CrabContinous, CrabFinal, Stun, Fish):
self.Packs = Packs
self.TreePulls = TreePulls
self.CrabContinous = CrabContinous
self.CrabFinal = CrabFinal
self.Stun = Stun
self.Fish = Fish
class WorldState:
Rewards: List[RewardType]
Medallions: List[Medallion]
TowerCrystals: int
GanonCrystals: int
TourianBossTokens: int
DropPrizes: DropPrizeRecord
def __init__(self, config, rnd):
self.Rewards = self.DistributeRewards(rnd)
self.Medallions = self.GenerateMedallions(rnd)
self.TowerCrystals = rnd.randint(0, 7) if config.OpenTower == OpenTower.Random else config.OpenTower.value
self.GanonCrystals = rnd.randint(0, 7) if config.GanonVulnerable == GanonVulnerable.Random else config.GanonVulnerable.value
self.TourianBossTokens = rnd.randint(0, 4) if config.OpenTourian == OpenTourian.Random else config.OpenTourian.value
self.DropPrizes = self.ShuffleDropPrizes(rnd)
@staticmethod
def Generate(config, rnd):
return WorldState(config, rnd)
BaseRewards = [
RewardType.PendantGreen, RewardType.PendantNonGreen, RewardType.PendantNonGreen, RewardType.CrystalRed, RewardType.CrystalRed,
RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue,
RewardType.AnyBossToken, RewardType.AnyBossToken, RewardType.AnyBossToken, RewardType.AnyBossToken,
]
BossTokens = [
RewardType.BossTokenKraid, RewardType.BossTokenPhantoon, RewardType.BossTokenDraygon, RewardType.BossTokenRidley
]
@staticmethod
def DistributeRewards(rnd):
#// Assign four rewards for SM using a "loot table", randomized result
gen = WorldState.Distribution().Generate(lambda dist: dist.Hit(rnd.randrange(dist.Sum)))
smRewards = [next(gen) for x in range(4)]
#// Exclude the SM rewards to get the Z3 lineup
z3Rewards = WorldState.BaseRewards[:]
for reward in smRewards:
z3Rewards.remove(reward)
rnd.shuffle(z3Rewards)
#// Replace "any token" with random specific tokens
rewards = z3Rewards + smRewards
tokens = WorldState.BossTokens[:]
rnd.shuffle(tokens)
rewards = [tokens.pop() if reward == RewardType.AnyBossToken else reward for reward in rewards]
return rewards
class Distribution:
factor = 3
def __init__(self, distribution = None, boss = None, blue = None, red = None, pend = None, green = None):
self.Boss = 4 * self.factor
self.Blue = 5 * self.factor
self.Red = 2 * self.factor
self.Pend = 2
self.Green = 1
if (distribution is not None):
self = copy(distribution)
if (boss is not None):
self.Boss = boss
if (blue is not None):
self.Blue = blue
if (red is not None):
self.Red = red
if (pend is not None):
self.Pend = pend
if (green is not None):
self.Green = green
@property
def Sum(self):
return self.Boss + self.Blue + self.Red + self.Pend + self.Green
def Hit(self, p):
p -= self.Boss
if (p < 0): return (RewardType.AnyBossToken, WorldState.Distribution(self, boss = self.Boss - WorldState.Distribution.factor))
p -= self.Blue
if (p - self.Blue < 0): return (RewardType.CrystalBlue, WorldState.Distribution(self, blue = self.Blue - WorldState.Distribution.factor))
p -= self.Red
if (p - self.Red < 0): return (RewardType.CrystalRed, WorldState.Distribution(self, red = self.Red - WorldState.Distribution.factor))
p -= self.Pend
if (p - self.Pend < 0): return (RewardType.PendantNonGreen, WorldState.Distribution(self, pend = self.Pend - 1))
return (RewardType.PendantGreen, WorldState.Distribution(self, green = self.Green - 1))
def Generate(self, func):
result = None
while (True):
(result, newSelf) = func(self)
self.Boss = newSelf.Boss
self.Blue = newSelf.Blue
self.Red = newSelf.Red
self.Pend = newSelf.Pend
self.Green = newSelf.Green
yield result
@staticmethod
def GenerateMedallions(rnd):
return [
Medallion(rnd.randrange(3)),
Medallion(rnd.randrange(3)),
]
BaseDropPrizes = [
DropPrize.Heart, DropPrize.Heart, DropPrize.Heart, DropPrize.Heart, DropPrize.Green, DropPrize.Heart, DropPrize.Heart, DropPrize.Green, #// pack 1
DropPrize.Blue, DropPrize.Green, DropPrize.Blue, DropPrize.Red, DropPrize.Blue, DropPrize.Green, DropPrize.Blue, DropPrize.Blue, #// pack 2
DropPrize.FullMagic, DropPrize.Magic, DropPrize.Magic, DropPrize.Blue, DropPrize.FullMagic, DropPrize.Magic, DropPrize.Heart, DropPrize.Magic, #// pack 3
DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb4, DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb8, DropPrize.Bomb1, #// pack 4
DropPrize.Arrow5, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Arrow10, DropPrize.Arrow5, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Arrow10,#// pack 5
DropPrize.Magic, DropPrize.Green, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Magic, DropPrize.Bomb1, DropPrize.Green, DropPrize.Heart, #// pack 6
DropPrize.Heart, DropPrize.Fairy, DropPrize.FullMagic, DropPrize.Red, DropPrize.Bomb8, DropPrize.Heart, DropPrize.Red, DropPrize.Arrow10, #// pack 7
DropPrize.Green, DropPrize.Blue, DropPrize.Red,#// from pull trees
DropPrize.Green, DropPrize.Red,#// from prize crab
DropPrize.Green, #// stunned prize
DropPrize.Red,#// saved fish prize
]
@staticmethod
def ShuffleDropPrizes(rnd):
nrPackDrops = 8 * 7
nrTreePullDrops = 3
prizes = WorldState.BaseDropPrizes[:]
rnd.shuffle(prizes)
(packs, prizes) = (prizes[:nrPackDrops], prizes[nrPackDrops:])
(treePulls, prizes) = (prizes[:nrTreePullDrops], prizes[nrTreePullDrops:])
(crabContinous, crabFinalDrop, prizes) = (prizes[0], prizes[1], prizes[2:])
(stun, prizes) = (prizes[0], prizes[1:])
fish = prizes[0]
return DropPrizeRecord(packs, treePulls, crabContinous, crabFinalDrop, stun, fish)
@staticmethod
def SplitOff(source, count):
return (source[:count], source[count:])

View File

@ -12,9 +12,10 @@ from worlds.smz3.TotalSMZ3.Item import ItemType
import worlds.smz3.TotalSMZ3.Item as TotalSMZ3Item import worlds.smz3.TotalSMZ3.Item as TotalSMZ3Item
from worlds.smz3.TotalSMZ3.World import World as TotalSMZ3World from worlds.smz3.TotalSMZ3.World import World as TotalSMZ3World
from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower
from worlds.smz3.TotalSMZ3.Config import Config, GameMode, GanonInvincible, Goal, KeyShuffle, MorphLocation, SMLogic, SwordLocation, Z3Logic from worlds.smz3.TotalSMZ3.Config import Config, GameMode, Goal, KeyShuffle, MorphLocation, SMLogic, SwordLocation, Z3Logic, OpenTower, GanonVulnerable, OpenTourian
from worlds.smz3.TotalSMZ3.Location import LocationType, locations_start_id, Location as TotalSMZ3Location from worlds.smz3.TotalSMZ3.Location import LocationType, locations_start_id, Location as TotalSMZ3Location
from worlds.smz3.TotalSMZ3.Patch import Patch as TotalSMZ3Patch, getWord, getWordArray from worlds.smz3.TotalSMZ3.Patch import Patch as TotalSMZ3Patch, getWord, getWordArray
from worlds.smz3.TotalSMZ3.WorldState import WorldState
from ..AutoWorld import World, AutoLogicRegister, WebWorld from ..AutoWorld import World, AutoLogicRegister, WebWorld
from .Rom import get_base_rom_bytes, SMZ3DeltaPatch from .Rom import get_base_rom_bytes, SMZ3DeltaPatch
from .ips import IPS_Patch from .ips import IPS_Patch
@ -60,12 +61,12 @@ class SMZ3World(World):
""" """
game: str = "SMZ3" game: str = "SMZ3"
topology_present = False topology_present = False
data_version = 1 data_version = 2
options = smz3_options options = smz3_options
item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id) item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id)
location_names: Set[str] location_names: Set[str]
item_name_to_id = TotalSMZ3Item.lookup_name_to_id item_name_to_id = TotalSMZ3Item.lookup_name_to_id
location_name_to_id: Dict[str, int] = {key : locations_start_id + value.Id for key, value in TotalSMZ3World(Config({}), "", 0, "").locationLookup.items()} location_name_to_id: Dict[str, int] = {key : locations_start_id + value.Id for key, value in TotalSMZ3World(Config(), "", 0, "").locationLookup.items()}
web = SMZ3Web() web = SMZ3Web()
remote_items: bool = False remote_items: bool = False
@ -180,30 +181,32 @@ class SMZ3World(World):
base_combined_rom = get_base_rom_bytes() base_combined_rom = get_base_rom_bytes()
def generate_early(self): def generate_early(self):
config = Config({}) self.config = Config()
config.GameMode = GameMode.Multiworld self.config.GameMode = GameMode.Multiworld
config.Z3Logic = Z3Logic.Normal self.config.Z3Logic = Z3Logic.Normal
config.SMLogic = SMLogic(self.world.sm_logic[self.player].value) self.config.SMLogic = SMLogic(self.world.sm_logic[self.player].value)
config.SwordLocation = SwordLocation(self.world.sword_location[self.player].value) self.config.SwordLocation = SwordLocation(self.world.sword_location[self.player].value)
config.MorphLocation = MorphLocation(self.world.morph_location[self.player].value) self.config.MorphLocation = MorphLocation(self.world.morph_location[self.player].value)
config.Goal = Goal.DefeatBoth self.config.Goal = Goal(self.world.goal[self.player].value)
config.KeyShuffle = KeyShuffle(self.world.key_shuffle[self.player].value) self.config.KeyShuffle = KeyShuffle(self.world.key_shuffle[self.player].value)
config.Keysanity = config.KeyShuffle != KeyShuffle.Null self.config.OpenTower = OpenTower(self.world.open_tower[self.player].value)
config.GanonInvincible = GanonInvincible.BeforeCrystals self.config.GanonVulnerable = GanonVulnerable(self.world.ganon_vulnerable[self.player].value)
self.config.OpenTourian = OpenTourian(self.world.open_tourian[self.player].value)
self.local_random = random.Random(self.world.random.randint(0, 1000)) self.local_random = random.Random(self.world.random.randint(0, 1000))
self.smz3World = TotalSMZ3World(config, self.world.get_player_name(self.player), self.player, self.world.seed_name) self.smz3World = TotalSMZ3World(self.config, self.world.get_player_name(self.player), self.player, self.world.seed_name)
self.smz3DungeonItems = [] self.smz3DungeonItems = []
SMZ3World.location_names = frozenset(self.smz3World.locationLookup.keys()) SMZ3World.location_names = frozenset(self.smz3World.locationLookup.keys())
self.world.state.smz3state[self.player] = TotalSMZ3Item.Progression([]) self.world.state.smz3state[self.player] = TotalSMZ3Item.Progression([])
def generate_basic(self): def generate_basic(self):
self.smz3World.Setup(self.world.random) self.smz3World.Setup(WorldState.Generate(self.config, self.world.random))
self.dungeon = TotalSMZ3Item.Item.CreateDungeonPool(self.smz3World) self.dungeon = TotalSMZ3Item.Item.CreateDungeonPool(self.smz3World)
self.dungeon.reverse() self.dungeon.reverse()
self.progression = TotalSMZ3Item.Item.CreateProgressionPool(self.smz3World) self.progression = TotalSMZ3Item.Item.CreateProgressionPool(self.smz3World)
self.keyCardsItems = TotalSMZ3Item.Item.CreateKeycards(self.smz3World) self.keyCardsItems = TotalSMZ3Item.Item.CreateKeycards(self.smz3World)
self.SmMapsItems = TotalSMZ3Item.Item.CreateSmMaps(self.smz3World)
niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World) niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World)
junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World) junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World)
@ -211,7 +214,7 @@ class SMZ3World(World):
self.junkItemsNames = [item.Type.name for item in junkItems] self.junkItemsNames = [item.Type.name for item in junkItems]
if (self.smz3World.Config.Keysanity): if (self.smz3World.Config.Keysanity):
progressionItems = self.progression + self.dungeon + self.keyCardsItems progressionItems = self.progression + self.dungeon + self.keyCardsItems + self.SmMapsItems
else: else:
progressionItems = self.progression progressionItems = self.progression
for item in self.keyCardsItems: for item in self.keyCardsItems:
@ -352,6 +355,49 @@ class SMZ3World(World):
return patch return patch
def SnesCustomization(self, addr: int):
addr = (0x400000 if addr < 0x800000 else 0)| (addr & 0x3FFFFF)
return addr
def apply_customization(self):
patch = {}
# smSpinjumps
if (self.world.spin_jumps_animation[self.player].value == 1):
patch[self.SnesCustomization(0x9B93FE)] = bytearray([0x01])
# z3HeartBeep
values = [ 0x00, 0x80, 0x40, 0x20, 0x10]
index = self.world.heart_beep_speed[self.player].value
patch[0x400033] = bytearray([values[index if index < len(values) else 2]])
# z3HeartColor
values = [
[0x24, [0x18, 0x00]],
[0x3C, [0x04, 0x17]],
[0x2C, [0xC9, 0x69]],
[0x28, [0xBC, 0x02]]
]
index = self.world.heart_color[self.player].value
(hud, fileSelect) = values[index if index < len(values) else 0]
for i in range(0, 20, 2):
patch[self.SnesCustomization(0xDFA1E + i)] = bytearray([hud])
patch[self.SnesCustomization(0x1BD6AA)] = bytearray(fileSelect)
# z3QuickSwap
patch[0x40004B] = bytearray([0x01 if self.world.quick_swap[self.player].value else 0x00])
# smEnergyBeepOff
if (self.world.energy_beep[self.player].value == 0):
for ([addr, value]) in [
[0x90EA9B, 0x80],
[0x90F337, 0x80],
[0x91E6D5, 0x80]
]:
patch[self.SnesCustomization(addr)] = bytearray([value])
return patch
def generate_output(self, output_directory: str): def generate_output(self, output_directory: str):
try: try:
base_combined_rom = get_base_rom_bytes() base_combined_rom = get_base_rom_bytes()
@ -368,6 +414,7 @@ class SMZ3World(World):
patches = patcher.Create(self.smz3World.Config) patches = patcher.Create(self.smz3World.Config)
patches.update(self.apply_sm_custom_sprite()) patches.update(self.apply_sm_custom_sprite())
patches.update(self.apply_item_names()) patches.update(self.apply_item_names())
patches.update(self.apply_customization())
for addr, bytes in patches.items(): for addr, bytes in patches.items():
offset = 0 offset = 0
for byte in bytes: for byte in bytes:
@ -463,7 +510,7 @@ class SMZ3World(World):
item.item.Progression = False item.item.Progression = False
item.location.event = False item.location.event = False
self.unreachable.append(item.location) self.unreachable.append(item.location)
self.JunkFillGT() self.JunkFillGT(0.5)
def get_pre_fill_items(self): def get_pre_fill_items(self):
if (not self.smz3World.Config.Keysanity): if (not self.smz3World.Config.Keysanity):
@ -477,21 +524,34 @@ class SMZ3World(World):
def write_spoiler(self, spoiler_handle: TextIO): def write_spoiler(self, spoiler_handle: TextIO):
self.world.spoiler.unreachables.update(self.unreachable) self.world.spoiler.unreachables.update(self.unreachable)
def JunkFillGT(self): def JunkFillGT(self, factor):
poolLength = len(self.world.itempool)
junkPoolIdx = [i for i in range(0, poolLength)
if self.world.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) and
self.world.itempool[i].player == self.player]
toRemove = []
for loc in self.locations.values(): for loc in self.locations.values():
# commenting this for now since doing a partial GT pre fill would allow for non SMZ3 progression in GT
# which isnt desirable (SMZ3 logic only filters for SMZ3 items). Having progression in GT can only happen in Single Player.
# if len(toRemove) >= int(len(self.locationNamesGT) * factor * self.smz3World.TowerCrystals / 7):
# break
if loc.name in self.locationNamesGT and loc.item is None: if loc.name in self.locationNamesGT and loc.item is None:
poolLength = len(self.world.itempool) poolLength = len(junkPoolIdx)
# start looking at a random starting index and loop at start if no match found # start looking at a random starting index and loop at start if no match found
start = self.world.random.randint(0, poolLength) start = self.world.random.randint(0, poolLength)
for off in range(0, poolLength): for off in range(0, poolLength):
i = (start + off) % poolLength i = (start + off) % poolLength
if self.world.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) \ candidate = self.world.itempool[junkPoolIdx[i]]
and loc.can_fill(self.world.state, self.world.itempool[i], False): if junkPoolIdx[i] not in toRemove and loc.can_fill(self.world.state, candidate, False):
itemFromPool = self.world.itempool.pop(i) itemFromPool = candidate
toRemove.append(junkPoolIdx[i])
break break
self.world.push_item(loc, itemFromPool, False) self.world.push_item(loc, itemFromPool, False)
loc.event = False loc.event = False
toRemove.sort(reverse = True)
for i in toRemove:
self.world.itempool.pop(i)
def FillItemAtLocation(self, itemPool, itemType, location): def FillItemAtLocation(self, itemPool, itemType, location):
itemToPlace = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World) itemToPlace = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World)
if (itemToPlace == None): if (itemToPlace == None):
@ -524,7 +584,8 @@ class SMZ3World(World):
def InitialFillInOwnWorld(self): def InitialFillInOwnWorld(self):
self.FillItemAtLocation(self.dungeon, TotalSMZ3Item.ItemType.KeySW, self.smz3World.GetLocation("Skull Woods - Pinball Room")) self.FillItemAtLocation(self.dungeon, TotalSMZ3Item.ItemType.KeySW, self.smz3World.GetLocation("Skull Woods - Pinball Room"))
self.FillItemAtLocation(self.dungeon, TotalSMZ3Item.ItemType.KeySP, self.smz3World.GetLocation("Swamp Palace - Entrance")) if (not self.smz3World.Config.Keysanity):
self.FillItemAtLocation(self.dungeon, TotalSMZ3Item.ItemType.KeySP, self.smz3World.GetLocation("Swamp Palace - Entrance"))
# /* Check Swords option and place as needed */ # /* Check Swords option and place as needed */
if self.smz3World.Config.SwordLocation == SwordLocation.Uncle: if self.smz3World.Config.SwordLocation == SwordLocation.Uncle:

Binary file not shown.