diff --git a/worlds/mlss/Client.py b/worlds/mlss/Client.py index 75f6ac65..17d87197 100644 --- a/worlds/mlss/Client.py +++ b/worlds/mlss/Client.py @@ -269,7 +269,7 @@ class MLSSClient(BizHawkClient): self.local_checked_locations = locs_to_send if locs_to_send is not None: - await ctx.send_msgs([{"cmd": "LocationChecks", "locations": list(locs_to_send)}]) + await ctx.check_locations(locs_to_send) except bizhawk.RequestFailedError: # Exit handler and return to main loop to reconnect. diff --git a/worlds/mlss/Data.py b/worlds/mlss/Data.py index add14aa0..7e3605f1 100644 --- a/worlds/mlss/Data.py +++ b/worlds/mlss/Data.py @@ -153,7 +153,6 @@ enemies = [ 0x50458C, 0x5045AC, 0x50468C, - # 0x5046CC, 6 enemy formation 0x5046EC, 0x50470C ] @@ -166,6 +165,7 @@ bosses = [ 0x50360C, 0x5037AC, 0x5037CC, + 0x50396C, 0x503A8C, 0x503D6C, 0x503F0C, diff --git a/worlds/mlss/Items.py b/worlds/mlss/Items.py index 717443dd..5fca829b 100644 --- a/worlds/mlss/Items.py +++ b/worlds/mlss/Items.py @@ -160,6 +160,7 @@ itemList: typing.List[ItemData] = [ ItemData(77771142, "Game Boy Horror SP", ItemClassification.useful, 0xFE), ItemData(77771143, "Woo Bean", ItemClassification.skip_balancing, 0x1C), ItemData(77771144, "Hee Bean", ItemClassification.skip_balancing, 0x1F), + ItemData(77771145, "Beanstar Emblem", ItemClassification.progression, 0x3E), ] item_frequencies: typing.Dict[str, int] = { @@ -186,5 +187,12 @@ item_frequencies: typing.Dict[str, int] = { "Hammers": 3, } +mlss_item_name_groups = { + "Beanstar Piece": { "Beanstar Piece 1", "Beanstar Piece 2", "Beanstar Piece 3", "Beanstar Piece 4"}, + "Beanfruit": { "Bean Fruit 1", "Bean Fruit 2", "Bean Fruit 3", "Bean Fruit 4", "Bean Fruit 5", "Bean Fruit 6", "Bean Fruit 7"}, + "Neon Egg": { "Blue Neon Egg", "Red Neon Egg", "Green Neon Egg", "Yellow Neon Egg", "Purple Neon Egg", "Orange Neon Egg", "Azure Neon Egg"}, + "Chuckola Fruit": { "Red Chuckola Fruit", "Purple Chuckola Fruit", "White Chuckola Fruit"} +} + item_table: typing.Dict[str, ItemData] = {item.itemName: item for item in itemList} items_by_id: typing.Dict[int, ItemData] = {item.code: item for item in itemList} diff --git a/worlds/mlss/Locations.py b/worlds/mlss/Locations.py index a2787ef9..9a114bc4 100644 --- a/worlds/mlss/Locations.py +++ b/worlds/mlss/Locations.py @@ -251,9 +251,9 @@ coins: typing.List[LocationData] = [ LocationData("Hoohoo Village North Cave Room 1 Coin Block", 0x39DAA0, 0), LocationData("Hoohoo Village South Cave Coin Block 1", 0x39DAC5, 0), LocationData("Hoohoo Village South Cave Coin Block 2", 0x39DAD5, 0), - LocationData("Hoohoo Mountain Base Boo Statue Cave Coin Block 1", 0x39DAE2, 0), - LocationData("Hoohoo Mountain Base Boo Statue Cave Coin Block 2", 0x39DAF2, 0), - LocationData("Hoohoo Mountain Base Boo Statue Cave Coin Block 3", 0x39DAFA, 0), + LocationData("Hoohoo Mountain Base Boostatue Cave Coin Block 1", 0x39DAE2, 0), + LocationData("Hoohoo Mountain Base Boostatue Cave Coin Block 2", 0x39DAF2, 0), + LocationData("Hoohoo Mountain Base Boostatue Cave Coin Block 3", 0x39DAFA, 0), LocationData("Beanbean Outskirts NW Coin Block", 0x39DB8F, 0), LocationData("Beanbean Outskirts S Room 1 Coin Block", 0x39DC18, 0), LocationData("Beanbean Outskirts S Room 2 Coin Block", 0x39DC3D, 0), @@ -262,6 +262,8 @@ coins: typing.List[LocationData] = [ LocationData("Chucklehuck Woods Cave Room 1 Coin Block", 0x39DD7A, 0), LocationData("Chucklehuck Woods Cave Room 2 Coin Block", 0x39DD97, 0), LocationData("Chucklehuck Woods Cave Room 3 Coin Block", 0x39DDB4, 0), + LocationData("Chucklehuck Woods Solo Luigi Cave Room 1 Coin Block 1", 0x39DB48, 0), + LocationData("Chucklehuck Woods Solo Luigi Cave Room 1 Coin Block 2", 0x39DB50, 0), LocationData("Chucklehuck Woods Pipe 5 Room Coin Block", 0x39DDE6, 0), LocationData("Chucklehuck Woods Room 7 Coin Block", 0x39DE31, 0), LocationData("Chucklehuck Woods Past Chuckleroot Coin Block", 0x39DF14, 0), @@ -289,6 +291,7 @@ baseUltraRocks: typing.List[LocationData] = [ LocationData("Teehee Valley Upper Maze Room 1 Block", 0x39E5E0, 0), LocationData("Teehee Valley Upper Maze Room 2 Digspot 1", 0x39E5C8, 0), LocationData("Teehee Valley Upper Maze Room 2 Digspot 2", 0x39E5D0, 0), + LocationData("Guffawha Ruins Block", 0x39E6A3, 0), LocationData("Hoohoo Mountain Base Guffawha Ruins Entrance Digspot", 0x39DA0B, 0), LocationData("Hoohoo Mountain Base Teehee Valley Entrance Digspot", 0x39DA20, 0), LocationData("Hoohoo Mountain Base Teehee Valley Entrance Block", 0x39DA18, 0), @@ -298,7 +301,7 @@ booStatue: typing.List[LocationData] = [ LocationData("Beanbean Outskirts Before Harhall Digspot 1", 0x39E951, 0), LocationData("Beanbean Outskirts Before Harhall Digspot 2", 0x39E959, 0), LocationData("Beanstar Piece Harhall", 0x1E9441, 2), - LocationData("Beanbean Outskirts Boo Statue Mole", 0x1E9434, 2), + LocationData("Beanbean Outskirts Boostatue Mole", 0x1E9434, 2), LocationData("Harhall's Pants", 0x1E9444, 2), LocationData("Beanbean Outskirts S Room 2 Digspot 1", 0x39DC65, 0), LocationData("Beanbean Outskirts S Room 2 Digspot 2", 0x39DC5D, 0), @@ -317,6 +320,9 @@ chucklehuck: typing.List[LocationData] = [ LocationData("Chucklehuck Woods Cave Room 1 Block 2", 0x39DD8A, 0), LocationData("Chucklehuck Woods Cave Room 2 Block", 0x39DD9F, 0), LocationData("Chucklehuck Woods Cave Room 3 Block", 0x39DDAC, 0), + LocationData("Chucklehuck Woods Solo Luigi Cave Room 2 Block", 0x39DB72, 0), + LocationData("Chucklehuck Woods Solo Luigi Cave Room 3 Block 1", 0x39DB5D, 0), + LocationData("Chucklehuck Woods Solo Luigi Cave Room 3 Block 2", 0x39DB65, 0), LocationData("Chucklehuck Woods Room 2 Block", 0x39DDC1, 0), LocationData("Chucklehuck Woods Room 2 Digspot", 0x39DDC9, 0), LocationData("Chucklehuck Woods Pipe Room Block 1", 0x39DDD6, 0), @@ -786,7 +792,7 @@ nonBlock = [ (0x4373, 0x10, 0x277A45), # Teehee Valley Mole (0x434D, 0x8, 0x1E9444), # Harhall's Pants (0x432E, 0x10, 0x1E9441), # Harhall Beanstar Piece - (0x434B, 0x8, 0x1E9434), # Outskirts Boo Statue Mole + (0x434B, 0x8, 0x1E9434), # Outskirts Boostatue Mole (0x42FE, 0x2, 0x1E943E), # Red Goblet (0x42FE, 0x4, 0x24E628), # Green Goblet (0x4301, 0x10, 0x250621), # Red Chuckola Fruit diff --git a/worlds/mlss/Names/LocationName.py b/worlds/mlss/Names/LocationName.py index 5b38b2a1..43b75bf5 100644 --- a/worlds/mlss/Names/LocationName.py +++ b/worlds/mlss/Names/LocationName.py @@ -59,7 +59,7 @@ class LocationName: HoohooMountainBaseBoostatueRoomDigspot1 = "Hoohoo Mountain Base Boostatue Room Digspot 1" HoohooMountainBaseBoostatueRoomDigspot2 = "Hoohoo Mountain Base Boostatue Room Digspot 2" HoohooMountainBaseBoostatueRoomDigspot3 = "Hoohoo Mountain Base Boostatue Room Digspot 3" - BeanbeanOutskirtsBooStatueMole = "Beanbean Outskirts Boo Statue Mole" + BeanbeanOutskirtsBooStatueMole = "Beanbean Outskirts Boostatue Mole" HoohooMountainBaseGrassyAreaBlock1 = "Hoohoo Mountain Base Grassy Area Block 1" HoohooMountainBaseGrassyAreaBlock2 = "Hoohoo Mountain Base Grassy Area Block 2" HoohooMountainBaseGuffawhaRuinsEntranceDigspot = "Hoohoo Mountain Base Guffawha Ruins Entrance Digspot" @@ -533,9 +533,9 @@ class LocationName: BadgeShopMomPiranhaFlag2 = "Badge Shop Mom Piranha Flag 2" BadgeShopMomPiranhaFlag3 = "Badge Shop Mom Piranha Flag 3" HarhallsPants = "Harhall's Pants" - HoohooMountainBaseBooStatueCaveCoinBlock1 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 1" - HoohooMountainBaseBooStatueCaveCoinBlock2 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 2" - HoohooMountainBaseBooStatueCaveCoinBlock3 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 3" + HoohooMountainBaseBooStatueCaveCoinBlock1 = "Hoohoo Mountain Base Boostatue Cave Coin Block 1" + HoohooMountainBaseBooStatueCaveCoinBlock2 = "Hoohoo Mountain Base Boostatue Cave Coin Block 2" + HoohooMountainBaseBooStatueCaveCoinBlock3 = "Hoohoo Mountain Base Boostatue Cave Coin Block 3" BeanbeanOutskirtsNWCoinBlock = "Beanbean Outskirts NW Coin Block" BeanbeanOutskirtsSRoom1CoinBlock = "Beanbean Outskirts S Room 1 Coin Block" BeanbeanOutskirtsSRoom2CoinBlock = "Beanbean Outskirts S Room 2 Coin Block" diff --git a/worlds/mlss/Options.py b/worlds/mlss/Options.py index 73e8ebd4..dbf581b2 100644 --- a/worlds/mlss/Options.py +++ b/worlds/mlss/Options.py @@ -2,13 +2,13 @@ from Options import Choice, Toggle, StartInventoryPool, PerGameCommonOptions, Ra from dataclasses import dataclass -class BowsersCastleSkip(Toggle): +class SkipBowsersCastle(Toggle): """ - Skip straight from the entrance hall to Bowletta in Bowser's Castle. + Skip straight from the Entrance Hall to Bowletta in Bowser's Castle. All Bowser's Castle locations will be removed from the location pool. """ - display_name = "Bowser's Castle Skip" + display_name = "Skip Bowser's Castle" class ExtraPipes(Toggle): @@ -272,13 +272,47 @@ class ChuckleBeans(Choice): option_all = 2 default = 2 +class Goal(Choice): + """ + Vanilla: Complete jokes end with the required items and defeat Birdo to unlock Bowser's Castle. + + Emblem Hunt: Find the required number of Beanstar Emblems to gain access to Bowser's Castle. + """ + display_name = "Goal" + option_vanilla = 0 + option_emblem_hunt = 1 + default = 0 + +class EmblemsRequired(Range): + """ + Number of Beanstar Emblems to collect to unlock Bowser's Castle. + + If Goal is not Emblem Hunt, this does nothing. + """ + display_name = "Emblems Required" + range_start = 1 + range_end = 100 + default = 50 + + +class EmblemsAmount(Range): + """ + Number of Beanstar Emblems that are in the pool. + + If Goal is not Emblem Hunt, this does nothing. + """ + display_name = "Emblems Available" + range_start = 1 + range_end = 150 + default = 75 + @dataclass class MLSSOptions(PerGameCommonOptions): start_inventory_from_pool: StartInventoryPool coins: Coins difficult_logic: DifficultLogic - castle_skip: BowsersCastleSkip + castle_skip: SkipBowsersCastle extra_pipes: ExtraPipes skip_minecart: SkipMinecart disable_surf: DisableSurf @@ -286,6 +320,9 @@ class MLSSOptions(PerGameCommonOptions): harhalls_pants: Removed block_visibility: HiddenVisible chuckle_beans: ChuckleBeans + goal: Goal + emblems_required: EmblemsRequired + emblems_amount: EmblemsAmount music_options: MusicOptions randomize_sounds: RandomSounds randomize_enemies: RandomizeEnemies diff --git a/worlds/mlss/Regions.py b/worlds/mlss/Regions.py index 7dd5e945..e9008b86 100644 --- a/worlds/mlss/Regions.py +++ b/worlds/mlss/Regions.py @@ -91,6 +91,16 @@ def connect_regions(world: "MLSSWorld"): connect(world, names, "Main Area", "BaseUltraRocks", lambda state: StateLogic.ultra(state, world.player)) connect(world, names, "Main Area", "Chucklehuck Woods", lambda state: StateLogic.brooch(state, world.player)) connect(world, names, "Main Area", "BooStatue", lambda state: StateLogic.canCrash(state, world.player)) + if world.options.goal == "emblem_hunt": + if world.options.castle_skip: + connect(world, names, "Main Area", "Cackletta's Soul", + lambda state: state.has("Beanstar Emblem", world.player, world.options.emblems_required.value)) + else: + connect(world, names, "Main Area", "Bowser's Castle", lambda state: state.has("Beanstar Emblem", world.player, world.options.emblems_required.value)) + connect(world, names, "Bowser's Castle", "Bowser's Castle Mini", lambda state: + StateLogic.canMini(state, world.player) + and StateLogic.thunder(state,world.player)) + connect(world, names, "Bowser's Castle Mini", "Cackletta's Soul", lambda state: StateLogic.soul(state, world.player)) connect( world, names, @@ -213,8 +223,8 @@ def connect_regions(world: "MLSSWorld"): connect(world, names, "Surfable", "GwarharEntrance") connect(world, names, "Surfable", "Oasis") connect(world, names, "Surfable", "JokesEntrance", lambda state: StateLogic.fire(state, world.player)) - connect(world, names, "JokesMain", "PostJokes", lambda state: StateLogic.postJokes(state, world.player)) - if not world.options.castle_skip: + connect(world, names, "JokesMain", "PostJokes", lambda state: StateLogic.postJokes(state, world.player, world.options.goal.value)) + if not world.options.castle_skip and world.options.goal != "emblem_hunt": connect(world, names, "PostJokes", "Bowser's Castle") connect( world, @@ -224,7 +234,7 @@ def connect_regions(world: "MLSSWorld"): lambda state: StateLogic.canMini(state, world.player) and StateLogic.thunder(state, world.player), ) connect(world, names, "Bowser's Castle Mini", "Cackletta's Soul") - else: + elif world.options.goal != "emblem_hunt": connect(world, names, "PostJokes", "Cackletta's Soul") connect(world, names, "Chucklehuck Woods", "Winkle", lambda state: StateLogic.canDash(state, world.player)) connect( @@ -247,14 +257,14 @@ def connect_regions(world: "MLSSWorld"): names, "Shop Starting Flag", "Shop Birdo Flag", - lambda state: StateLogic.postJokes(state, world.player), + lambda state: StateLogic.postJokes(state, world.player, world.options.goal.value), ) connect( world, names, "Fungitown", "Fungitown Shop Birdo Flag", - lambda state: StateLogic.postJokes(state, world.player), + lambda state: StateLogic.postJokes(state, world.player, world.options.goal.value), ) else: connect( @@ -276,14 +286,14 @@ def connect_regions(world: "MLSSWorld"): names, "Shop Starting Flag", "Shop Birdo Flag", - lambda state: StateLogic.canCrash(state, world.player) and StateLogic.postJokes(state, world.player), + lambda state: StateLogic.canCrash(state, world.player) and StateLogic.postJokes(state, world.player, world.options.goal.value), ) connect( world, names, "Fungitown", "Fungitown Shop Birdo Flag", - lambda state: StateLogic.canCrash(state, world.player) and StateLogic.postJokes(state, world.player), + lambda state: StateLogic.canCrash(state, world.player) and StateLogic.postJokes(state, world.player, world.options.goal.value), ) diff --git a/worlds/mlss/Rom.py b/worlds/mlss/Rom.py index 03eac040..c83c0121 100644 --- a/worlds/mlss/Rom.py +++ b/worlds/mlss/Rom.py @@ -177,10 +177,10 @@ class MLSSPatchExtension(APPatchExtension): for pos in enemies: stream.seek(pos + 8) for _ in range(6): - enemy = int.from_bytes(stream.read(1)) + enemy = int.from_bytes(stream.read(1), "little") if enemy > 0: stream.seek(1, 1) - flag = int.from_bytes(stream.read(1)) + flag = int.from_bytes(stream.read(1), "little") if flag == 0x7: break if flag in [0x0, 0x2, 0x4]: @@ -196,12 +196,12 @@ class MLSSPatchExtension(APPatchExtension): stream.seek(pos + 8) for _ in range(6): - enemy = int.from_bytes(stream.read(1)) + enemy = int.from_bytes(stream.read(1), "little") if enemy > 0 and enemy not in Data.flying and enemy not in Data.pestnut: if enemy == 0x52: chomp = True stream.seek(1, 1) - flag = int.from_bytes(stream.read(1)) + flag = int.from_bytes(stream.read(1), "little") if flag not in [0x0, 0x2, 0x4]: stream.seek(1, 1) continue @@ -234,7 +234,7 @@ class MLSSPatchExtension(APPatchExtension): stream.seek(pos) temp = stream.read(1) stream.seek(pos) - stream.write(bytes([temp[0] | 0x8])) + stream.write(bytes([temp[0] | 0x80])) stream.seek(pos + 1) stream.write(groups.pop()) @@ -316,6 +316,10 @@ def write_tokens(world: "MLSSWorld", patch: MLSSProcedurePatch) -> None: patch.write_token(APTokenTypes.WRITE, 0xD00003, bytes([world.options.xp_multiplier.value])) + if world.options.goal == 1: + patch.write_token(APTokenTypes.WRITE, 0xD00008, bytes([world.options.goal.value])) + patch.write_token(APTokenTypes.WRITE, 0xD00009, bytes([world.options.emblems_required.value])) + if world.options.tattle_hp: patch.write_token(APTokenTypes.WRITE, 0xD00000, bytes([0x1])) @@ -427,4 +431,4 @@ def desc_inject(world: "MLSSWorld", patch: MLSSProcedurePatch, location: Locatio index = value.index(location.address) + 66 dstring = f"{world.multiworld.player_name[item.player]}: {item.name}" - patch.write_token(APTokenTypes.WRITE, 0xD11000 + (index * 0x40), dstring.encode("UTF8")) + patch.write_token(APTokenTypes.WRITE, 0xD12000 + (index * 0x40), dstring.encode("UTF8")) diff --git a/worlds/mlss/Rules.py b/worlds/mlss/Rules.py index b0b5a364..6592a805 100644 --- a/worlds/mlss/Rules.py +++ b/worlds/mlss/Rules.py @@ -28,11 +28,14 @@ def set_rules(world: "MLSSWorld", excluded): lambda state: StateLogic.canDig(state, world.player), ) if "Shop" in location.name and "Coffee" not in location.name and location.name not in excluded: - forbid_item(world.get_location(location.name), "Hammers", world.player) if "Badge" in location.name or "Pants" in location.name: add_rule( world.get_location(location.name), - lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player), + lambda state: (StateLogic.brooch(state, world.player) and StateLogic.fruits(state, world.player) + and (StateLogic.hammers(state, world.player) + or StateLogic.fire(state, world.player) + or StateLogic.thunder(state, world.player))) + or StateLogic.rose(state, world.player), ) if location.itemType != 0 and location.name not in excluded: if "Bowser" in location.name and world.options.castle_skip: @@ -99,9 +102,86 @@ def set_rules(world: "MLSSWorld", excluded): lambda state: StateLogic.ultra(state, world.player) and StateLogic.thunder(state, world.player), ) - forbid_item( - world.get_location(LocationName.SSChuckolaMembershipCard), "Nuts", world.player - ) # Bandaid Fix + if world.options.goal == 1 and not world.options.castle_skip: + add_rule( + world.get_location(LocationName.BowsersCastleRoyCorridorBlock1), + lambda state: StateLogic.canDig(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleRoyCorridorBlock2), + lambda state: StateLogic.canDig(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleMiniMarioSidescrollerBlock1), + lambda state: StateLogic.canDig(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleMiniMarioSidescrollerBlock2), + lambda state: StateLogic.canDig(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleMiniMarioMazeBlock1), + lambda state: StateLogic.canDig(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleMiniMarioMazeBlock2), + lambda state: StateLogic.canDig(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleBeforeWendyFightBlock1), + lambda state: StateLogic.canDig(state, world.player) + and StateLogic.ultra(state, world.player) + and StateLogic.fire(state, world.player) + and StateLogic.canCrash(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleBeforeWendyFightBlock2), + lambda state: StateLogic.canDig(state, world.player) + and StateLogic.ultra(state, world.player) + and StateLogic.fire(state, world.player) + and StateLogic.canCrash(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleLarryRoomBlock), + lambda state: StateLogic.canDig(state, world.player) + and StateLogic.ultra(state, world.player) + and StateLogic.canDash(state, world.player) + and StateLogic.canCrash(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleWendyLarryHallwayDigspot), + lambda state: StateLogic.ultra(state, world.player) + and StateLogic.fire(state, world.player) + and StateLogic.canCrash(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleBeforeFawfulFightBlock1), + lambda state: StateLogic.canDig(state, world.player) + and StateLogic.ultra(state, world.player) + and StateLogic.canDash(state, world.player) + and StateLogic.canCrash(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleBeforeFawfulFightBlock2), + lambda state: StateLogic.canDig(state, world.player) + and StateLogic.ultra(state, world.player) + and StateLogic.canDash(state, world.player) + and StateLogic.canCrash(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleGreatDoorBlock1), + lambda state: StateLogic.canDig(state, world.player) + and StateLogic.ultra(state, world.player) + and StateLogic.canDash(state, world.player) + and StateLogic.canCrash(state, world.player) + ) + add_rule( + world.get_location(LocationName.BowsersCastleGreatDoorBlock2), + lambda state: StateLogic.canDig(state, world.player) + and StateLogic.ultra(state, world.player) + and StateLogic.canDash(state, world.player) + and StateLogic.canCrash(state, world.player) + ) add_rule( world.get_location(LocationName.HoohooVillageHammerHouseBlock), @@ -398,6 +478,10 @@ def set_rules(world: "MLSSWorld", excluded): world.get_location(LocationName.BeanstarPieceWinkleArea), lambda state: StateLogic.winkle(state, world.player), ) + add_rule( + world.get_location("Guffawha Ruins Block"), + lambda state: StateLogic.thunder(state, world.player), + ) add_rule( world.get_location(LocationName.GwarharLagoonSpangleReward), lambda state: StateLogic.spangle(state, world.player), @@ -406,6 +490,18 @@ def set_rules(world: "MLSSWorld", excluded): world.get_location(LocationName.PantsShopMomPiranhaFlag1), lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player), ) + add_rule( + world.get_location("Chucklehuck Woods Solo Luigi Cave Room 2 Block"), + lambda state: StateLogic.brooch(state, world.player) and StateLogic.canDig(state, world.player), + ) + add_rule( + world.get_location("Chucklehuck Woods Solo Luigi Cave Room 3 Block 1"), + lambda state: StateLogic.brooch(state, world.player) and StateLogic.canDig(state, world.player), + ) + add_rule( + world.get_location("Chucklehuck Woods Solo Luigi Cave Room 3 Block 2"), + lambda state: StateLogic.brooch(state, world.player) and StateLogic.canDig(state, world.player), + ) add_rule( world.get_location(LocationName.PantsShopMomPiranhaFlag2), lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player), @@ -600,6 +696,14 @@ def set_rules(world: "MLSSWorld", excluded): world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock1), lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player), ) + add_rule( + world.get_location("Chucklehuck Woods Solo Luigi Cave Room 1 Coin Block 1"), + lambda state: StateLogic.canDig(state, world.player) and StateLogic.brooch(state, world.player), + ) + add_rule( + world.get_location("Chucklehuck Woods Solo Luigi Cave Room 1 Coin Block 2"), + lambda state: StateLogic.canDig(state, world.player) and StateLogic.brooch(state, world.player), + ) add_rule( world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock2), lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player), @@ -679,7 +783,7 @@ def set_rules(world: "MLSSWorld", excluded): add_rule( world.get_location(LocationName.GwarharLagoonFirstUnderwaterAreaRoom2CoinBlock), lambda state: StateLogic.canDash(state, world.player) - and (StateLogic.membership(state, world.player) or StateLogic.surfable(state, world.player)), + and (StateLogic.membership(state, world.player) or StateLogic.surfable(state, world.player)), ) add_rule( world.get_location(LocationName.JokesEndSecondFloorWestRoomCoinBlock), diff --git a/worlds/mlss/StateLogic.py b/worlds/mlss/StateLogic.py index 39f08e16..74ad5aa3 100644 --- a/worlds/mlss/StateLogic.py +++ b/worlds/mlss/StateLogic.py @@ -1,3 +1,6 @@ +from .Options import Goal + + def canDig(state, player): return state.has("Green Goblet", player) and state.has("Hammers", player) @@ -105,8 +108,9 @@ def surfable(state, player): ) -def postJokes(state, player): - return ( +def postJokes(state, player, goal): + if goal == Goal.option_vanilla: # Logic for beating jokes end without beanstar emblems + return ( surfable(state, player) and canDig(state, player) and dressBeanstar(state, player) @@ -115,7 +119,13 @@ def postJokes(state, player): and brooch(state, player) and rose(state, player) and canDash(state, player) - ) + ) + else: # Logic for beating jokes end with beanstar emblems + return ( + surfable(state, player) + and canDig(state, player) + and canDash(state, player) + ) def teehee(state, player): @@ -153,3 +163,10 @@ def birdo_shop(state, player): def fungitown_birdo_shop(state, player): return state.can_reach("Fungitown Shop Birdo Flag", "Region", player) + +def soul(state, player): + return (ultra(state, player) + and canMini(state, player) + and canDig(state, player) + and canDash(state, player) + and canCrash(state, player)) diff --git a/worlds/mlss/__init__.py b/worlds/mlss/__init__.py index bb7ed051..10359795 100644 --- a/worlds/mlss/__init__.py +++ b/worlds/mlss/__init__.py @@ -1,3 +1,4 @@ +import logging import os import pkgutil import typing @@ -7,7 +8,7 @@ from worlds.AutoWorld import WebWorld, World from typing import Set, Dict, Any from .Locations import all_locations, location_table, bowsers, bowsersMini, hidden, coins from .Options import MLSSOptions -from .Items import MLSSItem, itemList, item_frequencies, item_table +from .Items import MLSSItem, itemList, item_frequencies, item_table, mlss_item_name_groups from .Names.LocationName import LocationName from .Client import MLSSClient from .Regions import create_regions, connect_regions @@ -53,6 +54,7 @@ class MLSSWorld(World): options_dataclass = MLSSOptions options: MLSSOptions settings: typing.ClassVar[MLSSSettings] + item_name_groups = mlss_item_name_groups item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations} required_client_version = (0, 5, 0) @@ -61,6 +63,12 @@ class MLSSWorld(World): def generate_early(self) -> None: self.disabled_locations = set() + if self.options.goal == "emblem_hunt": + if self.options.emblems_amount < self.options.emblems_required: + self.options.emblems_amount.value = self.options.emblems_required.value + logging.warning( + f"{self.player_name}'s number of emblems required is greater than the number of emblems available. " + f"Changing to {self.options.emblems_required.value}.") if self.options.skip_minecart: self.disabled_locations.update([LocationName.HoohooMountainBaseMinecartCaveDigspot]) if self.options.disable_surf: @@ -111,6 +119,8 @@ class MLSSWorld(World): for item in itemList: if item.classification != ItemClassification.filler and item.classification != ItemClassification.skip_balancing: freq = item_frequencies.get(item.itemName, 1) + if item.itemName == "Beanstar Emblem": + freq = (0 if self.options.goal != "emblem_hunt" else self.options.emblems_amount.value) if item in precollected: freq = max(freq - precollected.count(item), 0) if self.options.disable_harhalls_pants and "Harhall's" in item.itemName: @@ -138,7 +148,6 @@ class MLSSWorld(World): # And finally take as many fillers as we need to have the same amount of items and locations. remaining = len(all_locations) - len(required_items) - len(self.disabled_locations) - 5 - self.multiworld.itempool += [ self.create_item(filler_item_name) for filler_item_name in self.random.sample(filler_items, remaining) ] diff --git a/worlds/mlss/data/basepatch.bsdiff b/worlds/mlss/data/basepatch.bsdiff index 7ed6c38e..18d6b56e 100644 Binary files a/worlds/mlss/data/basepatch.bsdiff and b/worlds/mlss/data/basepatch.bsdiff differ