mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-23 21:23:22 -07:00
Exempt's Code Review Updates (#43)
* Round 1 of code review updates, the easy stuff. * Factor options checking away from region/rule creation. * Code review updates round 2, more complex stuff. * Code review updates round 3: the mental health annihilator * Code review updates part 4: redemption. * More code review feedback, simplifying code, etc.
This commit is contained in:
committed by
GitHub
parent
b7ca9cbc2f
commit
746b281f48
@@ -1,14 +1,17 @@
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import typing
|
||||
import asyncio
|
||||
import colorama
|
||||
|
||||
import asyncio
|
||||
from asyncio import Task
|
||||
|
||||
from typing import Set, Awaitable, Optional, List
|
||||
|
||||
import pymem
|
||||
from pymem.exception import ProcessNotFound, ProcessError
|
||||
from pymem.exception import ProcessNotFound
|
||||
|
||||
import Utils
|
||||
from NetUtils import ClientStatus, NetworkItem
|
||||
from NetUtils import ClientStatus
|
||||
from CommonClient import ClientCommandProcessor, CommonContext, logger, server_loop, gui_enabled
|
||||
from .JakAndDaxterOptions import EnableOrbsanity
|
||||
|
||||
@@ -20,10 +23,10 @@ import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
||||
|
||||
all_tasks = set()
|
||||
all_tasks: Set[Task] = set()
|
||||
|
||||
|
||||
def create_task_log_exception(awaitable: typing.Awaitable) -> asyncio.Task:
|
||||
def create_task_log_exception(awaitable: Awaitable) -> asyncio.Task:
|
||||
async def _log_exception(a):
|
||||
try:
|
||||
return await a
|
||||
@@ -81,7 +84,7 @@ class JakAndDaxterContext(CommonContext):
|
||||
repl_task: asyncio.Task
|
||||
memr_task: asyncio.Task
|
||||
|
||||
def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None:
|
||||
def __init__(self, server_address: Optional[str], password: Optional[str]) -> None:
|
||||
self.repl = JakAndDaxterReplClient()
|
||||
self.memr = JakAndDaxterMemoryReader()
|
||||
# self.repl.load_data()
|
||||
@@ -195,11 +198,11 @@ class JakAndDaxterContext(CommonContext):
|
||||
self.repl.received_deathlink = True
|
||||
super().on_deathlink(data)
|
||||
|
||||
async def ap_inform_location_check(self, location_ids: typing.List[int]):
|
||||
async def ap_inform_location_check(self, location_ids: List[int]):
|
||||
message = [{"cmd": "LocationChecks", "locations": location_ids}]
|
||||
await self.send_msgs(message)
|
||||
|
||||
def on_location_check(self, location_ids: typing.List[int]):
|
||||
def on_location_check(self, location_ids: List[int]):
|
||||
create_task_log_exception(self.ap_inform_location_check(location_ids))
|
||||
|
||||
async def ap_inform_finished_game(self):
|
||||
|
||||
@@ -51,6 +51,7 @@ class GlobalOrbsanityBundleSize(Choice):
|
||||
option_500_orbs = 500
|
||||
option_1000_orbs = 1000
|
||||
option_2000_orbs = 2000
|
||||
friendly_minimum = 5
|
||||
default = 20
|
||||
|
||||
|
||||
@@ -64,6 +65,7 @@ class PerLevelOrbsanityBundleSize(Choice):
|
||||
option_10_orbs = 10
|
||||
option_25_orbs = 25
|
||||
option_50_orbs = 50
|
||||
friendly_minimum = 5
|
||||
default = 25
|
||||
|
||||
|
||||
@@ -72,6 +74,7 @@ class FireCanyonCellCount(Range):
|
||||
display_name = "Fire Canyon Cell Count"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
friendly_maximum = 30
|
||||
default = 20
|
||||
|
||||
|
||||
@@ -80,6 +83,7 @@ class MountainPassCellCount(Range):
|
||||
display_name = "Mountain Pass Cell Count"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
friendly_maximum = 60
|
||||
default = 45
|
||||
|
||||
|
||||
@@ -88,6 +92,7 @@ class LavaTubeCellCount(Range):
|
||||
display_name = "Lava Tube Cell Count"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
friendly_maximum = 90
|
||||
default = 72
|
||||
|
||||
|
||||
@@ -95,10 +100,12 @@ class LavaTubeCellCount(Range):
|
||||
class CitizenOrbTradeAmount(Range):
|
||||
"""Set the number of orbs you need to trade to ordinary citizens for a power cell (Mayor, Uncle, etc.).
|
||||
|
||||
Along with Oracle Orb Trade Amount, this setting cannot exceed the total number of orbs in the game (2000)."""
|
||||
Along with Oracle Orb Trade Amount, this setting cannot exceed the total number of orbs in the game (2000).
|
||||
The equation to determine the total number of trade orbs is (9 * Citizen Trades) + (6 * Oracle Trades)."""
|
||||
display_name = "Citizen Orb Trade Amount"
|
||||
range_start = 0
|
||||
range_end = 222
|
||||
friendly_maximum = 120
|
||||
default = 90
|
||||
|
||||
|
||||
@@ -106,10 +113,12 @@ class CitizenOrbTradeAmount(Range):
|
||||
class OracleOrbTradeAmount(Range):
|
||||
"""Set the number of orbs you need to trade to the Oracles for a power cell.
|
||||
|
||||
Along with Citizen Orb Trade Amount, this setting cannot exceed the total number of orbs in the game (2000)."""
|
||||
Along with Citizen Orb Trade Amount, this setting cannot exceed the total number of orbs in the game (2000).
|
||||
The equation to determine the total number of trade orbs is (9 * Citizen Trades) + (6 * Oracle Trades)."""
|
||||
display_name = "Oracle Orb Trade Amount"
|
||||
range_start = 0
|
||||
range_end = 333
|
||||
friendly_maximum = 150
|
||||
default = 120
|
||||
|
||||
|
||||
|
||||
76
worlds/jakanddaxter/Levels.py
Normal file
76
worlds/jakanddaxter/Levels.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# This contains the list of levels in Jak and Daxter.
|
||||
# Not to be confused with Regions - there can be multiple Regions in every Level.
|
||||
level_table = {
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
level_table_with_global = {
|
||||
**level_table,
|
||||
"": {
|
||||
"level_index": 16, # Global
|
||||
"orbs": 2000
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ class JakAndDaxterLocation(Location):
|
||||
game: str = jak1_name
|
||||
|
||||
|
||||
# All Locations
|
||||
# Different tables for location groups.
|
||||
# Each Item ID == its corresponding Location ID. While we're here, do all the ID conversions needed.
|
||||
location_table = {
|
||||
cell_location_table = {
|
||||
**{Cells.to_ap_id(k): Cells.loc7SF_cellTable[k] for k in Cells.loc7SF_cellTable},
|
||||
**{Cells.to_ap_id(k): Cells.locGR_cellTable[k] for k in Cells.locGR_cellTable},
|
||||
**{Cells.to_ap_id(k): Cells.locSV_cellTable[k] for k in Cells.locSV_cellTable},
|
||||
@@ -30,7 +30,10 @@ location_table = {
|
||||
**{Cells.to_ap_id(k): Cells.locSC_cellTable[k] for k in Cells.locSC_cellTable},
|
||||
**{Cells.to_ap_id(k): Cells.locSM_cellTable[k] for k in Cells.locSM_cellTable},
|
||||
**{Cells.to_ap_id(k): Cells.locLT_cellTable[k] for k in Cells.locLT_cellTable},
|
||||
**{Cells.to_ap_id(k): Cells.locGMC_cellTable[k] for k in Cells.locGMC_cellTable},
|
||||
**{Cells.to_ap_id(k): Cells.locGMC_cellTable[k] for k in Cells.locGMC_cellTable}
|
||||
}
|
||||
|
||||
scout_location_table = {
|
||||
**{Scouts.to_ap_id(k): Scouts.locGR_scoutTable[k] for k in Scouts.locGR_scoutTable},
|
||||
**{Scouts.to_ap_id(k): Scouts.locSV_scoutTable[k] for k in Scouts.locSV_scoutTable},
|
||||
**{Scouts.to_ap_id(k): Scouts.locFJ_scoutTable[k] for k in Scouts.locFJ_scoutTable},
|
||||
@@ -46,8 +49,18 @@ location_table = {
|
||||
**{Scouts.to_ap_id(k): Scouts.locSC_scoutTable[k] for k in Scouts.locSC_scoutTable},
|
||||
**{Scouts.to_ap_id(k): Scouts.locSM_scoutTable[k] for k in Scouts.locSM_scoutTable},
|
||||
**{Scouts.to_ap_id(k): Scouts.locLT_scoutTable[k] for k in Scouts.locLT_scoutTable},
|
||||
**{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}
|
||||
**{Scouts.to_ap_id(k): Scouts.locGMC_scoutTable[k] for k in Scouts.locGMC_scoutTable}
|
||||
}
|
||||
|
||||
special_location_table = {Specials.to_ap_id(k): Specials.loc_specialTable[k] for k in Specials.loc_specialTable}
|
||||
cache_location_table = {Caches.to_ap_id(k): Caches.loc_orbCacheTable[k] for k in Caches.loc_orbCacheTable}
|
||||
orb_location_table = {Orbs.to_ap_id(k): Orbs.loc_orbBundleTable[k] for k in Orbs.loc_orbBundleTable}
|
||||
|
||||
# All Locations
|
||||
location_table = {
|
||||
**cell_location_table,
|
||||
**scout_location_table,
|
||||
**special_location_table,
|
||||
**cache_location_table,
|
||||
**orb_location_table
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
from BaseClasses import MultiWorld, CollectionState, ItemClassification
|
||||
import typing
|
||||
|
||||
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 . import JakAndDaxterWorld
|
||||
from .Items import item_table
|
||||
from .JakAndDaxterOptions import EnableOrbsanity, CompletionCondition
|
||||
from .Rules import can_reach_orbs_global
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
from .regs import (GeyserRockRegions as GeyserRock,
|
||||
SandoverVillageRegions as SandoverVillage,
|
||||
ForbiddenJungleRegions as ForbiddenJungle,
|
||||
@@ -27,9 +22,13 @@ from .regs import (GeyserRockRegions as GeyserRock,
|
||||
SnowyMountainRegions as SnowyMountain,
|
||||
LavaTubeRegions as LavaTube,
|
||||
GolAndMaiasCitadelRegions as GolAndMaiasCitadel)
|
||||
from .regs.RegionBase import JakAndDaxterRegion
|
||||
|
||||
|
||||
def create_regions(multiworld: MultiWorld, options: JakAndDaxterOptions, player: int):
|
||||
def create_regions(world: JakAndDaxterWorld):
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
# Always start with Menu.
|
||||
menu = JakAndDaxterRegion("Menu", player, multiworld)
|
||||
@@ -42,7 +41,7 @@ def create_regions(multiworld: MultiWorld, options: JakAndDaxterOptions, player:
|
||||
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_id = Scouts.to_ap_id(Cells.to_game_id(typing.cast(int, 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)
|
||||
@@ -52,37 +51,34 @@ def create_regions(multiworld: MultiWorld, options: JakAndDaxterOptions, player:
|
||||
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)
|
||||
bundle_count = 2000 // world.orb_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)))
|
||||
can_reach_orbs_global(state, player, world, bundle))
|
||||
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)
|
||||
[gr] = GeyserRock.build_regions("Geyser Rock", world)
|
||||
[sv] = SandoverVillage.build_regions("Sandover Village", world)
|
||||
[fj, fjp] = ForbiddenJungle.build_regions("Forbidden Jungle", world)
|
||||
[sb] = SentinelBeach.build_regions("Sentinel Beach", world)
|
||||
[mi] = MistyIsland.build_regions("Misty Island", world)
|
||||
[fc] = FireCanyon.build_regions("Fire Canyon", world)
|
||||
[rv, rvp, rvc] = RockVillage.build_regions("Rock Village", world)
|
||||
[pb] = PrecursorBasin.build_regions("Precursor Basin", world)
|
||||
[lpc] = LostPrecursorCity.build_regions("Lost Precursor City", world)
|
||||
[bs] = BoggySwamp.build_regions("Boggy Swamp", world)
|
||||
[mp, mpr] = MountainPass.build_regions("Mountain Pass", world)
|
||||
[vc] = VolcanicCrater.build_regions("Volcanic Crater", world)
|
||||
[sc] = SpiderCave.build_regions("Spider Cave", world)
|
||||
[sm] = SnowyMountain.build_regions("Snowy Mountain", world)
|
||||
[lt] = LavaTube.build_regions("Lava Tube", world)
|
||||
[gmc, fb, fd] = GolAndMaiasCitadel.build_regions("Gol and Maia's Citadel", world)
|
||||
|
||||
# Configurable counts of cells for connector levels.
|
||||
fc_count = options.fire_canyon_cell_count.value
|
||||
@@ -129,82 +125,6 @@ def create_regions(multiworld: MultiWorld, options: JakAndDaxterOptions, 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)
|
||||
else:
|
||||
raise OptionError(f"Unknown completion goal ID ({options.jak_completion_condition.value}).")
|
||||
|
||||
# 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}.")
|
||||
|
||||
@@ -1,102 +1,117 @@
|
||||
import math
|
||||
import typing
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from .JakAndDaxterOptions import JakAndDaxterOptions, EnableOrbsanity
|
||||
from .Items import orb_item_table
|
||||
from Options import OptionError
|
||||
from . import JakAndDaxterWorld
|
||||
from .JakAndDaxterOptions import (JakAndDaxterOptions,
|
||||
EnableOrbsanity,
|
||||
GlobalOrbsanityBundleSize,
|
||||
PerLevelOrbsanityBundleSize,
|
||||
FireCanyonCellCount,
|
||||
MountainPassCellCount,
|
||||
LavaTubeCellCount,
|
||||
CitizenOrbTradeAmount,
|
||||
OracleOrbTradeAmount)
|
||||
from .locs import CellLocations as Cells
|
||||
from .Locations import location_table
|
||||
from .Levels import level_table
|
||||
from .regs.RegionBase import JakAndDaxterRegion
|
||||
|
||||
|
||||
def can_reach_orbs(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
options: JakAndDaxterOptions,
|
||||
level_name: str = None) -> int:
|
||||
def set_orb_trade_rule(world: JakAndDaxterWorld):
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
# 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 != EnableOrbsanity.option_per_level:
|
||||
return can_reach_orbs_global(state, player, multiworld)
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_off:
|
||||
world.can_trade = lambda state, required_orbs, required_previous_trade: (
|
||||
can_trade_vanilla(state, player, required_orbs, required_previous_trade))
|
||||
else:
|
||||
return can_reach_orbs_level(state, player, multiworld, level_name)
|
||||
world.can_trade = lambda state, required_orbs, required_previous_trade: (
|
||||
can_trade_orbsanity(state, player, required_orbs, required_previous_trade))
|
||||
|
||||
|
||||
def recalculate_reachable_orbs(state: CollectionState, player: int) -> None:
|
||||
|
||||
if not state.prog_items[player]["Reachable Orbs Fresh"]:
|
||||
|
||||
# Recalculate every level, every time the cache is stale, because you don't know
|
||||
# when a specific bundle of orbs in one level may unlock access to another.
|
||||
for level in level_table:
|
||||
state.prog_items[player][f"{level} Reachable Orbs".strip()] = (
|
||||
count_reachable_orbs_level(state, player, state.multiworld, level))
|
||||
|
||||
# Also recalculate the global count, still used even when Orbsanity is Off.
|
||||
state.prog_items[player]["Reachable Orbs"] = count_reachable_orbs_global(state, player, state.multiworld)
|
||||
state.prog_items[player]["Reachable Orbs Fresh"] = True
|
||||
|
||||
|
||||
def count_reachable_orbs_global(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld) -> int:
|
||||
|
||||
accessible_orbs = 0
|
||||
for region in multiworld.get_regions(player):
|
||||
if region.can_reach(state):
|
||||
# Only cast the region when we need to.
|
||||
accessible_orbs += typing.cast(JakAndDaxterRegion, region).orb_count
|
||||
return accessible_orbs
|
||||
|
||||
|
||||
def count_reachable_orbs_level(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
level_name: str = "") -> int:
|
||||
|
||||
accessible_orbs = 0
|
||||
# Need to cast all regions upfront.
|
||||
for region in typing.cast(typing.List[JakAndDaxterRegion], multiworld.get_regions(player)):
|
||||
if region.level_name == level_name and region.can_reach(state):
|
||||
accessible_orbs += region.orb_count
|
||||
return accessible_orbs
|
||||
|
||||
|
||||
def can_reach_orbs_global(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld) -> int:
|
||||
world: JakAndDaxterWorld,
|
||||
bundle: int) -> 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
|
||||
|
||||
return accessible_orbs
|
||||
recalculate_reachable_orbs(state, player)
|
||||
return state.has("Reachable Orbs", player, world.orb_bundle_size * (bundle + 1))
|
||||
|
||||
|
||||
def can_reach_orbs_level(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
level_name: str) -> int:
|
||||
world: JakAndDaxterWorld,
|
||||
level_name: str,
|
||||
bundle: int) -> bool:
|
||||
|
||||
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
|
||||
recalculate_reachable_orbs(state, player)
|
||||
return state.has(f"{level_name} Reachable Orbs", player, world.orb_bundle_size * (bundle + 1))
|
||||
|
||||
|
||||
# TODO - Until we come up with a better progressive system for the traders (that avoids hard-locking if you pay the
|
||||
# wrong ones and can't afford the right ones) just make all the traders locked behind the total amount to pay them all.
|
||||
def can_trade(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
options: JakAndDaxterOptions,
|
||||
required_orbs: int,
|
||||
required_previous_trade: int = None) -> bool:
|
||||
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
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 == EnableOrbsanity.option_global:
|
||||
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,
|
||||
def can_trade_vanilla(state: CollectionState,
|
||||
player: int,
|
||||
multiworld: MultiWorld,
|
||||
required_orbs: int,
|
||||
required_previous_trade: int = None) -> bool:
|
||||
required_previous_trade: typing.Optional[int] = None) -> bool:
|
||||
|
||||
# We know that Orbsanity is off, so count orbs globally.
|
||||
accessible_orbs = can_reach_orbs_global(state, player, multiworld)
|
||||
recalculate_reachable_orbs(state, player) # With Orbsanity Off, Reachable Orbs are in fact Tradeable Orbs.
|
||||
if required_previous_trade:
|
||||
name_of_previous_trade = location_table[Cells.to_ap_id(required_previous_trade)]
|
||||
return (accessible_orbs >= required_orbs
|
||||
and state.can_reach(name_of_previous_trade, "Location", player=player))
|
||||
else:
|
||||
return accessible_orbs >= required_orbs
|
||||
return (state.has("Reachable Orbs", player, required_orbs)
|
||||
and state.can_reach_location(name_of_previous_trade, player=player))
|
||||
return state.has("Reachable Orbs", player, required_orbs)
|
||||
|
||||
|
||||
def can_trade_orbsanity(state: CollectionState,
|
||||
player: int,
|
||||
orb_bundle_size: int,
|
||||
required_orbs: int,
|
||||
required_previous_trade: int = None) -> bool:
|
||||
required_previous_trade: typing.Optional[int] = None) -> bool:
|
||||
|
||||
required_count = math.ceil(required_orbs / orb_bundle_size)
|
||||
orb_bundle_name = orb_item_table[orb_bundle_size]
|
||||
recalculate_reachable_orbs(state, player) # Yes, even Orbsanity trades may unlock access to new Reachable Orbs.
|
||||
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)
|
||||
return (state.has("Tradeable Orbs", player, required_orbs)
|
||||
and state.can_reach_location(name_of_previous_trade, player=player))
|
||||
return state.has("Tradeable Orbs", player, required_orbs)
|
||||
|
||||
|
||||
def can_free_scout_flies(state: CollectionState, player: int) -> bool:
|
||||
@@ -105,3 +120,65 @@ def can_free_scout_flies(state: CollectionState, player: int) -> bool:
|
||||
|
||||
def can_fight(state: CollectionState, player: int) -> bool:
|
||||
return state.has_any({"Jump Dive", "Jump Kick", "Punch", "Kick"}, player)
|
||||
|
||||
|
||||
def enforce_multiplayer_limits(options: JakAndDaxterOptions):
|
||||
friendly_message = ""
|
||||
|
||||
if (options.enable_orbsanity == EnableOrbsanity.option_global
|
||||
and options.global_orbsanity_bundle_size.value < GlobalOrbsanityBundleSize.friendly_minimum):
|
||||
friendly_message += (f" "
|
||||
f"{options.global_orbsanity_bundle_size.display_name} must be no less than "
|
||||
f"{GlobalOrbsanityBundleSize.friendly_minimum} (currently "
|
||||
f"{options.global_orbsanity_bundle_size.value}).\n")
|
||||
|
||||
if (options.enable_orbsanity == EnableOrbsanity.option_per_level
|
||||
and options.level_orbsanity_bundle_size.value < PerLevelOrbsanityBundleSize.friendly_minimum):
|
||||
friendly_message += (f" "
|
||||
f"{options.level_orbsanity_bundle_size.display_name} must be no less than "
|
||||
f"{PerLevelOrbsanityBundleSize.friendly_minimum} (currently "
|
||||
f"{options.level_orbsanity_bundle_size.value}).\n")
|
||||
|
||||
if options.fire_canyon_cell_count.value > FireCanyonCellCount.friendly_maximum:
|
||||
friendly_message += (f" "
|
||||
f"{options.fire_canyon_cell_count.display_name} must be no greater than "
|
||||
f"{FireCanyonCellCount.friendly_maximum} (currently "
|
||||
f"{options.fire_canyon_cell_count.value}).\n")
|
||||
|
||||
if options.mountain_pass_cell_count.value > MountainPassCellCount.friendly_maximum:
|
||||
friendly_message += (f" "
|
||||
f"{options.mountain_pass_cell_count.display_name} must be no greater than "
|
||||
f"{MountainPassCellCount.friendly_maximum} (currently "
|
||||
f"{options.mountain_pass_cell_count.value}).\n")
|
||||
|
||||
if options.lava_tube_cell_count.value > LavaTubeCellCount.friendly_maximum:
|
||||
friendly_message += (f" "
|
||||
f"{options.lava_tube_cell_count.display_name} must be no greater than "
|
||||
f"{LavaTubeCellCount.friendly_maximum} (currently "
|
||||
f"{options.lava_tube_cell_count.value}).\n")
|
||||
|
||||
if options.citizen_orb_trade_amount.value > CitizenOrbTradeAmount.friendly_maximum:
|
||||
friendly_message += (f" "
|
||||
f"{options.citizen_orb_trade_amount.display_name} must be no greater than "
|
||||
f"{CitizenOrbTradeAmount.friendly_maximum} (currently "
|
||||
f"{options.citizen_orb_trade_amount.value}).\n")
|
||||
|
||||
if options.oracle_orb_trade_amount.value > OracleOrbTradeAmount.friendly_maximum:
|
||||
friendly_message += (f" "
|
||||
f"{options.oracle_orb_trade_amount.display_name} must be no greater than "
|
||||
f"{OracleOrbTradeAmount.friendly_maximum} (currently "
|
||||
f"{options.oracle_orb_trade_amount.value}).\n")
|
||||
|
||||
if friendly_message != "":
|
||||
raise OptionError(f"Please adjust the following Options for a multiplayer game.\n"
|
||||
f"{friendly_message}")
|
||||
|
||||
|
||||
def verify_orb_trade_amounts(options: JakAndDaxterOptions):
|
||||
|
||||
total_trade_orbs = (9 * options.citizen_orb_trade_amount) + (6 * options.oracle_orb_trade_amount)
|
||||
if total_trade_orbs > 2000:
|
||||
raise OptionError(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}.")
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
from typing import Dict, Any, ClassVar
|
||||
from typing import Dict, Any, ClassVar, Tuple, Callable, Optional
|
||||
import settings
|
||||
|
||||
from Utils import local_path, visualize_regions
|
||||
from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from Utils import local_path
|
||||
from BaseClasses import Item, ItemClassification, Tutorial, CollectionState
|
||||
from .GameID import jak1_id, jak1_name, jak1_max
|
||||
from .JakAndDaxterOptions import JakAndDaxterOptions, EnableOrbsanity
|
||||
from .Locations import JakAndDaxterLocation, location_table
|
||||
from .Items import JakAndDaxterItem, item_table
|
||||
from .Locations import (JakAndDaxterLocation,
|
||||
location_table,
|
||||
cell_location_table,
|
||||
scout_location_table,
|
||||
special_location_table,
|
||||
cache_location_table,
|
||||
orb_location_table)
|
||||
from .Items import (JakAndDaxterItem,
|
||||
item_table,
|
||||
cell_item_table,
|
||||
scout_item_table,
|
||||
special_item_table,
|
||||
move_item_table,
|
||||
orb_item_table)
|
||||
from .Levels import level_table, level_table_with_global
|
||||
from .locs import (CellLocations as Cells,
|
||||
ScoutLocations as Scouts,
|
||||
SpecialLocations as Specials,
|
||||
OrbCacheLocations as Caches,
|
||||
OrbLocations as Orbs)
|
||||
from .Regions import create_regions
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import components, Component, launch_subprocess, Type, icon_paths
|
||||
|
||||
@@ -79,48 +91,62 @@ class JakAndDaxterWorld(World):
|
||||
item_name_to_id = {item_table[k]: k for k in item_table}
|
||||
location_name_to_id = {location_table[k]: k for k in location_table}
|
||||
item_name_groups = {
|
||||
"Power Cells": {item_table[k]: k for k in item_table
|
||||
if k in range(jak1_id, jak1_id + Scouts.fly_offset)},
|
||||
"Scout Flies": {item_table[k]: k for k in item_table
|
||||
if k in range(jak1_id + Scouts.fly_offset, jak1_id + Specials.special_offset)},
|
||||
"Specials": {item_table[k]: k for k in item_table
|
||||
if k in range(jak1_id + Specials.special_offset, jak1_id + Caches.orb_cache_offset)},
|
||||
"Moves": {item_table[k]: k for k in item_table
|
||||
if k in range(jak1_id + Caches.orb_cache_offset, jak1_id + Orbs.orb_offset)},
|
||||
"Precursor Orbs": {item_table[k]: k for k in item_table
|
||||
if k in range(jak1_id + Orbs.orb_offset, jak1_max)},
|
||||
"Power Cells": set(cell_item_table.values()),
|
||||
"Scout Flies": set(scout_item_table.values()),
|
||||
"Specials": set(special_item_table.values()),
|
||||
"Moves": set(move_item_table.values()),
|
||||
"Precursor Orbs": set(orb_item_table.values()),
|
||||
}
|
||||
location_name_groups = {
|
||||
"Power Cells": {location_table[k]: k for k in location_table
|
||||
if k in range(jak1_id, jak1_id + Scouts.fly_offset)},
|
||||
"Scout Flies": {location_table[k]: k for k in location_table
|
||||
if k in range(jak1_id + Scouts.fly_offset, jak1_id + Specials.special_offset)},
|
||||
"Specials": {location_table[k]: k for k in location_table
|
||||
if k in range(jak1_id + Specials.special_offset, jak1_id + Caches.orb_cache_offset)},
|
||||
"Orb Caches": {location_table[k]: k for k in location_table
|
||||
if k in range(jak1_id + Caches.orb_cache_offset, jak1_id + Orbs.orb_offset)},
|
||||
"Precursor Orbs": {location_table[k]: k for k in location_table
|
||||
if k in range(jak1_id + Orbs.orb_offset, jak1_max)},
|
||||
"Trades": {location_table[k]: k for k in location_table
|
||||
if k in {Cells.to_ap_id(t) for t in {11, 12, 31, 32, 33, 96, 97, 98, 99, 13, 14, 34, 35, 100, 101}}},
|
||||
"Power Cells": set(cell_location_table.values()),
|
||||
"Scout Flies": set(scout_location_table.values()),
|
||||
"Specials": set(special_location_table.values()),
|
||||
"Orb Caches": set(cache_location_table.values()),
|
||||
"Precursor Orbs": set(orb_location_table.values()),
|
||||
"Trades": {location_table[Cells.to_ap_id(k)] for k in
|
||||
{11, 12, 31, 32, 33, 96, 97, 98, 99, 13, 14, 34, 35, 100, 101}},
|
||||
}
|
||||
|
||||
# Functions and Variables that are Options-driven, keep them as instance variables here so that we don't clog up
|
||||
# the seed generation routines with options checking. So we set these once, and then just use them as needed.
|
||||
can_trade: Callable[[CollectionState, int, Optional[int]], bool]
|
||||
orb_bundle_size: int = 0
|
||||
orb_bundle_item_name: str = ""
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# For the fairness of other players in a multiworld game, enforce some friendly limitations on our options,
|
||||
# so we don't cause chaos during seed generation. These friendly limits should **guarantee** a successful gen.
|
||||
if self.multiworld.players > 1:
|
||||
from .Rules import enforce_multiplayer_limits
|
||||
enforce_multiplayer_limits(self.options)
|
||||
|
||||
# Verify that we didn't overload the trade amounts with more orbs than exist in the world.
|
||||
# This is easy to do by accident even in a single-player world.
|
||||
from .Rules import verify_orb_trade_amounts
|
||||
verify_orb_trade_amounts(self.options)
|
||||
|
||||
# Cache the orb bundle size and item name for quicker reference.
|
||||
if self.options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
self.orb_bundle_size = self.options.level_orbsanity_bundle_size.value
|
||||
self.orb_bundle_item_name = orb_item_table[self.orb_bundle_size]
|
||||
elif self.options.enable_orbsanity == EnableOrbsanity.option_global:
|
||||
self.orb_bundle_size = self.options.global_orbsanity_bundle_size.value
|
||||
self.orb_bundle_item_name = orb_item_table[self.orb_bundle_size]
|
||||
|
||||
# Options drive which trade rules to use, so they need to be setup before we create_regions.
|
||||
from .Rules import set_orb_trade_rule
|
||||
set_orb_trade_rule(self)
|
||||
|
||||
# This will also set Locations, Location access rules, Region access rules, etc.
|
||||
def create_regions(self) -> None:
|
||||
create_regions(self.multiworld, self.options, self.player)
|
||||
visualize_regions(self.multiworld.get_region("Menu", self.player), "jakanddaxter.puml")
|
||||
from .Regions import create_regions
|
||||
create_regions(self)
|
||||
|
||||
# Helper function to get the correct orb bundle size.
|
||||
def get_orb_bundle_size(self) -> int:
|
||||
if self.options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
return self.options.level_orbsanity_bundle_size.value
|
||||
elif self.options.enable_orbsanity == EnableOrbsanity.option_global:
|
||||
return self.options.global_orbsanity_bundle_size.value
|
||||
else:
|
||||
return 0
|
||||
# from Utils import visualize_regions
|
||||
# visualize_regions(self.multiworld.get_region("Menu", self.player), "jakanddaxter.puml")
|
||||
|
||||
# Helper function to reuse some nasty if/else trees.
|
||||
def item_type_helper(self, item) -> (int, ItemClassification):
|
||||
def item_type_helper(self, item) -> Tuple[int, ItemClassification]:
|
||||
# Make 101 Power Cells.
|
||||
if item in range(jak1_id, jak1_id + Scouts.fly_offset):
|
||||
classification = ItemClassification.progression_skip_balancing
|
||||
@@ -144,8 +170,7 @@ class JakAndDaxterWorld(World):
|
||||
# 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
|
||||
size = self.get_orb_bundle_size()
|
||||
count = int(2000 / size) if size > 0 else 0 # Don't divide by zero!
|
||||
count = 2000 // self.orb_bundle_size if self.orb_bundle_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.
|
||||
@@ -168,15 +193,15 @@ class JakAndDaxterWorld(World):
|
||||
# then fill the item pool with a corresponding amount of filler items.
|
||||
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())]
|
||||
self.multiworld.itempool += [self.create_filler()]
|
||||
continue
|
||||
|
||||
# Handle Orbsanity option.
|
||||
# If it is OFF, don't add any orbs to the item pool.
|
||||
# If it is OFF, don't add any orb bundles to the item pool, period.
|
||||
# If it is ON, don't add any orb bundles that don't match the chosen option.
|
||||
if (item_name in self.item_name_groups["Precursor Orbs"]
|
||||
and ((self.options.enable_orbsanity == EnableOrbsanity.option_off
|
||||
or Orbs.to_game_id(item_id) != self.get_orb_bundle_size()))):
|
||||
and (self.options.enable_orbsanity == EnableOrbsanity.option_off
|
||||
or item_name != self.orb_bundle_item_name)):
|
||||
continue
|
||||
|
||||
# In every other scenario, do this.
|
||||
@@ -192,6 +217,41 @@ class JakAndDaxterWorld(World):
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "Green Eco Pill"
|
||||
|
||||
def collect(self, state: CollectionState, item: Item) -> bool:
|
||||
change = super().collect(state, item)
|
||||
if change:
|
||||
# No matter the option, no matter the item, set the caches to stale.
|
||||
state.prog_items[self.player]["Reachable Orbs Fresh"] = False
|
||||
|
||||
# Matching the item name implies Orbsanity is ON, so we don't need to check the option.
|
||||
# When Orbsanity is OFF, there won't even be any orb bundle items to collect.
|
||||
# Give the player the appropriate number of Tradeable Orbs based on bundle size.
|
||||
if item.name == self.orb_bundle_item_name:
|
||||
state.prog_items[self.player]["Tradeable Orbs"] += self.orb_bundle_size
|
||||
return change
|
||||
|
||||
def remove(self, state: CollectionState, item: Item) -> bool:
|
||||
change = super().remove(state, item)
|
||||
if change:
|
||||
# No matter the option, no matter the item, set the caches to stale.
|
||||
state.prog_items[self.player]["Reachable Orbs Fresh"] = False
|
||||
|
||||
# The opposite of what we did in collect: Take away from the player
|
||||
# the appropriate number of Tradeable Orbs based on bundle size.
|
||||
if item.name == self.orb_bundle_item_name:
|
||||
state.prog_items[self.player]["Tradeable Orbs"] -= self.orb_bundle_size
|
||||
|
||||
# TODO - 3.8 compatibility, remove this block when no longer required.
|
||||
if state.prog_items[self.player]["Tradeable Orbs"] < 1:
|
||||
del state.prog_items[self.player]["Tradeable Orbs"]
|
||||
if state.prog_items[self.player]["Reachable Orbs"] < 1:
|
||||
del state.prog_items[self.player]["Reachable Orbs"]
|
||||
for level in level_table:
|
||||
if state.prog_items[self.player][f"{level} Reachable Orbs".strip()] < 1:
|
||||
del state.prog_items[self.player][f"{level} Reachable Orbs".strip()]
|
||||
|
||||
return change
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return self.options.as_dict("enable_move_randomizer",
|
||||
"enable_orbsanity",
|
||||
|
||||
@@ -99,7 +99,6 @@ def as_float(value: int) -> int:
|
||||
|
||||
# "Jak" to be replaced by player name in the Client.
|
||||
def autopsy(died: int) -> str:
|
||||
assert died > 0, f"Tried to find Jak's cause of death, but he's still alive!"
|
||||
if died in [1, 2, 3, 4]:
|
||||
return random.choice(["Jak said goodnight.",
|
||||
"Jak stepped into the light.",
|
||||
@@ -147,7 +146,7 @@ class JakAndDaxterMemoryReader:
|
||||
# The memory reader just needs the game running.
|
||||
gk_process: pymem.process = None
|
||||
|
||||
location_outbox = []
|
||||
location_outbox: List[int] = []
|
||||
outbox_index: int = 0
|
||||
finished_game: bool = False
|
||||
|
||||
@@ -224,7 +223,7 @@ class JakAndDaxterMemoryReader:
|
||||
if marker_address:
|
||||
# At this address is another address that contains the struct we're looking for: the game's state.
|
||||
# From here we need to add the length in bytes for the marker and 4 bytes of padding,
|
||||
# and the struct address is 8 bytes long (it's a uint64).
|
||||
# and the struct address is 8 bytes long (it's an uint64).
|
||||
goal_pointer = marker_address + len(self.marker) + 4
|
||||
self.goal_address = int.from_bytes(self.gk_process.read_bytes(goal_pointer, sizeof_uint64),
|
||||
byteorder="little",
|
||||
|
||||
@@ -2,7 +2,7 @@ import json
|
||||
import time
|
||||
import struct
|
||||
import random
|
||||
from typing import Dict, Callable
|
||||
from typing import Dict, Optional
|
||||
|
||||
import pymem
|
||||
from pymem.exception import ProcessNotFound, ProcessError
|
||||
@@ -41,10 +41,10 @@ class JakAndDaxterReplClient:
|
||||
item_inbox: Dict[int, NetworkItem] = {}
|
||||
inbox_index = 0
|
||||
|
||||
my_item_name: str = None
|
||||
my_item_finder: str = None
|
||||
their_item_name: str = None
|
||||
their_item_owner: str = None
|
||||
my_item_name: Optional[str] = None
|
||||
my_item_finder: Optional[str] = None
|
||||
their_item_name: Optional[str] = None
|
||||
their_item_owner: Optional[str] = None
|
||||
|
||||
def __init__(self, ip: str = "127.0.0.1", port: int = 8181):
|
||||
self.ip = ip
|
||||
@@ -353,6 +353,8 @@ class JakAndDaxterReplClient:
|
||||
logger.error(f"Unable to subtract {orb_count} traded orbs!")
|
||||
return ok
|
||||
|
||||
return True
|
||||
|
||||
async def setup_options(self,
|
||||
os_option: int, os_bundle: int,
|
||||
fc_count: int, mp_count: int,
|
||||
|
||||
@@ -180,14 +180,14 @@ Here is how the HUD works:
|
||||
Depending on the nature of the bug, there are a couple of different options.
|
||||
|
||||
* If you found a logical error in the randomizer, please create a new Issue
|
||||
[here.](https://github.com/ArchipelaGOAL/Archipelago/issues) Use this page if:
|
||||
[here](https://github.com/ArchipelaGOAL/Archipelago/issues). Use this page if:
|
||||
* An item required for progression is unreachable.
|
||||
* The randomizer did not respect one of the Options you chose.
|
||||
* You see a mistake, typo, etc. on this webpage.
|
||||
* You see an error or stack trace appear on the text client.
|
||||
|
||||
* If you encountered an error in OpenGOAL, please create a new Issue
|
||||
[here.](https://github.com/ArchipelaGOAL/ArchipelaGOAL/issues) Use this page if:
|
||||
[here](https://github.com/ArchipelaGOAL/ArchipelaGOAL/issues). Use this page if:
|
||||
* You encounter a crash, freeze, reset, etc. in the game.
|
||||
* You fail to send Items you find in the game to the Archipelago server.
|
||||
* You fail to receive Items the server sends to you.
|
||||
|
||||
@@ -26,8 +26,7 @@ At this time, this method of setup works on Windows only, but Linux support is a
|
||||
- Click `View Folder`.
|
||||
- In the new file explorer window, take note of the current path. It should contain `gk.exe` and `goalc.exe`.
|
||||
- Verify that the mod launcher copied the extracted ISO files to the mod directory:
|
||||
- `%appdata%/OpenGOAL-Mods/archipelagoal/iso_data` should have *all* the same files as
|
||||
- `%appdata%/OpenGOAL-Mods/_iso_data`, if it doesn't, copy those files over manually.
|
||||
- `%appdata%/OpenGOAL-Mods/archipelagoal/iso_data` and `%appdata%/OpenGOAL-Mods/_iso_data` should have *all* the same files; if they don't, copy those files over manually.
|
||||
- And then `Recompile` if you needed to copy the files over.
|
||||
- **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).
|
||||
|
||||
@@ -43,7 +42,7 @@ At this time, this method of setup works on Windows only, but Linux support is a
|
||||
- Back in the Archipelago Launcher, click `Open host.yaml`.
|
||||
- In the text file that opens, search for `jakanddaxter_options`.
|
||||
- You should see the block of YAML below. If you do not see it, you will need to add it.
|
||||
- If the default path does not contain `gk.exe` and `goalc.exe`, you will need to provide the path you noted earlier. **MAKE SURE YOU CHANGE ALL BACKSLASHES `\ ` TO FORWARD SLASHES `/ `.**
|
||||
- If the default path does not contain `gk.exe` and `goalc.exe`, you will need to provide the path you noted earlier. **MAKE SURE YOU CHANGE ALL BACKSLASHES `\ ` TO FORWARD SLASHES `/`.**
|
||||
|
||||
```
|
||||
jakanddaxter_options:
|
||||
@@ -162,8 +161,7 @@ PAL versions of the game seem to require additional troubleshooting/setup in ord
|
||||
|
||||
### Known Issues
|
||||
|
||||
- The game needs to run in debug mode in order to allow the repl to connect to it. We hide the debug text on screen and play the game's introductory cutscenes properly.
|
||||
- The game needs to boot in debug mode in order to allow the repl to connect to it. We disable debug mode once we connect to the AP server.
|
||||
- The powershell windows cannot be run as background processes due to how the repl works, so the best we can do is minimize them.
|
||||
- The client is currently not very robust and doesn't handle failures gracefully. This may result in items not being delivered to the game, or location checks not being delivered to the server.
|
||||
- Orbsanity checks may show up out of order in the text client.
|
||||
- Large item releases may take up to several minutes for the game to process them all.
|
||||
|
||||
@@ -11,12 +11,14 @@ from ..GameID import jak1_id
|
||||
# These helper functions do all the math required to get information about each
|
||||
# power cell and translate its ID between AP and OpenGOAL.
|
||||
def to_ap_id(game_id: int) -> int:
|
||||
assert game_id < jak1_id, f"Attempted to convert {game_id} to an AP ID, but it already is one."
|
||||
if game_id >= jak1_id:
|
||||
raise ValueError(f"Attempted to convert {game_id} to an AP ID, but it already is one.")
|
||||
return jak1_id + game_id
|
||||
|
||||
|
||||
def to_game_id(ap_id: int) -> int:
|
||||
assert ap_id >= jak1_id, f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one."
|
||||
if ap_id < jak1_id:
|
||||
raise ValueError(f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one.")
|
||||
return ap_id - jak1_id
|
||||
|
||||
|
||||
|
||||
@@ -19,13 +19,15 @@ orb_cache_offset = 4096
|
||||
# special check and translate its ID between AP and OpenGOAL. Similar to Scout Flies, these large numbers are not
|
||||
# necessary, and we can flatten out the range in which these numbers lie.
|
||||
def to_ap_id(game_id: int) -> int:
|
||||
assert game_id < jak1_id, f"Attempted to convert {game_id} to an AP ID, but it already is one."
|
||||
if game_id >= jak1_id:
|
||||
raise ValueError(f"Attempted to convert {game_id} to an AP ID, but it already is one.")
|
||||
uncompressed_id = jak1_id + orb_cache_offset + game_id # Add the offsets and the orb cache Actor ID.
|
||||
return uncompressed_id - 10344 # Subtract the smallest Actor ID.
|
||||
|
||||
|
||||
def to_game_id(ap_id: int) -> int:
|
||||
assert ap_id >= jak1_id, f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one."
|
||||
if ap_id < jak1_id:
|
||||
raise ValueError(f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one.")
|
||||
uncompressed_id = ap_id + 10344 # Reverse process, add back the smallest Actor ID.
|
||||
return uncompressed_id - jak1_id - orb_cache_offset # Subtract the offsets.
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ..GameID import jak1_id
|
||||
from ..Levels import level_table_with_global
|
||||
|
||||
# Precursor Orbs are not necessarily given ID's by the game.
|
||||
|
||||
@@ -24,12 +23,14 @@ orb_offset = 32768
|
||||
# These helper functions do all the math required to get information about each
|
||||
# precursor orb and translate its ID between AP and OpenGOAL.
|
||||
def to_ap_id(game_id: int) -> int:
|
||||
assert game_id < jak1_id, f"Attempted to convert {game_id} to an AP ID, but it already is one."
|
||||
if game_id >= jak1_id:
|
||||
raise ValueError(f"Attempted to convert {game_id} to an AP ID, but it already is one.")
|
||||
return jak1_id + orb_offset + game_id # Add the offsets and the orb Actor ID.
|
||||
|
||||
|
||||
def to_game_id(ap_id: int) -> int:
|
||||
assert ap_id >= jak1_id, f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one."
|
||||
if ap_id < jak1_id:
|
||||
raise ValueError(f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one.")
|
||||
return ap_id - jak1_id - orb_offset # Reverse process, subtract the offsets.
|
||||
|
||||
|
||||
@@ -50,79 +51,8 @@ def create_address(level_index: int, bundle_index: int) -> int:
|
||||
|
||||
# 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
|
||||
}
|
||||
}
|
||||
|
||||
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"])
|
||||
create_address(level_table_with_global[name]["level_index"], index): f"{name} Orb Bundle {index + 1}".strip()
|
||||
for name in level_table_with_global
|
||||
for index in range(level_table_with_global[name]["orbs"])
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ fly_offset = 1024
|
||||
# These helper functions do all the math required to get information about each
|
||||
# scout fly and translate its ID between AP and OpenGOAL.
|
||||
def to_ap_id(game_id: int) -> int:
|
||||
assert game_id < jak1_id, f"Attempted to convert {game_id} to an AP ID, but it already is one."
|
||||
if game_id >= jak1_id:
|
||||
raise ValueError(f"Attempted to convert {game_id} to an AP ID, but it already is one.")
|
||||
cell_id = get_cell_id(game_id) # Get the power cell ID from the lowest 7 bits.
|
||||
buzzer_index = (game_id - cell_id) >> 9 # Get the index, bit shift it down 9 places.
|
||||
compressed_id = fly_offset + buzzer_index + cell_id # Add the offset, the bit-shifted index, and the cell ID.
|
||||
@@ -31,7 +32,8 @@ def to_ap_id(game_id: int) -> int:
|
||||
|
||||
|
||||
def to_game_id(ap_id: int) -> int:
|
||||
assert ap_id >= jak1_id, f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one."
|
||||
if ap_id < jak1_id:
|
||||
raise ValueError(f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one.")
|
||||
compressed_id = ap_id - jak1_id # Reverse process. First thing: subtract the game's ID.
|
||||
cell_id = get_cell_id(compressed_id) # Get the power cell ID from the lowest 7 bits.
|
||||
buzzer_index = compressed_id - fly_offset - cell_id # Get the bit-shifted index.
|
||||
@@ -42,7 +44,8 @@ def to_game_id(ap_id: int) -> int:
|
||||
# Make sure to use this function ONLY when the input argument does NOT include jak1_id,
|
||||
# because that number may flip some of the bottom 7 bits, and that will throw off this bit mask.
|
||||
def get_cell_id(buzzer_id: int) -> int:
|
||||
assert buzzer_id < jak1_id, f"Attempted to bit mask {buzzer_id}, but it is polluted by the game's ID {jak1_id}."
|
||||
if buzzer_id >= jak1_id:
|
||||
raise ValueError(f"Attempted to bit mask {buzzer_id}, but it is polluted by the game's ID {jak1_id}.")
|
||||
return buzzer_id & 0b1111111
|
||||
|
||||
|
||||
@@ -184,7 +187,7 @@ locVC_scoutTable = {
|
||||
|
||||
# Spider Cave
|
||||
locSC_scoutTable = {
|
||||
327765: "SC: Scout Fly Near Dark Dave Entrance",
|
||||
327765: "SC: Scout Fly Near Dark Cave Entrance",
|
||||
262229: "SC: Scout Fly In Dark Cave",
|
||||
393301: "SC: Scout Fly Main Cave, Overlooking Entrance",
|
||||
196693: "SC: Scout Fly Main Cave, Near Dark Crystal",
|
||||
|
||||
@@ -21,12 +21,14 @@ special_offset = 2048
|
||||
# These helper functions do all the math required to get information about each
|
||||
# special check and translate its ID between AP and OpenGOAL.
|
||||
def to_ap_id(game_id: int) -> int:
|
||||
assert game_id < jak1_id, f"Attempted to convert {game_id} to an AP ID, but it already is one."
|
||||
if game_id >= jak1_id:
|
||||
raise ValueError(f"Attempted to convert {game_id} to an AP ID, but it already is one.")
|
||||
return jak1_id + special_offset + game_id # Add the offsets and the orb Actor ID.
|
||||
|
||||
|
||||
def to_game_id(ap_id: int) -> int:
|
||||
assert ap_id >= jak1_id, f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one."
|
||||
if ap_id < jak1_id:
|
||||
raise ValueError(f"Attempted to convert {ap_id} to a Jak 1 ID, but it already is one.")
|
||||
return ap_id - jak1_id - special_offset # Reverse process, subtract the offsets.
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_fight, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_fight, can_reach_orbs_level
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
# 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...)
|
||||
@@ -155,15 +159,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
bundle_count = 200 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_reach_orbs_level
|
||||
from ..locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 50)
|
||||
|
||||
@@ -21,15 +24,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
bundle_count = 50 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs_level
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 25)
|
||||
|
||||
@@ -86,15 +89,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(150 / bundle_size)
|
||||
bundle_count = 150 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_reach_orbs_level
|
||||
from ..locs import ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 50)
|
||||
main_area.add_cell_locations([92, 93])
|
||||
@@ -30,15 +33,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
bundle_count = 50 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs_level
|
||||
|
||||
|
||||
# God help me... here we go.
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
# 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...)
|
||||
def can_jump_farther(state: CollectionState, p: int) -> bool:
|
||||
return (state.has("Double Jump", p)
|
||||
or state.has("Jump Kick", p)
|
||||
return (state.has_any({"Double Jump", "Jump Kick"}, p)
|
||||
or state.has_all({"Punch", "Punch Uppercut"}, p))
|
||||
|
||||
def can_triple_jump(state: CollectionState, p: int) -> bool:
|
||||
@@ -115,15 +118,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
bundle_count = 200 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_reach_orbs_level
|
||||
from ..locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 50)
|
||||
|
||||
@@ -21,15 +24,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
bundle_count = 50 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs_level
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
# Just the starting area.
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 4)
|
||||
@@ -132,15 +135,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
bundle_count = 200 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs_level
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 9)
|
||||
|
||||
muse_course = JakAndDaxterRegion("Muse Course", player, multiworld, level_name, 21)
|
||||
@@ -114,15 +117,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(150 / bundle_size)
|
||||
bundle_count = 150 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_reach_orbs_level
|
||||
from ..locs import ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
# This is basically just Klaww.
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 0)
|
||||
@@ -37,15 +40,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
bundle_count = 50 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_reach_orbs_level
|
||||
from ..locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 200)
|
||||
|
||||
@@ -21,15 +24,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
bundle_count = 200 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from typing import List, Callable
|
||||
from typing import Iterable, Callable, Optional
|
||||
from BaseClasses import MultiWorld, Region
|
||||
from ..GameID import jak1_name
|
||||
from ..JakAndDaxterOptions import JakAndDaxterOptions
|
||||
from ..Locations import JakAndDaxterLocation, location_table
|
||||
from ..locs import (OrbLocations as Orbs,
|
||||
CellLocations as Cells,
|
||||
@@ -26,7 +25,7 @@ class JakAndDaxterRegion(Region):
|
||||
self.level_name = level_name
|
||||
self.orb_count = orb_count
|
||||
|
||||
def add_cell_locations(self, locations: List[int], access_rule: Callable = None):
|
||||
def add_cell_locations(self, locations: Iterable[int], access_rule: Optional[Callable] = None):
|
||||
"""
|
||||
Adds a Power Cell Location to this region with the given access rule.
|
||||
Converts Game ID's to AP ID's for you.
|
||||
@@ -35,7 +34,7 @@ class JakAndDaxterRegion(Region):
|
||||
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):
|
||||
def add_fly_locations(self, locations: Iterable[int], access_rule: Optional[Callable] = None):
|
||||
"""
|
||||
Adds a Scout Fly Location to this region with the given access rule.
|
||||
Converts Game ID's to AP ID's for you.
|
||||
@@ -44,7 +43,7 @@ class JakAndDaxterRegion(Region):
|
||||
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):
|
||||
def add_special_locations(self, locations: Iterable[int], access_rule: Optional[Callable] = None):
|
||||
"""
|
||||
Adds a Special Location to this region with the given access rule.
|
||||
Converts Game ID's to AP ID's for you.
|
||||
@@ -55,7 +54,7 @@ class JakAndDaxterRegion(Region):
|
||||
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):
|
||||
def add_cache_locations(self, locations: Iterable[int], access_rule: Optional[Callable] = None):
|
||||
"""
|
||||
Adds an Orb Cache Location to this region with the given access rule.
|
||||
Converts Game ID's to AP ID's for you.
|
||||
@@ -64,7 +63,7 @@ class JakAndDaxterRegion(Region):
|
||||
ap_id = Caches.to_ap_id(loc)
|
||||
self.add_jak_locations(ap_id, location_table[ap_id], access_rule)
|
||||
|
||||
def add_orb_locations(self, level_index: int, bundle_index: int, bundle_size: int, access_rule: Callable = None):
|
||||
def add_orb_locations(self, level_index: int, bundle_index: int, access_rule: Optional[Callable] = None):
|
||||
"""
|
||||
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.
|
||||
@@ -78,7 +77,7 @@ class JakAndDaxterRegion(Region):
|
||||
location.access_rule = access_rule
|
||||
self.locations.append(location)
|
||||
|
||||
def add_jak_locations(self, ap_id: int, name: str, access_rule: Callable = None):
|
||||
def add_jak_locations(self, ap_id: int, name: str, access_rule: Optional[Callable] = None):
|
||||
"""
|
||||
Helper function to add Locations. Not to be used directly.
|
||||
"""
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_trade, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_reach_orbs_level
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
total_trade_orbs = (9 * options.citizen_orb_trade_amount) + (6 * options.oracle_orb_trade_amount)
|
||||
|
||||
# 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, options, total_trade_orbs))
|
||||
main_area.add_cell_locations([32], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs))
|
||||
main_area.add_cell_locations([33], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs))
|
||||
main_area.add_cell_locations([34], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs))
|
||||
main_area.add_cell_locations([35], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs, 34))
|
||||
main_area.add_cell_locations([31], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
main_area.add_cell_locations([32], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
main_area.add_cell_locations([33], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
main_area.add_cell_locations([34], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
main_area.add_cell_locations([35], access_rule=lambda state: world.can_trade(state, total_trade_orbs, 34))
|
||||
|
||||
# These 2 scout fly boxes can be broken by running with nearby blue eco.
|
||||
main_area.add_fly_locations([196684, 262220])
|
||||
@@ -64,15 +62,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
bundle_count = 50 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_trade, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_reach_orbs_level
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
total_trade_orbs = (9 * options.citizen_orb_trade_amount) + (6 * options.oracle_orb_trade_amount)
|
||||
|
||||
@@ -13,10 +16,8 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
|
||||
# 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, options, total_trade_orbs))
|
||||
main_area.add_cell_locations([12], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs))
|
||||
main_area.add_cell_locations([11], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
main_area.add_cell_locations([12], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
|
||||
# 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])
|
||||
@@ -34,10 +35,8 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
yakow_cliff.add_fly_locations([75], access_rule=lambda state: can_free_scout_flies(state, player))
|
||||
|
||||
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, options, total_trade_orbs))
|
||||
oracle_platforms.add_cell_locations([14], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs, 13))
|
||||
oracle_platforms.add_cell_locations([13], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
oracle_platforms.add_cell_locations([14], access_rule=lambda state: world.can_trade(state, total_trade_orbs, 13))
|
||||
oracle_platforms.add_fly_locations([393291], access_rule=lambda state:
|
||||
can_free_scout_flies(state, player))
|
||||
|
||||
@@ -70,15 +69,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
bundle_count = 50 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs_level
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 128)
|
||||
main_area.add_cell_locations([18, 21, 22])
|
||||
@@ -84,15 +87,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(150 / bundle_size)
|
||||
bundle_count = 150 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs_level
|
||||
|
||||
|
||||
# God help me... here we go.
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
# We need a few helper functions.
|
||||
def can_cross_main_gap(state: CollectionState, p: int) -> bool:
|
||||
@@ -19,8 +23,7 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
or state.has_all({"Roll", "Roll Jump"}, p)))
|
||||
|
||||
def can_jump_blockers(state: CollectionState, p: int) -> bool:
|
||||
return (state.has("Double Jump", p)
|
||||
or state.has("Jump Dive", p)
|
||||
return (state.has_any({"Double Jump", "Jump Dive"}, p)
|
||||
or state.has_all({"Crouch", "Crouch Jump"}, p)
|
||||
or state.has_all({"Crouch", "Crouch Uppercut"}, p)
|
||||
or state.has_all({"Punch", "Punch Uppercut"}, p))
|
||||
@@ -190,15 +193,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
bundle_count = 200 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_fight, can_reach_orbs_level
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
# 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)
|
||||
@@ -113,15 +116,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(200 / bundle_size)
|
||||
bundle_count = 200 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
from typing import List
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
|
||||
from .RegionBase import JakAndDaxterRegion
|
||||
from .. import JakAndDaxterOptions, EnableOrbsanity
|
||||
from ..Rules import can_free_scout_flies, can_trade, can_reach_orbs
|
||||
from ..locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
from .. import EnableOrbsanity, JakAndDaxterWorld
|
||||
from ..Rules import can_free_scout_flies, can_reach_orbs_level
|
||||
from ..locs import ScoutLocations as Scouts
|
||||
|
||||
|
||||
def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxterOptions, player: int) -> List[JakAndDaxterRegion]:
|
||||
def build_regions(level_name: str, world: JakAndDaxterWorld) -> List[JakAndDaxterRegion]:
|
||||
multiworld = world.multiworld
|
||||
options = world.options
|
||||
player = world.player
|
||||
|
||||
total_trade_orbs = (9 * options.citizen_orb_trade_amount) + (6 * options.oracle_orb_trade_amount)
|
||||
|
||||
# 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, options, total_trade_orbs))
|
||||
main_area.add_cell_locations([97], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs, 96))
|
||||
main_area.add_cell_locations([98], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs, 97))
|
||||
main_area.add_cell_locations([99], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs, 98))
|
||||
main_area.add_cell_locations([100], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs))
|
||||
main_area.add_cell_locations([101], access_rule=lambda state:
|
||||
can_trade(state, player, multiworld, options, total_trade_orbs, 100))
|
||||
main_area.add_cell_locations([96], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
main_area.add_cell_locations([97], access_rule=lambda state: world.can_trade(state, total_trade_orbs, 96))
|
||||
main_area.add_cell_locations([98], access_rule=lambda state: world.can_trade(state, total_trade_orbs, 97))
|
||||
main_area.add_cell_locations([99], access_rule=lambda state: world.can_trade(state, total_trade_orbs, 98))
|
||||
main_area.add_cell_locations([100], access_rule=lambda state: world.can_trade(state, total_trade_orbs, None))
|
||||
main_area.add_cell_locations([101], access_rule=lambda state: world.can_trade(state, total_trade_orbs, 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).
|
||||
@@ -43,15 +40,12 @@ def build_regions(level_name: str, multiworld: MultiWorld, options: JakAndDaxter
|
||||
if options.enable_orbsanity == EnableOrbsanity.option_per_level:
|
||||
orbs = JakAndDaxterRegion("Orbsanity", player, multiworld, level_name)
|
||||
|
||||
bundle_size = options.level_orbsanity_bundle_size.value
|
||||
bundle_count = int(50 / bundle_size)
|
||||
bundle_count = 50 // world.orb_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)))
|
||||
access_rule=lambda state, level=level_name, bundle=bundle_index:
|
||||
can_reach_orbs_level(state, player, world, level, bundle))
|
||||
multiworld.regions.append(orbs)
|
||||
main_area.connect(orbs)
|
||||
|
||||
|
||||
@@ -3,11 +3,8 @@ import typing
|
||||
from . import JakAndDaxterTestBase
|
||||
from .. import jak1_id
|
||||
from ..regs.RegionBase import JakAndDaxterRegion
|
||||
from ..locs import (OrbLocations as Orbs,
|
||||
CellLocations as Cells,
|
||||
ScoutLocations as Scouts,
|
||||
SpecialLocations as Specials,
|
||||
OrbCacheLocations as Caches)
|
||||
from ..locs import (ScoutLocations as Scouts,
|
||||
SpecialLocations as Specials)
|
||||
|
||||
|
||||
class LocationsTest(JakAndDaxterTestBase):
|
||||
|
||||
Reference in New Issue
Block a user