MLSS: Add new goal + Update basepatch to standalone equivalent (#4409)

* Item groups + small changes

* Add alternate goal

* New Locations and Logic Updates + Basepatch

* Update basepatch.bsdiff

* Update Basepatch

* Update basepatch.bsdiff

* Update bowsers castle logic with emblem hunt

* Update Archipelago Unittests.run.xml

* Update Archipelago Unittests.run.xml

* Fix for overlapping ROM addresses

* Update Rom.py

* Update __init__.py

* Update basepatch.bsdiff

* Update Rom.py

* Update client with new helper function

* Update basepatch.bsdiff

* Update worlds/mlss/__init__.py

Co-authored-by: qwint <qwint.42@gmail.com>

* Update worlds/mlss/__init__.py

Co-authored-by: qwint <qwint.42@gmail.com>

* Review Refactor

* Review Refactor

---------

Co-authored-by: qwint <qwint.42@gmail.com>
This commit is contained in:
jamesbrq
2025-03-09 11:37:15 -04:00
committed by GitHub
parent 4ebabc1208
commit 2639796255
12 changed files with 234 additions and 39 deletions

View File

@@ -269,7 +269,7 @@ class MLSSClient(BizHawkClient):
self.local_checked_locations = locs_to_send self.local_checked_locations = locs_to_send
if locs_to_send is not None: 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: except bizhawk.RequestFailedError:
# Exit handler and return to main loop to reconnect. # Exit handler and return to main loop to reconnect.

View File

@@ -153,7 +153,6 @@ enemies = [
0x50458C, 0x50458C,
0x5045AC, 0x5045AC,
0x50468C, 0x50468C,
# 0x5046CC, 6 enemy formation
0x5046EC, 0x5046EC,
0x50470C 0x50470C
] ]
@@ -166,6 +165,7 @@ bosses = [
0x50360C, 0x50360C,
0x5037AC, 0x5037AC,
0x5037CC, 0x5037CC,
0x50396C,
0x503A8C, 0x503A8C,
0x503D6C, 0x503D6C,
0x503F0C, 0x503F0C,

View File

@@ -160,6 +160,7 @@ itemList: typing.List[ItemData] = [
ItemData(77771142, "Game Boy Horror SP", ItemClassification.useful, 0xFE), ItemData(77771142, "Game Boy Horror SP", ItemClassification.useful, 0xFE),
ItemData(77771143, "Woo Bean", ItemClassification.skip_balancing, 0x1C), ItemData(77771143, "Woo Bean", ItemClassification.skip_balancing, 0x1C),
ItemData(77771144, "Hee Bean", ItemClassification.skip_balancing, 0x1F), ItemData(77771144, "Hee Bean", ItemClassification.skip_balancing, 0x1F),
ItemData(77771145, "Beanstar Emblem", ItemClassification.progression, 0x3E),
] ]
item_frequencies: typing.Dict[str, int] = { item_frequencies: typing.Dict[str, int] = {
@@ -186,5 +187,12 @@ item_frequencies: typing.Dict[str, int] = {
"Hammers": 3, "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} 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} items_by_id: typing.Dict[int, ItemData] = {item.code: item for item in itemList}

View File

@@ -251,9 +251,9 @@ coins: typing.List[LocationData] = [
LocationData("Hoohoo Village North Cave Room 1 Coin Block", 0x39DAA0, 0), 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 1", 0x39DAC5, 0),
LocationData("Hoohoo Village South Cave Coin Block 2", 0x39DAD5, 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 Boostatue Cave Coin Block 1", 0x39DAE2, 0),
LocationData("Hoohoo Mountain Base Boo Statue Cave Coin Block 2", 0x39DAF2, 0), LocationData("Hoohoo Mountain Base Boostatue 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 3", 0x39DAFA, 0),
LocationData("Beanbean Outskirts NW Coin Block", 0x39DB8F, 0), LocationData("Beanbean Outskirts NW Coin Block", 0x39DB8F, 0),
LocationData("Beanbean Outskirts S Room 1 Coin Block", 0x39DC18, 0), LocationData("Beanbean Outskirts S Room 1 Coin Block", 0x39DC18, 0),
LocationData("Beanbean Outskirts S Room 2 Coin Block", 0x39DC3D, 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 1 Coin Block", 0x39DD7A, 0),
LocationData("Chucklehuck Woods Cave Room 2 Coin Block", 0x39DD97, 0), LocationData("Chucklehuck Woods Cave Room 2 Coin Block", 0x39DD97, 0),
LocationData("Chucklehuck Woods Cave Room 3 Coin Block", 0x39DDB4, 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 Pipe 5 Room Coin Block", 0x39DDE6, 0),
LocationData("Chucklehuck Woods Room 7 Coin Block", 0x39DE31, 0), LocationData("Chucklehuck Woods Room 7 Coin Block", 0x39DE31, 0),
LocationData("Chucklehuck Woods Past Chuckleroot Coin Block", 0x39DF14, 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 1 Block", 0x39E5E0, 0),
LocationData("Teehee Valley Upper Maze Room 2 Digspot 1", 0x39E5C8, 0), LocationData("Teehee Valley Upper Maze Room 2 Digspot 1", 0x39E5C8, 0),
LocationData("Teehee Valley Upper Maze Room 2 Digspot 2", 0x39E5D0, 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 Guffawha Ruins Entrance Digspot", 0x39DA0B, 0),
LocationData("Hoohoo Mountain Base Teehee Valley Entrance Digspot", 0x39DA20, 0), LocationData("Hoohoo Mountain Base Teehee Valley Entrance Digspot", 0x39DA20, 0),
LocationData("Hoohoo Mountain Base Teehee Valley Entrance Block", 0x39DA18, 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 1", 0x39E951, 0),
LocationData("Beanbean Outskirts Before Harhall Digspot 2", 0x39E959, 0), LocationData("Beanbean Outskirts Before Harhall Digspot 2", 0x39E959, 0),
LocationData("Beanstar Piece Harhall", 0x1E9441, 2), 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("Harhall's Pants", 0x1E9444, 2),
LocationData("Beanbean Outskirts S Room 2 Digspot 1", 0x39DC65, 0), LocationData("Beanbean Outskirts S Room 2 Digspot 1", 0x39DC65, 0),
LocationData("Beanbean Outskirts S Room 2 Digspot 2", 0x39DC5D, 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 1 Block 2", 0x39DD8A, 0),
LocationData("Chucklehuck Woods Cave Room 2 Block", 0x39DD9F, 0), LocationData("Chucklehuck Woods Cave Room 2 Block", 0x39DD9F, 0),
LocationData("Chucklehuck Woods Cave Room 3 Block", 0x39DDAC, 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 Block", 0x39DDC1, 0),
LocationData("Chucklehuck Woods Room 2 Digspot", 0x39DDC9, 0), LocationData("Chucklehuck Woods Room 2 Digspot", 0x39DDC9, 0),
LocationData("Chucklehuck Woods Pipe Room Block 1", 0x39DDD6, 0), LocationData("Chucklehuck Woods Pipe Room Block 1", 0x39DDD6, 0),
@@ -786,7 +792,7 @@ nonBlock = [
(0x4373, 0x10, 0x277A45), # Teehee Valley Mole (0x4373, 0x10, 0x277A45), # Teehee Valley Mole
(0x434D, 0x8, 0x1E9444), # Harhall's Pants (0x434D, 0x8, 0x1E9444), # Harhall's Pants
(0x432E, 0x10, 0x1E9441), # Harhall Beanstar Piece (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, 0x2, 0x1E943E), # Red Goblet
(0x42FE, 0x4, 0x24E628), # Green Goblet (0x42FE, 0x4, 0x24E628), # Green Goblet
(0x4301, 0x10, 0x250621), # Red Chuckola Fruit (0x4301, 0x10, 0x250621), # Red Chuckola Fruit

View File

@@ -59,7 +59,7 @@ class LocationName:
HoohooMountainBaseBoostatueRoomDigspot1 = "Hoohoo Mountain Base Boostatue Room Digspot 1" HoohooMountainBaseBoostatueRoomDigspot1 = "Hoohoo Mountain Base Boostatue Room Digspot 1"
HoohooMountainBaseBoostatueRoomDigspot2 = "Hoohoo Mountain Base Boostatue Room Digspot 2" HoohooMountainBaseBoostatueRoomDigspot2 = "Hoohoo Mountain Base Boostatue Room Digspot 2"
HoohooMountainBaseBoostatueRoomDigspot3 = "Hoohoo Mountain Base Boostatue Room Digspot 3" 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" HoohooMountainBaseGrassyAreaBlock1 = "Hoohoo Mountain Base Grassy Area Block 1"
HoohooMountainBaseGrassyAreaBlock2 = "Hoohoo Mountain Base Grassy Area Block 2" HoohooMountainBaseGrassyAreaBlock2 = "Hoohoo Mountain Base Grassy Area Block 2"
HoohooMountainBaseGuffawhaRuinsEntranceDigspot = "Hoohoo Mountain Base Guffawha Ruins Entrance Digspot" HoohooMountainBaseGuffawhaRuinsEntranceDigspot = "Hoohoo Mountain Base Guffawha Ruins Entrance Digspot"
@@ -533,9 +533,9 @@ class LocationName:
BadgeShopMomPiranhaFlag2 = "Badge Shop Mom Piranha Flag 2" BadgeShopMomPiranhaFlag2 = "Badge Shop Mom Piranha Flag 2"
BadgeShopMomPiranhaFlag3 = "Badge Shop Mom Piranha Flag 3" BadgeShopMomPiranhaFlag3 = "Badge Shop Mom Piranha Flag 3"
HarhallsPants = "Harhall's Pants" HarhallsPants = "Harhall's Pants"
HoohooMountainBaseBooStatueCaveCoinBlock1 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 1" HoohooMountainBaseBooStatueCaveCoinBlock1 = "Hoohoo Mountain Base Boostatue Cave Coin Block 1"
HoohooMountainBaseBooStatueCaveCoinBlock2 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 2" HoohooMountainBaseBooStatueCaveCoinBlock2 = "Hoohoo Mountain Base Boostatue Cave Coin Block 2"
HoohooMountainBaseBooStatueCaveCoinBlock3 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 3" HoohooMountainBaseBooStatueCaveCoinBlock3 = "Hoohoo Mountain Base Boostatue Cave Coin Block 3"
BeanbeanOutskirtsNWCoinBlock = "Beanbean Outskirts NW Coin Block" BeanbeanOutskirtsNWCoinBlock = "Beanbean Outskirts NW Coin Block"
BeanbeanOutskirtsSRoom1CoinBlock = "Beanbean Outskirts S Room 1 Coin Block" BeanbeanOutskirtsSRoom1CoinBlock = "Beanbean Outskirts S Room 1 Coin Block"
BeanbeanOutskirtsSRoom2CoinBlock = "Beanbean Outskirts S Room 2 Coin Block" BeanbeanOutskirtsSRoom2CoinBlock = "Beanbean Outskirts S Room 2 Coin Block"

View File

@@ -2,13 +2,13 @@ from Options import Choice, Toggle, StartInventoryPool, PerGameCommonOptions, Ra
from dataclasses import dataclass 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. 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): class ExtraPipes(Toggle):
@@ -272,13 +272,47 @@ class ChuckleBeans(Choice):
option_all = 2 option_all = 2
default = 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 @dataclass
class MLSSOptions(PerGameCommonOptions): class MLSSOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool start_inventory_from_pool: StartInventoryPool
coins: Coins coins: Coins
difficult_logic: DifficultLogic difficult_logic: DifficultLogic
castle_skip: BowsersCastleSkip castle_skip: SkipBowsersCastle
extra_pipes: ExtraPipes extra_pipes: ExtraPipes
skip_minecart: SkipMinecart skip_minecart: SkipMinecart
disable_surf: DisableSurf disable_surf: DisableSurf
@@ -286,6 +320,9 @@ class MLSSOptions(PerGameCommonOptions):
harhalls_pants: Removed harhalls_pants: Removed
block_visibility: HiddenVisible block_visibility: HiddenVisible
chuckle_beans: ChuckleBeans chuckle_beans: ChuckleBeans
goal: Goal
emblems_required: EmblemsRequired
emblems_amount: EmblemsAmount
music_options: MusicOptions music_options: MusicOptions
randomize_sounds: RandomSounds randomize_sounds: RandomSounds
randomize_enemies: RandomizeEnemies randomize_enemies: RandomizeEnemies

View File

@@ -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", "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", "Chucklehuck Woods", lambda state: StateLogic.brooch(state, world.player))
connect(world, names, "Main Area", "BooStatue", lambda state: StateLogic.canCrash(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( connect(
world, world,
names, names,
@@ -213,8 +223,8 @@ def connect_regions(world: "MLSSWorld"):
connect(world, names, "Surfable", "GwarharEntrance") connect(world, names, "Surfable", "GwarharEntrance")
connect(world, names, "Surfable", "Oasis") connect(world, names, "Surfable", "Oasis")
connect(world, names, "Surfable", "JokesEntrance", lambda state: StateLogic.fire(state, world.player)) connect(world, names, "Surfable", "JokesEntrance", lambda state: StateLogic.fire(state, world.player))
connect(world, names, "JokesMain", "PostJokes", lambda state: StateLogic.postJokes(state, world.player)) connect(world, names, "JokesMain", "PostJokes", lambda state: StateLogic.postJokes(state, world.player, world.options.goal.value))
if not world.options.castle_skip: if not world.options.castle_skip and world.options.goal != "emblem_hunt":
connect(world, names, "PostJokes", "Bowser's Castle") connect(world, names, "PostJokes", "Bowser's Castle")
connect( connect(
world, world,
@@ -224,7 +234,7 @@ def connect_regions(world: "MLSSWorld"):
lambda state: StateLogic.canMini(state, world.player) and StateLogic.thunder(state, world.player), lambda state: StateLogic.canMini(state, world.player) and StateLogic.thunder(state, world.player),
) )
connect(world, names, "Bowser's Castle Mini", "Cackletta's Soul") 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, "PostJokes", "Cackletta's Soul")
connect(world, names, "Chucklehuck Woods", "Winkle", lambda state: StateLogic.canDash(state, world.player)) connect(world, names, "Chucklehuck Woods", "Winkle", lambda state: StateLogic.canDash(state, world.player))
connect( connect(
@@ -247,14 +257,14 @@ def connect_regions(world: "MLSSWorld"):
names, names,
"Shop Starting Flag", "Shop Starting Flag",
"Shop Birdo Flag", "Shop Birdo Flag",
lambda state: StateLogic.postJokes(state, world.player), lambda state: StateLogic.postJokes(state, world.player, world.options.goal.value),
) )
connect( connect(
world, world,
names, names,
"Fungitown", "Fungitown",
"Fungitown Shop Birdo Flag", "Fungitown Shop Birdo Flag",
lambda state: StateLogic.postJokes(state, world.player), lambda state: StateLogic.postJokes(state, world.player, world.options.goal.value),
) )
else: else:
connect( connect(
@@ -276,14 +286,14 @@ def connect_regions(world: "MLSSWorld"):
names, names,
"Shop Starting Flag", "Shop Starting Flag",
"Shop Birdo 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( connect(
world, world,
names, names,
"Fungitown", "Fungitown",
"Fungitown Shop Birdo Flag", "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),
) )

View File

@@ -177,10 +177,10 @@ class MLSSPatchExtension(APPatchExtension):
for pos in enemies: for pos in enemies:
stream.seek(pos + 8) stream.seek(pos + 8)
for _ in range(6): for _ in range(6):
enemy = int.from_bytes(stream.read(1)) enemy = int.from_bytes(stream.read(1), "little")
if enemy > 0: if enemy > 0:
stream.seek(1, 1) stream.seek(1, 1)
flag = int.from_bytes(stream.read(1)) flag = int.from_bytes(stream.read(1), "little")
if flag == 0x7: if flag == 0x7:
break break
if flag in [0x0, 0x2, 0x4]: if flag in [0x0, 0x2, 0x4]:
@@ -196,12 +196,12 @@ class MLSSPatchExtension(APPatchExtension):
stream.seek(pos + 8) stream.seek(pos + 8)
for _ in range(6): 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 > 0 and enemy not in Data.flying and enemy not in Data.pestnut:
if enemy == 0x52: if enemy == 0x52:
chomp = True chomp = True
stream.seek(1, 1) 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]: if flag not in [0x0, 0x2, 0x4]:
stream.seek(1, 1) stream.seek(1, 1)
continue continue
@@ -234,7 +234,7 @@ class MLSSPatchExtension(APPatchExtension):
stream.seek(pos) stream.seek(pos)
temp = stream.read(1) temp = stream.read(1)
stream.seek(pos) stream.seek(pos)
stream.write(bytes([temp[0] | 0x8])) stream.write(bytes([temp[0] | 0x80]))
stream.seek(pos + 1) stream.seek(pos + 1)
stream.write(groups.pop()) 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])) 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: if world.options.tattle_hp:
patch.write_token(APTokenTypes.WRITE, 0xD00000, bytes([0x1])) 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 index = value.index(location.address) + 66
dstring = f"{world.multiworld.player_name[item.player]}: {item.name}" 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"))

View File

@@ -28,11 +28,14 @@ def set_rules(world: "MLSSWorld", excluded):
lambda state: StateLogic.canDig(state, world.player), lambda state: StateLogic.canDig(state, world.player),
) )
if "Shop" in location.name and "Coffee" not in location.name and location.name not in excluded: 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: if "Badge" in location.name or "Pants" in location.name:
add_rule( add_rule(
world.get_location(location.name), 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 location.itemType != 0 and location.name not in excluded:
if "Bowser" in location.name and world.options.castle_skip: 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), lambda state: StateLogic.ultra(state, world.player) and StateLogic.thunder(state, world.player),
) )
forbid_item( if world.options.goal == 1 and not world.options.castle_skip:
world.get_location(LocationName.SSChuckolaMembershipCard), "Nuts", world.player add_rule(
) # Bandaid Fix 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( add_rule(
world.get_location(LocationName.HoohooVillageHammerHouseBlock), world.get_location(LocationName.HoohooVillageHammerHouseBlock),
@@ -398,6 +478,10 @@ def set_rules(world: "MLSSWorld", excluded):
world.get_location(LocationName.BeanstarPieceWinkleArea), world.get_location(LocationName.BeanstarPieceWinkleArea),
lambda state: StateLogic.winkle(state, world.player), lambda state: StateLogic.winkle(state, world.player),
) )
add_rule(
world.get_location("Guffawha Ruins Block"),
lambda state: StateLogic.thunder(state, world.player),
)
add_rule( add_rule(
world.get_location(LocationName.GwarharLagoonSpangleReward), world.get_location(LocationName.GwarharLagoonSpangleReward),
lambda state: StateLogic.spangle(state, world.player), lambda state: StateLogic.spangle(state, world.player),
@@ -406,6 +490,18 @@ def set_rules(world: "MLSSWorld", excluded):
world.get_location(LocationName.PantsShopMomPiranhaFlag1), world.get_location(LocationName.PantsShopMomPiranhaFlag1),
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player), 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( add_rule(
world.get_location(LocationName.PantsShopMomPiranhaFlag2), world.get_location(LocationName.PantsShopMomPiranhaFlag2),
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player), 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), world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock1),
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player), 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( add_rule(
world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock2), world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock2),
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player), 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( add_rule(
world.get_location(LocationName.GwarharLagoonFirstUnderwaterAreaRoom2CoinBlock), world.get_location(LocationName.GwarharLagoonFirstUnderwaterAreaRoom2CoinBlock),
lambda state: StateLogic.canDash(state, world.player) 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( add_rule(
world.get_location(LocationName.JokesEndSecondFloorWestRoomCoinBlock), world.get_location(LocationName.JokesEndSecondFloorWestRoomCoinBlock),

View File

@@ -1,3 +1,6 @@
from .Options import Goal
def canDig(state, player): def canDig(state, player):
return state.has("Green Goblet", player) and state.has("Hammers", player) return state.has("Green Goblet", player) and state.has("Hammers", player)
@@ -105,8 +108,9 @@ def surfable(state, player):
) )
def postJokes(state, player): def postJokes(state, player, goal):
return ( if goal == Goal.option_vanilla: # Logic for beating jokes end without beanstar emblems
return (
surfable(state, player) surfable(state, player)
and canDig(state, player) and canDig(state, player)
and dressBeanstar(state, player) and dressBeanstar(state, player)
@@ -115,7 +119,13 @@ def postJokes(state, player):
and brooch(state, player) and brooch(state, player)
and rose(state, player) and rose(state, player)
and canDash(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): def teehee(state, player):
@@ -153,3 +163,10 @@ def birdo_shop(state, player):
def fungitown_birdo_shop(state, player): def fungitown_birdo_shop(state, player):
return state.can_reach("Fungitown Shop Birdo Flag", "Region", 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))

View File

@@ -1,3 +1,4 @@
import logging
import os import os
import pkgutil import pkgutil
import typing import typing
@@ -7,7 +8,7 @@ from worlds.AutoWorld import WebWorld, World
from typing import Set, Dict, Any from typing import Set, Dict, Any
from .Locations import all_locations, location_table, bowsers, bowsersMini, hidden, coins from .Locations import all_locations, location_table, bowsers, bowsersMini, hidden, coins
from .Options import MLSSOptions 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 .Names.LocationName import LocationName
from .Client import MLSSClient from .Client import MLSSClient
from .Regions import create_regions, connect_regions from .Regions import create_regions, connect_regions
@@ -53,6 +54,7 @@ class MLSSWorld(World):
options_dataclass = MLSSOptions options_dataclass = MLSSOptions
options: MLSSOptions options: MLSSOptions
settings: typing.ClassVar[MLSSSettings] 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()} 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} location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations}
required_client_version = (0, 5, 0) required_client_version = (0, 5, 0)
@@ -61,6 +63,12 @@ class MLSSWorld(World):
def generate_early(self) -> None: def generate_early(self) -> None:
self.disabled_locations = set() 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: if self.options.skip_minecart:
self.disabled_locations.update([LocationName.HoohooMountainBaseMinecartCaveDigspot]) self.disabled_locations.update([LocationName.HoohooMountainBaseMinecartCaveDigspot])
if self.options.disable_surf: if self.options.disable_surf:
@@ -111,6 +119,8 @@ class MLSSWorld(World):
for item in itemList: for item in itemList:
if item.classification != ItemClassification.filler and item.classification != ItemClassification.skip_balancing: if item.classification != ItemClassification.filler and item.classification != ItemClassification.skip_balancing:
freq = item_frequencies.get(item.itemName, 1) 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: if item in precollected:
freq = max(freq - precollected.count(item), 0) freq = max(freq - precollected.count(item), 0)
if self.options.disable_harhalls_pants and "Harhall's" in item.itemName: 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. # 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 remaining = len(all_locations) - len(required_items) - len(self.disabled_locations) - 5
self.multiworld.itempool += [ self.multiworld.itempool += [
self.create_item(filler_item_name) for filler_item_name in self.random.sample(filler_items, remaining) self.create_item(filler_item_name) for filler_item_name in self.random.sample(filler_items, remaining)
] ]

Binary file not shown.