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
from Options import Choice, Option
from Options import Choice, Option, Toggle, DefaultOnToggle, Range
class SMLogic(Choice):
"""This option selects what kind of logic to use for item placement inside
@ -45,6 +45,22 @@ class MorphLocation(Choice):
option_Original = 2
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):
"""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
@ -55,9 +71,75 @@ class KeyShuffle(Choice):
option_Keysanity = 1
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)] = {
"sm_logic": SMLogic,
"sword_location": SwordLocation,
"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):
DefeatBoth = 0
FastGanonDefeatMotherBrain = 1
AllDungeonsDefeatMotherBrain = 2
class KeyShuffle(Enum):
Null = 0
Keysanity = 1
class GanonInvincible(Enum):
Never = 0
BeforeCrystals = 1
BeforeAllDungeons = 2
Always = 3
class OpenTower(Enum):
Random = -1
NoCrystals = 0
OneCrystal = 1
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:
GameMode: GameMode = GameMode.Multiworld
@ -45,63 +71,20 @@ class Config:
MorphLocation: MorphLocation = MorphLocation.Randomized
Goal: Goal = Goal.DefeatBoth
KeyShuffle: KeyShuffle = KeyShuffle.Null
Keysanity: bool = KeyShuffle != KeyShuffle.Null
Race: bool = False
GanonInvincible: GanonInvincible = GanonInvincible.BeforeCrystals
def __init__(self, options: Dict[str, str]):
self.GameMode = self.ParseOption(options, GameMode.Multiworld)
self.Z3Logic = self.ParseOption(options, Z3Logic.Normal)
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)
OpenTower: OpenTower = OpenTower.SevenCrystals
GanonVulnerable: GanonVulnerable = GanonVulnerable.SevenCrystals
OpenTourian: OpenTourian = OpenTourian.FourBosses
def ParseOption(self, options:Dict[str, str], defaultValue:Enum):
enumKey = defaultValue.__class__.__name__.lower()
if (enumKey in options):
return defaultValue.__class__[options[enumKey]]
return defaultValue
@property
def SingleWorld(self) -> bool:
return self.GameMode == GameMode.Normal
def ParseOptionWith(self, options:Dict[str, str], option:str, defaultValue:bool):
if (option.lower() in options):
return options[option.lower()]
return defaultValue
@property
def Multiworld(self) -> bool:
return self.GameMode == GameMode.Multiworld
""" 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;
}
} """
@property
def Keysanity(self) -> bool:
return self.KeyShuffle != KeyShuffle.Null

View File

@ -130,6 +130,11 @@ class ItemType(Enum):
CardLowerNorfairL1 = 0xDE
CardLowerNorfairBoss = 0xDF
SmMapBrinstar = 0xCA
SmMapWreckedShip = 0xCB
SmMapMaridia = 0xCC
SmMapLowerNorfair = 0xCD
Missile = 0xC2
Super = 0xC3
PowerBomb = 0xC4
@ -174,6 +179,7 @@ class Item:
map = re.compile("^Map")
compass = re.compile("^Compass")
keycard = re.compile("^Card")
smMap = re.compile("^SmMap")
def IsDungeonItem(self): return self.dungeon.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 IsCompass(self): return self.compass.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):
return self.Type == type and self.World == world
@ -313,7 +320,7 @@ class Item:
Item.AddRange(itemPool, 4, Item(ItemType.BombUpgrade5))
Item.AddRange(itemPool, 2, Item(ItemType.OneRupee))
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, 5, Item(ItemType.ThreeHundredRupees))
@ -421,6 +428,21 @@ class Item:
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
def Get(items, itemType:ItemType):
item = next((i for i in items if i.Type == itemType), None)
@ -725,7 +747,7 @@ class Progression:
def CanAccessMiseryMirePortal(self, config: Config):
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:
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) \
@ -769,11 +791,11 @@ class Progression:
if (world.Config.SMLogic == SMLogic.Normal):
return self.MoonPearl and self.Flippers 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:
return self.MoonPearl and self.Flippers 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
items_start_id = 84000

View File

