mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-25 02:23:37 -07:00
Jak and Daxter: Genericize Items, Update Scout Fly logic, Add Victory Condition. (#3)
* Jak 1: Update to 0.4.6. Decouple locations from items, support filler items. * Jak 1: Total revamp of Items. This is where everything broke. * Jak 1: Decouple 7 scout fly checks from normal checks, update regions/rules for orb counts/traders. * Jak 1: correct regions/rules, account for sequential oracle/miner locations. * Jak 1: make nicer strings. * Jak 1: Add logic for finished game. First full run complete! * Jak 1: update group names.
This commit is contained in:
committed by
GitHub
parent
0f4c2211d8
commit
7cf50b0935
@@ -6,6 +6,7 @@ import asyncio
|
||||
import colorama
|
||||
|
||||
import Utils
|
||||
from NetUtils import ClientStatus
|
||||
from CommonClient import ClientCommandProcessor, CommonContext, logger, server_loop, gui_enabled
|
||||
|
||||
from worlds.jakanddaxter.GameID import jak1_name
|
||||
@@ -108,12 +109,21 @@ class JakAndDaxterContext(CommonContext):
|
||||
logger.info(args)
|
||||
self.repl.item_inbox[index] = item
|
||||
|
||||
async def ap_inform_location_checks(self, location_ids: typing.List[int]):
|
||||
async def ap_inform_location_check(self, location_ids: typing.List[int]):
|
||||
message = [{"cmd": "LocationChecks", "locations": location_ids}]
|
||||
await self.send_msgs(message)
|
||||
|
||||
def on_locations(self, location_ids: typing.List[int]):
|
||||
create_task_log_exception(self.ap_inform_location_checks(location_ids))
|
||||
def on_location_check(self, location_ids: typing.List[int]):
|
||||
create_task_log_exception(self.ap_inform_location_check(location_ids))
|
||||
|
||||
async def ap_inform_finished_game(self):
|
||||
if not self.finished_game and self.memr.finished_game:
|
||||
message = [{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]
|
||||
await self.send_msgs(message)
|
||||
self.finished_game = True
|
||||
|
||||
def on_finish(self):
|
||||
create_task_log_exception(self.ap_inform_finished_game())
|
||||
|
||||
async def run_repl_loop(self):
|
||||
while True:
|
||||
@@ -122,7 +132,7 @@ class JakAndDaxterContext(CommonContext):
|
||||
|
||||
async def run_memr_loop(self):
|
||||
while True:
|
||||
await self.memr.main_tick(self.on_locations)
|
||||
await self.memr.main_tick(self.on_location_check, self.on_finish)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
@@ -189,7 +199,7 @@ async def main():
|
||||
ctx.run_cli()
|
||||
|
||||
# Find and run the game (gk) and compiler/repl (goalc).
|
||||
await run_game(ctx)
|
||||
# await run_game(ctx)
|
||||
await ctx.exit_event.wait()
|
||||
await ctx.shutdown()
|
||||
|
||||
|
||||
@@ -1,7 +1,64 @@
|
||||
from BaseClasses import Item
|
||||
from .GameID import jak1_name
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts, SpecialLocations as Specials
|
||||
|
||||
|
||||
class JakAndDaxterItem(Item):
|
||||
game: str = jak1_name
|
||||
|
||||
|
||||
# Power Cells are generic, fungible, interchangeable items. Every cell is indistinguishable from every other.
|
||||
cell_item_table = {
|
||||
0: "Power Cell",
|
||||
}
|
||||
|
||||
# Scout flies are interchangeable within their respective sets of 7. Notice the abbreviated level name after each item.
|
||||
# Also, notice that their Item ID equals their respective Power Cell's Location ID. This is necessary for
|
||||
# game<->archipelago communication.
|
||||
scout_item_table = {
|
||||
95: "Scout Fly - GR",
|
||||
75: "Scout Fly - SV",
|
||||
7: "Scout Fly - FJ",
|
||||
20: "Scout Fly - SB",
|
||||
28: "Scout Fly - MI",
|
||||
68: "Scout Fly - FC",
|
||||
76: "Scout Fly - RV",
|
||||
57: "Scout Fly - PB",
|
||||
49: "Scout Fly - LPC",
|
||||
43: "Scout Fly - BS",
|
||||
88: "Scout Fly - MP",
|
||||
77: "Scout Fly - VC",
|
||||
85: "Scout Fly - SC",
|
||||
65: "Scout Fly - SM",
|
||||
90: "Scout Fly - LT",
|
||||
91: "Scout Fly - GMC",
|
||||
}
|
||||
|
||||
# TODO - Orbs are also generic and interchangeable.
|
||||
# orb_item_table = {
|
||||
# ???: "Precursor Orb",
|
||||
# }
|
||||
|
||||
# These are special items representing unique unlocks in the world. Notice that their Item ID equals their
|
||||
# respective Location ID. Like scout flies, this is necessary for game<->archipelago communication.
|
||||
special_item_table = {
|
||||
5: "Fisherman's Boat",
|
||||
4: "Jungle Elevator",
|
||||
2: "Blue Eco Switch",
|
||||
17: "Flut Flut",
|
||||
60: "Yellow Eco Switch",
|
||||
63: "Snowy Fort Gate",
|
||||
71: "Freed The Blue Sage",
|
||||
72: "Freed The Red Sage",
|
||||
73: "Freed The Yellow Sage",
|
||||
70: "Freed The Green Sage",
|
||||
}
|
||||
|
||||
# All Items
|
||||
# While we're here, do all the ID conversions needed.
|
||||
item_table = {
|
||||
**{Cells.to_ap_id(k): cell_item_table[k] for k in cell_item_table},
|
||||
**{Scouts.to_ap_id(k): scout_item_table[k] for k in scout_item_table},
|
||||
# **{Orbs.to_ap_id(k): orb_item_table[k] for k in orb_item_table},
|
||||
**{Specials.to_ap_id(k): special_item_table[k] for k in special_item_table},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from BaseClasses import Location
|
||||
from .GameID import jak1_name
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts, SpecialLocations as Specials
|
||||
|
||||
|
||||
class JakAndDaxterLocation(Location):
|
||||
@@ -8,9 +8,9 @@ class JakAndDaxterLocation(Location):
|
||||
|
||||
|
||||
# All Locations
|
||||
# Because all items in Jak And Daxter are unique and do not regenerate, we can use this same table as our item table.
|
||||
# Each Item ID == its corresponding Location ID. While we're here, do all the ID conversions needed.
|
||||
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},
|
||||
**{Cells.to_ap_id(k): Cells.locFJ_cellTable[k] for k in Cells.locFJ_cellTable},
|
||||
@@ -42,5 +42,6 @@ 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}
|
||||
**{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},
|
||||
}
|
||||
|
||||
@@ -4,10 +4,27 @@ from BaseClasses import MultiWorld, Region
|
||||
from .GameID import jak1_name
|
||||
from .JakAndDaxterOptions import JakAndDaxterOptions
|
||||
from .Locations import JakAndDaxterLocation, location_table
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts, SpecialLocations as Specials
|
||||
|
||||
|
||||
class JakAndDaxterRegion(Region):
|
||||
game: str = jak1_name
|
||||
|
||||
|
||||
# Holds information like the level name, the number of orbs available there, etc. Applies to both Levels and SubLevels.
|
||||
# We especially need orb_counts to be tracked here because we need to know how many orbs you have access to
|
||||
# in order to know when you can afford the 90-orb and 120-orb payments for more checks.
|
||||
class Jak1LevelInfo:
|
||||
name: str
|
||||
orb_count: int
|
||||
|
||||
def __init__(self, name: str, orb_count: int):
|
||||
self.name = name
|
||||
self.orb_count = orb_count
|
||||
|
||||
|
||||
class Jak1Level(int, Enum):
|
||||
SCOUT_FLY_POWER_CELLS = auto() # This is a virtual location to reward you receiving 7 scout flies.
|
||||
GEYSER_ROCK = auto()
|
||||
SANDOVER_VILLAGE = auto()
|
||||
FORBIDDEN_JUNGLE = auto()
|
||||
@@ -27,7 +44,6 @@ class Jak1Level(int, Enum):
|
||||
|
||||
|
||||
class Jak1SubLevel(int, Enum):
|
||||
MAIN_AREA = auto()
|
||||
FORBIDDEN_JUNGLE_SWITCH_ROOM = auto()
|
||||
FORBIDDEN_JUNGLE_PLANT_ROOM = auto()
|
||||
SENTINEL_BEACH_CANNON_TOWER = auto()
|
||||
@@ -44,172 +60,213 @@ class Jak1SubLevel(int, Enum):
|
||||
GOL_AND_MAIAS_CITADEL_FINAL_BOSS = auto()
|
||||
|
||||
|
||||
level_table: typing.Dict[Jak1Level, str] = {
|
||||
Jak1Level.GEYSER_ROCK: "Geyser Rock",
|
||||
Jak1Level.SANDOVER_VILLAGE: "Sandover Village",
|
||||
Jak1Level.FORBIDDEN_JUNGLE: "Forbidden Jungle",
|
||||
Jak1Level.SENTINEL_BEACH: "Sentinel Beach",
|
||||
Jak1Level.MISTY_ISLAND: "Misty Island",
|
||||
Jak1Level.FIRE_CANYON: "Fire Canyon",
|
||||
Jak1Level.ROCK_VILLAGE: "Rock Village",
|
||||
Jak1Level.PRECURSOR_BASIN: "Precursor Basin",
|
||||
Jak1Level.LOST_PRECURSOR_CITY: "Lost Precursor City",
|
||||
Jak1Level.BOGGY_SWAMP: "Boggy Swamp",
|
||||
Jak1Level.MOUNTAIN_PASS: "Mountain Pass",
|
||||
Jak1Level.VOLCANIC_CRATER: "Volcanic Crater",
|
||||
Jak1Level.SPIDER_CAVE: "Spider Cave",
|
||||
Jak1Level.SNOWY_MOUNTAIN: "Snowy Mountain",
|
||||
Jak1Level.LAVA_TUBE: "Lava Tube",
|
||||
Jak1Level.GOL_AND_MAIAS_CITADEL: "Gol and Maia's Citadel"
|
||||
level_table: typing.Dict[Jak1Level, Jak1LevelInfo] = {
|
||||
Jak1Level.SCOUT_FLY_POWER_CELLS:
|
||||
Jak1LevelInfo("Scout Fly Power Cells", 0), # Virtual location.
|
||||
Jak1Level.GEYSER_ROCK:
|
||||
Jak1LevelInfo("Geyser Rock", 50),
|
||||
Jak1Level.SANDOVER_VILLAGE:
|
||||
Jak1LevelInfo("Sandover Village", 50),
|
||||
Jak1Level.FORBIDDEN_JUNGLE:
|
||||
Jak1LevelInfo("Forbidden Jungle", 99),
|
||||
Jak1Level.SENTINEL_BEACH:
|
||||
Jak1LevelInfo("Sentinel Beach", 128),
|
||||
Jak1Level.MISTY_ISLAND:
|
||||
Jak1LevelInfo("Misty Island", 150),
|
||||
Jak1Level.FIRE_CANYON:
|
||||
Jak1LevelInfo("Fire Canyon", 50),
|
||||
Jak1Level.ROCK_VILLAGE:
|
||||
Jak1LevelInfo("Rock Village", 50),
|
||||
Jak1Level.PRECURSOR_BASIN:
|
||||
Jak1LevelInfo("Precursor Basin", 200),
|
||||
Jak1Level.LOST_PRECURSOR_CITY:
|
||||
Jak1LevelInfo("Lost Precursor City", 133),
|
||||
Jak1Level.BOGGY_SWAMP:
|
||||
Jak1LevelInfo("Boggy Swamp", 177),
|
||||
Jak1Level.MOUNTAIN_PASS:
|
||||
Jak1LevelInfo("Mountain Pass", 0),
|
||||
Jak1Level.VOLCANIC_CRATER:
|
||||
Jak1LevelInfo("Volcanic Crater", 50),
|
||||
Jak1Level.SPIDER_CAVE:
|
||||
Jak1LevelInfo("Spider Cave", 200),
|
||||
Jak1Level.SNOWY_MOUNTAIN:
|
||||
Jak1LevelInfo("Snowy Mountain", 113),
|
||||
Jak1Level.LAVA_TUBE:
|
||||
Jak1LevelInfo("Lava Tube", 50),
|
||||
Jak1Level.GOL_AND_MAIAS_CITADEL:
|
||||
Jak1LevelInfo("Gol and Maia's Citadel", 180),
|
||||
}
|
||||
|
||||
subLevel_table: typing.Dict[Jak1SubLevel, str] = {
|
||||
Jak1SubLevel.MAIN_AREA: "Main Area",
|
||||
Jak1SubLevel.FORBIDDEN_JUNGLE_SWITCH_ROOM: "Forbidden Jungle Switch Room",
|
||||
Jak1SubLevel.FORBIDDEN_JUNGLE_PLANT_ROOM: "Forbidden Jungle Plant Room",
|
||||
Jak1SubLevel.SENTINEL_BEACH_CANNON_TOWER: "Sentinel Beach Cannon Tower",
|
||||
Jak1SubLevel.PRECURSOR_BASIN_BLUE_RINGS: "Precursor Basin Blue Rings",
|
||||
Jak1SubLevel.LOST_PRECURSOR_CITY_SUNKEN_ROOM: "Lost Precursor City Sunken Room",
|
||||
Jak1SubLevel.LOST_PRECURSOR_CITY_HELIX_ROOM: "Lost Precursor City Helix Room",
|
||||
Jak1SubLevel.BOGGY_SWAMP_FLUT_FLUT: "Boggy Swamp Flut Flut",
|
||||
Jak1SubLevel.MOUNTAIN_PASS_RACE: "Mountain Pass Race",
|
||||
Jak1SubLevel.MOUNTAIN_PASS_SHORTCUT: "Mountain Pass Shortcut",
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_FLUT_FLUT: "Snowy Mountain Flut Flut",
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_LURKER_FORT: "Snowy Mountain Lurker Fort",
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_FROZEN_BOX: "Snowy Mountain Frozen Box",
|
||||
Jak1SubLevel.GOL_AND_MAIAS_CITADEL_ROTATING_TOWER: "Gol and Maia's Citadel Rotating Tower",
|
||||
Jak1SubLevel.GOL_AND_MAIAS_CITADEL_FINAL_BOSS: "Gol and Maia's Citadel Final Boss"
|
||||
sub_level_table: typing.Dict[Jak1SubLevel, Jak1LevelInfo] = {
|
||||
Jak1SubLevel.FORBIDDEN_JUNGLE_SWITCH_ROOM:
|
||||
Jak1LevelInfo("Forbidden Jungle Switch Room", 24),
|
||||
Jak1SubLevel.FORBIDDEN_JUNGLE_PLANT_ROOM:
|
||||
Jak1LevelInfo("Forbidden Jungle Plant Room", 27),
|
||||
Jak1SubLevel.SENTINEL_BEACH_CANNON_TOWER:
|
||||
Jak1LevelInfo("Sentinel Beach Cannon Tower", 22),
|
||||
Jak1SubLevel.PRECURSOR_BASIN_BLUE_RINGS:
|
||||
Jak1LevelInfo("Precursor Basin Blue Rings", 0), # Another virtual location, no orbs.
|
||||
Jak1SubLevel.LOST_PRECURSOR_CITY_SUNKEN_ROOM:
|
||||
Jak1LevelInfo("Lost Precursor City Sunken Room", 37),
|
||||
Jak1SubLevel.LOST_PRECURSOR_CITY_HELIX_ROOM:
|
||||
Jak1LevelInfo("Lost Precursor City Helix Room", 30),
|
||||
Jak1SubLevel.BOGGY_SWAMP_FLUT_FLUT:
|
||||
Jak1LevelInfo("Boggy Swamp Flut Flut", 23),
|
||||
Jak1SubLevel.MOUNTAIN_PASS_RACE:
|
||||
Jak1LevelInfo("Mountain Pass Race", 50),
|
||||
Jak1SubLevel.MOUNTAIN_PASS_SHORTCUT:
|
||||
Jak1LevelInfo("Mountain Pass Shortcut", 0),
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_FLUT_FLUT:
|
||||
Jak1LevelInfo("Snowy Mountain Flut Flut", 15),
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_LURKER_FORT:
|
||||
Jak1LevelInfo("Snowy Mountain Lurker Fort", 72),
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_FROZEN_BOX:
|
||||
Jak1LevelInfo("Snowy Mountain Frozen Box", 0),
|
||||
Jak1SubLevel.GOL_AND_MAIAS_CITADEL_ROTATING_TOWER:
|
||||
Jak1LevelInfo("Gol and Maia's Citadel Rotating Tower", 20),
|
||||
Jak1SubLevel.GOL_AND_MAIAS_CITADEL_FINAL_BOSS:
|
||||
Jak1LevelInfo("Gol and Maia's Citadel Final Boss", 0),
|
||||
}
|
||||
|
||||
|
||||
class JakAndDaxterRegion(Region):
|
||||
game: str = jak1_name
|
||||
|
||||
|
||||
# Use the original game ID's for each item to tell the Region which Locations are available in it.
|
||||
# You do NOT need to add the item offsets or game ID, that will be handled by create_*_locations.
|
||||
def create_regions(multiworld: MultiWorld, options: JakAndDaxterOptions, player: int):
|
||||
create_region(player, multiworld, "Menu")
|
||||
|
||||
region_gr = create_region(player, multiworld, level_table[Jak1Level.GEYSER_ROCK])
|
||||
# Always start with Menu.
|
||||
multiworld.regions.append(JakAndDaxterRegion("Menu", player, multiworld))
|
||||
|
||||
region_7sf = create_region(player, multiworld, Jak1Level.SCOUT_FLY_POWER_CELLS)
|
||||
create_cell_locations(region_7sf, Cells.loc7SF_cellTable)
|
||||
|
||||
region_gr = create_region(player, multiworld, Jak1Level.GEYSER_ROCK)
|
||||
create_cell_locations(region_gr, Cells.locGR_cellTable)
|
||||
create_fly_locations(region_gr, Scouts.locGR_scoutTable)
|
||||
|
||||
region_sv = create_region(player, multiworld, level_table[Jak1Level.SANDOVER_VILLAGE])
|
||||
region_sv = create_region(player, multiworld, Jak1Level.SANDOVER_VILLAGE)
|
||||
create_cell_locations(region_sv, Cells.locSV_cellTable)
|
||||
create_fly_locations(region_sv, Scouts.locSV_scoutTable)
|
||||
|
||||
region_fj = create_region(player, multiworld, level_table[Jak1Level.FORBIDDEN_JUNGLE])
|
||||
create_cell_locations(region_fj, {k: Cells.locFJ_cellTable[k] for k in {3, 4, 5, 8, 9, 7}})
|
||||
region_fj = create_region(player, multiworld, Jak1Level.FORBIDDEN_JUNGLE)
|
||||
create_cell_locations(region_fj, {k: Cells.locFJ_cellTable[k] for k in {3, 4, 5, 8, 9}})
|
||||
create_fly_locations(region_fj, Scouts.locFJ_scoutTable)
|
||||
create_special_locations(region_fj, {k: Specials.loc_specialTable[k] for k in {4, 5}})
|
||||
|
||||
sub_region_fjsr = create_subregion(region_fj, subLevel_table[Jak1SubLevel.FORBIDDEN_JUNGLE_SWITCH_ROOM])
|
||||
sub_region_fjsr = create_subregion(region_fj, Jak1SubLevel.FORBIDDEN_JUNGLE_SWITCH_ROOM)
|
||||
create_cell_locations(sub_region_fjsr, {k: Cells.locFJ_cellTable[k] for k in {2}})
|
||||
create_special_locations(sub_region_fjsr, {k: Specials.loc_specialTable[k] for k in {2}})
|
||||
|
||||
sub_region_fjpr = create_subregion(sub_region_fjsr, subLevel_table[Jak1SubLevel.FORBIDDEN_JUNGLE_PLANT_ROOM])
|
||||
sub_region_fjpr = create_subregion(sub_region_fjsr, Jak1SubLevel.FORBIDDEN_JUNGLE_PLANT_ROOM)
|
||||
create_cell_locations(sub_region_fjpr, {k: Cells.locFJ_cellTable[k] for k in {6}})
|
||||
|
||||
region_sb = create_region(player, multiworld, level_table[Jak1Level.SENTINEL_BEACH])
|
||||
create_cell_locations(region_sb, {k: Cells.locSB_cellTable[k] for k in {15, 17, 16, 18, 21, 22, 20}})
|
||||
region_sb = create_region(player, multiworld, Jak1Level.SENTINEL_BEACH)
|
||||
create_cell_locations(region_sb, {k: Cells.locSB_cellTable[k] for k in {15, 17, 16, 18, 21, 22}})
|
||||
create_fly_locations(region_sb, Scouts.locSB_scoutTable)
|
||||
create_special_locations(region_sb, {k: Specials.loc_specialTable[k] for k in {17}})
|
||||
|
||||
sub_region_sbct = create_subregion(region_sb, subLevel_table[Jak1SubLevel.SENTINEL_BEACH_CANNON_TOWER])
|
||||
sub_region_sbct = create_subregion(region_sb, Jak1SubLevel.SENTINEL_BEACH_CANNON_TOWER)
|
||||
create_cell_locations(sub_region_sbct, {k: Cells.locSB_cellTable[k] for k in {19}})
|
||||
|
||||
region_mi = create_region(player, multiworld, level_table[Jak1Level.MISTY_ISLAND])
|
||||
region_mi = create_region(player, multiworld, Jak1Level.MISTY_ISLAND)
|
||||
create_cell_locations(region_mi, Cells.locMI_cellTable)
|
||||
create_fly_locations(region_mi, Scouts.locMI_scoutTable)
|
||||
|
||||
region_fc = create_region(player, multiworld, level_table[Jak1Level.FIRE_CANYON])
|
||||
region_fc = create_region(player, multiworld, Jak1Level.FIRE_CANYON)
|
||||
create_cell_locations(region_fc, Cells.locFC_cellTable)
|
||||
create_fly_locations(region_fc, Scouts.locFC_scoutTable)
|
||||
|
||||
region_rv = create_region(player, multiworld, level_table[Jak1Level.ROCK_VILLAGE])
|
||||
region_rv = create_region(player, multiworld, Jak1Level.ROCK_VILLAGE)
|
||||
create_cell_locations(region_rv, Cells.locRV_cellTable)
|
||||
create_fly_locations(region_rv, Scouts.locRV_scoutTable)
|
||||
|
||||
region_pb = create_region(player, multiworld, level_table[Jak1Level.PRECURSOR_BASIN])
|
||||
create_cell_locations(region_pb, {k: Cells.locPB_cellTable[k] for k in {54, 53, 52, 56, 55, 58, 57}})
|
||||
region_pb = create_region(player, multiworld, Jak1Level.PRECURSOR_BASIN)
|
||||
create_cell_locations(region_pb, {k: Cells.locPB_cellTable[k] for k in {54, 53, 52, 56, 55, 58}})
|
||||
create_fly_locations(region_pb, Scouts.locPB_scoutTable)
|
||||
|
||||
sub_region_pbbr = create_subregion(region_pb, subLevel_table[Jak1SubLevel.PRECURSOR_BASIN_BLUE_RINGS])
|
||||
sub_region_pbbr = create_subregion(region_pb, Jak1SubLevel.PRECURSOR_BASIN_BLUE_RINGS)
|
||||
create_cell_locations(sub_region_pbbr, {k: Cells.locPB_cellTable[k] for k in {59}})
|
||||
|
||||
region_lpc = create_region(player, multiworld, level_table[Jak1Level.LOST_PRECURSOR_CITY])
|
||||
region_lpc = create_region(player, multiworld, Jak1Level.LOST_PRECURSOR_CITY)
|
||||
create_cell_locations(region_lpc, {k: Cells.locLPC_cellTable[k] for k in {45, 48, 44, 51}})
|
||||
create_fly_locations(region_lpc, {k: Scouts.locLPC_scoutTable[k]
|
||||
for k in {262193, 131121, 393265, 196657, 49, 65585}})
|
||||
|
||||
sub_region_lpcsr = create_subregion(region_lpc, subLevel_table[Jak1SubLevel.LOST_PRECURSOR_CITY_SUNKEN_ROOM])
|
||||
create_cell_locations(sub_region_lpcsr, {k: Cells.locLPC_cellTable[k] for k in {47, 49}})
|
||||
sub_region_lpcsr = create_subregion(region_lpc, Jak1SubLevel.LOST_PRECURSOR_CITY_SUNKEN_ROOM)
|
||||
create_cell_locations(sub_region_lpcsr, {k: Cells.locLPC_cellTable[k] for k in {47}})
|
||||
create_fly_locations(region_lpc, {k: Scouts.locLPC_scoutTable[k] for k in {327729}})
|
||||
|
||||
sub_region_lpchr = create_subregion(region_lpc, subLevel_table[Jak1SubLevel.LOST_PRECURSOR_CITY_HELIX_ROOM])
|
||||
sub_region_lpchr = create_subregion(region_lpc, Jak1SubLevel.LOST_PRECURSOR_CITY_HELIX_ROOM)
|
||||
create_cell_locations(sub_region_lpchr, {k: Cells.locLPC_cellTable[k] for k in {46, 50}})
|
||||
|
||||
region_bs = create_region(player, multiworld, level_table[Jak1Level.BOGGY_SWAMP])
|
||||
region_bs = create_region(player, multiworld, Jak1Level.BOGGY_SWAMP)
|
||||
create_cell_locations(region_bs, {k: Cells.locBS_cellTable[k] for k in {36, 38, 39, 40, 41, 42}})
|
||||
create_fly_locations(region_bs, {k: Scouts.locBS_scoutTable[k] for k in {43, 393259, 65579, 262187, 196651}})
|
||||
|
||||
sub_region_bsff = create_subregion(region_bs, subLevel_table[Jak1SubLevel.BOGGY_SWAMP_FLUT_FLUT])
|
||||
create_cell_locations(sub_region_bsff, {k: Cells.locBS_cellTable[k] for k in {43, 37}})
|
||||
sub_region_bsff = create_subregion(region_bs, Jak1SubLevel.BOGGY_SWAMP_FLUT_FLUT)
|
||||
create_cell_locations(sub_region_bsff, {k: Cells.locBS_cellTable[k] for k in {37}})
|
||||
create_fly_locations(sub_region_bsff, {k: Scouts.locBS_scoutTable[k] for k in {327723, 131115}})
|
||||
|
||||
region_mp = create_region(player, multiworld, level_table[Jak1Level.MOUNTAIN_PASS])
|
||||
region_mp = create_region(player, multiworld, Jak1Level.MOUNTAIN_PASS)
|
||||
create_cell_locations(region_mp, {k: Cells.locMP_cellTable[k] for k in {86}})
|
||||
|
||||
sub_region_mpr = create_subregion(region_mp, subLevel_table[Jak1SubLevel.MOUNTAIN_PASS_RACE])
|
||||
create_cell_locations(sub_region_mpr, {k: Cells.locMP_cellTable[k] for k in {87, 88}})
|
||||
sub_region_mpr = create_subregion(region_mp, Jak1SubLevel.MOUNTAIN_PASS_RACE)
|
||||
create_cell_locations(sub_region_mpr, {k: Cells.locMP_cellTable[k] for k in {87}})
|
||||
create_fly_locations(sub_region_mpr, Scouts.locMP_scoutTable)
|
||||
|
||||
sub_region_mps = create_subregion(sub_region_mpr, subLevel_table[Jak1SubLevel.MOUNTAIN_PASS_SHORTCUT])
|
||||
sub_region_mps = create_subregion(sub_region_mpr, Jak1SubLevel.MOUNTAIN_PASS_SHORTCUT)
|
||||
create_cell_locations(sub_region_mps, {k: Cells.locMP_cellTable[k] for k in {110}})
|
||||
|
||||
region_vc = create_region(player, multiworld, level_table[Jak1Level.VOLCANIC_CRATER])
|
||||
region_vc = create_region(player, multiworld, Jak1Level.VOLCANIC_CRATER)
|
||||
create_cell_locations(region_vc, Cells.locVC_cellTable)
|
||||
create_fly_locations(region_vc, Scouts.locVC_scoutTable)
|
||||
|
||||
region_sc = create_region(player, multiworld, level_table[Jak1Level.SPIDER_CAVE])
|
||||
region_sc = create_region(player, multiworld, Jak1Level.SPIDER_CAVE)
|
||||
create_cell_locations(region_sc, Cells.locSC_cellTable)
|
||||
create_fly_locations(region_sc, Scouts.locSC_scoutTable)
|
||||
|
||||
region_sm = create_region(player, multiworld, level_table[Jak1Level.SNOWY_MOUNTAIN])
|
||||
region_sm = create_region(player, multiworld, Jak1Level.SNOWY_MOUNTAIN)
|
||||
create_cell_locations(region_sm, {k: Cells.locSM_cellTable[k] for k in {60, 61, 66, 64}})
|
||||
create_fly_locations(region_sm, {k: Scouts.locSM_scoutTable[k] for k in {65, 327745, 65601, 131137, 393281}})
|
||||
create_special_locations(region_sm, {k: Specials.loc_specialTable[k] for k in {60}})
|
||||
|
||||
sub_region_smfb = create_subregion(region_sm, subLevel_table[Jak1SubLevel.SNOWY_MOUNTAIN_FROZEN_BOX])
|
||||
sub_region_smfb = create_subregion(region_sm, Jak1SubLevel.SNOWY_MOUNTAIN_FROZEN_BOX)
|
||||
create_cell_locations(sub_region_smfb, {k: Cells.locSM_cellTable[k] for k in {67}})
|
||||
|
||||
sub_region_smff = create_subregion(region_sm, subLevel_table[Jak1SubLevel.SNOWY_MOUNTAIN_FLUT_FLUT])
|
||||
sub_region_smff = create_subregion(region_sm, Jak1SubLevel.SNOWY_MOUNTAIN_FLUT_FLUT)
|
||||
create_cell_locations(sub_region_smff, {k: Cells.locSM_cellTable[k] for k in {63}})
|
||||
create_special_locations(sub_region_smff, {k: Specials.loc_specialTable[k] for k in {63}})
|
||||
|
||||
sub_region_smlf = create_subregion(region_sm, subLevel_table[Jak1SubLevel.SNOWY_MOUNTAIN_LURKER_FORT])
|
||||
create_cell_locations(sub_region_smlf, {k: Cells.locSM_cellTable[k] for k in {62, 65}})
|
||||
sub_region_smlf = create_subregion(region_sm, Jak1SubLevel.SNOWY_MOUNTAIN_LURKER_FORT)
|
||||
create_cell_locations(sub_region_smlf, {k: Cells.locSM_cellTable[k] for k in {62}})
|
||||
create_fly_locations(sub_region_smlf, {k: Scouts.locSM_scoutTable[k] for k in {196673, 262209}})
|
||||
|
||||
region_lt = create_region(player, multiworld, level_table[Jak1Level.LAVA_TUBE])
|
||||
region_lt = create_region(player, multiworld, Jak1Level.LAVA_TUBE)
|
||||
create_cell_locations(region_lt, Cells.locLT_cellTable)
|
||||
create_fly_locations(region_lt, Scouts.locLT_scoutTable)
|
||||
|
||||
region_gmc = create_region(player, multiworld, level_table[Jak1Level.GOL_AND_MAIAS_CITADEL])
|
||||
region_gmc = create_region(player, multiworld, Jak1Level.GOL_AND_MAIAS_CITADEL)
|
||||
create_cell_locations(region_gmc, {k: Cells.locGMC_cellTable[k] for k in {71, 72, 73}})
|
||||
create_fly_locations(region_gmc, {k: Scouts.locGMC_scoutTable[k]
|
||||
for k in {91, 65627, 196699, 262235, 393307, 131163}})
|
||||
create_special_locations(region_gmc, {k: Specials.loc_specialTable[k] for k in {71, 72, 73}})
|
||||
|
||||
sub_region_gmcrt = create_subregion(region_gmc, subLevel_table[Jak1SubLevel.GOL_AND_MAIAS_CITADEL_ROTATING_TOWER])
|
||||
create_cell_locations(sub_region_gmcrt, {k: Cells.locGMC_cellTable[k] for k in {70, 91}})
|
||||
sub_region_gmcrt = create_subregion(region_gmc, Jak1SubLevel.GOL_AND_MAIAS_CITADEL_ROTATING_TOWER)
|
||||
create_cell_locations(sub_region_gmcrt, {k: Cells.locGMC_cellTable[k] for k in {70}})
|
||||
create_fly_locations(sub_region_gmcrt, {k: Scouts.locGMC_scoutTable[k] for k in {327771}})
|
||||
create_special_locations(sub_region_gmcrt, {k: Specials.loc_specialTable[k] for k in {70}})
|
||||
|
||||
create_subregion(sub_region_gmcrt, subLevel_table[Jak1SubLevel.GOL_AND_MAIAS_CITADEL_FINAL_BOSS])
|
||||
create_subregion(sub_region_gmcrt, Jak1SubLevel.GOL_AND_MAIAS_CITADEL_FINAL_BOSS)
|
||||
|
||||
|
||||
def create_region(player: int, multiworld: MultiWorld, name: str) -> JakAndDaxterRegion:
|
||||
def create_region(player: int, multiworld: MultiWorld, level: Jak1Level) -> JakAndDaxterRegion:
|
||||
name = level_table[level].name
|
||||
region = JakAndDaxterRegion(name, player, multiworld)
|
||||
multiworld.regions.append(region)
|
||||
return region
|
||||
|
||||
|
||||
def create_subregion(parent: Region, name: str) -> JakAndDaxterRegion:
|
||||
def create_subregion(parent: Region, sub_level: Jak1SubLevel) -> JakAndDaxterRegion:
|
||||
name = sub_level_table[sub_level].name
|
||||
region = JakAndDaxterRegion(name, parent.player, parent.multiworld)
|
||||
parent.multiworld.regions.append(region)
|
||||
return region
|
||||
@@ -227,3 +284,12 @@ def create_fly_locations(region: Region, locations: typing.Dict[int, str]):
|
||||
location_table[Scouts.to_ap_id(loc)],
|
||||
Scouts.to_ap_id(loc),
|
||||
region) for loc in locations]
|
||||
|
||||
|
||||
# Special Locations should be matched alongside their respective Power Cell Locations,
|
||||
# so you get 2 unlocks for these rather than 1.
|
||||
def create_special_locations(region: Region, locations: typing.Dict[int, str]):
|
||||
region.locations += [JakAndDaxterLocation(region.player,
|
||||
location_table[Specials.to_ap_id(loc)],
|
||||
Specials.to_ap_id(loc),
|
||||
region) for loc in locations]
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
from typing import List
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from .JakAndDaxterOptions import JakAndDaxterOptions
|
||||
from .Regions import Jak1Level, Jak1SubLevel, level_table, subLevel_table
|
||||
from .Locations import location_table as item_table
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts
|
||||
|
||||
|
||||
# Helper function for a handful of special cases
|
||||
# where we need "at least any N" number of a specific set of cells.
|
||||
def has_count_of(cell_list: set, required_count: int, player: int, state: CollectionState) -> bool:
|
||||
c: int = 0
|
||||
for k in cell_list:
|
||||
if state.has(item_table[k], player):
|
||||
c += 1
|
||||
if c >= required_count:
|
||||
return True
|
||||
return False
|
||||
from .Regions import Jak1Level, Jak1SubLevel, level_table, sub_level_table
|
||||
from .Items import item_table
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts, SpecialLocations as Specials
|
||||
from worlds.jakanddaxter.Locations import location_table
|
||||
|
||||
|
||||
def set_rules(multiworld: MultiWorld, options: JakAndDaxterOptions, player: int):
|
||||
|
||||
# Setting up some useful variables here because the offset numbers can get confusing
|
||||
# for access rules. Feel free to add more variables here to keep the code more readable.
|
||||
# You DO need to convert the game ID's to AP ID's here.
|
||||
gr_cells = {Cells.to_ap_id(k) for k in Cells.locGR_cellTable}
|
||||
fj_temple_top = Cells.to_ap_id(4)
|
||||
fj_blue_switch = Cells.to_ap_id(2)
|
||||
fj_plant_boss = Cells.to_ap_id(6)
|
||||
fj_fisherman = Cells.to_ap_id(5)
|
||||
sb_flut_flut = Cells.to_ap_id(17)
|
||||
fc_end = Cells.to_ap_id(69)
|
||||
pb_purple_rings = Cells.to_ap_id(58)
|
||||
lpc_sunken = Cells.to_ap_id(47)
|
||||
lpc_helix = Cells.to_ap_id(50)
|
||||
mp_klaww = Cells.to_ap_id(86)
|
||||
mp_end = Cells.to_ap_id(87)
|
||||
pre_sm_cells = {Cells.to_ap_id(k) for k in {**Cells.locVC_cellTable, **Cells.locSC_cellTable}}
|
||||
sm_yellow_switch = Cells.to_ap_id(60)
|
||||
sm_fort_gate = Cells.to_ap_id(63)
|
||||
lt_end = Cells.to_ap_id(89)
|
||||
gmc_rby_sages = {Cells.to_ap_id(k) for k in {71, 72, 73}}
|
||||
gmc_green_sage = Cells.to_ap_id(70)
|
||||
power_cell = item_table[Cells.to_ap_id(0)]
|
||||
|
||||
# The int/list structure here is intentional, see `set_trade_requirements` for how we handle these.
|
||||
sv_traders = [11, 12, [13, 14]] # Mayor, Uncle, Oracle 1 and 2
|
||||
rv_traders = [31, 32, 33, [34, 35]] # Geologist, Gambler, Warrior, Oracle 3 and 4
|
||||
vc_traders = [[96, 97, 98, 99], [100, 101]] # Miners 1-4, Oracle 5 and 6
|
||||
|
||||
fj_jungle_elevator = item_table[Specials.to_ap_id(4)]
|
||||
fj_blue_switch = item_table[Specials.to_ap_id(2)]
|
||||
fj_fisherman = item_table[Specials.to_ap_id(5)]
|
||||
|
||||
sb_flut_flut = item_table[Specials.to_ap_id(17)]
|
||||
sm_yellow_switch = item_table[Specials.to_ap_id(60)]
|
||||
sm_fort_gate = item_table[Specials.to_ap_id(63)]
|
||||
|
||||
gmc_blue_sage = item_table[Specials.to_ap_id(71)]
|
||||
gmc_red_sage = item_table[Specials.to_ap_id(72)]
|
||||
gmc_yellow_sage = item_table[Specials.to_ap_id(73)]
|
||||
gmc_green_sage = item_table[Specials.to_ap_id(70)]
|
||||
|
||||
# Start connecting regions and set their access rules.
|
||||
connect_start(multiworld, player, Jak1Level.GEYSER_ROCK)
|
||||
|
||||
# Scout Fly Power Cells is a virtual region, not a physical one, so connect it to Menu.
|
||||
connect_start(multiworld, player, Jak1Level.SCOUT_FLY_POWER_CELLS)
|
||||
set_fly_requirements(multiworld, player)
|
||||
|
||||
# You start the game in front of Green Sage's Hut, so you don't get stuck on Geyser Rock in the first 5 minutes.
|
||||
connect_start(multiworld, player, Jak1Level.SANDOVER_VILLAGE)
|
||||
set_trade_requirements(multiworld, player, Jak1Level.SANDOVER_VILLAGE, sv_traders, 1530)
|
||||
|
||||
# Geyser Rock is accessible at any time, just check the 3 naked cell Locations to return.
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.GEYSER_ROCK,
|
||||
Jak1Level.SANDOVER_VILLAGE,
|
||||
lambda state: has_count_of(gr_cells, 4, player, state))
|
||||
Jak1Level.GEYSER_ROCK)
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.SANDOVER_VILLAGE,
|
||||
@@ -55,50 +55,52 @@ def set_rules(multiworld: MultiWorld, options: JakAndDaxterOptions, player: int)
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.FORBIDDEN_JUNGLE,
|
||||
Jak1SubLevel.FORBIDDEN_JUNGLE_SWITCH_ROOM,
|
||||
lambda state: state.has(item_table[fj_temple_top], player))
|
||||
lambda state: state.has(fj_jungle_elevator, player))
|
||||
|
||||
connect_subregions(multiworld, player,
|
||||
Jak1SubLevel.FORBIDDEN_JUNGLE_SWITCH_ROOM,
|
||||
Jak1SubLevel.FORBIDDEN_JUNGLE_PLANT_ROOM,
|
||||
lambda state: state.has(item_table[fj_blue_switch], player))
|
||||
lambda state: state.has(fj_blue_switch, player))
|
||||
|
||||
# You just need to defeat the plant boss to escape this subregion, no specific Item required.
|
||||
connect_sub_to_region(multiworld, player,
|
||||
Jak1SubLevel.FORBIDDEN_JUNGLE_PLANT_ROOM,
|
||||
Jak1Level.FORBIDDEN_JUNGLE,
|
||||
lambda state: state.has(item_table[fj_plant_boss], player))
|
||||
Jak1Level.FORBIDDEN_JUNGLE)
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.SANDOVER_VILLAGE,
|
||||
Jak1Level.SENTINEL_BEACH)
|
||||
|
||||
# Just jump off the tower to escape this subregion.
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.SENTINEL_BEACH,
|
||||
Jak1SubLevel.SENTINEL_BEACH_CANNON_TOWER,
|
||||
lambda state: state.has(item_table[fj_blue_switch], player))
|
||||
lambda state: state.has(fj_blue_switch, player))
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.SANDOVER_VILLAGE,
|
||||
Jak1Level.MISTY_ISLAND,
|
||||
lambda state: state.has(item_table[fj_fisherman], player))
|
||||
lambda state: state.has(fj_fisherman, player))
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.SANDOVER_VILLAGE,
|
||||
Jak1Level.FIRE_CANYON,
|
||||
lambda state: state.count_group("Power Cell", player) >= 20)
|
||||
lambda state: state.has(power_cell, player, 20))
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.FIRE_CANYON,
|
||||
Jak1Level.ROCK_VILLAGE,
|
||||
lambda state: state.has(item_table[fc_end], player))
|
||||
Jak1Level.ROCK_VILLAGE)
|
||||
set_trade_requirements(multiworld, player, Jak1Level.ROCK_VILLAGE, rv_traders, 1530)
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.ROCK_VILLAGE,
|
||||
Jak1Level.PRECURSOR_BASIN)
|
||||
|
||||
# This is another virtual location that shares it's "borders" with its parent location.
|
||||
# You can do blue rings as soon as you finish purple rings.
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.PRECURSOR_BASIN,
|
||||
Jak1SubLevel.PRECURSOR_BASIN_BLUE_RINGS,
|
||||
lambda state: state.has(item_table[pb_purple_rings], player))
|
||||
Jak1SubLevel.PRECURSOR_BASIN_BLUE_RINGS)
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.ROCK_VILLAGE,
|
||||
@@ -112,121 +114,179 @@ def set_rules(multiworld: MultiWorld, options: JakAndDaxterOptions, player: int)
|
||||
Jak1SubLevel.LOST_PRECURSOR_CITY_SUNKEN_ROOM,
|
||||
Jak1SubLevel.LOST_PRECURSOR_CITY_HELIX_ROOM)
|
||||
|
||||
# LPC is such a mess logistically... once you complete the climb up the helix room,
|
||||
# you are back to the room before the first slide, which is still the "main area" of LPC.
|
||||
connect_sub_to_region(multiworld, player,
|
||||
Jak1SubLevel.LOST_PRECURSOR_CITY_HELIX_ROOM,
|
||||
Jak1Level.LOST_PRECURSOR_CITY,
|
||||
lambda state: state.has(item_table[lpc_helix], player))
|
||||
Jak1Level.LOST_PRECURSOR_CITY)
|
||||
|
||||
# Once you raise the sunken room to the surface, you can access Rock Village directly.
|
||||
# You just need to complete the Location check to do this, you don't need to receive the power cell Item.
|
||||
connect_sub_to_region(multiworld, player,
|
||||
Jak1SubLevel.LOST_PRECURSOR_CITY_SUNKEN_ROOM,
|
||||
Jak1Level.ROCK_VILLAGE,
|
||||
lambda state: state.has(item_table[lpc_sunken], player))
|
||||
Jak1Level.ROCK_VILLAGE)
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.ROCK_VILLAGE,
|
||||
Jak1Level.BOGGY_SWAMP)
|
||||
|
||||
# Flut Flut only has one landing pad here, so leaving this subregion is as easy
|
||||
# as dismounting Flut Flut right where you found her.
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.BOGGY_SWAMP,
|
||||
Jak1SubLevel.BOGGY_SWAMP_FLUT_FLUT,
|
||||
lambda state: state.has(item_table[sb_flut_flut], player))
|
||||
lambda state: state.has(sb_flut_flut, player))
|
||||
|
||||
# Klaww is considered the "main area" of MP, and the "race" is a subregion.
|
||||
# It's not really intended to get back up the ledge overlooking Klaww's lava pit.
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.ROCK_VILLAGE,
|
||||
Jak1Level.MOUNTAIN_PASS,
|
||||
lambda state: state.count_group("Power Cell", player) >= 45)
|
||||
lambda state: state.has(power_cell, player, 45))
|
||||
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.MOUNTAIN_PASS,
|
||||
Jak1SubLevel.MOUNTAIN_PASS_RACE,
|
||||
lambda state: state.has(item_table[mp_klaww], player))
|
||||
Jak1SubLevel.MOUNTAIN_PASS_RACE)
|
||||
|
||||
connect_subregions(multiworld, player,
|
||||
Jak1SubLevel.MOUNTAIN_PASS_RACE,
|
||||
Jak1SubLevel.MOUNTAIN_PASS_SHORTCUT,
|
||||
lambda state: state.has(item_table[sm_yellow_switch], player))
|
||||
lambda state: state.has(sm_yellow_switch, player))
|
||||
|
||||
connect_sub_to_region(multiworld, player,
|
||||
Jak1SubLevel.MOUNTAIN_PASS_RACE,
|
||||
Jak1Level.VOLCANIC_CRATER,
|
||||
lambda state: state.has(item_table[mp_end], player))
|
||||
Jak1Level.VOLCANIC_CRATER)
|
||||
set_trade_requirements(multiworld, player, Jak1Level.VOLCANIC_CRATER, vc_traders, 1530)
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.VOLCANIC_CRATER,
|
||||
Jak1Level.SPIDER_CAVE)
|
||||
|
||||
# TODO - Yeah, this is a weird one. You technically need either 71 power cells OR
|
||||
# any 2 power cells after arriving at Volcanic Crater. Not sure how to model this...
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.VOLCANIC_CRATER,
|
||||
Jak1Level.SNOWY_MOUNTAIN,
|
||||
lambda state: has_count_of(pre_sm_cells, 2, player, state)
|
||||
or state.count_group("Power Cell", player) >= 71) # Yeah, this is a weird one.
|
||||
Jak1Level.SNOWY_MOUNTAIN)
|
||||
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.SNOWY_MOUNTAIN,
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_FROZEN_BOX,
|
||||
lambda state: state.has(item_table[sm_yellow_switch], player))
|
||||
lambda state: state.has(sm_yellow_switch, player))
|
||||
|
||||
# Flut Flut has both a start and end landing pad here, but there's an elevator that takes you up
|
||||
# from the end pad to the entrance of the fort, so you're back to the "main area."
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.SNOWY_MOUNTAIN,
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_FLUT_FLUT,
|
||||
lambda state: state.has(item_table[sb_flut_flut], player))
|
||||
lambda state: state.has(sb_flut_flut, player))
|
||||
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.SNOWY_MOUNTAIN,
|
||||
Jak1SubLevel.SNOWY_MOUNTAIN_LURKER_FORT,
|
||||
lambda state: state.has(item_table[sm_fort_gate], player))
|
||||
lambda state: state.has(sm_fort_gate, player))
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.VOLCANIC_CRATER,
|
||||
Jak1Level.LAVA_TUBE,
|
||||
lambda state: state.count_group("Power Cell", player) >= 72)
|
||||
lambda state: state.has(power_cell, player, 72))
|
||||
|
||||
connect_regions(multiworld, player,
|
||||
Jak1Level.LAVA_TUBE,
|
||||
Jak1Level.GOL_AND_MAIAS_CITADEL,
|
||||
lambda state: state.has(item_table[lt_end], player))
|
||||
Jak1Level.GOL_AND_MAIAS_CITADEL)
|
||||
|
||||
# The stairs up to Samos's cage is only activated when you get the Items for freeing the other 3 Sages.
|
||||
# But you can climb back down that staircase (or fall down from the top) to escape this subregion.
|
||||
connect_region_to_sub(multiworld, player,
|
||||
Jak1Level.GOL_AND_MAIAS_CITADEL,
|
||||
Jak1SubLevel.GOL_AND_MAIAS_CITADEL_ROTATING_TOWER,
|
||||
lambda state: has_count_of(gmc_rby_sages, 3, player, state))
|
||||
lambda state: state.has(gmc_blue_sage, player) and
|
||||
state.has(gmc_red_sage, player) and
|
||||
state.has(gmc_yellow_sage, player))
|
||||
|
||||
# This is the final elevator, only active when you get the Item for freeing the Green Sage.
|
||||
connect_subregions(multiworld, player,
|
||||
Jak1SubLevel.GOL_AND_MAIAS_CITADEL_ROTATING_TOWER,
|
||||
Jak1SubLevel.GOL_AND_MAIAS_CITADEL_FINAL_BOSS,
|
||||
lambda state: state.has(item_table[gmc_green_sage], player))
|
||||
lambda state: state.has(gmc_green_sage, player))
|
||||
|
||||
multiworld.completion_condition[player] = lambda state: state.can_reach(
|
||||
multiworld.get_region(subLevel_table[Jak1SubLevel.GOL_AND_MAIAS_CITADEL_FINAL_BOSS], player),
|
||||
multiworld.get_region(sub_level_table[Jak1SubLevel.GOL_AND_MAIAS_CITADEL_FINAL_BOSS].name, player),
|
||||
"Region",
|
||||
player)
|
||||
|
||||
|
||||
def connect_start(multiworld: MultiWorld, player: int, target: Jak1Level):
|
||||
menu_region = multiworld.get_region("Menu", player)
|
||||
start_region = multiworld.get_region(level_table[target], player)
|
||||
start_region = multiworld.get_region(level_table[target].name, player)
|
||||
menu_region.connect(start_region)
|
||||
|
||||
|
||||
def connect_regions(multiworld: MultiWorld, player: int, source: Jak1Level, target: Jak1Level, rule=None):
|
||||
source_region = multiworld.get_region(level_table[source], player)
|
||||
target_region = multiworld.get_region(level_table[target], player)
|
||||
source_region = multiworld.get_region(level_table[source].name, player)
|
||||
target_region = multiworld.get_region(level_table[target].name, player)
|
||||
source_region.connect(target_region, rule=rule)
|
||||
|
||||
|
||||
def connect_region_to_sub(multiworld: MultiWorld, player: int, source: Jak1Level, target: Jak1SubLevel, rule=None):
|
||||
source_region = multiworld.get_region(level_table[source], player)
|
||||
target_region = multiworld.get_region(subLevel_table[target], player)
|
||||
source_region = multiworld.get_region(level_table[source].name, player)
|
||||
target_region = multiworld.get_region(sub_level_table[target].name, player)
|
||||
source_region.connect(target_region, rule=rule)
|
||||
|
||||
|
||||
def connect_sub_to_region(multiworld: MultiWorld, player: int, source: Jak1SubLevel, target: Jak1Level, rule=None):
|
||||
source_region = multiworld.get_region(subLevel_table[source], player)
|
||||
target_region = multiworld.get_region(level_table[target], player)
|
||||
source_region = multiworld.get_region(sub_level_table[source].name, player)
|
||||
target_region = multiworld.get_region(level_table[target].name, player)
|
||||
source_region.connect(target_region, rule=rule)
|
||||
|
||||
|
||||
def connect_subregions(multiworld: MultiWorld, player: int, source: Jak1SubLevel, target: Jak1SubLevel, rule=None):
|
||||
source_region = multiworld.get_region(subLevel_table[source], player)
|
||||
target_region = multiworld.get_region(subLevel_table[target], player)
|
||||
source_region = multiworld.get_region(sub_level_table[source].name, player)
|
||||
target_region = multiworld.get_region(sub_level_table[target].name, player)
|
||||
source_region.connect(target_region, rule=rule)
|
||||
|
||||
|
||||
# The "Free 7 Scout Fly" Locations are automatically checked when you receive the 7th scout fly Item.
|
||||
def set_fly_requirements(multiworld: MultiWorld, player: int):
|
||||
region = multiworld.get_region(level_table[Jak1Level.SCOUT_FLY_POWER_CELLS].name, player)
|
||||
for loc in region.locations:
|
||||
scout_fly_id = Scouts.to_ap_id(Cells.to_game_id(loc.address)) # Translate using game ID as an intermediary.
|
||||
loc.access_rule = lambda state, flies=scout_fly_id: state.has(item_table[flies], player, 7)
|
||||
|
||||
|
||||
# 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 set_trade_requirements(multiworld: MultiWorld, player: int, level: Jak1Level, traders: List, orb_count: int):
|
||||
|
||||
def count_accessible_orbs(state) -> int:
|
||||
accessible_orbs = 0
|
||||
for level_info in [*level_table.values(), *sub_level_table.values()]:
|
||||
reg = multiworld.get_region(level_info.name, player)
|
||||
if reg.can_reach(state):
|
||||
accessible_orbs += level_info.orb_count
|
||||
return accessible_orbs
|
||||
|
||||
region = multiworld.get_region(level_table[level].name, player)
|
||||
names_to_index = {region.locations[i].name: i for i in range(0, len(region.locations))}
|
||||
for trader in traders:
|
||||
|
||||
# Singleton integers indicate a trader who has only one Location to check.
|
||||
# (Mayor, Uncle, etc)
|
||||
if type(trader) is int:
|
||||
loc = region.locations[names_to_index[location_table[Cells.to_ap_id(trader)]]]
|
||||
loc.access_rule = lambda state, orbs=orb_count: (
|
||||
count_accessible_orbs(state) >= orbs)
|
||||
|
||||
# Lists of integers indicate a trader who has sequential Locations to check, each dependent on the last.
|
||||
# (Oracles and Miners)
|
||||
elif type(trader) is list:
|
||||
previous_loc = None
|
||||
for trade in trader:
|
||||
loc = region.locations[names_to_index[location_table[Cells.to_ap_id(trade)]]]
|
||||
loc.access_rule = lambda state, orbs=orb_count, prev=previous_loc: (
|
||||
count_accessible_orbs(state) >= orbs and
|
||||
(state.can_reach(prev, player) if prev else True)) # TODO - Can Reach or Has Reached?
|
||||
previous_loc = loc
|
||||
|
||||
# Any other type of element in the traders list is wrong.
|
||||
else:
|
||||
raise TypeError(f"Tried to set trade requirements on an unknown type {trader}.")
|
||||
|
||||
@@ -5,12 +5,13 @@ from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from .GameID import jak1_id, jak1_name
|
||||
from .JakAndDaxterOptions import JakAndDaxterOptions
|
||||
from .Items import JakAndDaxterItem
|
||||
from .Locations import JakAndDaxterLocation, location_table as item_table
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts, OrbLocations as Orbs
|
||||
from .Locations import JakAndDaxterLocation, location_table
|
||||
from .Items import JakAndDaxterItem, item_table
|
||||
from .locs import CellLocations as Cells, ScoutLocations as Scouts, OrbLocations as Orbs, SpecialLocations as Specials
|
||||
from .Regions import create_regions
|
||||
from .Rules import set_rules
|
||||
from ..AutoWorld import World, WebWorld
|
||||
from ..LauncherComponents import components, Component, launch_subprocess, Type
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import components, Component, launch_subprocess, Type
|
||||
|
||||
|
||||
class JakAndDaxterSettings(settings.Group):
|
||||
@@ -46,8 +47,7 @@ class JakAndDaxterWorld(World):
|
||||
"""
|
||||
# ID, name, version
|
||||
game = jak1_name
|
||||
data_version = 1
|
||||
required_client_version = (0, 4, 5)
|
||||
required_client_version = (0, 4, 6)
|
||||
|
||||
# Options
|
||||
settings: typing.ClassVar[JakAndDaxterSettings]
|
||||
@@ -61,13 +61,17 @@ class JakAndDaxterWorld(World):
|
||||
# Stored as {ID: Name} pairs, these must now be swapped to {Name: ID} pairs.
|
||||
# Remember, the game ID and various offsets for each item type have already been calculated.
|
||||
item_name_to_id = {item_table[k]: k for k in item_table}
|
||||
location_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 Cell": {item_table[k]: k for k in item_table
|
||||
if k in range(jak1_id, jak1_id + Scouts.fly_offset)},
|
||||
"Scout Fly": {item_table[k]: k for k in item_table
|
||||
if k in range(jak1_id + Scouts.fly_offset, jak1_id + Orbs.orb_offset)}
|
||||
# "Precursor Orb": {} # TODO
|
||||
"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 + Orbs.orb_offset)},
|
||||
# TODO - Make group for Precursor Orbs.
|
||||
# "Precursor Orbs": {item_table[k]: k for k in item_table
|
||||
# if k in range(jak1_id + Orbs.orb_offset, ???)},
|
||||
}
|
||||
|
||||
def create_regions(self):
|
||||
@@ -76,25 +80,47 @@ class JakAndDaxterWorld(World):
|
||||
def set_rules(self):
|
||||
set_rules(self.multiworld, self.options, self.player)
|
||||
|
||||
# Helper function to reuse some nasty if/else trees.
|
||||
@staticmethod
|
||||
def item_type_helper(item) -> (int, ItemClassification):
|
||||
# Make 101 Power Cells.
|
||||
if item in range(jak1_id, jak1_id + Scouts.fly_offset):
|
||||
classification = ItemClassification.progression_skip_balancing
|
||||
count = 101
|
||||
|
||||
# Make 7 Scout Flies per level.
|
||||
elif item in range(jak1_id + Scouts.fly_offset, jak1_id + Specials.special_offset):
|
||||
classification = ItemClassification.progression_skip_balancing
|
||||
count = 7
|
||||
|
||||
# Make only 1 of each Special Item.
|
||||
elif item in range(jak1_id + Specials.special_offset, jak1_id + Orbs.orb_offset):
|
||||
classification = ItemClassification.progression
|
||||
count = 1
|
||||
|
||||
# TODO - Make ??? Precursor Orbs.
|
||||
# elif item in range(jak1_id + Orbs.orb_offset, ???):
|
||||
# classification = ItemClassification.filler
|
||||
# count = ???
|
||||
|
||||
# If we try to make items with ID's higher than we've defined, something has gone wrong.
|
||||
else:
|
||||
raise KeyError(f"Tried to fill item pool with unknown ID {item}.")
|
||||
|
||||
return count, classification
|
||||
|
||||
def create_items(self):
|
||||
self.multiworld.itempool += [self.create_item(item_table[k]) for k in item_table]
|
||||
for item_id in item_table:
|
||||
count, _ = self.item_type_helper(item_id)
|
||||
self.multiworld.itempool += [self.create_item(item_table[item_id]) for k in range(0, count)]
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item_id = self.item_name_to_id[name]
|
||||
if item_id in range(jak1_id, jak1_id + Scouts.fly_offset):
|
||||
# Power Cell
|
||||
classification = ItemClassification.progression_skip_balancing
|
||||
elif item_id in range(jak1_id + Scouts.fly_offset, jak1_id + Orbs.orb_offset):
|
||||
# Scout Fly
|
||||
classification = ItemClassification.progression_skip_balancing
|
||||
elif item_id > jak1_id + Orbs.orb_offset:
|
||||
# Precursor Orb
|
||||
classification = ItemClassification.filler # TODO
|
||||
else:
|
||||
classification = ItemClassification.filler
|
||||
_, classification = self.item_type_helper(item_id)
|
||||
return JakAndDaxterItem(name, classification, item_id, self.player)
|
||||
|
||||
item = JakAndDaxterItem(name, classification, item_id, self.player)
|
||||
return item
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "Power Cell" # TODO - Make Precursor Orb the filler item. Until then, enjoy the free progression.
|
||||
|
||||
|
||||
def launch_client():
|
||||
|
||||
@@ -4,20 +4,25 @@ from pymem import pattern
|
||||
from pymem.exception import ProcessNotFound, ProcessError, MemoryReadError, WinAPIError
|
||||
|
||||
from CommonClient import logger
|
||||
from worlds.jakanddaxter.locs import CellLocations as Cells, ScoutLocations as Flies
|
||||
from worlds.jakanddaxter.locs import CellLocations as Cells, ScoutLocations as Flies, SpecialLocations as Specials
|
||||
|
||||
# Some helpful constants.
|
||||
next_cell_index_offset = 0 # Each of these is an uint64, so 8 bytes.
|
||||
next_buzzer_index_offset = 8 # Each of these is an uint64, so 8 bytes.
|
||||
cells_offset = 16
|
||||
buzzers_offset = 420 # cells_offset + (sizeof uint32 * 101 cells) = 16 + (4 * 101)
|
||||
sizeof_uint64 = 8
|
||||
sizeof_uint32 = 4
|
||||
sizeof_uint8 = 1
|
||||
|
||||
next_cell_index_offset = 0 # Each of these is an uint64, so 8 bytes.
|
||||
next_buzzer_index_offset = 8 # Each of these is an uint64, so 8 bytes.
|
||||
next_special_index_offset = 16 # Each of these is an uint64, so 8 bytes.
|
||||
|
||||
# buzzers_offset
|
||||
# + (sizeof uint32 * 112 flies) <-- The buzzers themselves.
|
||||
# + (sizeof uint8 * 116 tasks) <-- A "cells-received" array for the game to handle new ownership logic.
|
||||
# = 420 + (4 * 112) + (1 * 116)
|
||||
end_marker_offset = 984
|
||||
cells_checked_offset = 24
|
||||
buzzers_checked_offset = 428 # cells_checked_offset + (sizeof uint32 * 101 cells)
|
||||
specials_checked_offset = 876 # buzzers_checked_offset + (sizeof uint32 * 112 buzzers)
|
||||
|
||||
buzzers_received_offset = 1004 # specials_checked_offset + (sizeof uint32 * 32 specials)
|
||||
specials_received_offset = 1020 # buzzers_received_offset + (sizeof uint8 * 16 levels (for scout fly groups))
|
||||
|
||||
end_marker_offset = 1052 # specials_received_offset + (sizeof uint8 * 32 specials)
|
||||
|
||||
|
||||
class JakAndDaxterMemoryReader:
|
||||
@@ -31,12 +36,13 @@ class JakAndDaxterMemoryReader:
|
||||
|
||||
location_outbox = []
|
||||
outbox_index = 0
|
||||
finished_game = False
|
||||
|
||||
def __init__(self, marker: typing.ByteString = b'UnLiStEdStRaTs_JaK1\x00'):
|
||||
self.marker = marker
|
||||
self.connect()
|
||||
|
||||
async def main_tick(self, location_callback: typing.Callable):
|
||||
async def main_tick(self, location_callback: typing.Callable, finish_callback: typing.Callable):
|
||||
if self.initiated_connect:
|
||||
await self.connect()
|
||||
self.initiated_connect = False
|
||||
@@ -59,6 +65,9 @@ class JakAndDaxterMemoryReader:
|
||||
location_callback(self.location_outbox)
|
||||
self.outbox_index += 1
|
||||
|
||||
if self.finished_game:
|
||||
finish_callback()
|
||||
|
||||
async def connect(self):
|
||||
try:
|
||||
self.gk_process = pymem.Pymem("gk.exe") # The GOAL Kernel
|
||||
@@ -74,9 +83,9 @@ 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 u64).
|
||||
# and the struct address is 8 bytes long (it's a uint64).
|
||||
goal_pointer = marker_address + len(self.marker) + 4
|
||||
self.goal_address = int.from_bytes(self.gk_process.read_bytes(goal_pointer, 8),
|
||||
self.goal_address = int.from_bytes(self.gk_process.read_bytes(goal_pointer, sizeof_uint64),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
logger.info("Found the archipelago memory address: " + str(self.goal_address))
|
||||
@@ -98,17 +107,23 @@ class JakAndDaxterMemoryReader:
|
||||
def read_memory(self) -> typing.List[int]:
|
||||
try:
|
||||
next_cell_index = int.from_bytes(
|
||||
self.gk_process.read_bytes(self.goal_address, 8),
|
||||
self.gk_process.read_bytes(self.goal_address, sizeof_uint64),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
next_buzzer_index = int.from_bytes(
|
||||
self.gk_process.read_bytes(self.goal_address + next_buzzer_index_offset, 8),
|
||||
self.gk_process.read_bytes(self.goal_address + next_buzzer_index_offset, sizeof_uint64),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
next_special_index = int.from_bytes(
|
||||
self.gk_process.read_bytes(self.goal_address + next_special_index_offset, sizeof_uint64),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
|
||||
for k in range(0, next_cell_index):
|
||||
next_cell = int.from_bytes(
|
||||
self.gk_process.read_bytes(self.goal_address + cells_offset + (k * 4), 4),
|
||||
self.gk_process.read_bytes(
|
||||
self.goal_address + cells_checked_offset + (k * sizeof_uint32),
|
||||
sizeof_uint32),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
cell_ap_id = Cells.to_ap_id(next_cell)
|
||||
@@ -118,7 +133,9 @@ class JakAndDaxterMemoryReader:
|
||||
|
||||
for k in range(0, next_buzzer_index):
|
||||
next_buzzer = int.from_bytes(
|
||||
self.gk_process.read_bytes(self.goal_address + buzzers_offset + (k * 4), 4),
|
||||
self.gk_process.read_bytes(
|
||||
self.goal_address + buzzers_checked_offset + (k * sizeof_uint32),
|
||||
sizeof_uint32),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
buzzer_ap_id = Flies.to_ap_id(next_buzzer)
|
||||
@@ -126,6 +143,27 @@ class JakAndDaxterMemoryReader:
|
||||
self.location_outbox.append(buzzer_ap_id)
|
||||
logger.info("Checked scout fly: " + str(next_buzzer))
|
||||
|
||||
for k in range(0, next_special_index):
|
||||
next_special = int.from_bytes(
|
||||
self.gk_process.read_bytes(
|
||||
self.goal_address + specials_checked_offset + (k * sizeof_uint32),
|
||||
sizeof_uint32),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
|
||||
# 112 is the game-task ID of `finalboss-movies`, which is written to this array when you grab
|
||||
# the white eco. This is our victory condition, so we need to catch it and act on it.
|
||||
if next_special == 112 and not self.finished_game:
|
||||
self.finished_game = True
|
||||
logger.info("Congratulations! You finished the game!")
|
||||
else:
|
||||
|
||||
# All other special checks handled as normal.
|
||||
special_ap_id = Specials.to_ap_id(next_special)
|
||||
if special_ap_id not in self.location_outbox:
|
||||
self.location_outbox.append(special_ap_id)
|
||||
logger.info("Checked special: " + str(next_special))
|
||||
|
||||
except (ProcessError, MemoryReadError, WinAPIError):
|
||||
logger.error("The gk process has died. Restart the game and run \"/memr connect\" again.")
|
||||
self.connected = False
|
||||
|
||||
@@ -6,8 +6,13 @@ import pymem
|
||||
from pymem.exception import ProcessNotFound, ProcessError
|
||||
|
||||
from CommonClient import logger
|
||||
from worlds.jakanddaxter.locs import CellLocations as Cells, ScoutLocations as Flies, OrbLocations as Orbs
|
||||
from worlds.jakanddaxter.GameID import jak1_id
|
||||
from worlds.jakanddaxter.Items import item_table
|
||||
from worlds.jakanddaxter.locs import (
|
||||
CellLocations as Cells,
|
||||
ScoutLocations as Flies,
|
||||
OrbLocations as Orbs,
|
||||
SpecialLocations as Specials)
|
||||
|
||||
|
||||
class JakAndDaxterReplClient:
|
||||
@@ -170,12 +175,14 @@ class JakAndDaxterReplClient:
|
||||
# Determine the type of item to receive.
|
||||
if ap_id in range(jak1_id, jak1_id + Flies.fly_offset):
|
||||
self.receive_power_cell(ap_id)
|
||||
|
||||
elif ap_id in range(jak1_id + Flies.fly_offset, jak1_id + Orbs.orb_offset):
|
||||
elif ap_id in range(jak1_id + Flies.fly_offset, jak1_id + Specials.special_offset):
|
||||
self.receive_scout_fly(ap_id)
|
||||
|
||||
elif ap_id > jak1_id + Orbs.orb_offset:
|
||||
pass # TODO
|
||||
elif ap_id in range(jak1_id + Specials.special_offset, jak1_id + Orbs.orb_offset):
|
||||
self.receive_special(ap_id)
|
||||
# elif ap_id in range(jak1_id + Orbs.orb_offset, ???):
|
||||
# self.receive_precursor_orb(ap_id) # TODO -- Ponder the Orbs.
|
||||
else:
|
||||
raise KeyError(f"Tried to receive item with unknown AP ID {ap_id}.")
|
||||
|
||||
def receive_power_cell(self, ap_id: int) -> bool:
|
||||
cell_id = Cells.to_game_id(ap_id)
|
||||
@@ -184,9 +191,9 @@ class JakAndDaxterReplClient:
|
||||
"(pickup-type fuel-cell) "
|
||||
"(the float " + str(cell_id) + "))")
|
||||
if ok:
|
||||
logger.info(f"Received power cell {cell_id}!")
|
||||
logger.info(f"Received a Power Cell!")
|
||||
else:
|
||||
logger.error(f"Unable to receive power cell {cell_id}!")
|
||||
logger.error(f"Unable to receive a Power Cell!")
|
||||
return ok
|
||||
|
||||
def receive_scout_fly(self, ap_id: int) -> bool:
|
||||
@@ -196,7 +203,19 @@ class JakAndDaxterReplClient:
|
||||
"(pickup-type buzzer) "
|
||||
"(the float " + str(fly_id) + "))")
|
||||
if ok:
|
||||
logger.info(f"Received scout fly {fly_id}!")
|
||||
logger.info(f"Received a {item_table[ap_id]}!")
|
||||
else:
|
||||
logger.error(f"Unable to receive scout fly {fly_id}!")
|
||||
logger.error(f"Unable to receive a {item_table[ap_id]}!")
|
||||
return ok
|
||||
|
||||
def receive_special(self, ap_id: int) -> bool:
|
||||
special_id = Specials.to_game_id(ap_id)
|
||||
ok = self.send_form("(send-event "
|
||||
"*target* \'get-archipelago "
|
||||
"(pickup-type ap-special) "
|
||||
"(the float " + str(special_id) + "))")
|
||||
if ok:
|
||||
logger.info(f"Received special unlock {item_table[ap_id]}!")
|
||||
else:
|
||||
logger.error(f"Unable to receive special unlock {item_table[ap_id]}!")
|
||||
return ok
|
||||
|
||||
@@ -22,12 +22,31 @@ def to_game_id(ap_id: int) -> int:
|
||||
|
||||
# The ID's you see below correspond directly to that cell's game-task ID.
|
||||
|
||||
# The "Free 7 Scout Flies" Power Cells will be unlocked separately from their respective levels.
|
||||
loc7SF_cellTable = {
|
||||
95: "GR: Free 7 Scout Flies",
|
||||
75: "SV: Free 7 Scout Flies",
|
||||
7: "FJ: Free 7 Scout Flies",
|
||||
20: "SB: Free 7 Scout Flies",
|
||||
28: "MI: Free 7 Scout Flies",
|
||||
68: "FC: Free 7 Scout Flies",
|
||||
76: "RV: Free 7 Scout Flies",
|
||||
57: "PB: Free 7 Scout Flies",
|
||||
49: "LPC: Free 7 Scout Flies",
|
||||
43: "BS: Free 7 Scout Flies",
|
||||
88: "MP: Free 7 Scout Flies",
|
||||
77: "VC: Free 7 Scout Flies",
|
||||
85: "SC: Free 7 Scout Flies",
|
||||
65: "SM: Free 7 Scout Flies",
|
||||
90: "LT: Free 7 Scout Flies",
|
||||
91: "GMC: Free 7 Scout Flies",
|
||||
}
|
||||
|
||||
# Geyser Rock
|
||||
locGR_cellTable = {
|
||||
92: "GR: Find The Cell On The Path",
|
||||
93: "GR: Open The Precursor Door",
|
||||
94: "GR: Climb Up The Cliff",
|
||||
95: "GR: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Sandover Village
|
||||
@@ -37,7 +56,6 @@ locSV_cellTable = {
|
||||
10: "SV: Herd The Yakows Into The Pen",
|
||||
13: "SV: Bring 120 Orbs To The Oracle (1)",
|
||||
14: "SV: Bring 120 Orbs To The Oracle (2)",
|
||||
75: "SV: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Forbidden Jungle
|
||||
@@ -49,7 +67,6 @@ locFJ_cellTable = {
|
||||
5: "FJ: Catch 200 Pounds Of Fish",
|
||||
8: "FJ: Follow The Canyon To The Sea",
|
||||
9: "FJ: Open The Locked Temple Door",
|
||||
7: "FJ: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Sentinel Beach
|
||||
@@ -61,7 +78,6 @@ locSB_cellTable = {
|
||||
19: "SB: Launch Up To The Cannon Tower",
|
||||
21: "SB: Explore The Beach",
|
||||
22: "SB: Climb The Sentinel",
|
||||
20: "SB: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Misty Island
|
||||
@@ -73,13 +89,11 @@ locMI_cellTable = {
|
||||
27: "MI: Destroy the Balloon Lurkers",
|
||||
29: "MI: Use Zoomer To Reach Power Cell",
|
||||
30: "MI: Use Blue Eco To Reach Power Cell",
|
||||
28: "MI: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Fire Canyon
|
||||
locFC_cellTable = {
|
||||
69: "FC: Reach The End Of Fire Canyon",
|
||||
68: "FC: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Rock Village
|
||||
@@ -89,7 +103,6 @@ locRV_cellTable = {
|
||||
33: "RV: Bring 90 Orbs To The Warrior",
|
||||
34: "RV: Bring 120 Orbs To The Oracle (1)",
|
||||
35: "RV: Bring 120 Orbs To The Oracle (2)",
|
||||
76: "RV: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Precursor Basin
|
||||
@@ -101,7 +114,6 @@ locPB_cellTable = {
|
||||
55: "PB: Cure Dark Eco Infected Plants",
|
||||
58: "PB: Navigate The Purple Precursor Rings",
|
||||
59: "PB: Navigate The Blue Precursor Rings",
|
||||
57: "PB: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Lost Precursor City
|
||||
@@ -113,7 +125,6 @@ locLPC_cellTable = {
|
||||
44: "LPC: Match The Platform Colors",
|
||||
50: "LPC: Climb The Slide Tube",
|
||||
51: "LPC: Reach The Center Of The Complex",
|
||||
49: "LPC: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Boggy Swamp
|
||||
@@ -125,7 +136,6 @@ locBS_cellTable = {
|
||||
40: "BS: Break The Tethers To The Zeppelin (2)",
|
||||
41: "BS: Break The Tethers To The Zeppelin (3)",
|
||||
42: "BS: Break The Tethers To The Zeppelin (4)",
|
||||
43: "BS: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Mountain Pass
|
||||
@@ -133,7 +143,6 @@ locMP_cellTable = {
|
||||
86: "MP: Defeat Klaww",
|
||||
87: "MP: Reach The End Of The Mountain Pass",
|
||||
110: "MP: Find The Hidden Power Cell",
|
||||
88: "MP: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Volcanic Crater
|
||||
@@ -145,7 +154,6 @@ locVC_cellTable = {
|
||||
100: "VC: Bring 120 Orbs To The Oracle (1)",
|
||||
101: "VC: Bring 120 Orbs To The Oracle (2)",
|
||||
74: "VC: Find The Hidden Power Cell",
|
||||
77: "VC: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Spider Cave
|
||||
@@ -157,7 +165,6 @@ locSC_cellTable = {
|
||||
82: "SC: Launch To The Poles",
|
||||
83: "SC: Navigate The Spider Tunnel",
|
||||
84: "SC: Climb the Precursor Platforms",
|
||||
85: "SC: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Snowy Mountain
|
||||
@@ -169,13 +176,11 @@ locSM_cellTable = {
|
||||
63: "SM: Open The Lurker Fort Gate",
|
||||
62: "SM: Get Through The Lurker Fort",
|
||||
64: "SM: Survive The Lurker Infested Cave",
|
||||
65: "SM: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Lava Tube
|
||||
locLT_cellTable = {
|
||||
89: "LT: Cross The Lava Tube",
|
||||
90: "LT: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
# Gol and Maias Citadel
|
||||
@@ -184,5 +189,4 @@ locGMC_cellTable = {
|
||||
72: "GMC: Free The Red Sage",
|
||||
73: "GMC: Free The Yellow Sage",
|
||||
70: "GMC: Free The Green Sage",
|
||||
91: "GMC: Free 7 Scout Flies"
|
||||
}
|
||||
|
||||
47
worlds/jakanddaxter/locs/SpecialLocations.py
Normal file
47
worlds/jakanddaxter/locs/SpecialLocations.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from ..GameID import jak1_id
|
||||
|
||||
# These are special checks that the game normally does not track. They are not game entities and thus
|
||||
# don't have game ID's.
|
||||
|
||||
# Normally, for example, completing the fishing minigame is what gives you access to the
|
||||
# fisherman's boat to get to Misty Island. The game treats completion of the fishing minigame as well as the
|
||||
# power cell you receive as one and the same. The fisherman only gives you one item, a power cell.
|
||||
|
||||
# We're significantly altering the game logic here to decouple these concepts. First, completing the fishing minigame
|
||||
# now counts as 2 Location checks. Second, the fisherman should give you a power cell (a generic item) as well as
|
||||
# the "keys" to his boat (a special item). It is the "keys" that we are defining in this file, and the respective
|
||||
# Item representing those keys will be defined in Items.py. These aren't real in the sense that
|
||||
# they have a model and texture, they are just the logical representation of the boat unlock.
|
||||
|
||||
# We can use 2^11 to offset these from scout flies, just like we offset scout flies from power cells
|
||||
# by 2^10. Even with the high-16 reminder bits, scout flies don't exceed an ID of (jak1_id + 1887).
|
||||
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."
|
||||
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."
|
||||
return ap_id - jak1_id - special_offset # Reverse process, subtract the offsets.
|
||||
|
||||
|
||||
# The ID's you see below correlate to each of their respective game-tasks, even though they are separate.
|
||||
# This makes it easier for the new game logic to know what relates to what. I hope. God I hope.
|
||||
|
||||
loc_specialTable = {
|
||||
5: "Fisherman's Boat",
|
||||
4: "Jungle Elevator",
|
||||
2: "Blue Eco Switch",
|
||||
17: "Flut Flut",
|
||||
60: "Yellow Eco Switch",
|
||||
63: "Snowy Fort Gate",
|
||||
71: "Freed The Blue Sage",
|
||||
72: "Freed The Red Sage",
|
||||
73: "Freed The Yellow Sage",
|
||||
70: "Freed The Green Sage",
|
||||
}
|
||||
Reference in New Issue
Block a user