mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-24 05:53:19 -07:00
Orbsanity (#32)
* My big dumb shortcut: a 2000 item array. * A better idea: bundle orbs as a numerical option and make array variable size. * Have Item/Region generation respect the chosen Orbsanity bundle size. Fix trade logic. * Separate Global/Local Orbsanity options. TODO - re-introduce orb factory for per-level option. * Per-level Orbsanity implemented w/ orb bundle factory. * Implement Orbsanity for client, fix some things up for regions. * Fix location name/id mappings. * Fix client orb collection on connection. * Fix minor Deathlink bug, add Update instructions.
This commit is contained in:
committed by
GitHub
parent
f8751699fc
commit
f7b688de38
@@ -107,6 +107,16 @@ class JakAndDaxterContext(CommonContext):
|
||||
await self.send_connect()
|
||||
|
||||
def on_package(self, cmd: str, args: dict):
|
||||
|
||||
if cmd == "Connected":
|
||||
slot_data = args["slot_data"]
|
||||
if slot_data["enable_orbsanity"] == 1:
|
||||
self.repl.setup_orbsanity(slot_data["enable_orbsanity"], slot_data["level_orbsanity_bundle_size"])
|
||||
elif slot_data["enable_orbsanity"] == 2:
|
||||
self.repl.setup_orbsanity(slot_data["enable_orbsanity"], slot_data["global_orbsanity_bundle_size"])
|
||||
else:
|
||||
self.repl.setup_orbsanity(slot_data["enable_orbsanity"], 1)
|
||||
|
||||
if cmd == "ReceivedItems":
|
||||
for index, item in enumerate(args["items"], start=args["index"]):
|
||||
logger.debug(f"index: {str(index)}, item: {str(item)}")
|
||||
@@ -137,7 +147,8 @@ class JakAndDaxterContext(CommonContext):
|
||||
|
||||
async def ap_inform_deathlink(self):
|
||||
if self.memr.deathlink_enabled:
|
||||
death_text = self.memr.cause_of_death.replace("Jak", self.player_names[self.slot])
|
||||
player = self.player_names[self.slot] if self.slot is not None else "Jak"
|
||||
death_text = self.memr.cause_of_death.replace("Jak", player)
|
||||
await self.send_death(death_text)
|
||||
logger.info(death_text)
|
||||
|
||||
@@ -155,6 +166,14 @@ class JakAndDaxterContext(CommonContext):
|
||||
def on_deathlink_toggle(self):
|
||||
create_task_log_exception(self.ap_inform_deathlink_toggle())
|
||||
|
||||
async def repl_reset_orbsanity(self):
|
||||
if self.memr.orbsanity_enabled:
|
||||
self.memr.reset_orbsanity = False
|
||||
self.repl.reset_orbsanity()
|
||||
|
||||
def on_orbsanity_check(self):
|
||||
create_task_log_exception(self.repl_reset_orbsanity())
|
||||
|
||||
async def run_repl_loop(self):
|
||||
while True:
|
||||
await self.repl.main_tick()
|
||||
@@ -165,7 +184,8 @@ class JakAndDaxterContext(CommonContext):
|
||||
await self.memr.main_tick(self.on_location_check,
|
||||
self.on_finish_check,
|
||||
self.on_deathlink_check,
|
||||
self.on_deathlink_toggle)
|
||||
self.on_deathlink_toggle,
|
||||
self.on_orbsanity_check)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
|
||||
@@ -39,8 +39,29 @@ scout_item_table = {
|
||||
}
|
||||
|
||||
# Orbs are also generic and interchangeable.
|
||||
# These items are only used by Orbsanity, and only one of these
|
||||
# items will be used corresponding to the chosen bundle size.
|
||||
orb_item_table = {
|
||||
1: "Precursor Orb",
|
||||
2: "Bundle of 2 Precursor Orbs",
|
||||
4: "Bundle of 4 Precursor Orbs",
|
||||
5: "Bundle of 5 Precursor Orbs",
|
||||
8: "Bundle of 8 Precursor Orbs",
|
||||
10: "Bundle of 10 Precursor Orbs",
|
||||
16: "Bundle of 16 Precursor Orbs",
|
||||
20: "Bundle of 20 Precursor Orbs",
|
||||
25: "Bundle of 25 Precursor Orbs",
|
||||
40: "Bundle of 40 Precursor Orbs",
|
||||
50: "Bundle of 50 Precursor Orbs",
|
||||
80: "Bundle of 80 Precursor Orbs",
|
||||
100: "Bundle of 100 Precursor Orbs",
|
||||
125: "Bundle of 125 Precursor Orbs",
|
||||
200: "Bundle of 200 Precursor Orbs",
|
||||
250: "Bundle of 250 Precursor Orbs",
|
||||
400: "Bundle of 400 Precursor Orbs",
|
||||
500: "Bundle of 500 Precursor Orbs",
|
||||
1000: "Bundle of 1000 Precursor Orbs",
|
||||
2000: "Bundle of 2000 Precursor Orbs",
|
||||
}
|
||||
|
||||
# These are special items representing unique unlocks in the world. Notice that their Item ID equals their
|
||||
@@ -85,8 +106,8 @@ move_item_table = {
|
||||
item_table = {
|
||||
**{Cells.to_ap_id(k): cell_item_table[k] for k in cell_item_table},
|
||||
**{Scouts.to_ap_id(k): scout_item_table[k] for k in scout_item_table},
|
||||
**{Orbs.to_ap_id(k): orb_item_table[k] for k in orb_item_table},
|
||||
**{Specials.to_ap_id(k): special_item_table[k] for k in special_item_table},
|
||||
**{Caches.to_ap_id(k): move_item_table[k] for k in move_item_table},
|
||||
**{Orbs.to_ap_id(k): orb_item_table[k] for k in orb_item_table},
|
||||
jak1_max: "Green Eco Pill" # Filler item.
|
||||
}
|
||||
|
||||
@@ -1,23 +1,74 @@
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from Options import Toggle, PerGameCommonOptions
|
||||
from Options import Toggle, PerGameCommonOptions, Choice
|
||||
|
||||
|
||||
class EnableMoveRandomizer(Toggle):
|
||||
"""Enable to include movement options as items in the randomizer.
|
||||
Jak is only able to run, swim, and single jump, until you find his other moves.
|
||||
Adds 11 items to the pool."""
|
||||
"""Enable to include movement options as items in the randomizer. Jak is only able to run, swim, and single jump,
|
||||
until you find his other moves. This adds 11 items to the pool."""
|
||||
display_name = "Enable Move Randomizer"
|
||||
|
||||
|
||||
# class EnableOrbsanity(Toggle):
|
||||
# """Enable to include Precursor Orbs as an ordered list of progressive checks.
|
||||
# Each orb you collect triggers the next release in the list.
|
||||
# Adds 2000 items to the pool."""
|
||||
# display_name = "Enable Orbsanity"
|
||||
class EnableOrbsanity(Choice):
|
||||
"""Enable to include bundles of Precursor Orbs as an ordered list of progressive checks. Every time you collect
|
||||
the chosen number of orbs, you will trigger the next release in the list. "Per Level" means these lists are
|
||||
generated and populated for each level in the game (Geyser Rock, Sandover Village, etc.). "Global" means there is
|
||||
only one list for the entire game.
|
||||
|
||||
This adds a number of Items and Locations to the pool inversely proportional to the size of the bundle.
|
||||
For example, if your bundle size is 20 orbs, you will add 100 items to the pool. If your bundle size is 250 orbs,
|
||||
you will add 8 items to the pool."""
|
||||
display_name = "Enable Orbsanity"
|
||||
option_off = 0
|
||||
option_per_level = 1
|
||||
option_global = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class GlobalOrbsanityBundleSize(Choice):
|
||||
"""Set the size of the bundle for Global Orbsanity.
|
||||
This only applies if "Enable Orbsanity" is set to "Global."
|
||||
There are 2000 orbs in the game, so your bundle size must be a factor of 2000."""
|
||||
display_name = "Global Orbsanity Bundle Size"
|
||||
option_1_orb = 1
|
||||
option_2_orbs = 2
|
||||
option_4_orbs = 4
|
||||
option_5_orbs = 5
|
||||
option_8_orbs = 8
|
||||
option_10_orbs = 10
|
||||
option_16_orbs = 16
|
||||
option_20_orbs = 20
|
||||
option_25_orbs = 25
|
||||
option_40_orbs = 40
|
||||
option_50_orbs = 50
|
||||
option_80_orbs = 80
|
||||
option_100_orbs = 100
|
||||
option_125_orbs = 125
|
||||
option_200_orbs = 200
|
||||
option_250_orbs = 250
|
||||
option_400_orbs = 400
|
||||
option_500_orbs = 500
|
||||
option_1000_orbs = 1000
|
||||
option_2000_orbs = 2000
|
||||
default = 1
|
||||
|
||||
|
||||
class PerLevelOrbsanityBundleSize(Choice):
|
||||
"""Set the size of the bundle for Per Level Orbsanity.
|
||||
This only applies if "Enable Orbsanity" is set to "Per Level."
|
||||
There are 50, 150, or 200 orbs per level, so your bundle size must be a factor of 50."""
|
||||
display_name = "Per Level Orbsanity Bundle Size"
|
||||
option_1_orb = 1
|
||||
option_2_orbs = 2
|
||||
option_5_orbs = 5
|
||||
option_10_orbs = 10
|
||||
option_25_orbs = 25
|
||||
option_50_orbs = 50
|
||||
default = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class JakAndDaxterOptions(PerGameCommonOptions):
|
||||
enable_move_randomizer: EnableMoveRandomizer
|
||||
# enable_orbsanity: EnableOrbsanity
|
||||
enable_orbsanity: EnableOrbsanity
|
||||
global_orbsanity_bundle_size: GlobalOrbsanityBundleSize
|
||||
level_orbsanity_bundle_size: PerLevelOrbsanityBundleSize
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from BaseClasses import Location
|
||||
from .GameID import jak1_name
|
||||
from .locs import (CellLocations as Cells,
|
||||
from .locs import (OrbLocations as Orbs,
|
||||
CellLocations as Cells,
|
||||
ScoutLocations as Scouts,
|
||||
SpecialLocations as Specials,
|
||||
OrbCacheLocations as Caches)
|
||||
@@ -48,4 +49,5 @@ location_table = {
|
||||
**{Scouts.to_ap_id(k): Scouts.locGMC_scoutTable[k] for k in Scouts.locGMC_scoutTable},
|
||||
**{Specials.to_ap_id(k): Specials.loc_specialTable[k] for k in Specials.loc_specialTable},
|
||||
**{Caches.to_ap_id(k): Caches.loc_orbCacheTable[k] for k in Caches.loc_orbCacheTable},
|
||||
**{Orbs.to_ap_id(k): Orbs.loc_orbBundleTable[k] for k in Orbs.loc_orbBundleTable}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import typing
|
||||
from BaseClasses import MultiWorld
|
||||
from .Items import item_table
|
||||
from .JakAndDaxterOptions import JakAndDaxterOptions
|
||||
from .locs import (CellLocations as Cells,
|
||||
from .Items import item_table
|
||||
from .Rules import can_reach_orbs
|
||||
from .locs import (OrbLocations as Orbs,
|
||||
CellLocations as Cells,
|
||||
ScoutLocations as Scouts)
|
||||
from .regs.RegionBase import JakAndDaxterRegion
|
||||
from .regs import (GeyserRockRegions as GeyserRock,
|
||||
@@ -30,7 +31,7 @@ def create_regions(multiworld: MultiWorld, options: JakAndDaxterOptions, player:
|
||||
multiworld.regions.append(menu)
|
||||
|
||||
# Build the special "Free 7 Scout Flies" Region. This is a virtual region always accessible to Menu.
|
||||
# The Power Cells within it are automatically checked when you receive the 7th scout fly for the corresponding cell.
|
||||
# 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:
|
||||
@@ -39,27 +40,46 @@ def create_regions(multiworld: MultiWorld, options: JakAndDaxterOptions, player:
|
||||
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.value == 2:
|
||||
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", player, multiworld)
|
||||
[sv] = SandoverVillage.build_regions("Sandover Village", player, multiworld)
|
||||
[fj] = ForbiddenJungle.build_regions("Forbidden Jungle", player, multiworld)
|
||||
[sb] = SentinelBeach.build_regions("Sentinel Beach", player, multiworld)
|
||||
[mi] = MistyIsland.build_regions("Misty Island", player, multiworld)
|
||||
[fc] = FireCanyon.build_regions("Fire Canyon", player, multiworld)
|
||||
[rv, rvp, rvc] = RockVillage.build_regions("Rock Village", player, multiworld)
|
||||
[pb] = PrecursorBasin.build_regions("Precursor Basin", player, multiworld)
|
||||
[lpc] = LostPrecursorCity.build_regions("Lost Precursor City", player, multiworld)
|
||||
[bs] = BoggySwamp.build_regions("Boggy Swamp", player, multiworld)
|
||||
[mp, mpr] = MountainPass.build_regions("Mountain Pass", player, multiworld)
|
||||
[vc] = VolcanicCrater.build_regions("Volcanic Crater", player, multiworld)
|
||||
[sc] = SpiderCave.build_regions("Spider Cave", player, multiworld)
|
||||
[sm] = SnowyMountain.build_regions("Snowy Mountain", player, multiworld)
|
||||
[lt] = LavaTube.build_regions("Lava Tube", player, multiworld)
|
||||
[gmc, fb] = GolAndMaiasCitadel.build_regions("Gol and Maia's Citadel", player, multiworld)
|
||||
[gr] = GeyserRock.build_regions("Geyser Rock", multiworld, options, player)
|
||||
[sv] = SandoverVillage.build_regions("Sandover Village", multiworld, options, player)
|
||||
[fj] = 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] = GolAndMaiasCitadel.build_regions("Gol and Maia's Citadel", multiworld, options, player)
|
||||
|
||||
# Define the interconnecting rules.
|
||||
menu.connect(free7)
|
||||
menu.connect(gr)
|
||||
gr.connect(sv) # Geyser Rock modified to let you leave at any time.
|
||||
sv.connect(fj)
|
||||
|
||||
@@ -1,9 +1,51 @@
|
||||
import math
|
||||
import typing
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from .JakAndDaxterOptions import JakAndDaxterOptions
|
||||
from .Items import orb_item_table
|
||||
from .locs import CellLocations as Cells
|
||||
from .Locations import location_table
|
||||
from .Regions import JakAndDaxterRegion
|
||||
from .regs.RegionBase import JakAndDaxterRegion
|
||||
|
||||
|
||||
def can_reach_orbs(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
options: JakAndDaxterOptions,
|
||||
level_name: str = None) -> int:
|
||||
|
||||
# Global Orbsanity and No Orbsanity both treat orbs as completely interchangeable.
|
||||
# Per Level Orbsanity needs to know if you can reach orbs *in a particular level.*
|
||||
if options.enable_orbsanity.value in [0, 2]:
|
||||
return can_reach_orbs_global(state, player, multiworld)
|
||||
else:
|
||||
return can_reach_orbs_level(state, player, multiworld, level_name)
|
||||
|
||||
|
||||
def can_reach_orbs_global(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld) -> int:
|
||||
|
||||
accessible_orbs = 0
|
||||
for region in multiworld.get_regions(player):
|
||||
if state.can_reach(region, "Region", player):
|
||||
accessible_orbs += typing.cast(JakAndDaxterRegion, region).orb_count
|
||||
|
||||
return accessible_orbs
|
||||
|
||||
|
||||
def can_reach_orbs_level(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
level_name: str) -> int:
|
||||
|
||||
accessible_orbs = 0
|
||||
regions = [typing.cast(JakAndDaxterRegion, reg) for reg in multiworld.get_regions(player)]
|
||||
for region in regions:
|
||||
if region.level_name == level_name and state.can_reach(region, "Region", player):
|
||||
accessible_orbs += region.orb_count
|
||||
|
||||
return accessible_orbs
|
||||
|
||||
|
||||
# TODO - Until we come up with a better progressive system for the traders (that avoids hard-locking if you pay the
|
||||
@@ -11,14 +53,28 @@ from .Regions import JakAndDaxterRegion
|
||||
def can_trade(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
options: JakAndDaxterOptions,
|
||||
required_orbs: int,
|
||||
required_previous_trade: int = None) -> bool:
|
||||
|
||||
accessible_orbs = 0
|
||||
for region in multiworld.get_regions(player):
|
||||
if state.can_reach(region, "Region", player):
|
||||
accessible_orbs += typing.cast(JakAndDaxterRegion, region).orb_count
|
||||
if options.enable_orbsanity.value == 1:
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
return can_trade_orbsanity(state, player, bundle_size, required_orbs, required_previous_trade)
|
||||
elif options.enable_orbsanity.value == 2:
|
||||
bundle_size = options.global_orbsanity_bundle_size.value
|
||||
return can_trade_orbsanity(state, player, bundle_size, required_orbs, required_previous_trade)
|
||||
else:
|
||||
return can_trade_regular(state, player, multiworld, required_orbs, required_previous_trade)
|
||||
|
||||
|
||||
def can_trade_regular(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
required_orbs: int,
|
||||
required_previous_trade: int = None) -> bool:
|
||||
|
||||
# We know that Orbsanity is off, so count orbs globally.
|
||||
accessible_orbs = can_reach_orbs_global(state, player, multiworld)
|
||||
if required_previous_trade:
|
||||
name_of_previous_trade = location_table[Cells.to_ap_id(required_previous_trade)]
|
||||
return (accessible_orbs >= required_orbs
|
||||
@@ -27,6 +83,22 @@ def can_trade(state: CollectionState,
|
||||
return accessible_orbs >= required_orbs
|
||||
|
||||
|
||||
def can_trade_orbsanity(state: CollectionState,
|
||||
player: int,
|
||||
orb_bundle_size: int,
|
||||
required_orbs: int,
|
||||
required_previous_trade: int = None) -> bool:
|
||||
|
||||
required_count = math.ceil(required_orbs / orb_bundle_size)
|
||||
orb_bundle_name = orb_item_table[orb_bundle_size]
|
||||
if required_previous_trade:
|
||||
name_of_previous_trade = location_table[Cells.to_ap_id(required_previous_trade)]
|
||||
return (state.has(orb_bundle_name, player, required_count)
|
||||
and state.can_reach(name_of_previous_trade, "Location", player=player))
|
||||
else:
|
||||
return state.has(orb_bundle_name, player, required_count)
|
||||
|
||||
|
||||
def can_free_scout_flies(state: CollectionState, player: int) -> bool:
|
||||
return (state.has("Jump Dive", player)
|
||||
or (state.has("Crouch", player)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import typing
|
||||
from typing import Dict, Any, ClassVar
|
||||
import settings
|
||||
|
||||
from Utils import local_path, visualize_regions
|
||||
@@ -66,14 +66,13 @@ class JakAndDaxterWorld(World):
|
||||
required_client_version = (0, 4, 6)
|
||||
|
||||
# Options
|
||||
settings: typing.ClassVar[JakAndDaxterSettings]
|
||||
settings: ClassVar[JakAndDaxterSettings]
|
||||
options_dataclass = JakAndDaxterOptions
|
||||
options: JakAndDaxterOptions
|
||||
|
||||
# Web world
|
||||
web = JakAndDaxterWebWorld()
|
||||
|
||||
# Items and Locations
|
||||
# Stored as {ID: Name} pairs, these must now be swapped to {Name: ID} pairs.
|
||||
# Remember, the game ID and various offsets for each item type have already been calculated.
|
||||
item_name_to_id = {item_table[k]: k for k in item_table}
|
||||
@@ -91,15 +90,22 @@ class JakAndDaxterWorld(World):
|
||||
if k in range(jak1_id + Orbs.orb_offset, jak1_max)},
|
||||
}
|
||||
|
||||
# Regions and Rules
|
||||
# This will also set Locations, Location access rules, Region access rules, etc.
|
||||
def create_regions(self):
|
||||
def create_regions(self) -> None:
|
||||
create_regions(self.multiworld, self.options, self.player)
|
||||
# visualize_regions(self.multiworld.get_region("Menu", self.player), "jak.puml")
|
||||
|
||||
# Helper function to get the correct orb bundle size.
|
||||
def get_orb_bundle_size(self) -> int:
|
||||
if self.options.enable_orbsanity.value == 1:
|
||||
return self.options.level_orbsanity_bundle_size.value
|
||||
elif self.options.enable_orbsanity.value == 2:
|
||||
return self.options.global_orbsanity_bundle_size.value
|
||||
else:
|
||||
return 0
|
||||
|
||||
# Helper function to reuse some nasty if/else trees.
|
||||
@staticmethod
|
||||
def item_type_helper(item) -> (int, ItemClassification):
|
||||
def item_type_helper(self, item) -> (int, ItemClassification):
|
||||
# Make 101 Power Cells.
|
||||
if item in range(jak1_id, jak1_id + Scouts.fly_offset):
|
||||
classification = ItemClassification.progression_skip_balancing
|
||||
@@ -120,10 +126,11 @@ class JakAndDaxterWorld(World):
|
||||
classification = ItemClassification.progression
|
||||
count = 1
|
||||
|
||||
# TODO - Make 2000 Precursor Orbs, ONLY IF Orbsanity is enabled.
|
||||
# Make N Precursor Orb bundles, where N is 2000 / bundle size.
|
||||
elif item in range(jak1_id + Orbs.orb_offset, jak1_max):
|
||||
classification = ItemClassification.progression_skip_balancing
|
||||
count = 0
|
||||
size = self.get_orb_bundle_size()
|
||||
count = int(2000 / size) if size > 0 else 0 # Don't divide by zero!
|
||||
|
||||
# Under normal circumstances, we will create 0 filler items.
|
||||
# We will manually create filler items as needed.
|
||||
@@ -137,19 +144,30 @@ class JakAndDaxterWorld(World):
|
||||
|
||||
return count, classification
|
||||
|
||||
def create_items(self):
|
||||
for item_id in item_table:
|
||||
def create_items(self) -> None:
|
||||
for item_name in self.item_name_to_id:
|
||||
item_id = self.item_name_to_id[item_name]
|
||||
|
||||
# Handle Move Randomizer option.
|
||||
# If it is OFF, put all moves in your starting inventory instead of the item pool,
|
||||
# then fill the item pool with a corresponding amount of filler items.
|
||||
if not self.options.enable_move_randomizer and item_table[item_id] in self.item_name_groups["Moves"]:
|
||||
self.multiworld.push_precollected(self.create_item(item_table[item_id]))
|
||||
if item_name in self.item_name_groups["Moves"] and not self.options.enable_move_randomizer:
|
||||
self.multiworld.push_precollected(self.create_item(item_name))
|
||||
self.multiworld.itempool += [self.create_item(self.get_filler_item_name())]
|
||||
else:
|
||||
count, classification = self.item_type_helper(item_id)
|
||||
self.multiworld.itempool += [JakAndDaxterItem(item_table[item_id], classification, item_id, self.player)
|
||||
for _ in range(count)]
|
||||
continue
|
||||
|
||||
# Handle Orbsanity option.
|
||||
# If it is OFF, don't add any orbs to the item pool.
|
||||
# If it is ON, only add the orb bundle that matches the choice in options.
|
||||
if (item_name in self.item_name_groups["Precursor Orbs"]
|
||||
and ((self.options.enable_orbsanity.value == 0
|
||||
or Orbs.to_game_id(item_id) != self.get_orb_bundle_size()))):
|
||||
continue
|
||||
|
||||
# In every other scenario, do this.
|
||||
count, classification = self.item_type_helper(item_id)
|
||||
self.multiworld.itempool += [JakAndDaxterItem(item_name, classification, item_id, self.player)
|
||||
for _ in range(count)]
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item_id = self.item_name_to_id[name]
|
||||
@@ -158,3 +176,9 @@ class JakAndDaxterWorld(World):
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "Green Eco Pill"
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return self.options.as_dict("enable_move_randomizer",
|
||||
"enable_orbsanity",
|
||||
"global_orbsanity_bundle_size",
|
||||
"level_orbsanity_bundle_size")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import random
|
||||
import typing
|
||||
from typing import ByteString, List, Callable
|
||||
import json
|
||||
import pymem
|
||||
from pymem import pattern
|
||||
@@ -7,7 +7,8 @@ from pymem.exception import ProcessNotFound, ProcessError, MemoryReadError, WinA
|
||||
from dataclasses import dataclass
|
||||
|
||||
from CommonClient import logger
|
||||
from ..locs import (CellLocations as Cells,
|
||||
from ..locs import (OrbLocations as Orbs,
|
||||
CellLocations as Cells,
|
||||
ScoutLocations as Flies,
|
||||
SpecialLocations as Specials,
|
||||
OrbCacheLocations as Caches)
|
||||
@@ -65,6 +66,12 @@ orb_caches_checked_offset = offsets.define(sizeof_uint32, 16)
|
||||
moves_received_offset = offsets.define(sizeof_uint8, 16)
|
||||
moverando_enabled_offset = offsets.define(sizeof_uint8)
|
||||
|
||||
# Orbsanity information.
|
||||
orbsanity_option_offset = offsets.define(sizeof_uint8)
|
||||
orbsanity_bundle_offset = offsets.define(sizeof_uint32)
|
||||
collected_bundle_level_offset = offsets.define(sizeof_uint8)
|
||||
collected_bundle_count_offset = offsets.define(sizeof_uint32)
|
||||
|
||||
# The End.
|
||||
end_marker_offset = offsets.define(sizeof_uint8, 4)
|
||||
|
||||
@@ -111,7 +118,7 @@ def autopsy(died: int) -> str:
|
||||
|
||||
|
||||
class JakAndDaxterMemoryReader:
|
||||
marker: typing.ByteString
|
||||
marker: ByteString
|
||||
goal_address = None
|
||||
connected: bool = False
|
||||
initiated_connect: bool = False
|
||||
@@ -128,15 +135,20 @@ class JakAndDaxterMemoryReader:
|
||||
send_deathlink: bool = False
|
||||
cause_of_death: str = ""
|
||||
|
||||
def __init__(self, marker: typing.ByteString = b'UnLiStEdStRaTs_JaK1\x00'):
|
||||
# Orbsanity handling
|
||||
orbsanity_enabled: bool = False
|
||||
reset_orbsanity: bool = False
|
||||
|
||||
def __init__(self, marker: ByteString = b'UnLiStEdStRaTs_JaK1\x00'):
|
||||
self.marker = marker
|
||||
self.connect()
|
||||
|
||||
async def main_tick(self,
|
||||
location_callback: typing.Callable,
|
||||
finish_callback: typing.Callable,
|
||||
deathlink_callback: typing.Callable,
|
||||
deathlink_toggle: typing.Callable):
|
||||
location_callback: Callable,
|
||||
finish_callback: Callable,
|
||||
deathlink_callback: Callable,
|
||||
deathlink_toggle: Callable,
|
||||
orbsanity_callback: Callable):
|
||||
if self.initiated_connect:
|
||||
await self.connect()
|
||||
self.initiated_connect = False
|
||||
@@ -171,6 +183,9 @@ class JakAndDaxterMemoryReader:
|
||||
if self.send_deathlink:
|
||||
deathlink_callback()
|
||||
|
||||
if self.reset_orbsanity:
|
||||
orbsanity_callback()
|
||||
|
||||
async def connect(self):
|
||||
try:
|
||||
self.gk_process = pymem.Pymem("gk.exe") # The GOAL Kernel
|
||||
@@ -207,7 +222,7 @@ class JakAndDaxterMemoryReader:
|
||||
logger.info(" Last location checked: " + (str(self.location_outbox[self.outbox_index])
|
||||
if self.outbox_index else "None"))
|
||||
|
||||
def read_memory(self) -> typing.List[int]:
|
||||
def read_memory(self) -> List[int]:
|
||||
try:
|
||||
next_cell_index = self.read_goal_address(0, sizeof_uint64)
|
||||
next_buzzer_index = self.read_goal_address(next_buzzer_index_offset, sizeof_uint64)
|
||||
@@ -262,8 +277,30 @@ class JakAndDaxterMemoryReader:
|
||||
logger.debug("Checked orb cache: " + str(next_cache))
|
||||
|
||||
# Listen for any changes to this setting.
|
||||
moverando_flag = self.read_goal_address(moverando_enabled_offset, sizeof_uint8)
|
||||
self.moverando_enabled = bool(moverando_flag)
|
||||
# moverando_flag = self.read_goal_address(moverando_enabled_offset, sizeof_uint8)
|
||||
# self.moverando_enabled = bool(moverando_flag)
|
||||
|
||||
orbsanity_option = self.read_goal_address(orbsanity_option_offset, sizeof_uint8)
|
||||
orbsanity_bundle = self.read_goal_address(orbsanity_bundle_offset, sizeof_uint32)
|
||||
self.orbsanity_enabled = orbsanity_option > 0
|
||||
|
||||
# Treat these values like the Deathlink flag. They need to be reset once they are checked.
|
||||
collected_bundle_level = self.read_goal_address(collected_bundle_level_offset, sizeof_uint8)
|
||||
collected_bundle_count = self.read_goal_address(collected_bundle_count_offset, sizeof_uint32)
|
||||
|
||||
if orbsanity_option > 0 and collected_bundle_count > 0:
|
||||
# Count up from the first bundle, by bundle size, until you reach the latest collected bundle.
|
||||
# e.g. {25, 50, 75, 100, 125...}
|
||||
for k in range(orbsanity_bundle,
|
||||
orbsanity_bundle + collected_bundle_count, # Range max is non-inclusive.
|
||||
orbsanity_bundle):
|
||||
|
||||
bundle_ap_id = Orbs.to_ap_id(Orbs.find_address(collected_bundle_level, k, orbsanity_bundle))
|
||||
if bundle_ap_id not in self.location_outbox:
|
||||
self.location_outbox.append(bundle_ap_id)
|
||||
logger.debug("Checked orb bundle: " + str(bundle_ap_id))
|
||||
|
||||
# self.reset_orbsanity = True
|
||||
|
||||
except (ProcessError, MemoryReadError, WinAPIError):
|
||||
logger.error("The gk process has died. Restart the game and run \"/memr connect\" again.")
|
||||
|
||||
@@ -257,15 +257,15 @@ class JakAndDaxterReplClient:
|
||||
return ok
|
||||
|
||||
def receive_precursor_orb(self, ap_id: int) -> bool:
|
||||
orb_id = Orbs.to_game_id(ap_id)
|
||||
orb_amount = Orbs.to_game_id(ap_id)
|
||||
ok = self.send_form("(send-event "
|
||||
"*target* \'get-archipelago "
|
||||
"(pickup-type money) "
|
||||
"(the float " + str(orb_id) + "))")
|
||||
"(the float " + str(orb_amount) + "))")
|
||||
if ok:
|
||||
logger.debug(f"Received a Precursor Orb!")
|
||||
logger.debug(f"Received {orb_amount} Precursor Orbs!")
|
||||
else:
|
||||
logger.error(f"Unable to receive a Precursor Orb!")
|
||||
logger.error(f"Unable to receive {orb_amount} Precursor Orbs!")
|
||||
return ok
|
||||
|
||||
# Green eco pills are our filler item. Use the get-pickup event instead to handle being full health.
|
||||
@@ -308,6 +308,28 @@ class JakAndDaxterReplClient:
|
||||
logger.error(f"Unable to reset deathlink flag!")
|
||||
return ok
|
||||
|
||||
def setup_orbsanity(self, option: int, bundle: int) -> bool:
|
||||
ok = self.send_form(f"(ap-setup-orbs! (the uint {option}) (the uint {bundle}))")
|
||||
if ok:
|
||||
logger.debug(f"Set up orbsanity: Option {option}, Bundle {bundle}!")
|
||||
else:
|
||||
logger.error(f"Unable to set up orbsanity: Option {option}, Bundle {bundle}!")
|
||||
return ok
|
||||
|
||||
def reset_orbsanity(self) -> bool:
|
||||
ok = self.send_form(f"(set! (-> *ap-info-jak1* collected-bundle-level) 0)")
|
||||
if ok:
|
||||
logger.debug(f"Reset level ID for collected orbsanity bundle!")
|
||||
else:
|
||||
logger.error(f"Unable to reset level ID for collected orbsanity bundle!")
|
||||
|
||||
ok = self.send_form(f"(set! (-> *ap-info-jak1* collected-bundle-count) 0)")
|
||||
if ok:
|
||||
logger.debug(f"Reset orb count for collected orbsanity bundle!")
|
||||
else:
|
||||
logger.error(f"Unable to reset orb count for collected orbsanity bundle!")
|
||||
return ok
|
||||
|
||||
def save_data(self):
|
||||
with open("jakanddaxter_item_inbox.json", "w+") as f:
|
||||
dump = {
|
||||
|
||||
@@ -29,11 +29,11 @@ At this time, this method of setup works on Windows only, but Linux support is a
|
||||
- `C:\Users\<YourName>\AppData\Roaming\OpenGOAL-Mods\archipelagoal\iso_data` should have *all* the same files as
|
||||
- `C:\Users\<YourName>\AppData\Roaming\OpenGOAL-Mods\_iso_data`, if it doesn't, copy those files over manually.
|
||||
- And then `Recompile` if you needed to copy the files over.
|
||||
- **DO NOT LAUNCH THE GAME FROM THE MOD LAUNCHER.** It will run in retail mode, which is incompatible with Archipelago. We need it to run in debug mode (see below).
|
||||
- **DO NOT PLAY AN ARCHIPELAGO GAME THROUGH THE MOD LAUNCHER.** It will run in retail mode, which is incompatible with Archipelago. We need it to run in debug mode (see below).
|
||||
|
||||
***Archipelago Launcher***
|
||||
|
||||
- Copy the `jakanddaxter.apworld` file into your `Archipelago/lib/worlds` directory.
|
||||
- Copy the `jakanddaxter.apworld` file into your `Archipelago/custom_worlds` directory.
|
||||
- Reminder: the default installation location for Archipelago is `C:\ProgramData\Archipelago`.
|
||||
- Run the Archipelago Launcher.
|
||||
- From the left-most list, click `Generate Template Options`.
|
||||
@@ -44,6 +44,20 @@ At this time, this method of setup works on Windows only, but Linux support is a
|
||||
- When asked to select your multiworld seed, navigate to `Archipelago/output` and select the zip file containing the seed you just generated.
|
||||
- You can sort by Date Modified to make it easy to find.
|
||||
|
||||
## Updates and New Releases
|
||||
|
||||
***OpenGOAL Mod Launcher***
|
||||
|
||||
- Run the Mod Launcher and click `ArchipelaGOAL` in the mod list.
|
||||
- Click `Launch` to download and install any new updates that have been released.
|
||||
- You can verify your version once you reach the title screen menu by navigating to `Options > Game Options > Miscellaneous > Speedrunner Mode`.
|
||||
- Turn on `Speedrunner Mode` and exit the menu. You should see the installed version number in the bottom left corner. Then turn `Speedrunner Mode` back off.
|
||||
- Once you've verified your version, you can close the game. Remember, this is just for downloading updates. **DO NOT PLAY AN ARCHIPELAGO GAME THROUGH THE MOD LAUNCHER.**
|
||||
|
||||
***Archipelago Launcher***
|
||||
|
||||
- Copy the latest `jakanddaxter.apworld` file into your `Archipelago/custom_worlds` directory.
|
||||
|
||||
## Starting a Game
|
||||
|
||||
***New Game***
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ..GameID import jak1_id
|
||||
|
||||
# Precursor Orbs are not necessarily given ID's by the game.
|
||||
@@ -7,14 +9,13 @@ from ..GameID import jak1_id
|
||||
# so like Power Cells these are not ordered, nor contiguous, nor exclusively orbs.
|
||||
|
||||
# In fact, other ID's in this range belong to actors that spawn orbs when they are activated or when they die,
|
||||
# like steel crates, orb caches, Spider Cave gnawers, or jumping on the Plant Boss's head.
|
||||
# like steel crates, orb caches, Spider Cave gnawers, or jumping on the Plant Boss's head. These orbs that spawn
|
||||
# from parent actors DON'T have an Actor ID themselves - the parent object keeps track of how many of its orbs
|
||||
# have been picked up.
|
||||
|
||||
# These orbs that spawn from parent actors DON'T have an Actor ID themselves - the parent object keeps
|
||||
# track of how many of its orbs have been picked up. If you pick up only some of its orbs, it
|
||||
# will respawn when you leave the area, and only drop the remaining number of orbs when activated/killed.
|
||||
# Once all the orbs are picked up, the actor will permanently "retire" and never spawn again.
|
||||
# The maximum number of orbs that any actor can spawn is 30 (the orb caches in citadel). Covering
|
||||
# these ID-less orbs may need to be a future enhancement. TODO ^^
|
||||
# In order to deal with this mess, we're creating a factory class that will generate Orb Locations for us.
|
||||
# This will be compatible with both Global Orbsanity and Per-Level Orbsanity, allowing us to create any
|
||||
# number of Locations depending on the bundle size chosen, while also guaranteeing that each has a unique address.
|
||||
|
||||
# We can use 2^15 to offset them from Orb Caches, because Orb Cache ID's max out at (jak1_id + 17792).
|
||||
orb_offset = 32768
|
||||
@@ -32,68 +33,96 @@ def to_game_id(ap_id: int) -> int:
|
||||
return ap_id - jak1_id - orb_offset # Reverse process, subtract the offsets.
|
||||
|
||||
|
||||
# The ID's you see below correspond directly to that orb's Actor ID in the game.
|
||||
# Use this when the Memory Reader learns that you checked a specific bundle.
|
||||
# Offset each level by 200 orbs (max number in any level), {200, 400, ...}
|
||||
# then divide orb count by bundle size, {201, 202, ...}
|
||||
# then subtract 1. {200, 201, ...}
|
||||
def find_address(level_index: int, orb_count: int, bundle_size: int) -> int:
|
||||
result = (level_index * 200) + (orb_count // bundle_size) - 1
|
||||
return result
|
||||
|
||||
# Geyser Rock
|
||||
locGR_orbTable = {
|
||||
|
||||
# Use this when assigning addresses during region generation.
|
||||
def create_address(level_index: int, bundle_index: int) -> int:
|
||||
result = (level_index * 200) + bundle_index
|
||||
return result
|
||||
|
||||
|
||||
# What follows is our method of generating all the name/ID pairs for location_name_to_id.
|
||||
# Remember that not every bundle will be used in the actual seed, we just need this as a static map of strings to ints.
|
||||
level_info = {
|
||||
"": {
|
||||
"level_index": 16, # Global
|
||||
"orbs": 2000
|
||||
},
|
||||
"Geyser Rock": {
|
||||
"level_index": 0,
|
||||
"orbs": 50
|
||||
},
|
||||
"Sandover Village": {
|
||||
"level_index": 1,
|
||||
"orbs": 50
|
||||
},
|
||||
"Sentinel Beach": {
|
||||
"level_index": 2,
|
||||
"orbs": 150
|
||||
},
|
||||
"Forbidden Jungle": {
|
||||
"level_index": 3,
|
||||
"orbs": 150
|
||||
},
|
||||
"Misty Island": {
|
||||
"level_index": 4,
|
||||
"orbs": 150
|
||||
},
|
||||
"Fire Canyon": {
|
||||
"level_index": 5,
|
||||
"orbs": 50
|
||||
},
|
||||
"Rock Village": {
|
||||
"level_index": 6,
|
||||
"orbs": 50
|
||||
},
|
||||
"Lost Precursor City": {
|
||||
"level_index": 7,
|
||||
"orbs": 200
|
||||
},
|
||||
"Boggy Swamp": {
|
||||
"level_index": 8,
|
||||
"orbs": 200
|
||||
},
|
||||
"Precursor Basin": {
|
||||
"level_index": 9,
|
||||
"orbs": 200
|
||||
},
|
||||
"Mountain Pass": {
|
||||
"level_index": 10,
|
||||
"orbs": 50
|
||||
},
|
||||
"Volcanic Crater": {
|
||||
"level_index": 11,
|
||||
"orbs": 50
|
||||
},
|
||||
"Snowy Mountain": {
|
||||
"level_index": 12,
|
||||
"orbs": 200
|
||||
},
|
||||
"Spider Cave": {
|
||||
"level_index": 13,
|
||||
"orbs": 200
|
||||
},
|
||||
"Lava Tube": {
|
||||
"level_index": 14,
|
||||
"orbs": 50
|
||||
},
|
||||
"Gol and Maia's Citadel": {
|
||||
"level_index": 15,
|
||||
"orbs": 200
|
||||
}
|
||||
}
|
||||
|
||||
# Sandover Village
|
||||
locSV_orbTable = {
|
||||
}
|
||||
|
||||
# Forbidden Jungle
|
||||
locFJ_orbTable = {
|
||||
}
|
||||
|
||||
# Sentinel Beach
|
||||
locSB_orbTable = {
|
||||
}
|
||||
|
||||
# Misty Island
|
||||
locMI_orbTable = {
|
||||
}
|
||||
|
||||
# Fire Canyon
|
||||
locFC_orbTable = {
|
||||
}
|
||||
|
||||
# Rock Village
|
||||
locRV_orbTable = {
|
||||
}
|
||||
|
||||
# Precursor Basin
|
||||
locPB_orbTable = {
|
||||
}
|
||||
|
||||
# Lost Precursor City
|
||||
locLPC_orbTable = {
|
||||
}
|
||||
|
||||
# Boggy Swamp
|
||||
locBS_orbTable = {
|
||||
}
|
||||
|
||||
# Mountain Pass
|
||||
locMP_orbTable = {
|
||||
}
|
||||
|
||||
# Volcanic Crater
|
||||
locVC_orbTable = {
|
||||
}
|
||||
|
||||
# Spider Cave
|
||||
locSC_orbTable = {
|
||||
}
|
||||
|
||||
# Snowy Mountain
|
||||
locSM_orbTable = {
|
||||
}
|
||||
|
||||
# Lava Tube
|
||||
locLT_orbTable = {
|
||||
}
|
||||
|
||||
# Gol and Maias Citadel
|
||||
locGMC_orbTable = {
|
||||
loc_orbBundleTable = {
|
||||
create_address(level_info[name]["level_index"], index): f"{name} Orb Bundle {index + 1}".strip()
|
||||
for name in level_info
|
||||
for index in range(level_info[name]["orbs"])
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_fight
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_fight, can_reach_orbs
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
# This level is full of short-medium gaps that cannot be crossed by single jump alone.
|
||||
# These helper functions list out the moves that can cross all these gaps (painting with a broad brush but...)
|
||||
@@ -20,6 +21,7 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
or (state.has("Punch", p) and state.has("Punch Uppercut", p)))
|
||||
|
||||
# Orb crates and fly box in this area can be gotten with yellow eco and goggles.
|
||||
# Start with the first yellow eco cluster near first_bats and work your way backward toward the entrance.
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 23)
|
||||
main_area.add_fly_locations([43])
|
||||
|
||||
@@ -151,4 +153,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(last_tar_pit)
|
||||
multiworld.regions.append(fourth_tether)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(8,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_reach_orbs
|
||||
from ..locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 50)
|
||||
|
||||
@@ -14,4 +16,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
|
||||
multiworld.regions.append(main_area)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(5,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_fight
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 25)
|
||||
|
||||
@@ -80,4 +81,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(temple_int_pre_blue)
|
||||
multiworld.regions.append(temple_int_post_blue)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(150 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(3,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_reach_orbs
|
||||
from ..locs import ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 50)
|
||||
main_area.add_cell_locations([92, 93])
|
||||
@@ -23,4 +25,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(main_area)
|
||||
multiworld.regions.append(cliff)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(0,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_fight
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
|
||||
|
||||
# God help me... here we go.
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
# This level is full of short-medium gaps that cannot be crossed by single jump alone.
|
||||
# These helper functions list out the moves that can cross all these gaps (painting with a broad brush but...)
|
||||
@@ -112,4 +113,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(rotating_tower)
|
||||
multiworld.regions.append(final_boss)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(15,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area, final_boss]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_reach_orbs
|
||||
from ..locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 50)
|
||||
|
||||
@@ -14,4 +16,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
|
||||
multiworld.regions.append(main_area)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(14,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_fight
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
# Just the starting area.
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 4)
|
||||
@@ -127,4 +128,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(second_slide)
|
||||
multiworld.regions.append(helix_room)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(7,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_fight
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 9)
|
||||
|
||||
@@ -113,4 +114,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(lower_approach)
|
||||
multiworld.regions.append(arena)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(150 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(4,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_reach_orbs
|
||||
from ..locs import ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
# This is basically just Klaww.
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 0)
|
||||
@@ -30,5 +32,22 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(race)
|
||||
multiworld.regions.append(shortcut)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(10,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
# Return race required for inter-level connections.
|
||||
return [main_area, race]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_reach_orbs
|
||||
from ..locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 200)
|
||||
|
||||
@@ -14,4 +16,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
|
||||
multiworld.regions.append(main_area)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(9,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -3,7 +3,8 @@ from BaseClasses import MultiWorld, Region
|
||||
from ..GameID import jak1_name
|
||||
from ..JakAndDaxterOptions import JakAndDaxterOptions
|
||||
from ..Locations import JakAndDaxterLocation, location_table
|
||||
from ..locs import (CellLocations as Cells,
|
||||
from ..locs import (OrbLocations as Orbs,
|
||||
CellLocations as Cells,
|
||||
ScoutLocations as Scouts,
|
||||
SpecialLocations as Specials,
|
||||
OrbCacheLocations as Caches)
|
||||
@@ -31,7 +32,8 @@ class JakAndDaxterRegion(Region):
|
||||
Converts Game ID's to AP ID's for you.
|
||||
"""
|
||||
for loc in locations:
|
||||
self.add_jak_locations(Cells.to_ap_id(loc), access_rule)
|
||||
ap_id = Cells.to_ap_id(loc)
|
||||
self.add_jak_locations(ap_id, location_table[ap_id], access_rule)
|
||||
|
||||
def add_fly_locations(self, locations: List[int], access_rule: Callable = None):
|
||||
"""
|
||||
@@ -39,7 +41,8 @@ class JakAndDaxterRegion(Region):
|
||||
Converts Game ID's to AP ID's for you.
|
||||
"""
|
||||
for loc in locations:
|
||||
self.add_jak_locations(Scouts.to_ap_id(loc), access_rule)
|
||||
ap_id = Scouts.to_ap_id(loc)
|
||||
self.add_jak_locations(ap_id, location_table[ap_id], access_rule)
|
||||
|
||||
def add_special_locations(self, locations: List[int], access_rule: Callable = None):
|
||||
"""
|
||||
@@ -49,7 +52,8 @@ class JakAndDaxterRegion(Region):
|
||||
Power Cell Locations, so you get 2 unlocks for these rather than 1.
|
||||
"""
|
||||
for loc in locations:
|
||||
self.add_jak_locations(Specials.to_ap_id(loc), access_rule)
|
||||
ap_id = Specials.to_ap_id(loc)
|
||||
self.add_jak_locations(ap_id, location_table[ap_id], access_rule)
|
||||
|
||||
def add_cache_locations(self, locations: List[int], access_rule: Callable = None):
|
||||
"""
|
||||
@@ -57,13 +61,28 @@ class JakAndDaxterRegion(Region):
|
||||
Converts Game ID's to AP ID's for you.
|
||||
"""
|
||||
for loc in locations:
|
||||
self.add_jak_locations(Caches.to_ap_id(loc), access_rule)
|
||||
ap_id = Caches.to_ap_id(loc)
|
||||
self.add_jak_locations(ap_id, location_table[ap_id], access_rule)
|
||||
|
||||
def add_jak_locations(self, ap_id: int, access_rule: Callable = None):
|
||||
def add_orb_locations(self, level_index: int, bundle_index: int, bundle_size: int, access_rule: Callable = None):
|
||||
"""
|
||||
Helper function to add Locations. Not to be used directly.
|
||||
Adds Orb Bundle Locations to this region equal to `bundle_count`. Used only when Per-Level Orbsanity is enabled.
|
||||
The orb factory class will handle AP ID enumeration.
|
||||
"""
|
||||
location = JakAndDaxterLocation(self.player, location_table[ap_id], ap_id, self)
|
||||
bundle_address = Orbs.create_address(level_index, bundle_index)
|
||||
location = JakAndDaxterLocation(self.player,
|
||||
f"{self.level_name} Orb Bundle {bundle_index + 1}".strip(),
|
||||
Orbs.to_ap_id(bundle_address),
|
||||
self)
|
||||
if access_rule:
|
||||
location.access_rule = access_rule
|
||||
self.locations.append(location)
|
||||
|
||||
def add_jak_locations(self, ap_id: int, name: str, access_rule: Callable = None):
|
||||
"""
|
||||
Helper function to add Locations. Not to be used directly.
|
||||
"""
|
||||
location = JakAndDaxterLocation(self.player, name, ap_id, self)
|
||||
if access_rule:
|
||||
location.access_rule = access_rule
|
||||
self.locations.append(location)
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_trade
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_trade, can_reach_orbs
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
# This includes most of the area surrounding LPC as well, for orb_count purposes. You can swim and single jump.
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 23)
|
||||
main_area.add_cell_locations([31], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
main_area.add_cell_locations([32], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
main_area.add_cell_locations([33], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
main_area.add_cell_locations([34], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
main_area.add_cell_locations([35], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530, 34))
|
||||
can_trade(state, player, multiworld, options, 1530, 34))
|
||||
|
||||
# These 2 scout fly boxes can be broken by running with nearby blue eco.
|
||||
main_area.add_fly_locations([196684, 262220])
|
||||
@@ -33,8 +34,9 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
orb_cache.add_cache_locations([10945], access_rule=lambda state:
|
||||
(state.has("Roll", player) and state.has("Roll Jump", player)))
|
||||
|
||||
# Fly here can be gotten with Yellow Eco from Boggy, goggles, and no extra movement options (see fly ID 43).
|
||||
pontoon_bridge = JakAndDaxterRegion("Pontoon Bridge", player, multiworld, level_name, 7)
|
||||
pontoon_bridge.add_fly_locations([393292], access_rule=lambda state: can_free_scout_flies(state, player))
|
||||
pontoon_bridge.add_fly_locations([393292])
|
||||
|
||||
klaww_cliff = JakAndDaxterRegion("Klaww's Cliff", player, multiworld, level_name, 0)
|
||||
|
||||
@@ -59,5 +61,22 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(pontoon_bridge)
|
||||
multiworld.regions.append(klaww_cliff)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(6,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
# Return klaww_cliff required for inter-level connections.
|
||||
return [main_area, pontoon_bridge, klaww_cliff]
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_trade
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_trade, can_reach_orbs
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 26)
|
||||
|
||||
# Yakows requires no combat.
|
||||
main_area.add_cell_locations([10])
|
||||
main_area.add_cell_locations([11], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
main_area.add_cell_locations([12], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
|
||||
# These 4 scout fly boxes can be broken by running with all the blue eco from Sentinel Beach.
|
||||
main_area.add_fly_locations([262219, 327755, 131147, 65611])
|
||||
@@ -32,9 +33,9 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
|
||||
oracle_platforms = JakAndDaxterRegion("Oracle Platforms", player, multiworld, level_name, 6)
|
||||
oracle_platforms.add_cell_locations([13], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
oracle_platforms.add_cell_locations([14], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530, 13))
|
||||
can_trade(state, player, multiworld, options, 1530, 13))
|
||||
oracle_platforms.add_fly_locations([393291], access_rule=lambda state:
|
||||
can_free_scout_flies(state, player))
|
||||
|
||||
@@ -68,4 +69,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(yakow_cliff)
|
||||
multiworld.regions.append(oracle_platforms)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(1,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_fight
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 128)
|
||||
main_area.add_cell_locations([18, 21, 22])
|
||||
@@ -82,4 +83,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(blue_ridge)
|
||||
multiworld.regions.append(cannon_tower)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(150 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(2,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_fight
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
|
||||
|
||||
# God help me... here we go.
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
# We need a few helper functions.
|
||||
def can_cross_main_gap(state: CollectionState, p: int) -> bool:
|
||||
@@ -189,4 +190,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(fort_interior_base)
|
||||
multiworld.regions.append(fort_interior_course_end)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(12,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_fight
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
# A large amount of this area can be covered by single jump, floating platforms, web trampolines, and goggles.
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 63)
|
||||
@@ -107,4 +108,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
multiworld.regions.append(spider_tunnel)
|
||||
multiworld.regions.append(spider_tunnel_crates)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(13,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from ..Rules import can_free_scout_flies, can_trade
|
||||
from .. import JakAndDaxterOptions
|
||||
from ..Rules import can_free_scout_flies, can_trade, can_reach_orbs
|
||||
from ..locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
# No area is inaccessible in VC even with only running and jumping.
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 50)
|
||||
main_area.add_cell_locations([96], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
main_area.add_cell_locations([97], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530, 96))
|
||||
can_trade(state, player, multiworld, options, 1530, 96))
|
||||
main_area.add_cell_locations([98], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530, 97))
|
||||
can_trade(state, player, multiworld, options, 1530, 97))
|
||||
main_area.add_cell_locations([99], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530, 98))
|
||||
can_trade(state, player, multiworld, options, 1530, 98))
|
||||
main_area.add_cell_locations([100], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530))
|
||||
can_trade(state, player, multiworld, options, 1530))
|
||||
main_area.add_cell_locations([101], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, 1530, 100))
|
||||
can_trade(state, player, multiworld, options, 1530, 100))
|
||||
|
||||
# Hidden Power Cell: you can carry yellow eco from Spider Cave just by running and jumping
|
||||
# and using your Goggles to shoot the box (you do not need Punch to shoot from FP mode).
|
||||
@@ -35,4 +36,21 @@ def build_regions(level_name: str, player: int, multiworld: MultiWorld) -> List[
|
||||
|
||||
multiworld.regions.append(main_area)
|
||||
|
||||
# If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always
|
||||
# accessible to Main Area. The Locations within are automatically checked when you collect enough orbs.
|
||||
if options.enable_orbsanity.value == 1:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
for bundle_index in range(bundle_count):
|
||||
orbs.add_orb_locations(11,
|
||||
bundle_index,
|
||||
bundle_size,
|
||||
access_rule=lambda state, bundle=bundle_index:
|
||||
can_reach_orbs(state, player, multiworld, options, level_name)
|
||||
>= (bundle_size * (bundle + 1)))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
return [main_area]
|
||||
|
||||
Reference in New Issue
Block a user