@ -6,7 +6,7 @@ import typing
from BaseClasses import Location
from worlds.smz3.TotalSMZ3.Item import Item, ItemType
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.DesertPalace import DesertPalace
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.TurtleRock import TurtleRock
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.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.Dialog import Dialog
@ -30,6 +34,11 @@ class KeycardPlaque:
Level2 = 0xe1
Boss = 0xe2
Null = 0x00
Zero = 0xe3
One = 0xe4
Two = 0xe5
Three = 0xe6
Four = 0xe7
class KeycardDoors:
Left = 0xd414
@ -73,8 +82,8 @@ class DropPrize(Enum):
Fairy = 0xE3
class Patch:
Major = 0
Minor = 1
Major = 11
Minor = 3
allWorlds: List[World]
myWorld: World
seedGuid: str
@ -105,13 +114,16 @@ class Patch:
self.WriteDiggingGameRng()
self.WritePrizeShuffle()
self.WritePrizeShuffle(self.myWorld.WorldState.DropPrizes)
self.WriteRemoveEquipmentFromUncle( self.myWorld.GetLocation("Link's Uncle").APLocation.item.item if
self.myWorld.GetLocation("Link's Uncle").APLocation.item.game == "SMZ3" else
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.WriteSaveAndQuitFromBossRoom()
@ -135,26 +147,27 @@ class Patch:
return {patch[0]:patch[1] for patch in self.patches}
def WriteMedallions(self):
from worlds.smz3.TotalSMZ3.WorldState import Medallion
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))
turtleRockAddresses = [0x308023, 0xD020, 0xD0FF, 0xD1DE ]
miseryMireAddresses = [ 0x308022, 0xCFF2, 0xD0D1, 0xD1B0 ]
if turtleRock.Medallion == ItemType.Bombos:
if turtleRock.Medallion == Medallion.Bombos:
turtleRockValues = [0x00, 0x51, 0x10, 0x00]
elif turtleRock.Medallion == ItemType.Ether:
elif turtleRock.Medallion == Medallion.Ether:
turtleRockValues = [0x01, 0x51, 0x18, 0x00]
elif turtleRock.Medallion == ItemType.Quake:
elif turtleRock.Medallion == Medallion.Quake:
turtleRockValues = [0x02, 0x14, 0xEF, 0xC4]
else:
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]
elif miseryMire.Medallion == ItemType.Ether:
elif miseryMire.Medallion == Medallion.Ether:
miseryMireValues = [0x01, 0x13, 0x9F, 0xF1]
elif miseryMire.Medallion == ItemType.Quake:
elif miseryMire.Medallion == Medallion.Quake:
miseryMireValues = [0x02, 0x51, 0x08, 0x00]
else:
raise exception(f"Tried using {miseryMire.Medallion} in place of Misery Mire medallion")
@ -174,12 +187,19 @@ class Patch:
self.rnd.shuffle(pendantsBlueRed)
pendantRewards = pendantsGreen + pendantsBlueRed
bossTokens = [ 1, 2, 3, 4 ]
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]
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(pendantRegions, pendantRewards, self.PendantValues)
self.patches += self.RewardPatches(bossRegions, bossTokens, self.BossTokenValues)
def RewardPatches(self, regions: List[IReward], rewards: List[int], rewardValues: Callable):
addresses = [self.RewardAddresses(region) for region in regions]
@ -189,17 +209,22 @@ class Patch:
def RewardAddresses(self, region: IReward):
regionType = {
EasternPalace : [ 0x2A09D, 0xABEF8, 0xABEF9, 0x308052, 0x30807C, 0x1C6FE ],
DesertPalace : [ 0x2A09E, 0xABF1C, 0xABF1D, 0x308053, 0x308078, 0x1C6FF ],
TowerOfHera : [ 0x2A0A5, 0xABF0A, 0xABF0B, 0x30805A, 0x30807A, 0x1C706 ],
PalaceOfDarkness : [ 0x2A0A1, 0xABF00, 0xABF01, 0x308056, 0x30807D, 0x1C702 ],
SwampPalace : [ 0x2A0A0, 0xABF6C, 0xABF6D, 0x308055, 0x308071, 0x1C701 ],
SkullWoods : [ 0x2A0A3, 0xABF12, 0xABF13, 0x308058, 0x30807B, 0x1C704 ],
ThievesTown : [ 0x2A0A6, 0xABF36, 0xABF37, 0x30805B, 0x308077, 0x1C707 ],
IcePalace : [ 0x2A0A4, 0xABF5A, 0xABF5B, 0x308059, 0x308073, 0x1C705 ],
MiseryMire : [ 0x2A0A2, 0xABF48, 0xABF49, 0x308057, 0x308075, 0x1C703 ],
TurtleRock : [ 0x2A0A7, 0xABF24, 0xABF25, 0x30805C, 0x308079, 0x1C708 ]
EasternPalace : [ 0x2A09D, 0xABEF8, 0xABEF9, 0x308052, 0x30807C, 0x1C6FE, 0x30D100],
DesertPalace : [ 0x2A09E, 0xABF1C, 0xABF1D, 0x308053, 0x308078, 0x1C6FF, 0x30D101 ],
TowerOfHera : [ 0x2A0A5, 0xABF0A, 0xABF0B, 0x30805A, 0x30807A, 0x1C706, 0x30D102 ],
PalaceOfDarkness : [ 0x2A0A1, 0xABF00, 0xABF01, 0x308056, 0x30807D, 0x1C702, 0x30D103 ],
SwampPalace : [ 0x2A0A0, 0xABF6C, 0xABF6D, 0x308055, 0x308071, 0x1C701, 0x30D104 ],
SkullWoods : [ 0x2A0A3, 0xABF12, 0xABF13, 0x308058, 0x30807B, 0x1C704, 0x30D105 ],
ThievesTown : [ 0x2A0A6, 0xABF36, 0xABF37, 0x30805B, 0x308077, 0x1C707, 0x30D106 ],
IcePalace : [ 0x2A0A4, 0xABF5A, 0xABF5B, 0x308059, 0x308073, 0x1C705, 0x30D107 ],
MiseryMire : [ 0x2A0A2, 0xABF48, 0xABF49, 0x308057, 0x308075, 0x1C703, 0x30D108 ],
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)
if result is None:
raise exception(f"Region {region} should not be a dungeon reward region")
@ -208,13 +233,13 @@ class Patch:
def CrystalValues(self, crystal: int):
crystalMap = {
1 : [ 0x02, 0x34, 0x64, 0x40, 0x7F, 0x06 ],
2 : [ 0x10, 0x34, 0x64, 0x40, 0x79, 0x06 ],
3 : [ 0x40, 0x34, 0x64, 0x40, 0x6C, 0x06 ],
4 : [ 0x20, 0x34, 0x64, 0x40, 0x6D, 0x06 ],
5 : [ 0x04, 0x32, 0x64, 0x40, 0x6E, 0x06 ],
6 : [ 0x01, 0x32, 0x64, 0x40, 0x6F, 0x06 ],
7 : [ 0x08, 0x34, 0x64, 0x40, 0x7C, 0x06 ],
1 : [ 0x02, 0x34, 0x64, 0x40, 0x7F, 0x06, 0x10 ],
2 : [ 0x10, 0x34, 0x64, 0x40, 0x79, 0x06, 0x10 ],
3 : [ 0x40, 0x34, 0x64, 0x40, 0x6C, 0x06, 0x10 ],
4 : [ 0x20, 0x34, 0x64, 0x40, 0x6D, 0x06, 0x10 ],
5 : [ 0x04, 0x32, 0x64, 0x40, 0x6E, 0x06, 0x11 ],
6 : [ 0x01, 0x32, 0x64, 0x40, 0x6F, 0x06, 0x11 ],
7 : [ 0x08, 0x34, 0x64, 0x40, 0x7C, 0x06, 0x10 ],
}
result = crystalMap.get(crystal, None)
if result is None:
@ -224,9 +249,9 @@ class Patch:
def PendantValues(self, pendant: int):
pendantMap = {
1 : [ 0x04, 0x38, 0x62, 0x00, 0x69, 0x01 ],
2 : [ 0x01, 0x32, 0x60, 0x00, 0x69, 0x03 ],
3 : [ 0x02, 0x34, 0x60, 0x00, 0x69, 0x02 ],
1 : [ 0x04, 0x38, 0x62, 0x00, 0x69, 0x01, 0x12 ],
2 : [ 0x01, 0x32, 0x60, 0x00, 0x69, 0x03, 0x14 ],
3 : [ 0x02, 0x34, 0x60, 0x00, 0x69, 0x02, 0x13 ]
}
result = pendantMap.get(pendant, None)
if result is None:
@ -234,6 +259,19 @@ class Patch:
else:
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 GetSMItemPLM(location:Location):
itemMap = {
@ -259,7 +297,7 @@ class Patch:
ItemType.SpaceJump : 0xEF1B,
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)
if (plmId == 0xEFE0):
plmId += 4 if location.Type == LocationType.Chozo else 8 if location.Type == LocationType.Hidden else 0
@ -268,7 +306,7 @@ class Patch:
return plmId
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(self.ItemTablePatch(location, self.GetZ3ItemId(location)))
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]))
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))
dialog = Dialog.Simple(text)
if (location.Type == LocationType.Pedestal):
self.stringTable.SetPedestalText(text)
self.patches.append((Snes(0x308300), dialog))
elif (location.Type == LocationType.Ether):
self.stringTable.SetEtherText(text)
self.patches.append((Snes(0x308F00), dialog))
elif (location.Type == LocationType.Bombos):
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(self.ItemTablePatch(location, self.GetZ3ItemId(location)))
else:
@ -305,11 +339,11 @@ class Patch:
item = location.APLocation.item.item
itemDungeon = None
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():
itemDungeon = ItemType.BigKey
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():
itemDungeon = ItemType.Compass
@ -327,15 +361,11 @@ class Patch:
def WriteDungeonMusic(self, keysanity: bool):
if (not keysanity):
regions = [region for region in self.myWorld.Regions if isinstance(region, IReward)]
music = []
regions = [region for region in self.myWorld.Regions if isinstance(region, Z3Region) and isinstance(region, IReward) and
region.Reward != None and region.Reward != RewardType.Agahnim]
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]]
regions = pendantRegions + crystalRegions
music = [
0x11, 0x11, 0x11, 0x16, 0x16,
0x16, 0x16, 0x16, 0x16, 0x16,
]
music = [0x11 if (region.Reward == RewardType.PendantGreen or region.Reward == RewardType.PendantNonGreen) else 0x16 for region in regions]
self.patches += self.MusicPatches(regions, music)
#IEnumerable<byte> RandomDungeonMusic() {
@ -366,51 +396,13 @@ class Patch:
else:
return result
def WritePrizeShuffle(self):
prizePackItems = 56
treePullItems = 3
bytes = []
drop = 0
final = 0
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 ]))
def WritePrizeShuffle(self, dropPrizes):
self.patches.append((Snes(0x6FA78), [e.value for e in dropPrizes.Packs]))
self.patches.append((Snes(0x1DFBD4), [e.value for e in dropPrizes.TreePulls]))
self.patches.append((Snes(0x6A9C8), [dropPrizes.CrabContinous.value]))
self.patches.append((Snes(0x6A9C4), [dropPrizes.CrabFinal.value]))
self.patches.append((Snes(0x6F993), [dropPrizes.Stun.value]))
self.patches.append((Snes(0x1D82CC), [dropPrizes.Fish.value]))
self.patches += self.EnemyPrizePackDistribution()
@ -524,46 +516,29 @@ class Patch:
redCrystalDungeons = [region for region in regions if region.Reward == RewardType.CrystalRed]
sahasrahla = Texts.SahasrahlaReveal(greenPendantDungeon)
self.patches.append((Snes(0x308A00), Dialog.Simple(sahasrahla)))
self.stringTable.SetSahasrahlaRevealText(sahasrahla)
bombShop = Texts.BombShopReveal(redCrystalDungeons)
self.patches.append((Snes(0x308E00), Dialog.Simple(bombShop)))
self.stringTable.SetBombShopRevealText(bombShop)
blind = Texts.Blind(self.rnd)
self.patches.append((Snes(0x308800), Dialog.Simple(blind)))
self.stringTable.SetBlindText(blind)
tavernMan = Texts.TavernMan(self.rnd)
self.patches.append((Snes(0x308C00), Dialog.Simple(tavernMan)))
self.stringTable.SetTavernManText(tavernMan)
ganon = Texts.GanonFirstPhase(self.rnd)
self.patches.append((Snes(0x308600), Dialog.Simple(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)]
if len(silversLocation) == 0:
silvers = Texts.GanonThirdPhaseMulti(None, self.myWorld, self.silversWorldID, self.playerIDToNames[self.silversWorldID])
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)
self.patches.append((Snes(0x308700), Dialog.Simple(silvers)))
self.stringTable.SetGanonThirdPhaseText(silvers)
triforceRoom = Texts.TriforceRoom(self.rnd)
self.patches.append((Snes(0x308400), Dialog.Simple(triforceRoom)))
self.stringTable.SetTriforceRoomText(triforceRoom)
def WriteStringTable(self):
@ -579,26 +554,32 @@ class Patch:
return bytearray(name, 'utf8')
def WriteSeedData(self):
configField = \
configField1 = \
((1 if self.myWorld.Config.Race else 0) << 15) | \
((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.SMLogic.value << 8) | \
(Patch.Major << 4) | \
(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(0x80FF52), getWordArray(configField)))
self.patches.append((Snes(0x80FF52), getWordArray(configField1)))
self.patches.append((Snes(0x80FF54), getDoubleWordArray(self.seed)))
self.patches.append((Snes(0x80FF58), getWordArray(configField2)))
#/* 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(0x80FF80), bytearray(self.myWorld.Guid, 'utf8')))
def WriteCommonFlags(self):
#/* 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)))
if (self.myWorld.Config.Keysanity):
self.patches.append((Snes(0xF47006), getWordArray(0x0001)))
@ -619,14 +600,13 @@ class Patch:
if (self.myWorld.Config.Keysanity):
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(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):
if (not self.myWorld.Config.Keysanity):
return
plaquePLm = 0xd410
plaquePlm = 0xd410
plmTablePos = 0xf800
if ( self.myWorld.Config.Keysanity):
doorList = [
#// RoomId Door Facing yyxx Keycard Event Type Plaque type yyxx, Address (if 0 a dynamic PLM is created)
#// Crateria
@ -682,8 +662,10 @@ class Patch:
]
doorId = 0x0000
plmTablePos = 0xf800
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
@ -706,11 +688,17 @@ class Patch:
#// Plaque data
if (door[4] != KeycardPlaque.Null):
plaqueData = getWordArray(door[0]) + getWordArray(plaquePLm) + getWordArray(door[5]) + getWordArray(door[4])
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 ]))
def WriteDiggingGameRng(self):
@ -745,20 +733,32 @@ class Patch:
(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 */
invincibleMap = {
GanonInvincible.Never : 0x00,
GanonInvincible.Always : 0x01,
GanonInvincible.BeforeAllDungeons : 0x02,
GanonInvincible.BeforeCrystals : 0x03,
valueMap = {
Goal.DefeatBoth : 0x03,
Goal.FastGanonDefeatMotherBrain : 0x04,
Goal.AllDungeonsDefeatMotherBrain : 0x02
}
value = invincibleMap.get(invincible, None)
value = valueMap.get(goal, None)
if (value is None):
raise exception(f"Unknown Ganon invincible value {invincible}")
raise exception(f"Unknown Ganon invincible value {goal}")
else:
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):
#/* Repoint RNG Block */

View File

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

View File

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

View File

@ -40,5 +40,5 @@ class Pink(SMRegion):
else:
return items.CanOpenRedDoors() and (items.CanDestroyBombWalls() or items.SpeedBooster) 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())

