Smz3 updated to version 11.3 (#886)
This commit is contained in:
parent
c02c6ee58c
commit
898fa203ad
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
]
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
]
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 ]
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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: ""
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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:])
|
|
@ -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.
Loading…
Reference in New Issue