mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-09 16:08:15 -07:00
211 lines
11 KiB
Python
211 lines
11 KiB
Python
from BaseClasses import MultiWorld, CollectionState, ItemClassification
|
|
from Options import OptionError
|
|
from .JakAndDaxterOptions import (JakAndDaxterOptions,
|
|
EnableMoveRandomizer,
|
|
EnableOrbsanity,
|
|
CompletionCondition)
|
|
from .Items import (JakAndDaxterItem,
|
|
item_table,
|
|
move_item_table)
|
|
from .Rules import can_reach_orbs
|
|
from .locs import (CellLocations as Cells,
|
|
ScoutLocations as Scouts)
|
|
from .regs.RegionBase import JakAndDaxterRegion
|
|
from .regs import (GeyserRockRegions as GeyserRock,
|
|
SandoverVillageRegions as SandoverVillage,
|
|
ForbiddenJungleRegions as ForbiddenJungle,
|
|
SentinelBeachRegions as SentinelBeach,
|
|
MistyIslandRegions as MistyIsland,
|
|
FireCanyonRegions as FireCanyon,
|
|
RockVillageRegions as RockVillage,
|
|
PrecursorBasinRegions as PrecursorBasin,
|
|
LostPrecursorCityRegions as LostPrecursorCity,
|
|
BoggySwampRegions as BoggySwamp,
|
|
MountainPassRegions as MountainPass,
|
|
VolcanicCraterRegions as VolcanicCrater,
|
|
SpiderCaveRegions as SpiderCave,
|
|
SnowyMountainRegions as SnowyMountain,
|
|
LavaTubeRegions as LavaTube,
|
|
GolAndMaiasCitadelRegions as GolAndMaiasCitadel)
|
|
|
|
|
|
def create_regions(multiworld: MultiWorld, options: JakAndDaxterOptions, player: int):
|
|
|
|
# Always start with Menu.
|
|
menu = JakAndDaxterRegion("Menu", player, multiworld)
|
|
multiworld.regions.append(menu)
|
|
|
|
# Build the special "Free 7 Scout Flies" Region. This is a virtual region always accessible to Menu.
|
|
# The Locations within are automatically checked when you receive the 7th scout fly for the corresponding cell.
|
|
free7 = JakAndDaxterRegion("'Free 7 Scout Flies' Power Cells", player, multiworld)
|
|
free7.add_cell_locations(Cells.loc7SF_cellTable.keys())
|
|
for scout_fly_cell in free7.locations:
|
|
|
|
# Translate from Cell AP ID to Scout AP ID using game ID as an intermediary.
|
|
scout_fly_id = Scouts.to_ap_id(Cells.to_game_id(scout_fly_cell.address))
|
|
scout_fly_cell.access_rule = lambda state, flies=scout_fly_id: state.has(item_table[flies], player, 7)
|
|
multiworld.regions.append(free7)
|
|
menu.connect(free7)
|
|
|
|
# If Global Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
|
# accessible to Menu. The Locations within are automatically checked when you collect enough orbs.
|
|
if options.enable_orbsanity == EnableOrbsanity.option_global:
|
|
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld)
|
|
|
|
bundle_size = options.global_orbsanity_bundle_size.value
|
|
bundle_count = int(2000 / bundle_size)
|
|
for bundle_index in range(bundle_count):
|
|
|
|
# Unlike Per-Level Orbsanity, Global Orbsanity Locations always have a level_index of 16.
|
|
orbs.add_orb_locations(16,
|
|
bundle_index,
|
|
bundle_size,
|
|
access_rule=lambda state, bundle=bundle_index:
|
|
can_reach_orbs(state, player, multiworld, options)
|
|
>= (bundle_size * (bundle + 1)))
|
|
multiworld.regions.append(orbs)
|
|
menu.connect(orbs)
|
|
|
|
# Build all regions. Include their intra-connecting Rules, their Locations, and their Location access rules.
|
|
[gr] = GeyserRock.build_regions("Geyser Rock", multiworld, options, player)
|
|
[sv] = SandoverVillage.build_regions("Sandover Village", multiworld, options, player)
|
|
[fj, fjp] = ForbiddenJungle.build_regions("Forbidden Jungle", multiworld, options, player)
|
|
[sb] = SentinelBeach.build_regions("Sentinel Beach", multiworld, options, player)
|
|
[mi] = MistyIsland.build_regions("Misty Island", multiworld, options, player)
|
|
[fc] = FireCanyon.build_regions("Fire Canyon", multiworld, options, player)
|
|
[rv, rvp, rvc] = RockVillage.build_regions("Rock Village", multiworld, options, player)
|
|
[pb] = PrecursorBasin.build_regions("Precursor Basin", multiworld, options, player)
|
|
[lpc] = LostPrecursorCity.build_regions("Lost Precursor City", multiworld, options, player)
|
|
[bs] = BoggySwamp.build_regions("Boggy Swamp", multiworld, options, player)
|
|
[mp, mpr] = MountainPass.build_regions("Mountain Pass", multiworld, options, player)
|
|
[vc] = VolcanicCrater.build_regions("Volcanic Crater", multiworld, options, player)
|
|
[sc] = SpiderCave.build_regions("Spider Cave", multiworld, options, player)
|
|
[sm] = SnowyMountain.build_regions("Snowy Mountain", multiworld, options, player)
|
|
[lt] = LavaTube.build_regions("Lava Tube", multiworld, options, player)
|
|
[gmc, fb, fd] = GolAndMaiasCitadel.build_regions("Gol and Maia's Citadel", multiworld, options, player)
|
|
|
|
# Configurable counts of cells for connector levels.
|
|
fc_count = options.fire_canyon_cell_count.value
|
|
mp_count = options.mountain_pass_cell_count.value
|
|
lt_count = options.lava_tube_cell_count.value
|
|
|
|
# Define the interconnecting rules.
|
|
menu.connect(gr)
|
|
gr.connect(sv) # Geyser Rock modified to let you leave at any time.
|
|
sv.connect(fj)
|
|
sv.connect(sb)
|
|
sv.connect(mi, rule=lambda state: state.has("Fisherman's Boat", player))
|
|
sv.connect(fc, rule=lambda state: state.has("Power Cell", player, fc_count)) # Normally 20.
|
|
fc.connect(rv)
|
|
rv.connect(pb)
|
|
rv.connect(lpc)
|
|
rvp.connect(bs) # rv->rvp/rvc connections defined internally by RockVillageRegions.
|
|
rvc.connect(mp, rule=lambda state: state.has("Power Cell", player, mp_count)) # Normally 45.
|
|
mpr.connect(vc) # mp->mpr connection defined internally by MountainPassRegions.
|
|
vc.connect(sc)
|
|
vc.connect(sm, rule=lambda state: state.has("Snowy Mountain Gondola", player))
|
|
vc.connect(lt, rule=lambda state: state.has("Power Cell", player, lt_count)) # Normally 72.
|
|
lt.connect(gmc) # gmc->fb connection defined internally by GolAndMaiasCitadelRegions.
|
|
|
|
# Set the completion condition.
|
|
if options.jak_completion_condition == CompletionCondition.option_cross_fire_canyon:
|
|
multiworld.completion_condition[player] = lambda state: state.can_reach(rv, "Region", player)
|
|
|
|
elif options.jak_completion_condition == CompletionCondition.option_cross_mountain_pass:
|
|
multiworld.completion_condition[player] = lambda state: state.can_reach(vc, "Region", player)
|
|
|
|
elif options.jak_completion_condition == CompletionCondition.option_cross_lava_tube:
|
|
multiworld.completion_condition[player] = lambda state: state.can_reach(gmc, "Region", player)
|
|
|
|
elif options.jak_completion_condition == CompletionCondition.option_defeat_dark_eco_plant:
|
|
multiworld.completion_condition[player] = lambda state: state.can_reach(fjp, "Region", player)
|
|
|
|
elif options.jak_completion_condition == CompletionCondition.option_defeat_klaww:
|
|
multiworld.completion_condition[player] = lambda state: state.can_reach(mp, "Region", player)
|
|
|
|
elif options.jak_completion_condition == CompletionCondition.option_defeat_gol_and_maia:
|
|
multiworld.completion_condition[player] = lambda state: state.can_reach(fb, "Region", player)
|
|
|
|
elif options.jak_completion_condition == CompletionCondition.option_open_100_cell_door:
|
|
multiworld.completion_condition[player] = lambda state: state.can_reach(fd, "Region", player)
|
|
|
|
# As a final sanity check on these options, verify that we have enough locations to allow us to cross
|
|
# the connector levels. E.g. if you set Fire Canyon count to 99, we may not have 99 Locations in hub 1.
|
|
verify_connector_level_accessibility(multiworld, options, player)
|
|
|
|
# Also verify that we didn't overload the trade amounts with more orbs than exist in the world.
|
|
verify_orbs_for_trades(multiworld, options, player)
|
|
|
|
|
|
def verify_connector_level_accessibility(multiworld: MultiWorld, options: JakAndDaxterOptions, player: int):
|
|
|
|
# Set up a state where we only have the items we need to progress, exactly when we need them, as well as
|
|
# any items we would have/get from our other options. The only variable we're actually testing here is the
|
|
# number of power cells we need.
|
|
state = CollectionState(multiworld)
|
|
if options.enable_move_randomizer == EnableMoveRandomizer.option_false:
|
|
for move in move_item_table:
|
|
state.collect(JakAndDaxterItem(move_item_table[move], ItemClassification.progression, move, player))
|
|
|
|
thresholds = {
|
|
0: {
|
|
"option": options.fire_canyon_cell_count,
|
|
"required_items": {},
|
|
},
|
|
1: {
|
|
"option": options.mountain_pass_cell_count,
|
|
"required_items": {
|
|
33: "Warrior's Pontoons",
|
|
10945: "Double Jump",
|
|
},
|
|
},
|
|
2: {
|
|
"option": options.lava_tube_cell_count,
|
|
"required_items": {},
|
|
},
|
|
}
|
|
|
|
loc = 0
|
|
for k in thresholds:
|
|
option = thresholds[k]["option"]
|
|
required_items = thresholds[k]["required_items"]
|
|
|
|
# Given our current state (starting with 0 Power Cells), determine if there are enough
|
|
# Locations to fill with the number of Power Cells needed for the next threshold.
|
|
locations_available = multiworld.get_reachable_locations(state, player)
|
|
if len(locations_available) < option.value:
|
|
raise OptionError(f"Settings conflict with {option.display_name}: "
|
|
f"not enough potential locations ({len(locations_available)}) "
|
|
f"for the required number of power cells ({option.value}).")
|
|
|
|
# Once we've determined we can pass the current threshold, add what we need to reach the next one.
|
|
for _ in range(option.value):
|
|
state.collect(JakAndDaxterItem("Power Cell", ItemClassification.progression, loc, player))
|
|
loc += 1
|
|
|
|
for item in required_items:
|
|
state.collect(JakAndDaxterItem(required_items[item], ItemClassification.progression, item, player))
|
|
|
|
|
|
def verify_orbs_for_trades(multiworld: MultiWorld, options: JakAndDaxterOptions, player: int):
|
|
|
|
citizen_trade_orbs = 9 * options.citizen_orb_trade_amount
|
|
if citizen_trade_orbs > 2000:
|
|
raise OptionError(f"Settings conflict with {options.citizen_orb_trade_amount.display_name}: "
|
|
f"required number of orbs to trade with citizens ({citizen_trade_orbs}) "
|
|
f"is more than all the orbs in the game (2000).")
|
|
|
|
oracle_trade_orbs = 6 * options.oracle_orb_trade_amount
|
|
if oracle_trade_orbs > 2000:
|
|
raise OptionError(f"Settings conflict with {options.oracle_orb_trade_amount.display_name}: "
|
|
f"required number of orbs to trade with oracles ({oracle_trade_orbs}) "
|
|
f"is more than all the orbs in the game (2000).")
|
|
|
|
total_trade_orbs = (9 * options.citizen_orb_trade_amount) + (6 * options.oracle_orb_trade_amount)
|
|
if total_trade_orbs > 2000:
|
|
raise OptionError(f"Settings conflict with Orb Trade Amounts: "
|
|
f"required number of orbs for all trades ({total_trade_orbs}) "
|
|
f"is more than all the orbs in the game (2000). "
|
|
f"Reduce the value of either {options.citizen_orb_trade_amount.display_name} "
|
|
f"or {options.oracle_orb_trade_amount.display_name}.")
|