View File

@ -17,9 +17,9 @@ class East(SMRegion):
self.world.CanEnter("Wrecked Ship", items)) if self.Logic == SMLogic.Normal else \
lambda items: items.Morph),
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)",
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)",
lambda items: True)
]

View File

@ -9,20 +9,17 @@ class Inner(SMRegion, IReward):
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss
self.Reward = RewardType.Null
self.Locations = [
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
(items.Gravity or items.Ice or items.HiJump and items.CanSpringBallJump())),
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and self.CanReachAqueduct(items) and
(items.Gravity or items.Ice or items.HiJump and items.SpringBall)),
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
(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)",
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
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",
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
@ -31,17 +28,17 @@ class Inner(SMRegion, IReward):
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.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",
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.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)",
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),
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 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)",
lambda items: self.CanReachAqueduct(items) and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Gravity),

View File

@ -9,7 +9,7 @@ class East(SMRegion, IReward):
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss
self.Reward = RewardType.Null
self.Locations = [
Location(self, 74, 0x8F8FCA, LocationType.Visible, "Missile (lower Norfair above fire flea room)",
lambda items: self.CanExit(items)),
@ -30,11 +30,11 @@ class East(SMRegion, IReward):
def CanExit(self, items:Progression):
if self.Logic == SMLogic.Normal:
# /*Bubble Mountain*/
return items.CardNorfairL2 or (
return items.Morph and (items.CardNorfairL2 or (
# /* Volcano Room and Blue Gate */
items.Gravity) and items.Wave and (
# /*Spikey Acid Snakes and Croc Escape*/
items.Grapple or items.SpaceJump)
items.Grapple or items.SpaceJump))
else:
# /*Vanilla LN Escape*/
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)),
Location(self, 71, 0x8F8E74, LocationType.Hidden, "Super Missile (Gold Torizo)",
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)),
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.Varia or items.CanAccessNorfairLowerPortal())),
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.CanAccessNorfairLowerPortal() or items.Varia)),
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*/
(items.CardLowerNorfairL1 or
# /*Vanilla or Reverse Lava Dive*/
@ -35,15 +35,18 @@ class West(SMRegion):
# /*Spikey Acid Snakes and Croc Escape*/
(items.Grapple or items.SpaceJump) or
# /*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:
items.Morph and items.Varia and items.Super and ((items.CanFly() or items.CanSpringBallJump() or
items.SpeedBooster and (items.HiJump and items.CanUsePowerBombs() or items.Charge and items.Ice)) and
# /*Exit to Upper Norfair*/
(items.CardNorfairL2 or (items.SpeedBooster or items.CanFly() or items.Grapple or items.HiJump and
(items.CanSpringBallJump() or items.Ice))) or
# /*Return to Portal*/
items.CanUsePowerBombs()))
items.Varia and items.Morph and items.Super and
#/* Climb worst room (from LN East CanEnter) */
(items.CanFly() or items.HiJump or items.CanSpringBallJump() or items.Ice and items.Charge) and
(items.CanPassBombPassages() or items.ScrewAttack and items.SpaceJump) and (
#/* Exit to Upper Norfair */
items.CardNorfairL2 or items.SpeedBooster or items.CanFly() or items.Grapple or
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

View File

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

View File

@ -9,12 +9,13 @@ class WreckedShip(SMRegion, IReward):
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss
self.Weight = 4
self.Reward = RewardType.Null
self.Locations = [
Location(self, 128, 0x8FC265, LocationType.Visible, "Missile (Wrecked Ship middle)",
lambda items: items.CanPassBombPassages()),
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 \
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and items.CanUsePowerBombs() and items.SpeedBooster and
(items.Varia or items.HasEnergyReserves(2))),
@ -27,7 +28,7 @@ class WreckedShip(SMRegion, IReward):
Location(self, 132, 0x8FC337, LocationType.Visible, "Energy Tank, Wrecked Ship",
lambda items: self.CanUnlockShip(items) and
(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)),
Location(self, 133, 0x8FC357, LocationType.Visible, "Super Missile (Wrecked Ship left)",
lambda items: self.CanUnlockShip(items)),

View File

@ -14,15 +14,15 @@ class NorthEast(Z3Region):
lambda items: items.MoonPearl and items.CanLiftLight()),
Location(self, 256+79, 0x308147, LocationType.Regular, "Pyramid"),
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
(items.Hammer or items.Mirror and self.world.CanAquire(items, RewardType.Agahnim))),
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.CanAcquire(items, RewardType.Agahnim))),
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
(items.Hammer or items.Mirror and self.world.CanAquire(items, RewardType.Agahnim)))
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.CanAcquire(items, RewardType.Agahnim)))
]
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.CanLiftHeavy() and items.Flippers or
items.CanAccessDarkWorldPortal(self.Config) and items.Flippers)

