From a26f5d96587a76d25eb49c807d06f2e137ff8422 Mon Sep 17 00:00:00 2001 From: CookieCat Date: Mon, 24 Jul 2023 11:23:38 -0400 Subject: [PATCH] 0.4 --- A Hat in Time.yaml | 360 ++++++++++++++++++ APRandomizer/Classes/Archipelago_GameMod.uc | 151 ++++++-- APRandomizer/Classes/Archipelago_ItemInfo.uc | 24 +- APRandomizer/Classes/Archipelago_SlotData.uc | 2 + APRandomizer/Classes/Archipelago_TcpLink.uc | 160 +++++--- .../Items/Archipelago_RandomizedItem_Base.uc | 40 +- .../Items/Archipelago_Weapon_Unarmed.uc | 6 + .../Misc/Archipelago_NPC_BadgeSalesman.uc | 5 +- APRandomizer/modinfo.ini | Bin 1160 -> 1626 bytes ahit/Items.py | 13 +- ahit/Locations.py | 70 ++-- ahit/Options.py | 34 ++ ahit/Regions.py | 55 ++- ahit/Rules.py | 176 ++++++--- ahit/__init__.py | 41 +- 15 files changed, 924 insertions(+), 213 deletions(-) create mode 100644 A Hat in Time.yaml create mode 100644 APRandomizer/Classes/Items/Archipelago_Weapon_Unarmed.uc diff --git a/A Hat in Time.yaml b/A Hat in Time.yaml new file mode 100644 index 0000000..10c1c86 --- /dev/null +++ b/A Hat in Time.yaml @@ -0,0 +1,360 @@ +# Q. What is this file? +# A. This file contains options which allow you to configure your multiworld experience while allowing +# others to play how they want as well. +# +# Q. How do I use it? +# A. The options in this file are weighted. This means the higher number you assign to a value, the +# more chances you have for that option to be chosen. For example, an option like this: +# +# map_shuffle: +# on: 5 +# off: 15 +# +# Means you have 5 chances for map shuffle to occur, and 15 chances for map shuffle to be turned +# off. +# +# Q. I've never seen a file like this before. What characters am I allowed to use? +# A. This is a .yaml file. You are allowed to use most characters. +# To test if your yaml is valid or not, you can use this website: +# http://www.yamllint.com/ +# You can also verify your Archipelago settings are valid at this site: +# https://archipelago.gg/check + +# Your name in-game. Spaces will be replaced with underscores and there is a 16-character limit. +# {player} will be replaced with the player's slot number. +# {PLAYER} will be replaced with the player's slot number, if that slot number is greater than 1. +# {number} will be replaced with the counter value of the name. +# {NUMBER} will be replaced with the counter value of the name, if the counter value is greater than 1. +name: Player{number} + +# Used to describe your yaml. Useful if you have multiple files. +description: Default A Hat in Time Template + +game: A Hat in Time +requires: + version: 0.4.1 # Version of Archipelago required for this yaml to work as expected. + +A Hat in Time: + progression_balancing: + # A system that can move progression earlier, to try and prevent the player from getting stuck and bored early. + # A lower setting means more getting stuck. A higher setting means less getting stuck. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 0 + # Maximum value is 99 + random: 0 + random-low: 0 + random-high: 0 + disabled: 0 # equivalent to 0 + normal: 50 # equivalent to 50 + extreme: 0 # equivalent to 99 + + accessibility: + # Set rules for reachability of your items/locations. + # Locations: ensure everything can be reached and acquired. + # Items: ensure all logically relevant items can be acquired. + # Minimal: ensure what is needed to reach your goal can be acquired. + locations: 0 + items: 50 + minimal: 0 + + local_items: + # Forces these items to be in their native world. + [] + + non_local_items: + # Forces these items to be outside their native world. + [] + + start_inventory: + # Start with these items. + {} + + start_hints: + # Start with these item's locations prefilled into the !hint command. + [] + + start_location_hints: + # Start with these locations and their item prefilled into the !hint command + [] + + exclude_locations: + # Prevent these locations from having an important item + [] + + priority_locations: + # Prevent these locations from having an unimportant item + [] + + item_links: + # Share part of your item pool with other players. + [] + + ActRandomizer: + # If enabled, shuffle the game's Acts between each other. + false: 0 + true: 50 + + ShuffleAlpineZiplines: + # If enabled, Alpine's zipline paths leading to the peaks will be locked behind items. + false: 50 + true: 0 + + VanillaAlpine: + # If enabled, force Alpine (and optionally its finale) onto their vanilla locations in act shuffle. + no: 50 + yes: 0 + finale: 0 + + LogicDifficulty: + # Choose the difficulty setting for logic. Note that Hard or above will force SDJ logic on. + normal: 50 + hard: 0 + expert: 0 + + RandomizeHatOrder: + # Randomize the order that hats are stitched in. + false: 0 + true: 50 + + UmbrellaLogic: + # Makes Hat Kid's default punch attack do absolutely nothing, making the Umbrella much more relevant and useful + false: 50 + true: 0 + + StartWithCompassBadge: + # If enabled, start with the Compass Badge. In Archipelago, the Compass Badge will track all items in the world + # (instead of just Relics). Recommended if you're not familiar with where item locations are. + false: 0 + true: 50 + + CompassBadgeMode: + # closest - Compass Badge points to the closest item regardless of classification + # important_only - Compass Badge points to progression/useful items only + # important_first - Compass Badge points to progression/useful items first, then it will point to junk items + closest: 50 + important_only: 0 + important_first: 0 + + ShuffleStorybookPages: + # If enabled, each storybook page in the purple Time Rifts is an item check. + # The Compass Badge can track these down for you. + false: 0 + true: 50 + + ShuffleActContracts: + # If enabled, shuffle Snatcher's act contracts into the pool as items + false: 0 + true: 50 + + StartingChapter: + # Determines which chapter you will be guaranteed to be able to enter at the beginning of the game. + # Please note that in act randomizer, only 1 and 2 are allowed. + 1: 50 + 2: 0 + 3: 0 + 4: 0 + + SDJLogic: + # Allow the SDJ (Sprint Double Jump) technique to be considered in logic. + false: 50 + true: 0 + + CTRWithSprint: + # If enabled, clearing Cheating the Race with just Sprint Hat can be in logic. + false: 50 + true: 0 + + EnableDLC1: + # NOT IMPLEMENTED Shuffle content from The Arctic Cruise (Chapter 6) into the game. + # DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE THE DLC INSTALLED!!! + false: 50 + true: 0 + + EnableDeathWish: + # NOT IMPLEMENTED Shuffle Death Wish contracts into the game. + # Each contract by default will have a single check granted upon completion. + # DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE THE DLC INSTALLED!!! + false: 50 + true: 0 + + EnableDLC2: + # NOT IMPLEMENTED Shuffle content from Nyakuza Metro (Chapter 7) into the game. + # DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE THE DLC INSTALLED!!! + false: 50 + true: 0 + + LowestChapterCost: + # Value determining the lowest possible cost for a chapter. + # Chapter costs will, progressively, be calculated based on this value (except for Chapter 5). + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 0 + # Maximum value is 10 + 5: 50 + random: 0 + random-low: 0 + random-high: 0 + + HighestChapterCost: + # Value determining the highest possible cost for a chapter. + # Chapter costs will, progressively, be calculated based on this value (except for Chapter 5). + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 15 + # Maximum value is 35 + 25: 50 + random: 0 + random-low: 0 + random-high: 0 + + ChapterCostIncrement: + # Lower values mean chapter costs increase slower. Higher values make the cost differences more steep. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 1 + # Maximum value is 8 + 5: 50 + random: 0 + random-low: 0 + random-high: 0 + + ChapterCostMinDifference: + # The minimum difference between chapter costs. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 1 + # Maximum value is 8 + 5: 50 + random: 0 + random-low: 0 + random-high: 0 + + Chapter5MinCost: + # Minimum Time Pieces required to enter Chapter 5 (Time's End). This is your goal. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 0 + # Maximum value is 40 + 25: 50 + random: 0 + random-low: 0 + random-high: 0 + + Chapter5MaxCost: + # Maximum Time Pieces required to enter Chapter 5 (Time's End). This is your goal. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 0 + # Maximum value is 40 + 35: 50 + random: 0 + random-low: 0 + random-high: 0 + + YarnCostMin: + # The minimum possible yarn needed to stitch each hat. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 1 + # Maximum value is 12 + 4: 50 + random: 0 + random-low: 0 + random-high: 0 + + YarnCostMax: + # The maximum possible yarn needed to stitch each hat. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 1 + # Maximum value is 12 + 8: 50 + random: 0 + random-low: 0 + random-high: 0 + + YarnAvailable: + # How much yarn is available to collect in the item pool. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 30 + # Maximum value is 75 + 45: 50 + random: 0 + random-low: 0 + random-high: 0 + + MinPonCost: + # The minimum amount of Pons that any shop item can cost. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 10 + # Maximum value is 800 + 75: 50 + random: 0 + random-low: 0 + random-high: 0 + + MaxPonCost: + # The maximum amount of Pons that any shop item can cost. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 10 + # Maximum value is 800 + 400: 50 + random: 0 + random-low: 0 + random-high: 0 + + TrapChance: + # The chance for any junk item in the pool to be replaced by a trap. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 0 + # Maximum value is 100 + 0: 50 + random: 0 + random-low: 0 + random-high: 0 + + BabyTrapWeight: + # The weight of Baby Traps in the trap pool. + # Baby Traps place a multitude of the Conductor's grandkids into Hat Kid's hands, causing her to lose her balance. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 0 + # Maximum value is 100 + 40: 50 + random: 0 + random-low: 0 + random-high: 0 + + LaserTrapWeight: + # The weight of Laser Traps in the trap pool. + # Laser Traps will spawn multiple giant lasers (from Snatcher's boss fight) at Hat Kid's location. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 0 + # Maximum value is 100 + 40: 50 + random: 0 + random-low: 0 + random-high: 0 + + ParadeTrapWeight: + # The weight of Parade Traps in the trap pool. + # Parade Traps will summon multiple Express Band owls with knives that chase Hat Kid by mimicking her movement. + # + # You can define additional values between the minimum and maximum values. + # Minimum value is 0 + # Maximum value is 100 + 20: 50 + random: 0 + random-low: 0 + random-high: 0 + + death_link: + # When you die, everyone dies. Of course the reverse is true too. + false: 50 + true: 0 diff --git a/APRandomizer/Classes/Archipelago_GameMod.uc b/APRandomizer/Classes/Archipelago_GameMod.uc index 2af6af7..002ecf2 100644 --- a/APRandomizer/Classes/Archipelago_GameMod.uc +++ b/APRandomizer/Classes/Archipelago_GameMod.uc @@ -15,6 +15,7 @@ var transient string DebugMsg; var config int DebugMode; var config int DisableInjection; +var config int VerboseLogging; var const editconst Vector SpaceshipSpawnLocation; var transient array SelectContracts; @@ -31,9 +32,11 @@ struct immutable ShuffledAct struct immutable ShopItemInfo { var class ItemClass; + var int ID; var int ItemID; var int ItemFlags; var int PonCost; + var bool Hinted; }; struct immutable LocationInfo @@ -44,7 +47,7 @@ struct immutable LocationInfo var int Flags; var bool Checked; var string MapName; - var string OriginalName; + var string ItemName; var Vector Position; var class ItemClass; @@ -360,8 +363,10 @@ function OnPostInitGame() local TriggerVolume trigger; local Hat_SaveGame save; local Hat_Player ply; + local Hat_PlayerController ctr; local Hat_NPC mu; local Actor a; + local Emitter emitter; // If on titlescreen, find any Archipelago-enabled save files and do this stuff // to prevent the game from forcing the player into Mafia Town. @@ -468,6 +473,18 @@ function OnPostInitGame() ChestArray.AddItem(chest); chest.Content = class'Hat_Collectible_EnergyBit'; } + + if (`GameManager.GetChapterInfo().ChapterID == 1) + { + foreach AllActors(class'Emitter', emitter) + { + if (emitter.ParticleSystemComponent.Template == ParticleSystem'HatInTime_Items.ParticleSystems.ImportantItem') + { + emitter.ParticleSystemComponent.KillParticlesForced(); + emitter.Destroy(); + } + } + } // Shuffle items from cache? if (IsMapScouted(class'Hat_SaveBitHelper'.static.GetCorrectedMapFilename())) @@ -504,6 +521,8 @@ function OnPostInitGame() } } + ctr = Hat_PlayerController(GetALocalPlayerController()); + // If we load a new save file with at least 1 time piece, the game will force the umbrella into our inventory. save = `SaveManager.GetCurrentSaveData(); for (i = 0; i < save.MyBackpack2017.Weapons.Length; i++) @@ -514,7 +533,14 @@ function OnPostInitGame() if (class(item.BackpackClass) != None && class(item.BackpackClass) == None) { - Hat_PlayerController(GetALocalPlayerController()).MyLoadout.RemoveBackpack(item); + ctr.MyLoadout.RemoveBackpack(item); + } + + if (SlotData.UmbrellaLogic && class(item.BackpackClass) != None) + { + ctr.MyLoadout.RemoveBackpack(item); + item.BackpackClass = class'Archipelago_Weapon_Unarmed'; + ctr.MyLoadout.AddBackpack(item, true, true, Hat_Player(ctr.Pawn)); } } } @@ -737,6 +763,8 @@ function LoadSlotData(JsonObject json) SlotData.ActRando = json.GetBoolValue("ActRandomizer"); SlotData.ShuffleStorybookPages = json.GetBoolValue("ShuffleStorybookPages"); SlotData.ShuffleActContracts = json.GetBoolValue("ShuffleActContracts"); + SlotData.ShuffleZiplines = json.GetBoolValue("ShuffleAlpineZiplines"); + SlotData.UmbrellaLogic = json.GetBoolValue("UmbrellaLogic"); SlotData.DeathLink = json.GetBoolValue("death_link"); SlotData.CompassBadgeMode = json.GetIntValue("CompassBadgeMode"); @@ -1007,6 +1035,14 @@ function CheckShopOverride(Hat_HUD hud) { GetShopItemInfo(class(mShop.ItemsForSale[i].CollectibleClass), shopInfo); mShop.ItemsForSale[0].ItemCost = shopInfo.PonCost; + + if (!shopInfo.Hinted) + { + if (shopInfo.ItemFlags == ItemFlag_Important || shopInfo.ItemFlags == ItemFlag_ImportantSkipBalancing) + { + SendLocationCheck(shopInfo.ID, true, true); + } + } } shop.SetShopInventory(hud, mShop); @@ -1431,6 +1467,7 @@ function ShuffleCollectibles() local Actor a; local array locationArray, shopLocationArray; local array> shopItemClasses; + local LocationInfo locInfo; local class shopItem; local string mapName; @@ -1480,7 +1517,7 @@ function ShuffleCollectibles() { if (Hat_ImpactInteract_Breakable_ChemicalBadge(a).Rewards.Length <= 0) continue; - + for (i = 0; i < Hat_ImpactInteract_Breakable_ChemicalBadge(a).Rewards.Length; i++) { if (class(Hat_ImpactInteract_Breakable_ChemicalBadge(a).Rewards[i]) != None) @@ -1536,7 +1573,9 @@ function ShuffleCollectibles() // Load from cache for (i = 0; i < locationArray.Length; i++) { - if (GetLocationInfoFromID(locationArray[i]).ContainerClass != None) + locInfo = GetLocationInfoFromID(locationArray[i]); + if (locInfo.ContainerClass != None || locInfo.Checked + || locInfo.Position.x == 0 && locInfo.Position.y == 0 && locInfo.position.z == 0) continue; CreateItemFromInfo(GetLocationInfoFromID(locationArray[i])); @@ -1705,7 +1744,6 @@ function Archipelago_RandomizedItem_Base CreateItem(int locId, int itemId, int f locInfo.MapName = mapName; locInfo.ItemClass = item.class; locInfo.ItemName = item.ItemDisplayName; - locInfo.OriginalName = item.OriginalCollectibleName; locInfo.Position = item.Location; SlotData.LocationInfoArray.AddItem(locInfo); } @@ -1723,7 +1761,6 @@ function Archipelago_RandomizedItem_Base CreateItemFromInfo(LocationInfo locInfo item.ItemFlags = locInfo.Flags; item.ItemOwner = locInfo.Player; item.ItemDisplayName = locInfo.ItemName; - item.OriginalCollectibleName = locInfo.OriginalName; if (!item.Init()) return None; @@ -1769,6 +1806,7 @@ function ShopItemInfo CreateShopItemInfo(class itemCl { local ShopItemInfo shopInfo; + shopInfo.ID = itemClass.default.LocationID; shopInfo.ItemClass = itemClass; shopInfo.ItemID = itemId; shopInfo.ItemFlags = flags; @@ -1787,39 +1825,35 @@ function InitShopItemDisplayName(class itemClass) if (!GetShopItemInfo(itemClass, shopInfo)) return; - if (class'Archipelago_ItemInfo'.static.GetNativeItemData(shopInfo.ItemID, displayName)) - { - itemClass.static.SetDisplayName(displayName); - } - else if (class'Archipelago_ItemInfo'.static.GetTimePieceFromItemID(shopInfo.ItemID, , displayName) != "") - { - itemClass.static.SetDisplayName(displayName); - } - else + if (!class'Archipelago_ItemInfo'.static.GetNativeItemData(shopInfo.ItemID, displayName) + && class'Archipelago_ItemInfo'.static.GetTimePieceFromItemID(shopInfo.ItemID, , displayName) == "") { switch (shopInfo.ItemFlags) { case ItemFlag_Important: - itemClass.static.SetDisplayName("AP Item (Progression)"); + displayName = "AP Item (Progression)"; break; case ItemFlag_ImportantSkipBalancing: - itemClass.static.SetDisplayName("AP Item (Progression)"); + displayName = "AP Item (Progression)"; break; case ItemFlag_Trap: - itemClass.static.SetDisplayName("AP Item (Progression)"); + displayName = "AP Item (Progression)"; break; case ItemFlag_Useful: - itemClass.static.SetDisplayName("AP Item (Useful)"); + displayName = "AP Item (Useful)"; break; default: - itemClass.static.SetDisplayName("AP Item"); + displayName = "AP Item"; break; } } + + displayName $= " (" $itemClass.default.DisplayName $")"; + itemClass.static.SetDisplayName(displayName); } function int GetShopItemID(class itemClass) @@ -1995,7 +2029,8 @@ function OnPlayerDeath(Pawn Player) if (!IsDeathLinkEnabled() || !IsArchipelagoEnabled() || !IsFullyConnected()) return; - message = "[{\"cmd\":\"Bounce\",\"tags\":[\"DeathLink\"],\"data\":{\"time\":" $float(TimeStamp()) $",\"source\":\"CookieHat\",\"cause\":\"\"}}]"; + // commit myurder + message = "[{\"cmd\":\"Bounce\",\"tags\":[\"DeathLink\"],\"data\":{\"time\":" $float(TimeStamp()) $",\"source\:" $SlotData.SlotName $",\"cause\":\"\"}}]"; client.SendBinaryMessage(message); } @@ -2010,6 +2045,17 @@ function OnLoadoutChanged(PlayerController controller, Object loadout, Object ba if (item == None) return; + if (SlotData.Initialized && SlotData.UmbrellaLogic) + { + if (class(item.BackpackClass) != None + && class(item.BackpackClass) == None) + { + Hat_Loadout(loadout).RemoveBackpack(item); + item.BackpackClass = class'Archipelago_Weapon_Unarmed'; + Hat_Loadout(loadout).AddBackpack(item, true, true, Hat_Player(Hat_PlayerController(controller).Pawn)); + } + } + // remove base game umbrella in favor of our own. This is the umbrella check in Mafia Town. if (class(item.BackpackClass) != None && class(item.BackpackClass) == None) @@ -2020,6 +2066,33 @@ function OnLoadoutChanged(PlayerController controller, Object loadout, Object ba } } +function OnAbilityUsed(Pawn Player, Inventory Ability) +{ + local Actor hookpoint; + if (!IsArchipelagoEnabled()) + return; + + if (SlotData.ShuffleZiplines && Hat_Ability_Hookshot(ability) != None) + { + Hat_Ability_Hookshot(ability).UpdateTargetedSwingActor(); + hookpoint = Hat_Ability_Hookshot(ability).TargetedSwingActor; + + if (hookpoint.IsA('Hat_HookPoint_Desert')) + { + if (hookpoint.Name == 'Hat_HookPoint_Desert_2' || hookpoint.Name == 'Hat_HookPoint_Desert_16' + || hookpoint.Name == 'Hat_HookPoint_Desert_1' || hookpoint.Name == 'Hat_HookPoint_Desert_24') + { + if (!HasAPBit("ZiplineUnlock_"$string(hookpoint.Name), 1)) + { + Hat_Ability_Hookshot(ability).DoDeactivate(false, true); + Hat_Ability(ability).Activated = false; + ScreenMessage("You don't have the unlock for this zipline"); + } + } + } + } +} + function OnCollectibleSpawned(Object collectible) { local Archipelago_RandomizedItem_Base item; @@ -2246,7 +2319,7 @@ function int GetHatYarnCost(class hatClass) return 0; } -function SendLocationCheck(int id, optional bool scout) +function SendLocationCheck(int id, optional bool scout, optional bool hint) { local string jsonMessage; local int i; @@ -2287,7 +2360,14 @@ function SendLocationCheck(int id, optional bool scout) } else { - jsonMessage = "[{\"cmd\":\"LocationScouts\",\"locations\":[" $id $"]}]"; + if (hint) + { + jsonMessage = "[{\"cmd\":\"LocationScouts\",\"locations\":[" $id $"],\"create_as_hint\":1}]"; + } + else + { + jsonMessage = "[{\"cmd\":\"LocationScouts\",\"locations\":[" $id $"]}]"; + } } Client.SendBinaryMessage(jsonMessage); @@ -2305,7 +2385,7 @@ function SendMultipleLocationChecks(array locationArray, optional bool scou DebugMessage("[WARNING] Tried to scout locations while not connected!"); return; } - + ItemResender.AddMultipleLocations(locationArray); SaveGame(); return; @@ -2452,7 +2532,7 @@ function BabyTrapTimer() if (player.IsA('Hat_Player_MustacheGirl')) continue; - if (RandRange(1, 150) == 1) + if (RandRange(1, 250) == 2) { stackClass = class'Archipelago_Stackable_Conductor'; } @@ -2506,6 +2586,9 @@ function LaserTrapTimer() { local Hat_Player player; + if (WorldInfo.Pauser != None) + return; + foreach DynamicActors(class'Hat_Player', player) { if (player.IsA('Hat_Player_MustacheGirl')) @@ -2803,10 +2886,14 @@ function Hat_ChapterActInfo GetRiftActFromMapName(string MapName) function ScreenMessage(String message, optional Name type) { - // Don't ask. local PlayerController pc; - DebugMsg = message; - ConsoleCommand("getall Archipelago_GameMod DebugMsg"); + + if (bool(VerboseLogging)) + { + // Don't ask. + DebugMsg = message; + ConsoleCommand("getall Archipelago_GameMod DebugMsg"); + } pc = GetALocalPlayerController(); if (pc == None) @@ -2818,8 +2905,12 @@ function ScreenMessage(String message, optional Name type) function DebugMessage(String message, optional Name type) { local PlayerController pc; - DebugMsg = message; - ConsoleCommand("getall Archipelago_GameMod DebugMsg"); + + if (bool(VerboseLogging)) + { + DebugMsg = message; + ConsoleCommand("getall Archipelago_GameMod DebugMsg"); + } if (!bool(DebugMode)) return; diff --git a/APRandomizer/Classes/Archipelago_ItemInfo.uc b/APRandomizer/Classes/Archipelago_ItemInfo.uc index f539ee2..b9e4e1e 100644 --- a/APRandomizer/Classes/Archipelago_ItemInfo.uc +++ b/APRandomizer/Classes/Archipelago_ItemInfo.uc @@ -315,6 +315,26 @@ optional out class inventoryOverride) // Item inventory class override. M worldClass = class'Archipelago_RandomizedItem_Contract'; return true; + case 300204: + itemName = "Zipline Unlock (The Birdhouse Path)"; + worldClass = class'Archipelago_RandomizedItem_Misc'; + return true; + + case 300205: + itemName = "Zipline Unlock (The Lava Cake Path)"; + worldClass = class'Archipelago_RandomizedItem_Misc'; + return true; + + case 300206: + itemName = "Zipline Unlock (The Windmill Path)"; + worldClass = class'Archipelago_RandomizedItem_Misc'; + return true; + + case 300207: + itemName = "Zipline Unlock (The Twilight Bell Path)"; + worldClass = class'Archipelago_RandomizedItem_Misc'; + return true; + default: worldClass = class'Archipelago_RandomizedItem_Misc'; return false; @@ -461,7 +481,7 @@ static function string GetTimePieceFromItemID(int id, optional out int IsAct, op // -------------------------------------------------- ALPINE SKYLINE ----------------------------------------------------- \\ case 300077: displayName = "Time Piece - The Birdhouse"; - return "Alps_Birdhouse"; + return "Alps_Birdhouse1"; case 300078: displayName = "Time Piece - The Lava Cake"; @@ -477,7 +497,7 @@ static function string GetTimePieceFromItemID(int id, optional out int IsAct, op case 300081: displayName = "Time Piece - The Illness Has Spread"; - return "AlpineSkyline_Finale"; + return "AlpineSkyline_Finale1"; case 300082: displayName = "Time Piece - Time Rift - The Twilight Bell"; diff --git a/APRandomizer/Classes/Archipelago_SlotData.uc b/APRandomizer/Classes/Archipelago_SlotData.uc index f3652ab..6f58533 100644 --- a/APRandomizer/Classes/Archipelago_SlotData.uc +++ b/APRandomizer/Classes/Archipelago_SlotData.uc @@ -20,6 +20,8 @@ var array PendingMessages; var bool ActRando; var bool ShuffleStorybookPages; var bool ShuffleActContracts; +var bool ShuffleZiplines; +var bool UmbrellaLogic; var bool DeathLink; var int CompassBadgeMode; diff --git a/APRandomizer/Classes/Archipelago_TcpLink.uc b/APRandomizer/Classes/Archipelago_TcpLink.uc index 4d38d84..421402e 100644 --- a/APRandomizer/Classes/Archipelago_TcpLink.uc +++ b/APRandomizer/Classes/Archipelago_TcpLink.uc @@ -366,8 +366,8 @@ function ParseJSON(string json) break; - case "Bounce": - OnBounceCommand(json); + case "Bounced": + OnBouncedCommand(json); break; @@ -475,23 +475,30 @@ function OnLocationInfoCommand(string json) if (!class'Archipelago_ItemInfo'.static.GetNativeItemData(itemId, locInfo.ItemName, locInfo.ItemClass)) { - switch (flags) + if (class'Archipelago_ItemInfo'.static.GetTimePieceFromItemID(itemId, , locInfo.ItemName) != "") { - case ItemFlag_Important: - locInfo.ItemName = "AP Item - Important"; - break; + locInfo.itemClass = class'Archipelago_RandomizedItem_TimeObject'; + } + else + { + switch (flags) + { + case ItemFlag_Important: + locInfo.ItemName = "AP Item - Important"; + break; + + case ItemFlag_ImportantSkipBalancing: + locInfo.ItemName = "AP Item - Important"; + break; + + case ItemFlag_Useful: + locInfo.ItemName = "AP Item - Useful"; + break; - case ItemFlag_ImportantSkipBalancing: - locInfo.ItemName = "AP Item - Important"; - break; - - case ItemFlag_Useful: - locInfo.ItemName = "AP Item - Useful"; - break; - - default: - locInfo.ItemName = "AP Item"; - break; + default: + locInfo.ItemName = "AP Item"; + break; + } } } @@ -510,7 +517,6 @@ function OnLocationInfoCommand(string json) if (item != None) { - item.OriginalCollectibleName = locId == m.CameraBadgeCheck1 ? "AP_Camera1Check" : "AP_Camera2Check"; item.Init(); } } @@ -606,35 +612,37 @@ function OnReceivedItemsCommand(string json, optional bool connection) function GrantTimePiece(string timePieceId, bool IsAct, string itemName, int playerId) { + local Archipelago_GameMod m; local Archipelago_RandomizedItem_Base item; local Pawn player; + m = `AP; player = GetALocalPlayerController().Pawn; item = Spawn(class'Archipelago_RandomizedItem_TimeObject', , , player.Location, , , true); item.PickupActor = player; item.OnCollected(player); - if (playerId != `AP.SlotData.PlayerSlot) + if (playerId != m.SlotData.PlayerSlot) { - `AP.ScreenMessage("Got " $itemName $" (from " $`AP.PlayerIdToName(playerId)$")", 'Warning'); + m.ScreenMessage("Got " $itemName $" (from " $m.PlayerIdToName(playerId)$")", 'Warning'); } else { - `AP.ScreenMessage("Got " $itemName, 'Warning'); + m.ScreenMessage("Got " $itemName, 'Warning'); } // Tell AP to stop removing this Time Piece in OnTimePieceCollected() - `AP.SetAPBits(timePieceId, 1); + m.SetAPBits(timePieceId, 1); - `AP.IsItemTimePiece = true; + m.IsItemTimePiece = true; `SaveManager.GetCurrentSaveData().GiveTimePiece(timePieceId, IsAct); - `AP.IsItemTimePiece = false; + m.IsItemTimePiece = false; - if (`AP.IsInSpaceship() && `AP.SlotData.Initialized) + if (m.IsInSpaceship() && m.SlotData.Initialized) { - `AP.UpdateActUnlocks(); - `AP.UpdatePowerPanels(); + m.UpdateActUnlocks(); + m.UpdatePowerPanels(); } } @@ -710,6 +718,39 @@ function GrantItem(int itemId, int playerId) contract.static.UnlockActs(save); } + else if (itemId == 300204 || itemId == 300205 || itemId == 300206 || itemId == 300207) + { + UnlockZipline(itemId); + } +} + +function UnlockZipline(int id) +{ + local string zipline; + + switch (id) + { + case 300204: // Birdhouse Path + zipline = "Hat_HookPoint_Desert_2"; + break; + + case 300205: // Lava Cake Path + zipline = "Hat_HookPoint_Desert_16"; + break; + + case 300206: // Windmill Path + zipline = "Hat_HookPoint_Desert_1"; + break; + + case 300207: // Twilight Bell Path + zipline = "Hat_HookPoint_Desert_24"; + break; + + default: + return; + } + + `AP.SetAPBits("ZiplineUnlock_"$zipline, 1); } function DoSpecialItemEffects(ESpecialItemType special) @@ -745,34 +786,38 @@ function DoSpecialItemEffects(ESpecialItemType special) function DoTrapItemEffects(ETrapType trap) { + local Archipelago_GameMod m; + m = `AP; + switch (trap) { case TrapType_Baby: - if (`AP.BabyCount > 0) + if (m.BabyCount > 0) { - `AP.BabyCount += 10; + m.BabyCount += 10; } else { - `AP.BabyCount = 10; - `AP.SetTimer(0.5, true, NameOf(`AP.BabyTrapTimer), `AP); + m.BabyCount = 10; + m.SetTimer(0.5, true, NameOf(m.BabyTrapTimer)); + m.SetTimer(60.0, false, NameOf(m.DropAllBabies), m, GetALocalPlayerController().Pawn); } break; case TrapType_Laser: - if (`AP.LaserCount > 0) + if (m.LaserCount > 0) { - `AP.LaserCount += 15; + m.LaserCount += 15; } else { - `AP.LaserCount = 15; - `AP.SetTimer(1.0, true, NameOf(`AP.LaserTrapTimer), `AP); + m.LaserCount = 15; + m.SetTimer(1.0, true, NameOf(m.LaserTrapTimer)); } break; case TrapType_Parade: - `AP.DoParadeTrap(); + m.DoParadeTrap(); break; default: @@ -780,30 +825,35 @@ function DoTrapItemEffects(ETrapType trap) } } -// this is currently just to check for DeathLink packets -function OnBounceCommand(string json) +function OnBouncedCommand(string json) { local JsonObject jsonObj, jsonChild; + local string cause, msg, source; local Hat_Player player; - Repl(json, "[", ""); - Repl(json, "]", ""); - jsonObj = class'JsonObject'.static.DecodeJson(json); if (jsonObj == None) return; jsonChild = jsonObj.GetObject("data"); - if (jsonChild != None && `AP.IsDeathLinkEnabled() && jsonObj.GetStringValue("tags") == "DeathLink") + if (jsonChild != None && `AP.IsDeathLinkEnabled()) { - // commit myurder - foreach DynamicActors(class'Hat_Player', player) - player.Suicide(); - - if (jsonChild != None) - `AP.ScreenMessage("You were myurrderrred by: " $jsonChild.GetStringValue("source")); + source = jsonChild.GetStringValue("source"); + if (source != "") + { + // commit myurder + foreach DynamicActors(class'Hat_Player', player) + player.Suicide(); + + msg = "You were MYURRDERRRRED by: " $source; + cause = jsonChild.GetStringValue("cause"); + if (cause != "") + msg $= " (" $cause $")"; + + `AP.ScreenMessage(msg); + } } - + jsonObj = None; jsonChild = None; } @@ -811,6 +861,7 @@ function OnBounceCommand(string json) // the optional boolean is for recursion, do not use it function SendBinaryMessage(string message, optional bool continuation, optional bool pong) { + local Archipelago_GameMod m; local byte byteMessage[255]; local string buffer; local int length, offset, keyIndex, i, totalSent; @@ -819,12 +870,13 @@ function SendBinaryMessage(string message, optional bool continuation, optional if (!continuation) { + m = `AP; // wait until this is finished if (ParsingMessage) { - for (i = 0; i < `AP.SlotData.PendingMessages.Length; i++) + for (i = 0; i < m.SlotData.PendingMessages.Length; i++) { - if (`AP.SlotData.PendingMessages[i] == message) + if (m.SlotData.PendingMessages[i] == message) { found = true; break; @@ -833,13 +885,13 @@ function SendBinaryMessage(string message, optional bool continuation, optional if (!found) { - `AP.SlotData.PendingMessages.AddItem(message); + m.SlotData.PendingMessages.AddItem(message); } } else { - `AP.SlotData.PendingMessages.RemoveItem(message); - `AP.DebugMessage("Sending message: "$message); + m.SlotData.PendingMessages.RemoveItem(message); + m.DebugMessage("Sending message: "$message); } } diff --git a/APRandomizer/Classes/Items/Archipelago_RandomizedItem_Base.uc b/APRandomizer/Classes/Items/Archipelago_RandomizedItem_Base.uc index da9e00f..e60ff89 100644 --- a/APRandomizer/Classes/Items/Archipelago_RandomizedItem_Base.uc +++ b/APRandomizer/Classes/Items/Archipelago_RandomizedItem_Base.uc @@ -23,16 +23,12 @@ var string OriginalCollectibleName; // the "Name" variable, NOT the display name var AudioComponent IdleAudioComponent; var ParticleSystemComponent ImportantItemParticle; +var ParticleSystemComponent JunkItemParticle; var PointLightComponent LightComponent; function bool Init() { - if (HasOriginalLevelBit()) // Already collected - { - Destroy(); - return false; - } - else if (ItemFlags == ItemFlag_Garbage) + if (ItemFlags == ItemFlag_Garbage) { // No flashy effects for garbage items (still for traps though :v) if (!IsA('Archipelago_RandomizedItem_Yarn')) @@ -47,7 +43,12 @@ function bool Init() } } } - + else + { + JunkItemParticle.SetActive(false); + JunkItemParticle.KillParticlesForced(); + } + return true; } @@ -131,19 +132,6 @@ function SetOriginalLevelBit() class'Hat_SaveBitHelper'.static.SetLevelBits(OriginalCollectibleName, 1, class'Hat_SaveBitHelper'.static.GetCorrectedMapFilename(string(GetLevelName())), `SaveManager.GetCurrentSaveData()); } -function bool HasOriginalLevelBit() -{ - if (OriginalCollectibleName == "") - return false; - - if (InStr(OriginalCollectibleName, "AP_Camera") != -1) - { - return `AP.HasAPBit(OriginalCollectibleName, 1); - } - - return class'Hat_SaveBitHelper'.static.HasLevelBit(OriginalCollectibleName, 1, class'Hat_SaveBitHelper'.static.GetCorrectedMapFilename(string(GetLevelName())), `SaveManager.GetCurrentSaveData()); -} - function bool WasFromServer() { return LocationId == 0; @@ -207,13 +195,23 @@ defaultproperties Begin Object Class=ParticleSystemComponent Name=hParticle0 Template = ParticleSystem'HatInTime_Items.ParticleSystems.BadgeAttentionParticle'; Translation=(Z=10); - MaxDrawDistance = 6000; + MaxDrawDistance = 99999; bSelectable = false; End Object Components.Add(hParticle0); RotationComponents.Add(hParticle0); ImportantItemParticle = hParticle0; + Begin Object Class=ParticleSystemComponent Name=hParticle1 + Template = ParticleSystem'HatInTime_Items.ParticleSystems.ImportantItem'; + Translation=(Z=10); + MaxDrawDistance = 99999; + bSelectable = false; + End Object + Components.Add(hParticle1); + RotationComponents.Add(hParticle1); + JunkItemParticle = hParticle1; + Begin Object Class=PointLightComponent Name=PointLightComponent0 LightAffectsClassification=LAC_STATIC_AFFECTING CastShadows=TRUE diff --git a/APRandomizer/Classes/Items/Archipelago_Weapon_Unarmed.uc b/APRandomizer/Classes/Items/Archipelago_Weapon_Unarmed.uc new file mode 100644 index 0000000..ef0ae4c --- /dev/null +++ b/APRandomizer/Classes/Items/Archipelago_Weapon_Unarmed.uc @@ -0,0 +1,6 @@ +class Archipelago_Weapon_Unarmed extends Hat_Weapon_Unarmed; + +simulated function bool ProcessInstantHit2(ImpactInfo Impact, optional int NumHits, optional class dmg, optional float amount = 1.0, optional bool dead = false) +{ + return false; +} \ No newline at end of file diff --git a/APRandomizer/Classes/Misc/Archipelago_NPC_BadgeSalesman.uc b/APRandomizer/Classes/Misc/Archipelago_NPC_BadgeSalesman.uc index b80da69..0867715 100644 --- a/APRandomizer/Classes/Misc/Archipelago_NPC_BadgeSalesman.uc +++ b/APRandomizer/Classes/Misc/Archipelago_NPC_BadgeSalesman.uc @@ -119,9 +119,10 @@ function OpenShop(Controller c) continue; // Only hint progression - if (shopInfo.ItemFlags != ItemFlag_Important && shopInfo.ItemFlags != ItemFlag_ImportantSkipBalancing) + if (shopInfo.ItemFlags != ItemFlag_Important && shopInfo.ItemFlags != ItemFlag_ImportantSkipBalancing || shopInfo.Hinted) continue; + shopInfo.Hinted = true; shopLocationList.AddItem(shopInfo.ItemClass.default.LocationID); } @@ -129,6 +130,8 @@ function OpenShop(Controller c) { `AP.SendMultipleLocationChecks(shopLocationList, true, true); } + + `AP.SaveGame(); } function bool HasAnythingNewToSell(Controller pc) diff --git a/APRandomizer/modinfo.ini b/APRandomizer/modinfo.ini index 1aa87a474a0d4558f7e72d990dedaee022271154..719c677c7864c9cb5310dff0644894b75db1faa0 100644 GIT binary patch delta 368 zcmZ9Iv1$TQ5Jm5zO@gWXfrqWZG-9_^S}~A9N%=JHb76NC-3@GIYwwQ)@;CPWN6xGW z*)T9O+?hM~zIS@K^uBvhH2&#FRp(= typing.List[Item]: "Item Magnet Badge": ItemData(300028, ItemClassification.useful), "No Bonk Badge": ItemData(300029, ItemClassification.useful), "Compass Badge": ItemData(300030, ItemClassification.useful), - "Scooter Badge": ItemData(300031, ItemClassification.useful), + "Scooter Badge": ItemData(300031, ItemClassification.progression), "Badge Pin": ItemData(300043, ItemClassification.useful), # Other @@ -188,6 +187,13 @@ def create_junk_items(world: World, count: int) -> typing.List[Item]: "Snatcher's Contract - Mail Delivery Service": ItemData(300203, ItemClassification.progression), } +alps_hooks = { + "Zipline Unlock - The Birdhouse Path": ItemData(300204, ItemClassification.progression), + "Zipline Unlock - The Lava Cake Path": ItemData(300205, ItemClassification.progression), + "Zipline Unlock - The Windmill Path": ItemData(300206, ItemClassification.progression), + "Zipline Unlock - The Twilight Bell Path": ItemData(300207, ItemClassification.progression), +} + relic_groups = { "Burger": {"Relic (Burger Patty)", "Relic (Burger Cushion)"}, "Train": {"Relic (Mountain Set)", "Relic (Train)"}, @@ -213,6 +219,7 @@ def create_junk_items(world: World, count: int) -> typing.List[Item]: **ahit_items, **time_pieces, **act_contracts, + **alps_hooks, } lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in ahit_items.items() if data.code} diff --git a/ahit/Locations.py b/ahit/Locations.py index 9fb8f39..a848613 100644 --- a/ahit/Locations.py +++ b/ahit/Locations.py @@ -12,6 +12,10 @@ class LocData(NamedTuple): hookshot: Optional[bool] = False dlc_flags: Optional[HatDLC] = HatDLC.none + # For UmbrellaLogic setting + umbrella: Optional[bool] = False # Umbrella required for this check + dweller_bell: Optional[int] = 0 # Dweller bell hit required, 1 means must hit bell, 2 means can bypass w/mask + class HatInTimeLocation(Location): game: str = "A Hat in Time" @@ -52,7 +56,7 @@ def location_dlc_enabled(world: World, location: str) -> bool: ahit_locations = { - "Spaceship - Rumbi": LocData(301000, "Spaceship", required_tps=4), + "Spaceship - Rumbi": LocData(301000, "Spaceship", required_tps=4, dweller_bell=1), # same hit requirements, yes "Spaceship - Cooking Cat": LocData(301001, "Spaceship", required_tps=5), # 300000 range - Mafia Town/Batle of the Birds @@ -94,7 +98,7 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Mafia Town - Clock Tower Chest": LocData(303481, "Mafia Town Area", hookshot=True), "Mafia Town - Top of Lighthouse": LocData(304213, "Mafia Town Area", hookshot=True), "Mafia Town - Mafia Geek Platform": LocData(304212, "Mafia Town Area"), - "Mafia Town - Behind HQ Chest": LocData(303486, "Down with the Mafia!"), + "Mafia Town - Behind HQ Chest": LocData(303486, "Mafia Town Area"), "Mafia HQ - Hallway Brewing Crate": LocData(305387, "Down with the Mafia!", required_hats=[HatType.BREWING]), "Mafia HQ - Freezer Chest": LocData(303241, "Down with the Mafia!"), @@ -105,10 +109,10 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Dead Bird Studio - Red Building Top": LocData(305024, "Dead Bird Studio"), # Can be reached from basement "Dead Bird Studio - Behind Water Tower": LocData(305248, "Dead Bird Studio"), # Can be reached from basement "Dead Bird Studio - Side of House": LocData(305247, "Dead Bird Studio"), # Can be reached from basement - "Dead Bird Studio - DJ Grooves Sign Chest": LocData(303901, "Dead Bird Studio"), - "Dead Bird Studio - Tightrope Chest": LocData(303898, "Dead Bird Studio"), - "Dead Bird Studio - Tepee Chest": LocData(303899, "Dead Bird Studio"), - "Dead Bird Studio - Conductor Chest": LocData(303900, "Dead Bird Studio"), + "Dead Bird Studio - DJ Grooves Sign Chest": LocData(303901, "Dead Bird Studio", umbrella=True), + "Dead Bird Studio - Tightrope Chest": LocData(303898, "Dead Bird Studio", umbrella=True), + "Dead Bird Studio - Tepee Chest": LocData(303899, "Dead Bird Studio", umbrella=True), + "Dead Bird Studio - Conductor Chest": LocData(303900, "Dead Bird Studio", umbrella=True), "Murder on the Owl Express - Cafeteria": LocData(305313, "Murder on the Owl Express"), "Murder on the Owl Express - Luggage Room Top": LocData(305090, "Murder on the Owl Express"), @@ -150,7 +154,7 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Subcon Forest - Swamp Tree Chest": LocData(323728, "Subcon Forest Area"), "Subcon Forest - Dweller Stump": LocData(324767, "Subcon Forest Area", required_hats=[HatType.DWELLER]), "Subcon Forest - Dweller Floating Rocks": LocData(324464, "Subcon Forest Area", required_hats=[HatType.DWELLER]), - "Subcon Forest - Dweller Platforming Tree A": LocData(324709, "Subcon Forest Area", required_hats=[HatType.DWELLER]), + "Subcon Forest - Dweller Platforming Tree A": LocData(324709, "Subcon Forest Area"), "Subcon Forest - Dweller Platforming Tree B": LocData(324855, "Subcon Forest Area", required_hats=[HatType.DWELLER]), "Subcon Forest - Giant Time Piece": LocData(325473, "Subcon Forest Area"), "Subcon Forest - Gallows": LocData(325472, "Subcon Forest Area"), @@ -161,7 +165,7 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Subcon Forest - Burning House": LocData(324710, "Subcon Forest Area"), "Subcon Forest - Burning Tree Climb": LocData(325079, "Subcon Forest Area"), "Subcon Forest - Burning Stump Chest": LocData(323731, "Subcon Forest Area"), - "Subcon Forest - Burning Forest Treehouse": LocData(325467, "Subcon Forest Area"), + "Subcon Forest - Burning Forest Treehouse": LocData(325467, "Subcon Forest Area"), # Can hit the bell w/a cherry "Subcon Forest - Spider Bone Cage A": LocData(324462, "Subcon Forest Area"), "Subcon Forest - Spider Bone Cage B": LocData(325080, "Subcon Forest Area"), "Subcon Forest - Triple Spider Bounce": LocData(324765, "Subcon Forest Area"), @@ -169,15 +173,15 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Subcon Forest - Ice Cube Shack": LocData(324465, "Subcon Forest Area"), "Subcon Forest - Long Tree Climb Chest": LocData(323734, "Subcon Forest Area", required_hats=[HatType.DWELLER]), "Subcon Forest - Boss Arena Chest": LocData(323735, "Subcon Forest Area"), - "Subcon Well - Hookshot Badge Chest": LocData(324114, "The Subcon Well"), - "Subcon Well - Above Chest": LocData(324612, "The Subcon Well"), - "Subcon Well - On Pipe": LocData(324311, "The Subcon Well", hookshot=True), - "Subcon Well - Mushroom": LocData(325318, "The Subcon Well"), - "Queen Vanessa's Manor - Rooftop": LocData(325466, "Queen Vanessa's Manor"), - "Queen Vanessa's Manor - Cellar": LocData(324841, "Queen Vanessa's Manor"), - "Queen Vanessa's Manor - Bedroom Chest": LocData(323808, "Queen Vanessa's Manor"), - "Queen Vanessa's Manor - Hall Chest": LocData(323896, "Queen Vanessa's Manor"), - "Queen Vanessa's Manor - Chandelier": LocData(325546, "Queen Vanessa's Manor"), + "Subcon Forest - Manor Rooftop": LocData(325466, "Subcon Forest Area", dweller_bell=2), + "Subcon Well - Hookshot Badge Chest": LocData(324114, "The Subcon Well", dweller_bell=1), + "Subcon Well - Above Chest": LocData(324612, "The Subcon Well", dweller_bell=1), + "Subcon Well - On Pipe": LocData(324311, "The Subcon Well", hookshot=True, dweller_bell=1), + "Subcon Well - Mushroom": LocData(325318, "The Subcon Well", dweller_bell=1), + "Queen Vanessa's Manor - Cellar": LocData(324841, "Queen Vanessa's Manor", dweller_bell=2), + "Queen Vanessa's Manor - Bedroom Chest": LocData(323808, "Queen Vanessa's Manor", dweller_bell=2), + "Queen Vanessa's Manor - Hall Chest": LocData(323896, "Queen Vanessa's Manor", dweller_bell=2), + "Queen Vanessa's Manor - Chandelier": LocData(325546, "Queen Vanessa's Manor", dweller_bell=2), # 330000 range - Alpine Skyline "Alpine Skyline - Goat Village: Below Hookpoint": LocData(334856, "Goat Village"), @@ -204,8 +208,7 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Alpine Skyline - The Lava Cake: Top Cake": LocData(335418, "The Lava Cake"), "Alpine Skyline - The Twilight Path": LocData(334434, "Alpine Free Roam", required_hats=[HatType.DWELLER]), "Alpine Skyline - The Twilight Bell: Wide Purple Platform": LocData(336478, "The Twilight Bell"), - "Alpine Skyline - The Twilight Bell: Ice Platform": LocData(335826, "The Twilight Bell", - required_hats=[HatType.ICE]), + "Alpine Skyline - The Twilight Bell: Ice Platform": LocData(335826, "The Twilight Bell"), "Alpine Skyline - Goat Outpost Horn": LocData(334760, "Alpine Free Roam"), "Alpine Skyline - Windy Passage": LocData(334776, "Alpine Free Roam"), "Alpine Skyline - The Windmill: Inside Pon Cluster": LocData(336395, "The Windmill"), @@ -226,17 +229,17 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Act Completion (She Came from Outer Space)": LocData(312262, "She Came from Outer Space"), "Act Completion (Down with the Mafia!)": LocData(311326, "Down with the Mafia!"), "Act Completion (Cheating the Race)": LocData(312318, "Cheating the Race"), - "Act Completion (Heating Up Mafia Town)": LocData(311481, "Heating Up Mafia Town"), + "Act Completion (Heating Up Mafia Town)": LocData(311481, "Heating Up Mafia Town", umbrella=True), "Act Completion (The Golden Vault)": LocData(312250, "The Golden Vault"), "Act Completion (Time Rift - Bazaar)": LocData(312465, "Time Rift - Bazaar"), "Act Completion (Time Rift - Sewers)": LocData(312484, "Time Rift - Sewers"), "Act Completion (Time Rift - Mafia of Cooks)": LocData(311855, "Time Rift - Mafia of Cooks"), - "Act Completion (Dead Bird Studio)": LocData(311383, "Dead Bird Studio"), + "Act Completion (Dead Bird Studio)": LocData(311383, "Dead Bird Studio", umbrella=True), "Act Completion (Murder on the Owl Express)": LocData(311544, "Murder on the Owl Express"), "Act Completion (Picture Perfect)": LocData(311587, "Picture Perfect"), "Act Completion (Train Rush)": LocData(312481, "Train Rush", hookshot=True), - "Act Completion (The Big Parade)": LocData(311157, "The Big Parade"), + "Act Completion (The Big Parade)": LocData(311157, "The Big Parade", umbrella=True), "Act Completion (Award Ceremony)": LocData(311488, "Award Ceremony"), "Act Completion (Dead Bird Studio Basement)": LocData(312253, "Dead Bird Studio Basement", hookshot=True), "Act Completion (Time Rift - The Owl Express)": LocData(312807, "Time Rift - The Owl Express"), @@ -244,11 +247,11 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Act Completion (Time Rift - Dead Bird Studio)": LocData(312577, "Time Rift - Dead Bird Studio"), "Act Completion (Contractual Obligations)": LocData(312317, "Contractual Obligations"), - "Act Completion (The Subcon Well)": LocData(311160, "The Subcon Well", hookshot=True), + "Act Completion (The Subcon Well)": LocData(311160, "The Subcon Well", hookshot=True, umbrella=True), "Act Completion (Toilet of Doom)": LocData(311984, "Toilet of Doom", hookshot=True), - "Act Completion (Queen Vanessa's Manor)": LocData(312017, "Queen Vanessa's Manor"), + "Act Completion (Queen Vanessa's Manor)": LocData(312017, "Queen Vanessa's Manor", umbrella=True), "Act Completion (Mail Delivery Service)": LocData(312032, "Mail Delivery Service", required_hats=[HatType.SPRINT]), - "Act Completion (Your Contract has Expired)": LocData(311390, "Your Contract has Expired"), + "Act Completion (Your Contract has Expired)": LocData(311390, "Your Contract has Expired", umbrella=True), "Act Completion (Time Rift - Pipe)": LocData(313069, "Time Rift - Pipe", hookshot=True), "Act Completion (Time Rift - Village)": LocData(313056, "Time Rift - Village"), "Act Completion (Time Rift - Sleepy Subcon)": LocData(312086, "Time Rift - Sleepy Subcon"), @@ -308,6 +311,23 @@ def location_dlc_enabled(world: World, location: str) -> bool: "Snatcher's Contract - Mail Delivery Service": LocData(300203, "Subcon Forest Area"), } +# Don't put any of the items from peaks here, the rules for those entrances are set already +zipline_unlocks = { + "Alpine Skyline - Bird Pass Fork": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - Yellow Band Hills": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - The Purrloined Village: Horned Stone": "Zipline Unlock - The Birdhouse Path", + "Alpine Skyline - The Purrloined Village: Chest Reward": "Zipline Unlock - The Birdhouse Path", + + "Alpine Skyline - Mystifying Time Mesa: Zipline": "Zipline Unlock - The Lava Cake Path", + "Alpine Skyline - Mystifying Time Mesa: Gate Puzzle": "Zipline Unlock - The Lava Cake Path", + "Alpine Skyline - Ember Summit": "Zipline Unlock - The Lava Cake Path", + + "Alpine Skyline - Goat Outpost Horn": "Zipline Unlock - The Windmill Path", + "Alpine Skyline - Windy Passage": "Zipline Unlock - The Windmill Path", + + "Alpine Skyline - The Twilight Path": "Zipline Unlock - The Twilight Bell Path", +} + shop_locations = { "Badge Seller - Item 1": LocData(301003, "Badge Seller"), "Badge Seller - Item 2": LocData(301004, "Badge Seller"), diff --git a/ahit/Options.py b/ahit/Options.py index 77fdf10..9a68ddd 100644 --- a/ahit/Options.py +++ b/ahit/Options.py @@ -9,12 +9,42 @@ class ActRandomizer(Toggle): default = 1 +class ShuffleAlpineZiplines(Toggle): + """If enabled, Alpine's zipline paths leading to the peaks will be locked behind items.""" + display_name = "Shuffle Alpine Ziplines" + default = 0 + + +class VanillaAlpine(Choice): + """If enabled, force Alpine (and optionally its finale) onto their vanilla locations in act shuffle.""" + display_name = "Vanilla Alpine Skyline" + option_no = 0 + option_yes = 1 + option_finale = 2 + default = 0 + + +class LogicDifficulty(Choice): + """Choose the difficulty setting for logic. Note that Hard or above will force SDJ logic on.""" + display_name = "Logic Difficulty" + option_normal = 0 + option_hard = 1 + option_expert = 2 + default = 0 + + class RandomizeHatOrder(Toggle): """Randomize the order that hats are stitched in.""" display_name = "Randomize Hat Order" default = 1 +class UmbrellaLogic(Toggle): + """Makes Hat Kid's default punch attack do absolutely nothing, making the Umbrella much more relevant and useful""" + display_name = "Umbrella Logic" + default = 0 + + class StartWithCompassBadge(Toggle): """If enabled, start with the Compass Badge. In Archipelago, the Compass Badge will track all items in the world (instead of just Relics). Recommended if you're not familiar with where item locations are.""" @@ -241,7 +271,11 @@ class ParadeTrapWeight(Range): ahit_options: typing.Dict[str, type(Option)] = { "ActRandomizer": ActRandomizer, + "ShuffleAlpineZiplines": ShuffleAlpineZiplines, + "VanillaAlpine": VanillaAlpine, + "LogicDifficulty": LogicDifficulty, "RandomizeHatOrder": RandomizeHatOrder, + "UmbrellaLogic": UmbrellaLogic, "StartWithCompassBadge": StartWithCompassBadge, "CompassBadgeMode": CompassBadgeMode, "ShuffleStorybookPages": ShuffleStorybookPages, diff --git a/ahit/Regions.py b/ahit/Regions.py index 2b2531a..3f49474 100644 --- a/ahit/Regions.py +++ b/ahit/Regions.py @@ -163,6 +163,14 @@ "Time Rift - Gallery", ] +umbrella_logic_act_blacklist = [ + "Your Contract has Expired", + "Heating Up Mafia Town", + "Queen Vanessa's Manor", + "Dead Bird Studio", + "The Big Parade", +] + # One of the first three acts of a chapter in act rando must be one of these first_chapter_guaranteed_acts = [ "Welcome to Mafia Town", @@ -341,6 +349,9 @@ def randomize_act_entrances(world: World): act_whitelist: typing.List[Region] = [] for region in region_list.copy(): if region.name not in first_chapter_blacklist: + if world.multiworld.UmbrellaLogic[world.player].value > 0 and region.name in umbrella_logic_act_blacklist: + continue + act_whitelist.append(region) has_guaranteed_act: bool = False @@ -397,30 +408,38 @@ def randomize_act_entrances(world: World): # First, map Alpine Free Roam to something other than its own Time Rift alpine = world.multiworld.get_region("Alpine Free Roam", world.player) - e = entrance_list[world.multiworld.random.randint(0, len(entrance_list) - 1)] + original_region: Region - # Obviously don't map it to the finale, that's impossible - while e.name == "Alpine Skyline - Finale": + if world.multiworld.VanillaAlpine[world.player].value == 0: e = entrance_list[world.multiworld.random.randint(0, len(entrance_list) - 1)] - original_region: Region + # Obviously don't map it to the finale, that's impossible + while e.name == "Alpine Skyline - Finale": + e = entrance_list[world.multiworld.random.randint(0, len(entrance_list) - 1)] - # There are about 40% Time Rifts vs Acts in the game, so 40% chance to be mapped to a Time Rift - if world.multiworld.random.randint(1, 5) > 2: - original_region = e.connected_region - reconnect_regions(e, e.parent_region, alpine) - world.update_chapter_act_info(original_region, alpine) + # There are about 40% Time Rifts vs Acts in the game, so 40% chance to be mapped to a Time Rift + if world.multiworld.random.randint(1, 5) > 2: + original_region = e.connected_region + reconnect_regions(e, e.parent_region, alpine) + world.update_chapter_act_info(original_region, alpine) + else: + original_region = time_rifts[world.multiworld.random.randint(0, len(time_rifts) - 1)] + connect_time_rift(original_region, alpine, e, world) + world.update_chapter_act_info(original_region, alpine) + rift_dict.setdefault(original_region.name, alpine) + time_rifts.remove(original_region) + + entrance_list.remove(e) + alpine_freeroam_chapter = [index for index, name in chapter_regions.items() + if name == act_chapters[original_region.name]][0] else: - original_region = time_rifts[world.multiworld.random.randint(0, len(time_rifts) - 1)] - connect_time_rift(original_region, alpine, e, world) - world.update_chapter_act_info(original_region, alpine) - rift_dict.setdefault(original_region.name, alpine) - time_rifts.remove(original_region) - - alpine_freeroam_chapter = [index for index, name in chapter_regions.items() - if name == act_chapters[original_region.name]][0] + alpine_freeroam_chapter = ChapterIndex.ALPINE + entrance_list.remove(alpine.entrances[0]) + if world.multiworld.VanillaAlpine[world.player].value == 2: + alpine_finale = world.multiworld.get_region("The Illness has Spread", world.player) + region_list.remove(alpine_finale) + entrance_list.remove(alpine_finale.entrances[0]) - entrance_list.remove(e) region_list.remove(alpine) while len(entrance_list) > 0 or len(region_list) > 0 or len(time_rifts) > 0: diff --git a/ahit/Rules.py b/ahit/Rules.py index 37efbb4..66ab6a2 100644 --- a/ahit/Rules.py +++ b/ahit/Rules.py @@ -1,6 +1,6 @@ from worlds.AutoWorld import World, CollectionState from worlds.generic.Rules import add_rule, set_rule -from .Locations import location_table, humt_locations, tihs_locations, storybook_pages +from .Locations import location_table, humt_locations, tihs_locations, storybook_pages, zipline_unlocks from .Types import HatType, ChapterIndex from BaseClasses import Location, Entrance, Region import typing @@ -57,6 +57,13 @@ def get_relic_count(state: CollectionState, world: World, relic: str) -> int: return state.count_group(relic, world.player) +def can_hit_bells(state: CollectionState, world: World): + if world.multiworld.UmbrellaLogic[world.player].value == 0: + return True + + return state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING) + + # Only use for rifts def can_clear_act(state: CollectionState, world: World, act_entrance: str) -> bool: entrance: Entrance = world.multiworld.get_entrance(act_entrance, world.player) @@ -133,17 +140,12 @@ def set_rules(world: World): add_rule(mw.get_entrance("-> Alpine Skyline", p), lambda state: state.has_group("Time Pieces", p, w.get_chapter_cost(ChapterIndex.ALPINE))) - add_rule(mw.get_entrance("-> The Birdhouse", p), - lambda state: can_use_hat(state, w, HatType.BREWING)) - - add_rule(mw.get_entrance("-> The Twilight Bell", p), - lambda state: can_use_hat(state, w, HatType.DWELLER)) - add_rule(mw.get_entrance("-> Time's End", p), lambda state: state.has_group("Time Pieces", p, w.get_chapter_cost(ChapterIndex.FINALE)) and can_use_hat(state, w, HatType.BREWING) and can_use_hat(state, w, HatType.DWELLER)) set_indirect_connections(w) + if mw.ActRandomizer[p].value == 0: set_default_rift_rules(w) @@ -169,6 +171,69 @@ def set_rules(world: World): if data.hookshot: add_rule(location, lambda state: can_use_hookshot(state, w)) + if data.umbrella and mw.UmbrellaLogic[p].value > 0: + add_rule(location, lambda state: state.has("Umbrella", p)) + + if data.dweller_bell > 0: + if data.dweller_bell == 1: # Required to be hit regardless of Dweller Mask + add_rule(location, lambda state: can_hit_bells(state, w)) + else: # Can bypass with Dweller Mask + add_rule(location, lambda state: can_hit_bells(state, w) or can_use_hat(state, w, HatType.DWELLER)) + + set_specific_rules(w) + + if mw.LogicDifficulty[p].value >= 1: + mw.SDJLogic[p].value = 1 + + if mw.SDJLogic[p].value > 0: + set_sdj_rules(world) + + if mw.ShuffleAlpineZiplines[p].value > 0: + set_alps_zipline_rules(w) + + for (key, acts) in act_connections.items(): + i: int = 1 + entrance: Entrance = mw.get_entrance(key, p) + region: Region = entrance.connected_region + access_rules: typing.List[typing.Callable[[CollectionState], bool]] = [] + entrance.parent_region.exits.remove(entrance) + entrance.parent_region = None + + # Entrances to this act that we have to set access_rules on + entrances: typing.List[Entrance] = [] + + for act in acts: + act_entrance: Entrance = mw.get_entrance(act, p) + access_rules.append(act_entrance.access_rule) + required_region = act_entrance.connected_region + name: str = format("%s: Connection %i" % (key, i)) + new_entrance: Entrance = connect_regions(required_region, region, name, p) + entrances.append(new_entrance) + + # Copy access rules from act completions + if "Free Roam" not in required_region.name: + rule: typing.Callable[[CollectionState], bool] + name = format("Act Completion (%s)" % required_region.name) + rule = mw.get_location(name, p).access_rule + access_rules.append(rule) + + i += 1 + + for e in entrances: + for rules in access_rules: + add_rule(e, rules) + + mw.completion_condition[p] = lambda state: can_use_hat(state, w, HatType.BREWING) \ + and can_use_hat(state, w, HatType.DWELLER) \ + and can_use_hookshot(state, w) \ + and state.has_group("Time Pieces", p, w.get_chapter_cost(ChapterIndex.FINALE)) + + +def set_specific_rules(world: World): + mw = world.multiworld + w = world + p = world.player + add_rule(mw.get_entrance("Spaceship - Time Rift A", p), lambda state: can_use_hat(state, w, HatType.BREWING) and state.has_group("Time Pieces", p, w.get_chapter_cost(ChapterIndex.BIRDS))) @@ -180,6 +245,23 @@ def set_rules(world: World): add_rule(mw.get_entrance("Alpine Skyline - Finale", p), lambda state: can_clear_alpine(state, w)) + # Normal logic + if mw.LogicDifficulty[p].value == 0: + add_rule(mw.get_entrance("-> The Birdhouse", p), + lambda state: can_use_hat(state, w, HatType.BREWING)) + + # Hard logic, includes SDJ stuff + if mw.LogicDifficulty[p].value >= 1: + add_rule(mw.get_location("Act Completion (Time Rift - The Twilight Bell)", p), + lambda state: can_use_hat(state, w, HatType.SPRINT) and state.has("Scooter Badge", p), "or") + + # Expert logic + if mw.LogicDifficulty[p].value >= 2: + set_rule(mw.get_location("Alpine Skyline - The Twilight Path", p), lambda state: True) + else: + add_rule(mw.get_entrance("-> The Twilight Bell", p), + lambda state: can_use_hat(state, w, HatType.DWELLER)) + # Cooking Cat requires the player to either have a full relic set, or have 1 relic missing from a set # AND have the base piece add_rule(mw.get_location("Spaceship - Cooking Cat", p), @@ -188,6 +270,12 @@ def set_rules(world: World): or (state.count_group("UFO", p) >= 3 and state.has("Relic (UFO)", p)) or (state.count_group("Crayon", p) >= 3 and state.has("Relic (Crayon Box)", p))) + add_rule(mw.get_location("Mafia Town - Behind HQ Chest", p), + lambda state: state.can_reach("Act Completion (Heating Up Mafia Town)", "Location", p) + or state.can_reach("Down with the Mafia!", "Region", p) + or state.can_reach("Cheating the Race", "Region", p) + or state.can_reach("The Golden Vault", "Region", p)) + # Old guys don't appear in SCFOS add_rule(mw.get_location("Mafia Town - Old Man (Steel Beams)", p), lambda state: state.can_reach("Welcome to Mafia Town", "Region", p) @@ -247,6 +335,10 @@ def set_rules(world: World): lambda state: state.can_reach("Toilet of Doom", "Region", p) or state.can_reach("Your Contract has Expired", "Region", p)) + if mw.UmbrellaLogic[p].value > 0: + add_rule(mw.get_location("Act Completion (Toilet of Doom)", p), + lambda state: state.has("Umbrella", p) or can_use_hat(state, w, HatType.BREWING)) + set_rule(mw.get_location("Act Completion (Time Rift - Village)", p), lambda state: can_use_hat(state, w, HatType.BREWING) or state.has("Umbrella", p) or can_use_hat(state, w, HatType.DWELLER)) @@ -268,63 +360,45 @@ def set_rules(world: World): for entrance in mw.get_region("Alpine Free Roam", p).entrances: add_rule(entrance, lambda state: can_use_hookshot(state, w)) + if mw.UmbrellaLogic[p].value > 0: + add_rule(entrance, lambda state: state.has("Umbrella", p)) - if mw.SDJLogic[p].value > 0: - set_sdj_rules(world) - - for (key, acts) in act_connections.items(): - i: int = 1 - entrance: Entrance = mw.get_entrance(key, p) - region: Region = entrance.connected_region - access_rules: typing.List[typing.Callable[[CollectionState], bool]] = [] - entrance.parent_region.exits.remove(entrance) - entrance.parent_region = None - # Entrances to this act that we have to set access_rules on - entrances: typing.List[Entrance] = [] +def set_sdj_rules(world: World): + add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), + lambda state: can_sdj(state, world), "or") - for act in acts: - act_entrance: Entrance = mw.get_entrance(act, p) - access_rules.append(act_entrance.access_rule) - required_region = act_entrance.connected_region - name: str = format("%s: Connection %i" % (key, i)) - new_entrance: Entrance = connect_regions(required_region, region, name, p) - entrances.append(new_entrance) + add_rule(world.multiworld.get_location("Subcon Forest - Green and Purple Dweller Rocks", world.player), + lambda state: can_sdj(state, world), "or") - # Copy access rules from act completions - if "Free Roam" not in required_region.name: - rule: typing.Callable[[CollectionState], bool] - name = format("Act Completion (%s)" % required_region.name) - rule = mw.get_location(name, p).access_rule - access_rules.append(rule) + add_rule(world.multiworld.get_location("Alpine Skyline - The Birdhouse: Dweller Platforms Relic", world.player), + lambda state: can_sdj(state, world), "or") - i += 1 + add_rule(world.multiworld.get_location("Alpine Skyline - The Twilight Bell: Ice Platform", world.player), + lambda state: can_sdj(state, world), "or") - for e in entrances: - for rules in access_rules: - add_rule(e, rules) + add_rule(world.multiworld.get_location("Act Completion (Time Rift - Gallery)", world.player), + lambda state: can_sdj(state, world), "or") - mw.completion_condition[p] = lambda state: can_use_hat(state, w, HatType.BREWING) \ - and can_use_hat(state, w, HatType.DWELLER) \ - and can_use_hookshot(state, w) \ - and state.has_group("Time Pieces", p, w.get_chapter_cost(ChapterIndex.FINALE)) + add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player), + lambda state: can_sdj(state, world), "or") -def set_sdj_rules(world: World): - set_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), - lambda state: can_use_hat(state, world, HatType.DWELLER) or can_sdj(state, world)) +def set_alps_zipline_rules(world: World): + add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), + lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player)) - set_rule(world.multiworld.get_location("Alpine Skyline - The Birdhouse: Dweller Platforms Relic", world.player), - lambda state: can_use_hat(state, world, HatType.DWELLER) or can_sdj(state, world)) + add_rule(world.multiworld.get_entrance("-> The Lava Cake", world.player), + lambda state: state.has("Zipline Unlock - The Lava Cake Path", world.player)) - set_rule(world.multiworld.get_location("Alpine Skyline - The Twilight Bell: Ice Platform", world.player), - lambda state: can_use_hat(state, world, HatType.ICE) or can_sdj(state, world)) + add_rule(world.multiworld.get_entrance("-> The Windmill", world.player), + lambda state: state.has("Zipline Unlock - The Windmill Path", world.player)) - set_rule(world.multiworld.get_location("Act Completion (Time Rift - Gallery)", world.player), - lambda state: can_use_hat(state, world, HatType.BREWING) or can_sdj(state, world)) + add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player), + lambda state: state.has("Zipline Unlock - The Twilight Bell Path", world.player)) - set_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player), - lambda state: can_use_hat(state, world, HatType.ICE) or can_sdj(state, world)) + for (loc, zipline) in zipline_unlocks.items(): + add_rule(world.multiworld.get_location(loc, world.player), lambda state: state.has(zipline, world.player)) def reg_act_connection(world: World, region: typing.Union[str, Region], unlocked_entrance: typing.Union[str, Entrance]): diff --git a/ahit/__init__.py b/ahit/__init__.py index f63d1ab..c9d6d50 100644 --- a/ahit/__init__.py +++ b/ahit/__init__.py @@ -1,10 +1,10 @@ from BaseClasses import Item, ItemClassification, Region, LocationProgressType from .Items import HatInTimeItem, item_table, time_pieces, item_frequencies, item_dlc_enabled, junk_weights,\ - create_item, create_multiple_items, create_junk_items, relic_groups, act_contracts + create_item, create_multiple_items, create_junk_items, relic_groups, act_contracts, alps_hooks from .Regions import create_region, create_regions, connect_regions, randomize_act_entrances, chapter_act_info, \ - create_events + create_events, chapter_regions, act_chapters from .Locations import HatInTimeLocation, location_table, get_total_locations, contract_locations from .Types import HatDLC, HatType, ChapterIndex @@ -33,6 +33,11 @@ class HatInTimeWorld(World): chapter_timepiece_costs: typing.Dict[ChapterIndex, int] act_connections: typing.Dict[str, str] = {} + item_name_groups = relic_groups + item_name_groups["Time Pieces"] = set({}) + for name in time_pieces.keys(): + item_name_groups["Time Pieces"].add(name) + def generate_early(self): # If our starting chapter is 4, force hookshot into inventory. # Starting chapter 3/4 is banned in act rando, because they don't have enough starting acts @@ -65,10 +70,6 @@ def create_items(self): if self.multiworld.RandomizeHatOrder[self.player].value > 0: self.multiworld.random.shuffle(self.hat_craft_order) - self.item_name_groups = {"Time Pieces": set({})} - for key, relics in relic_groups.items(): - self.item_name_groups.setdefault(key, set(relics)) - for name in item_table.keys(): if name == "Yarn": continue @@ -83,8 +84,8 @@ def create_items(self): if name in act_contracts.keys() and self.multiworld.ShuffleActContracts[self.player].value == 0: continue - if name in time_pieces.keys(): - self.item_name_groups["Time Pieces"].add(name) + if name in alps_hooks.keys() and self.multiworld.ShuffleAlpineZiplines[self.player].value == 0: + continue itempool += create_multiple_items(self, name, item_frequencies.get(name, 1)) @@ -158,6 +159,17 @@ def fill_slot_data(self) -> dict: return slot_data + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): + new_hint_data = {} + for key, data in location_table.items(): + if data.region not in act_chapters.keys(): + continue + + location = self.multiworld.get_location(key, self.player) + new_hint_data[location.address] = self.get_shuffled_region(location.parent_region.name) + + hint_data[self.player] = new_hint_data + def calculate_yarn_costs(self): mw = self.multiworld p = self.player @@ -190,3 +202,16 @@ def update_chapter_act_info(self, original_region: Region, new_region: Region): original_act_info = chapter_act_info[original_region.name] new_act_info = chapter_act_info[new_region.name] self.act_connections[original_act_info] = new_act_info + + def get_shuffled_region(self, region: str) -> str: + if region not in chapter_act_info.keys(): + return region + + ci: str = chapter_act_info[region] + for name in self.act_connections.keys(): + if ci == name: + for key in chapter_act_info.keys(): + if chapter_act_info[key] == self.act_connections[ci]: + return key + + return ""