MMBN3: Logic and Bug Fixes, New Checks (#3646)

* PMDs now check to make sure you have enough unlockers for all of them before any are in logic, to avoid softlocks

* Adds Humor and BlckMnd to the pool and sets logic for Villain and Comedian. Patch not yet updated to remove starting inventory

* Adds Serenade as a check

* Fixes hide and seek completion to use proper Yoka Zoo map. Updates bsdiff patch to 1.2

* Adds option for excluding Secret Area, and item/location groups for further customization

* Update worlds/mmbn3/Locations.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mmbn3/Regions.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mmbn3/__init__.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mmbn3/__init__.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mmbn3/__init__.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Replaces can_reach generic with can_reach_region or can_reach_location, where applciable

* Unlocker is now a progression item, Excluded Locations is now a Set

* Missed a merge marker

* Excluded locations is no longer a set since you can't append to a set with +=

* Excluded locations is now a set again since you apparent can append to a set with |=

* Replaces more lists with sets. Fixes wording in option descriptions

* Update worlds/mmbn3/__init__.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
digiholic
2025-01-17 06:41:12 -07:00
committed by GitHub
parent 3d5c277c31
commit d218dec826
8 changed files with 165 additions and 99 deletions

View File

@@ -9,14 +9,14 @@ from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification, Region,
from worlds.AutoWorld import WebWorld, World
from .Rom import MMBN3DeltaPatch, LocalRom, get_base_rom_path
from .Items import MMBN3Item, ItemData, item_table, all_items, item_frequencies, items_by_id, ItemType
from .Items import MMBN3Item, ItemData, item_table, all_items, item_frequencies, items_by_id, ItemType, item_groups
from .Locations import Location, MMBN3Location, all_locations, location_table, location_data_table, \
always_excluded_locations, jobs
secret_locations, jobs, location_groups
from .Options import MMBN3Options
from .Regions import regions, RegionName
from .Names.ItemName import ItemName
from .Names.LocationName import LocationName
from worlds.generic.Rules import add_item_rule
from worlds.generic.Rules import add_item_rule, add_rule
class MMBN3Settings(settings.Group):
@@ -57,12 +57,16 @@ class MMBN3World(World):
settings: typing.ClassVar[MMBN3Settings]
topology_present = False
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}
excluded_locations: typing.List[str]
excluded_locations: typing.Set[str]
item_frequencies: typing.Dict[str, int]
location_name_groups = location_groups
item_name_groups = item_groups
web = MMBN3Web()
def generate_early(self) -> None:
@@ -74,10 +78,11 @@ class MMBN3World(World):
if self.options.extra_ranks > 0:
self.item_frequencies[ItemName.Progressive_Undernet_Rank] = 8 + self.options.extra_ranks
self.excluded_locations = set()
if not self.options.include_secret:
self.excluded_locations |= secret_locations
if not self.options.include_jobs:
self.excluded_locations = always_excluded_locations + [job.name for job in jobs]
else:
self.excluded_locations = always_excluded_locations
self.excluded_locations |= {job.name for job in jobs}
def create_regions(self) -> None:
"""
@@ -140,19 +145,19 @@ class MMBN3World(World):
if connection == RegionName.SciLab_Cyberworld:
entrance.access_rule = lambda state: \
state.has(ItemName.CSciPas, self.player) or \
state.can_reach(RegionName.SciLab_Overworld, "Region", self.player)
state.can_reach_region(RegionName.SciLab_Overworld, self.player)
self.multiworld.register_indirect_condition(self.get_region(RegionName.SciLab_Overworld), entrance)
if connection == RegionName.Yoka_Cyberworld:
entrance.access_rule = lambda state: \
state.has(ItemName.CYokaPas, self.player) or \
(
state.can_reach(RegionName.SciLab_Overworld, "Region", self.player) and
state.can_reach_region(RegionName.SciLab_Overworld, self.player) and
state.has(ItemName.Press, self.player)
)
self.multiworld.register_indirect_condition(self.get_region(RegionName.SciLab_Overworld), entrance)
if connection == RegionName.Beach_Cyberworld:
entrance.access_rule = lambda state: state.has(ItemName.CBeacPas, self.player) and\
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
self.multiworld.register_indirect_condition(self.get_region(RegionName.Yoka_Overworld), entrance)
if connection == RegionName.Undernet:
entrance.access_rule = lambda state: self.explore_score(state) > 8 and\
@@ -198,122 +203,138 @@ class MMBN3World(World):
# Set WWW ID requirements
def has_www_id(state): return state.has(ItemName.WWW_ID, self.player)
self.multiworld.get_location(LocationName.ACDC_1_PMD, self.player).access_rule = has_www_id
self.multiworld.get_location(LocationName.SciLab_1_WWW_BMD, self.player).access_rule = has_www_id
self.multiworld.get_location(LocationName.Yoka_1_WWW_BMD, self.player).access_rule = has_www_id
self.multiworld.get_location(LocationName.Undernet_1_WWW_BMD, self.player).access_rule = has_www_id
add_rule(self.multiworld.get_location(LocationName.ACDC_1_PMD, self.player), has_www_id)
add_rule(self.multiworld.get_location(LocationName.SciLab_1_WWW_BMD, self.player), has_www_id)
add_rule(self.multiworld.get_location(LocationName.Yoka_1_WWW_BMD, self.player), has_www_id)
add_rule(self.multiworld.get_location(LocationName.Undernet_1_WWW_BMD, self.player), has_www_id)
# Set Press Program requirements
def has_press(state): return state.has(ItemName.Press, self.player)
self.multiworld.get_location(LocationName.Yoka_1_PMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Yoka_2_Upper_BMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Beach_2_East_BMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Hades_South_BMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Secret_3_BugFrag_BMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Secret_3_Island_BMD, self.player).access_rule = has_press
add_rule(self.multiworld.get_location(LocationName.Yoka_1_PMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Yoka_2_Upper_BMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Beach_2_East_BMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Hades_South_BMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Secret_3_BugFrag_BMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Secret_3_Island_BMD, self.player), has_press)
# Set Purple Mystery Data Unlocker access
def can_unlock(state): return state.can_reach_region(RegionName.SciLab_Overworld, self.player) or \
state.can_reach_region(RegionName.SciLab_Cyberworld, self.player) or \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player) or \
state.has(ItemName.Unlocker, self.player, 8) # There are 8 PMDs that aren't in one of the above areas
add_rule(self.multiworld.get_location(LocationName.ACDC_1_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Yoka_1_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Beach_1_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Undernet_7_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Mayls_HP_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.SciLab_Dads_Computer_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Zoo_Panda_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Beach_DNN_Security_Panel_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Beach_DNN_Main_Console_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Tamakos_HP_PMD, self.player), can_unlock)
# Set Job additional area access
self.multiworld.get_location(LocationName.Please_deliver_this, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player)
state.can_reach_region(RegionName.ACDC_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player)
self.multiworld.get_location(LocationName.My_Navi_is_sick, self.player).access_rule =\
lambda state: \
state.has(ItemName.Recov30_star, self.player)
self.multiworld.get_location(LocationName.Help_me_with_my_son, self.player).access_rule =\
lambda state:\
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player)
self.multiworld.get_location(LocationName.Transmission_error, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
self.multiworld.get_location(LocationName.Chip_Prices, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player)
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player) and \
state.can_reach_region(RegionName.SciLab_Cyberworld, self.player)
self.multiworld.get_location(LocationName.Im_broke, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player)
self.multiworld.get_location(LocationName.Rare_chips_for_cheap, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player)
state.can_reach_region(RegionName.ACDC_Overworld, self.player)
self.multiworld.get_location(LocationName.Be_my_boyfriend, self.player).access_rule =\
lambda state: \
state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player)
state.can_reach_region(RegionName.Beach_Cyberworld, self.player)
self.multiworld.get_location(LocationName.Will_you_deliver, self.player).access_rule=\
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player)
self.multiworld.get_location(LocationName.Somebody_please_help, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player)
state.can_reach_region(RegionName.ACDC_Overworld, self.player)
self.multiworld.get_location(LocationName.Looking_for_condor, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Overworld, self.player)
self.multiworld.get_location(LocationName.Help_with_rehab, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Beach_Overworld, self.player)
self.multiworld.get_location(LocationName.Old_Master, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player)
state.can_reach_region(RegionName.ACDC_Overworld, self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player)
self.multiworld.get_location(LocationName.Catching_gang_members, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player) and \
state.has(ItemName.Press, self.player)
self.multiworld.get_location(LocationName.Please_adopt_a_virus, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player)
state.can_reach_region(RegionName.SciLab_Cyberworld, self.player)
self.multiworld.get_location(LocationName.Legendary_Tomes, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Undernet, "Region", self.player) and \
state.can_reach(RegionName.Deep_Undernet, "Region", self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.Undernet, self.player) and \
state.can_reach_region(RegionName.Deep_Undernet, self.player) and \
state.has_all({ItemName.Press, ItemName.Magnum1_A}, self.player)
self.multiworld.get_location(LocationName.Legendary_Tomes_Treasure, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \
state.can_reach(LocationName.Legendary_Tomes, "Location", self.player)
state.can_reach_region(RegionName.ACDC_Overworld, self.player) and \
state.can_reach_location(LocationName.Legendary_Tomes, self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_First_Child, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_Second_Child, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_Third_Child, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_Fourth_Child, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_Completion, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
self.multiworld.get_location(LocationName.Finding_the_blue_Navi, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Undernet, "Region", self.player)
state.can_reach_region(RegionName.Undernet, self.player)
self.multiworld.get_location(LocationName.Give_your_support, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player)
state.can_reach_region(RegionName.Beach_Overworld, self.player)
self.multiworld.get_location(LocationName.Stamp_collecting, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player)
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player) and \
state.can_reach_region(RegionName.SciLab_Cyberworld, self.player) and \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player) and \
state.can_reach_region(RegionName.Beach_Cyberworld, self.player)
self.multiworld.get_location(LocationName.Help_with_a_will, self.player).access_rule = \
lambda state: \
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Undernet, "Region", self.player)
state.can_reach_region(RegionName.ACDC_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player) and \
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.Undernet, self.player)
# Set Trade quests
self.multiworld.get_location(LocationName.ACDC_SonicWav_W_Trade, self.player).access_rule =\
@@ -390,6 +411,11 @@ class MMBN3World(World):
self.multiworld.get_location(LocationName.Numberman_Code_31, self.player).access_rule =\
lambda state: self.explore_score(state) > 10
#miscellaneous locations with extra requirements
add_rule(self.multiworld.get_location(LocationName.Comedian, self.player),
lambda state: state.has(ItemName.Humor, self.player))
add_rule(self.multiworld.get_location(LocationName.Villain, self.player),
lambda state: state.has(ItemName.BlckMnd, self.player))
def not_undernet(item): return item.code != item_table[ItemName.Progressive_Undernet_Rank].code or item.player != self.player
self.multiworld.get_location(LocationName.WWW_1_Central_BMD, self.player).item_rule = not_undernet
self.multiworld.get_location(LocationName.WWW_1_East_BMD, self.player).item_rule = not_undernet
@@ -500,24 +526,24 @@ class MMBN3World(World):
Determine roughly how much of the game you can explore to make certain checks not restrict much movement
"""
score = 0
if state.can_reach(RegionName.WWW_Island, "Region", self.player):
if state.can_reach_region(RegionName.WWW_Island, self.player):
return 999
if state.can_reach(RegionName.SciLab_Overworld, "Region", self.player):
if state.can_reach_region(RegionName.SciLab_Overworld, self.player):
score += 3
if state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player):
if state.can_reach_region(RegionName.SciLab_Cyberworld, self.player):
score += 1
if state.can_reach(RegionName.Yoka_Overworld, "Region", self.player):
if state.can_reach_region(RegionName.Yoka_Overworld, self.player):
score += 2
if state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player):
if state.can_reach_region(RegionName.Yoka_Cyberworld, self.player):
score += 1
if state.can_reach(RegionName.Beach_Overworld, "Region", self.player):
if state.can_reach_region(RegionName.Beach_Overworld, self.player):
score += 3
if state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player):
if state.can_reach_region(RegionName.Beach_Cyberworld, self.player):
score += 1
if state.can_reach(RegionName.Undernet, "Region", self.player):
if state.can_reach_region(RegionName.Undernet, self.player):
score += 2
if state.can_reach(RegionName.Deep_Undernet, "Region", self.player):
if state.can_reach_region(RegionName.Deep_Undernet, self.player):
score += 1
if state.can_reach(RegionName.Secret_Area, "Region", self.player):
if state.can_reach_region(RegionName.Secret_Area, self.player):
score += 1
return score