View File

@ -25,7 +25,7 @@ class NorthWest(Z3Region):
def CanEnter(self, items: Progression):
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
) and items.Hookshot and (items.Flippers or items.CanLiftLight() or items.Hammer) or
items.Hammer and items.CanLiftLight() or

View File

@ -21,7 +21,7 @@ class South(Z3Region):
def CanEnter(self, items: Progression):
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
) and (items.Hammer or items.Hookshot and (items.Flippers or 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 Right")
]) 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, [
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 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, [
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 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, [
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 - 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, [
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 - Bottom Left")
])),
Location(self, 256+200, 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+202, 0x1EAE2, LocationType.Regular, "Ganon's Tower - Tile Room",
Location(self, 256+234, 0x1EAD9, LocationType.Regular, "Ganon's Tower - Hope Room - Left"),
Location(self, 256+235, 0x1EADC, LocationType.Regular, "Ganon's Tower - Hope Room - Right"),
Location(self, 256+236, 0x1EAE2, LocationType.Regular, "Ganon's Tower - Tile Room",
lambda items: items.Somaria),
Location(self, 256+203, 0x1EAE5, LocationType.Regular, "Ganon's Tower - Compass Room - Top Left",
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)
def BigKeyRoom(self, items: Progression):
return items.KeyGT >= 3 and self.CanBeatArmos(items) \
and (items.Hammer and items.Hookshot or items.Firerod and items.Somaria)
return items.KeyGT >= 3 and \
(items.Hammer and items.Hookshot or items.Firerod and items.Somaria) \
and self.CanBeatArmos(items)
def TowerAscend(self, items: Progression):
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):
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):
if (self.Config.GameMode == GameMode.Multiworld):
if (self.Config.Multiworld):
if (item.World != self.world or item.Progression):
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 super().CanFill(item)

View File

@ -10,6 +10,7 @@ class IcePalace(Z3Region, IReward):
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Weight = 4
self.RegionItems = [ ItemType.KeyIP, ItemType.BigKeyIP, ItemType.MapIP, ItemType.CompassIP]
self.Reward = RewardType.Null
self.Locations = [
@ -43,7 +44,7 @@ class IcePalace(Z3Region, IReward):
]
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):
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+43, 0x1EA88, LocationType.Regular, "Sahasrahla's Hut - Right").Weighted(sphereOne),
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
self.Locations = [
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+16, 0x308000, LocationType.Regular, "Lost Woods Hideout").Weighted(sphereOne),
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",
lambda items: items.Boots),
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):
super().__init__(world, config)
self.Weight = 2
self.RegionItems = [ ItemType.KeyMM, ItemType.BigKeyMM, ItemType.MapMM, ItemType.CompassMM]
self.Reward = RewardType.Null
self.Medallion = ItemType.Nothing
self.Medallion = None
self.Locations = [
Location(self, 256+169, 0x1EA5E, LocationType.Regular, "Misery Mire - Main Lobby",
lambda items: items.BigKeyMM or items.KeyMM >= 1),
@ -34,8 +35,9 @@ class MiseryMire(Z3Region, IReward, IMedallionAccess):
# // Need "CanKillManyEnemies" if implementing swordless
def CanEnter(self, items: Progression):
return (items.Bombos if self.Medallion == ItemType.Bombos else (
items.Ether if self.Medallion == ItemType.Ether else items.Quake)) and items.Sword and \
from worlds.smz3.TotalSMZ3.WorldState import Medallion
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 \
self.world.CanEnter("Dark World Mire", items)

View File

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

View File

@ -10,9 +10,10 @@ class TurtleRock(Z3Region, IReward, IMedallionAccess):
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Weight = 6
self.RegionItems = [ ItemType.KeyTR, ItemType.BigKeyTR, ItemType.MapTR, ItemType.CompassTR]
self.Reward = RewardType.Null
self.Medallion = ItemType.Nothing
self.Medallion = None
self.Locations = [
Location(self, 256+177, 0x1EA22, LocationType.Regular, "Turtle Rock - Compass Chest"),
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
def CanEnter(self, items: Progression):
return (items.Bombos if self.Medallion == ItemType.Bombos else (
items.Ether if self.Medallion == ItemType.Ether else items.Quake)) and items.Sword and \
from worlds.smz3.TotalSMZ3.WorldState import Medallion
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 \
self.world.CanEnter("Light World Death Mountain East", items)

View File

@ -4,9 +4,7 @@ class Dialog:
command = re.compile(r"^\{[^}]*\}")
invalid = re.compile(r"(?<!^)\{[^}]*\}(?!$)", re.MULTILINE)
digit = re.compile(r"\d")
uppercaseLetter = re.compile(r"[A-Z]")
lowercaseLetter = re.compile(r"[a-z]")
character = re.compile(r"(?P<digit>[0-9])|(?P<upper>[A-Z])|(?P<lower>[a-z])")
@staticmethod
def Simple(text: str):
@ -29,19 +27,16 @@ class Dialog:
lineIndex += 1
if (lineIndex % 3 == 0 and lineIndex < len(lines)):
bytes.append(0x7E)
if (lineIndex >= 3 and lineIndex < len(lines)):
bytes.append(0x73)
if (lineIndex < len(lines)):
if (lineIndex % 3 == 0):
bytes.append(0x7E) # pause for input
if (lineIndex >= 3):
bytes.append(0x73) # scroll
bytes.append(0x7F)
if (len(bytes) > maxBytes):
return bytes[:maxBytes - 1].append(0x7F)
return bytes
@staticmethod
def Compiled(text: str, pause = True):
def Compiled(text: str):
maxBytes = 2046
wrap = 19
@ -49,6 +44,7 @@ class Dialog:
raise Exception("Dialog commands must be placed on separate lines", text)
padOut = False
pause = True
bytes = [ 0xFB ]
lines = Dialog.Wordwrap(text, wrap)
@ -61,7 +57,40 @@ class Dialog:
return [ 0xFB, 0xFE, 0x6E, 0x00, 0xFE, 0x6B, 0x04 ]
if (match.string == "{INTRO}"):
padOut = True
if (match.string == "{NOPAUSE}"):
pause = False
continue
result = Dialog.CommandBytesFor(match.string)
if (result is None):
raise Exception(f"Dialog text contained unknown command {match.string}", text)
else:
bytes += result
if (len(bytes) > maxBytes):
raise Exception("Command overflowed maximum byte length", text)
continue
if (lineIndex > 0):
bytes.append(0xF8 if lineIndex == 1 else #// row 2
0xF9 if lineIndex == 2 else #// row 3
0xF6) #// scroll
#// 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
for letter in letters:
bytes += Dialog.LetterToBytes(letter)
lineIndex += 1
if (pause and lineIndex % 3 == 0 and lineIndex < lineCount):
bytes.append(0xFA) #// pause for input
return bytes[:maxBytes]
@staticmethod
def CommandBytesFor(text: str):
bytesMap = {
"{SPEED0}" : [ 0xFC, 0x00 ],
"{SPEED2}" : [ 0xFC, 0x02 ],
@ -87,35 +116,7 @@ class Dialog:
"{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):
raise Exception(f"Dialog text contained unknown command {match.string}", text)
else:
bytes += result
if (len(bytes) > maxBytes):
raise Exception("Command overflowed maximum byte length", text)
continue
if (lineIndex == 1):
bytes.append(0xF8); #// row 2
elif (lineIndex >= 3 and lineIndex < lineCount):
bytes.append(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.
letters = line + (" " * wrap) if padOut and lineIndex < 3 else line
for letter in letters:
bytes += Dialog.LetterToBytes(letter)
lineIndex += 1
if (pause and lineIndex % 3 == 0 and lineIndex < lineCount):
bytes.append(0xFA) #// wait for input
return bytes[:maxBytes]
return bytesMap.get(text, None)
@staticmethod
def Wordwrap(text: str, width: int):
@ -146,9 +147,13 @@ class Dialog:
@staticmethod
def LetterToBytes(c: str):
if Dialog.digit.match(c): return [(ord(c) - ord('0') + 0xA0) ]
elif Dialog.uppercaseLetter.match(c): return [ (ord(c) - ord('A') + 0xAA) ]
elif Dialog.lowercaseLetter.match(c): return [ (ord(c) - ord('a') + 0x30) ]
match = Dialog.character.match(c)
if match is None:
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:
value = Dialog.letters.get(c, None)
return value if value else [ 0xFF ]

View File

@ -377,9 +377,76 @@ Items:
THE GREEN
BOOMERANG IS
THE FASTEST!
Keycard: |-
A key from
the future?
CardCrateriaL1: |-
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: |-
A small victory!

View File

@ -4,8 +4,8 @@
# The order of the dialog entries is significant
- 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]
- game_over_menu: { NoPause: "{SPEED0}\nSave and Continue\nSave and Quit\nContinue" }
- var_test: { NoPause: "0= ᚋ, 1= ᚌ\n2= ᚍ, 3= ᚎ" }
- game_over_menu: "{NOPAUSE}\n{SPEED0}\nSave and Continue\nSave and Quit\nContinue"
- var_test: "{NOPAUSE}\n0= ᚋ, 1= ᚌ\n2= ᚍ, 3= ᚎ"
- 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_2_3: [0xFB, 0xFC, 0x00, 0xF7, 0xFF, 0xF8, 0xE4, 0xF9, 0xFF, 0xFE, 0x71]
@ -290,10 +290,10 @@
# $110
- magic_bat_wake: "You bum! I was sleeping! Where's my magic bolts?"
- 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_throne_room: { NoPause: "{IBOX}\nLook at this Stalfos on the throne." }
- intro_zelda_cell: { NoPause: "{IBOX}\nIt is your time to shine!" }
- intro_agahnim: { NoPause: "{IBOX}\nAlso, you need to defeat this guy!" }
- 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}\n{IBOX}\nLook at this Stalfos on the throne."
- intro_zelda_cell: "{NOPAUSE}\n{IBOX}\nIt is your time to shine!"
- intro_agahnim: "{NOPAUSE}\n{IBOX}\nAlso, you need to defeat this guy!"
- 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_big_bomb: "30 bombs for 100 rupees, 100 rupees 1 BIG bomb. Good deals all day!"
@ -341,7 +341,6 @@
# $140
- agahnim_defeated: "Arrrgggghhh. Well you're coming with me!"
- 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_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."
@ -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?"
- blinds_hut_dude: "I'm just some dude. This is Blind's hut."
- end_triforce: "{SPEED2}\n{MENU}\n{NOBORDER}\n G G"
# $174
- toppi_fallen: "Ouch!\n\nYou Jerk!"
- 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_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."
- 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_2: "You found Shabadoo, huh?\nNiiiiice."
- 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}"
- 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?"
- menu_start_2: { NoPause: "{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_pause: { NoPause: "{SPEED0}\n≥Continue Game\n_Save and Quit\n{CHOICE3}" }
- menu_start_2: "{NOPAUSE}\n{MENU}\n{SPEED0}\n≥£'s House\n_Sanctuary\n{CHOICE3}"
- menu_start_3: "{NOPAUSE}\n{MENU}\n{SPEED0}\n≥£'s House\n_Sanctuary\n_Mountain Cave\n{CHOICE2}"
- 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_start: "Okay, use the shovel with Y!"
- 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_come_back_later: "Come back later, I have to bury things."
- 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_phase_3_alt: "Got wax in your ears? I cannot die!"
# $190
- sign_east_death_mountain_bridge: "How did you get up here?"
- 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: ""

View File

@ -3,7 +3,7 @@ from typing import Any, List
import copy
from worlds.smz3.TotalSMZ3.Text.Dialog import Dialog
from worlds.smz3.TotalSMZ3.Text.Texts import text_folder
from yaml import load, Loader
from Utils import unsafe_parse_yaml
class StringTable:
@ -11,7 +11,7 @@ class StringTable:
def ParseEntries(resource: str):
with open(resource, 'rb') as f:
yaml = str(f.read(), "utf-8")
content = load(yaml, Loader)
content = unsafe_parse_yaml(yaml)
result = []
for entryValue in content:
@ -20,8 +20,6 @@ class StringTable:
result.append((key, value))
elif isinstance(value, str):
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)}")
return result
@ -47,9 +45,11 @@ class StringTable:
def SetGanonThirdPhaseText(self, text: str):
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):
self.SetText("end_triforce", "{NOBORDER}\n" + text)
self.SetText("end_triforce", f"{{NOBORDER}}\n{text}")
def SetPedestalText(self, text: str):
self.SetText("mastersword_pedestal_translated", text)
@ -60,6 +60,12 @@ class StringTable:
def SetBombosText(self, text: str):
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):
count = 0
for key, value in self.entries:

View File

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

View File

@ -54,10 +54,26 @@ class World:
Player: str
Guid: str
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):
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]
regionLookup: Dict[str, Region.Region]
@ -95,22 +111,22 @@ class World:
DarkWorldNorthEast(self, self.Config),
DarkWorldSouth(self, self.Config),
DarkWorldMire(self, self.Config),
Central(self, self.Config),
CrateriaWest(self, self.Config),
Central(self, self.Config),
CrateriaEast(self, self.Config),
Blue(self, self.Config),
Green(self, self.Config),
Kraid(self, self.Config),
Pink(self, self.Config),
Red(self, self.Config),
Kraid(self, self.Config),
WreckedShip(self, self.Config),
Outer(self, self.Config),
Inner(self, self.Config),
NorfairUpperWest(self, self.Config),
NorfairUpperEast(self, self.Config),
Crocomire(self, self.Config),
NorfairLowerWest(self, self.Config),
NorfairLowerEast(self, self.Config),
WreckedShip(self, self.Config)
NorfairLowerEast(self, self.Config)
]
self.Locations = []
@ -130,37 +146,32 @@ class World:
raise Exception(f"World.CanEnter: Invalid region name {regionName}", f'{regionName=}'.partition('=')[0])
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)
def CanAquireAll(self, items: Item.Progression, *rewards: Region.RewardType):
for region in self.Regions:
if issubclass(type(region), Region.IReward):
if (region.Reward in rewards):
if not region.CanComplete(items):
return False
return True
def CanAcquireAll(self, items: Item.Progression, rewardsMask: Region.RewardType):
return all(region.CanComplete(items) for region in self.rewardLookup[rewardsMask.value])
# 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):
self.SetMedallions(rnd)
self.SetRewards(rnd)
def Setup(self, state):
self.WorldState = state
self.SetMedallions(state.Medallions)
self.SetRewards(state.Rewards)
self.SetRewardLookup()
def SetMedallions(self, rnd: random):
medallionMap = {0: Item.ItemType.Bombos, 1: Item.ItemType.Ether, 2: Item.ItemType.Quake}
regionList = [region for region in self.Regions if isinstance(region, Region.IMedallionAccess)]
for region in regionList:
region.Medallion = medallionMap[rnd.randint(0, 2)]
def SetRewards(self, rewards: List):
regions = [region for region in self.Regions if isinstance(region, Region.IReward) and region.Reward == Region.RewardType.Null]
for (region, reward) in zip(regions, rewards):
region.Reward = reward
def SetRewards(self, rnd: random):
rewards = [
Region.RewardType.PendantGreen, Region.RewardType.PendantNonGreen, Region.RewardType.PendantNonGreen, Region.RewardType.CrystalRed, Region.RewardType.CrystalRed,
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 SetMedallions(self, medallions: List):
self.GetRegion("Misery Mire").Medallion = medallions[0]
self.GetRegion("Turtle Rock").Medallion = medallions[1]
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
from worlds.smz3.TotalSMZ3.World import World as TotalSMZ3World
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.Patch import Patch as TotalSMZ3Patch, getWord, getWordArray
from worlds.smz3.TotalSMZ3.WorldState import WorldState
from ..AutoWorld import World, AutoLogicRegister, WebWorld
from .Rom import get_base_rom_bytes, SMZ3DeltaPatch
from .ips import IPS_Patch
@ -60,12 +61,12 @@ class SMZ3World(World):
"""
game: str = "SMZ3"
topology_present = False
data_version = 1
data_version = 2
options = smz3_options
item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id)
location_names: Set[str]
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()
remote_items: bool = False
@ -180,30 +181,32 @@ class SMZ3World(World):
base_combined_rom = get_base_rom_bytes()
def generate_early(self):
config = Config({})
config.GameMode = GameMode.Multiworld
config.Z3Logic = Z3Logic.Normal
config.SMLogic = SMLogic(self.world.sm_logic[self.player].value)
config.SwordLocation = SwordLocation(self.world.sword_location[self.player].value)
config.MorphLocation = MorphLocation(self.world.morph_location[self.player].value)
config.Goal = Goal.DefeatBoth
config.KeyShuffle = KeyShuffle(self.world.key_shuffle[self.player].value)
config.Keysanity = config.KeyShuffle != KeyShuffle.Null
config.GanonInvincible = GanonInvincible.BeforeCrystals
self.config = Config()
self.config.GameMode = GameMode.Multiworld
self.config.Z3Logic = Z3Logic.Normal
self.config.SMLogic = SMLogic(self.world.sm_logic[self.player].value)
self.config.SwordLocation = SwordLocation(self.world.sword_location[self.player].value)
self.config.MorphLocation = MorphLocation(self.world.morph_location[self.player].value)
self.config.Goal = Goal(self.world.goal[self.player].value)
self.config.KeyShuffle = KeyShuffle(self.world.key_shuffle[self.player].value)
self.config.OpenTower = OpenTower(self.world.open_tower[self.player].value)
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.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 = []
SMZ3World.location_names = frozenset(self.smz3World.locationLookup.keys())
self.world.state.smz3state[self.player] = TotalSMZ3Item.Progression([])
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.reverse()
self.progression = TotalSMZ3Item.Item.CreateProgressionPool(self.smz3World)
self.keyCardsItems = TotalSMZ3Item.Item.CreateKeycards(self.smz3World)
self.SmMapsItems = TotalSMZ3Item.Item.CreateSmMaps(self.smz3World)
niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World)
junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World)
@ -211,7 +214,7 @@ class SMZ3World(World):
self.junkItemsNames = [item.Type.name for item in junkItems]
if (self.smz3World.Config.Keysanity):
progressionItems = self.progression + self.dungeon + self.keyCardsItems
progressionItems = self.progression + self.dungeon + self.keyCardsItems + self.SmMapsItems
else:
progressionItems = self.progression
for item in self.keyCardsItems:
@ -352,6 +355,49 @@ class SMZ3World(World):
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):
try:
base_combined_rom = get_base_rom_bytes()
@ -368,6 +414,7 @@ class SMZ3World(World):
patches = patcher.Create(self.smz3World.Config)
patches.update(self.apply_sm_custom_sprite())
patches.update(self.apply_item_names())
patches.update(self.apply_customization())
for addr, bytes in patches.items():
offset = 0
for byte in bytes:
@ -463,7 +510,7 @@ class SMZ3World(World):
item.item.Progression = False
item.location.event = False
self.unreachable.append(item.location)
self.JunkFillGT()
self.JunkFillGT(0.5)
def get_pre_fill_items(self):
if (not self.smz3World.Config.Keysanity):
@ -477,20 +524,33 @@ class SMZ3World(World):
def write_spoiler(self, spoiler_handle: TextIO):
self.world.spoiler.unreachables.update(self.unreachable)
def JunkFillGT(self):
for loc in self.locations.values():
if loc.name in self.locationNamesGT and loc.item is None:
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():
# 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:
poolLength = len(junkPoolIdx)
# start looking at a random starting index and loop at start if no match found
start = self.world.random.randint(0, poolLength)
for off in range(0, poolLength):
i = (start + off) % poolLength
if self.world.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) \
and loc.can_fill(self.world.state, self.world.itempool[i], False):
itemFromPool = self.world.itempool.pop(i)
candidate = self.world.itempool[junkPoolIdx[i]]
if junkPoolIdx[i] not in toRemove and loc.can_fill(self.world.state, candidate, False):
itemFromPool = candidate
toRemove.append(junkPoolIdx[i])
break
self.world.push_item(loc, itemFromPool, False)
loc.event = False
toRemove.sort(reverse = True)
for i in toRemove:
self.world.itempool.pop(i)
def FillItemAtLocation(self, itemPool, itemType, location):
itemToPlace = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World)
@ -524,6 +584,7 @@ class SMZ3World(World):
def InitialFillInOwnWorld(self):
self.FillItemAtLocation(self.dungeon, TotalSMZ3Item.ItemType.KeySW, self.smz3World.GetLocation("Skull Woods - Pinball Room"))
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 */

Binary file not shown.